Skip to content

Commit c4a91bd

Browse files
committed
ed25519: require canonical signatures
https://tools.ietf.org/html/rfc8032#section-5.1.7 requires that s be in the range [0, order) in order to prevent signature malleability. This is a new requirement in the RFC so the ed25519 package predates it. This change aligns the code with the RFC. The linked bug says that libsodium is also enforcing this check by default. See golang/go#24350 Change-Id: Ib69ce7c9e5a58971cbe225318d9fd87660bd5e4b Reviewed-on: https://go-review.googlesource.com/100436 Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent 182114d commit c4a91bd

File tree

3 files changed

+56
-3
lines changed

3 files changed

+56
-3
lines changed

ed25519/ed25519.go

+10-3
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,16 @@ func Verify(publicKey PublicKey, message, sig []byte) bool {
171171
edwards25519.ScReduce(&hReduced, &digest)
172172

173173
var R edwards25519.ProjectiveGroupElement
174-
var b [32]byte
175-
copy(b[:], sig[32:])
176-
edwards25519.GeDoubleScalarMultVartime(&R, &hReduced, &A, &b)
174+
var s [32]byte
175+
copy(s[:], sig[32:])
176+
177+
// https://tools.ietf.org/html/rfc8032#section-5.1.7 requires that s be in
178+
// the range [0, order) in order to prevent signature malleability.
179+
if !edwards25519.ScMinimal(&s) {
180+
return false
181+
}
182+
183+
edwards25519.GeDoubleScalarMultVartime(&R, &hReduced, &A, &s)
177184

178185
var checkR [32]byte
179186
R.ToBytes(&checkR)

ed25519/ed25519_test.go

+24
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,30 @@ func TestGolden(t *testing.T) {
146146
}
147147
}
148148

149+
func TestMalleability(t *testing.T) {
150+
// https://tools.ietf.org/html/rfc8032#section-5.1.7 adds an additional test
151+
// that s be in [0, order). This prevents someone from adding a multiple of
152+
// order to s and obtaining a second valid signature for the same message.
153+
msg := []byte{0x54, 0x65, 0x73, 0x74}
154+
sig := []byte{
155+
0x7c, 0x38, 0xe0, 0x26, 0xf2, 0x9e, 0x14, 0xaa, 0xbd, 0x05, 0x9a,
156+
0x0f, 0x2d, 0xb8, 0xb0, 0xcd, 0x78, 0x30, 0x40, 0x60, 0x9a, 0x8b,
157+
0xe6, 0x84, 0xdb, 0x12, 0xf8, 0x2a, 0x27, 0x77, 0x4a, 0xb0, 0x67,
158+
0x65, 0x4b, 0xce, 0x38, 0x32, 0xc2, 0xd7, 0x6f, 0x8f, 0x6f, 0x5d,
159+
0xaf, 0xc0, 0x8d, 0x93, 0x39, 0xd4, 0xee, 0xf6, 0x76, 0x57, 0x33,
160+
0x36, 0xa5, 0xc5, 0x1e, 0xb6, 0xf9, 0x46, 0xb3, 0x1d,
161+
}
162+
publicKey := []byte{
163+
0x7d, 0x4d, 0x0e, 0x7f, 0x61, 0x53, 0xa6, 0x9b, 0x62, 0x42, 0xb5,
164+
0x22, 0xab, 0xbe, 0xe6, 0x85, 0xfd, 0xa4, 0x42, 0x0f, 0x88, 0x34,
165+
0xb1, 0x08, 0xc3, 0xbd, 0xae, 0x36, 0x9e, 0xf5, 0x49, 0xfa,
166+
}
167+
168+
if Verify(publicKey, msg, sig) {
169+
t.Fatal("non-canonical signature accepted")
170+
}
171+
}
172+
149173
func BenchmarkKeyGeneration(b *testing.B) {
150174
var zero zeroReader
151175
for i := 0; i < b.N; i++ {

ed25519/internal/edwards25519/edwards25519.go

+22
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
package edwards25519
66

7+
import "encoding/binary"
8+
79
// This code is a port of the public domain, “ref10” implementation of ed25519
810
// from SUPERCOP.
911

@@ -1769,3 +1771,23 @@ func ScReduce(out *[32]byte, s *[64]byte) {
17691771
out[30] = byte(s11 >> 9)
17701772
out[31] = byte(s11 >> 17)
17711773
}
1774+
1775+
// order is the order of Curve25519 in little-endian form.
1776+
var order = [4]uint64{0x5812631a5cf5d3ed, 0x14def9dea2f79cd6, 0, 0x1000000000000000}
1777+
1778+
// ScMinimal returns true if the given scalar is less than the order of the
1779+
// curve.
1780+
func ScMinimal(scalar *[32]byte) bool {
1781+
for i := 3; ; i-- {
1782+
v := binary.LittleEndian.Uint64(scalar[i*8:])
1783+
if v > order[i] {
1784+
return false
1785+
} else if v < order[i] {
1786+
break
1787+
} else if i == 0 {
1788+
return false
1789+
}
1790+
}
1791+
1792+
return true
1793+
}

0 commit comments

Comments
 (0)