Skip to content

Commit 2d69e9e

Browse files
committed
crypto/elliptic: fix incomplete addition used in CombinedMult.
The optimised P-256 includes a CombinedMult function, which doesn't do dual-scalar multiplication, but does avoid an affine conversion for ECDSA verification. However, it currently uses an assembly point addition function that doesn't handle exceptional cases. Fixes #20215. Change-Id: I4ba2ca1a546d883364a9bb6bf0bdbc7f7b44c94a Reviewed-on: https://go-review.googlesource.com/42611 Run-TryBot: Adam Langley <[email protected]> Reviewed-by: Adam Langley <[email protected]>
1 parent 7159ab4 commit 2d69e9e

File tree

4 files changed

+171
-6
lines changed

4 files changed

+171
-6
lines changed

src/crypto/ecdsa/ecdsa_test.go

+22
Original file line numberDiff line numberDiff line change
@@ -331,3 +331,25 @@ func TestNegativeInputs(t *testing.T) {
331331
testNegativeInputs(t, elliptic.P384(), "p384")
332332
testNegativeInputs(t, elliptic.P521(), "p521")
333333
}
334+
335+
func TestZeroHashSignature(t *testing.T) {
336+
zeroHash := make([]byte, 64)
337+
338+
for _, curve := range []elliptic.Curve{elliptic.P224(), elliptic.P256(), elliptic.P384(), elliptic.P521()} {
339+
privKey, err := GenerateKey(curve, rand.Reader)
340+
if err != nil {
341+
panic(err)
342+
}
343+
344+
// Sign a hash consisting of all zeros.
345+
r, s, err := Sign(rand.Reader, privKey, zeroHash)
346+
if err != nil {
347+
panic(err)
348+
}
349+
350+
// Confirm that it can be verified.
351+
if !Verify(&privKey.PublicKey, zeroHash, r, s) {
352+
t.Errorf("zero hash signature verify failed for %T", curve)
353+
}
354+
}
355+
}

src/crypto/elliptic/elliptic_test.go

+63
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,69 @@ func TestInfinity(t *testing.T) {
455455
}
456456
}
457457

458+
type synthCombinedMult struct {
459+
Curve
460+
}
461+
462+
func (s synthCombinedMult) CombinedMult(bigX, bigY *big.Int, baseScalar, scalar []byte) (x, y *big.Int) {
463+
x1, y1 := s.ScalarBaseMult(baseScalar)
464+
x2, y2 := s.ScalarMult(bigX, bigY, scalar)
465+
return s.Add(x1, y1, x2, y2)
466+
}
467+
468+
func TestCombinedMult(t *testing.T) {
469+
type combinedMult interface {
470+
Curve
471+
CombinedMult(bigX, bigY *big.Int, baseScalar, scalar []byte) (x, y *big.Int)
472+
}
473+
474+
p256, ok := P256().(combinedMult)
475+
if !ok {
476+
p256 = &synthCombinedMult{P256()}
477+
}
478+
479+
gx := p256.Params().Gx
480+
gy := p256.Params().Gy
481+
482+
zero := make([]byte, 32)
483+
one := make([]byte, 32)
484+
one[31] = 1
485+
two := make([]byte, 32)
486+
two[31] = 2
487+
488+
// 0×G + 0×G = ∞
489+
x, y := p256.CombinedMult(gx, gy, zero, zero)
490+
if x.Sign() != 0 || y.Sign() != 0 {
491+
t.Errorf("0×G + 0×G = (%d, %d), should be ∞", x, y)
492+
}
493+
494+
// 1×G + 0×G = G
495+
x, y = p256.CombinedMult(gx, gy, one, zero)
496+
if x.Cmp(gx) != 0 || y.Cmp(gy) != 0 {
497+
t.Errorf("1×G + 0×G = (%d, %d), should be (%d, %d)", x, y, gx, gy)
498+
}
499+
500+
// 0×G + 1×G = G
501+
x, y = p256.CombinedMult(gx, gy, zero, one)
502+
if x.Cmp(gx) != 0 || y.Cmp(gy) != 0 {
503+
t.Errorf("0×G + 1×G = (%d, %d), should be (%d, %d)", x, y, gx, gy)
504+
}
505+
506+
// 1×G + 1×G = 2×G
507+
x, y = p256.CombinedMult(gx, gy, one, one)
508+
ggx, ggy := p256.ScalarBaseMult(two)
509+
if x.Cmp(ggx) != 0 || y.Cmp(ggy) != 0 {
510+
t.Errorf("1×G + 1×G = (%d, %d), should be (%d, %d)", x, y, ggx, ggy)
511+
}
512+
513+
minusOne := new(big.Int).Sub(p256.Params().N, big.NewInt(1))
514+
// 1×G + (-1)×G = ∞
515+
x, y = p256.CombinedMult(gx, gy, one, minusOne.Bytes())
516+
if x.Sign() != 0 || y.Sign() != 0 {
517+
t.Errorf("1×G + (-1)×G = (%d, %d), should be ∞", x, y)
518+
}
519+
}
520+
458521
func BenchmarkBaseMult(b *testing.B) {
459522
b.ResetTimer()
460523
p224 := P224()

src/crypto/elliptic/p256_amd64.go

+44-4
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,10 @@ func p256OrdSqr(res, in []uint64, n int)
8686
// if zero == 0 -> res = in2
8787
func p256PointAddAffineAsm(res, in1, in2 []uint64, sign, sel, zero int)
8888

89-
// Point add
90-
func p256PointAddAsm(res, in1, in2 []uint64)
89+
// Point add. Returns one if the two input points were equal and zero
90+
// otherwise. (Note that, due to the way that the equations work out, some
91+
// representations of ∞ are considered equal to everything by this function.)
92+
func p256PointAddAsm(res, in1, in2 []uint64) int
9193

9294
// Point double
9395
func p256PointDoubleAsm(res, in []uint64)
@@ -213,9 +215,11 @@ func (curve p256Curve) CombinedMult(bigX, bigY *big.Int, baseScalar, scalar []by
213215
scalarReversed := make([]uint64, 4)
214216
var r1, r2 p256Point
215217
p256GetScalar(scalarReversed, baseScalar)
218+
r1IsInfinity := scalarIsZero(scalarReversed)
216219
r1.p256BaseMult(scalarReversed)
217220

218221
p256GetScalar(scalarReversed, scalar)
222+
r2IsInfinity := scalarIsZero(scalarReversed)
219223
fromBig(r2.xyz[0:4], maybeReduceModP(bigX))
220224
fromBig(r2.xyz[4:8], maybeReduceModP(bigY))
221225
p256Mul(r2.xyz[0:4], r2.xyz[0:4], rr[:])
@@ -228,8 +232,15 @@ func (curve p256Curve) CombinedMult(bigX, bigY *big.Int, baseScalar, scalar []by
228232
r2.xyz[11] = 0x00000000fffffffe
229233

230234
r2.p256ScalarMult(scalarReversed)
231-
p256PointAddAsm(r1.xyz[:], r1.xyz[:], r2.xyz[:])
232-
return r1.p256PointToAffine()
235+
236+
var sum, double p256Point
237+
pointsEqual := p256PointAddAsm(sum.xyz[:], r1.xyz[:], r2.xyz[:])
238+
p256PointDoubleAsm(double.xyz[:], r1.xyz[:])
239+
sum.CopyConditional(&double, pointsEqual)
240+
sum.CopyConditional(&r1, r2IsInfinity)
241+
sum.CopyConditional(&r2, r1IsInfinity)
242+
243+
return sum.p256PointToAffine()
233244
}
234245

235246
func (curve p256Curve) ScalarBaseMult(scalar []byte) (x, y *big.Int) {
@@ -260,6 +271,24 @@ func (curve p256Curve) ScalarMult(bigX, bigY *big.Int, scalar []byte) (x, y *big
260271
return r.p256PointToAffine()
261272
}
262273

274+
// uint64IsZero returns 1 if x is zero and zero otherwise.
275+
func uint64IsZero(x uint64) int {
276+
x = ^x
277+
x &= x >> 32
278+
x &= x >> 16
279+
x &= x >> 8
280+
x &= x >> 4
281+
x &= x >> 2
282+
x &= x >> 1
283+
return int(x&1)
284+
}
285+
286+
// scalarIsZero returns 1 if scalar represents the zero value, and zero
287+
// otherwise.
288+
func scalarIsZero(scalar []uint64) int {
289+
return uint64IsZero(scalar[0] | scalar[1] | scalar[2] | scalar[3])
290+
}
291+
263292
func (p *p256Point) p256PointToAffine() (x, y *big.Int) {
264293
zInv := make([]uint64, 4)
265294
zInvSq := make([]uint64, 4)
@@ -281,6 +310,17 @@ func (p *p256Point) p256PointToAffine() (x, y *big.Int) {
281310
return new(big.Int).SetBytes(xOut), new(big.Int).SetBytes(yOut)
282311
}
283312

313+
// CopyConditional copies overwrites p with src if v == 1, and leaves p
314+
// unchanged if v == 0.
315+
func (p *p256Point) CopyConditional(src *p256Point, v int) {
316+
pMask := uint64(v) - 1
317+
srcMask := ^pMask
318+
319+
for i, n := range p.xyz {
320+
p.xyz[i] = (n & pMask) | (src.xyz[i] & srcMask)
321+
}
322+
}
323+
284324
// p256Inverse sets out to in^-1 mod p.
285325
func p256Inverse(out, in []uint64) {
286326
var stack [6 * 4]uint64

src/crypto/elliptic/p256_asm_amd64.s

+42-2
Original file line numberDiff line numberDiff line change
@@ -1972,6 +1972,36 @@ TEXT ·p256PointAddAffineAsm(SB),0,$512-96
19721972
#undef rptr
19731973
#undef sel_save
19741974
#undef zero_save
1975+
1976+
// p256IsZero returns 1 in AX if [acc4..acc7] represents zero and zero
1977+
// otherwise. It writes to [acc4..acc7], t0 and t1.
1978+
TEXT p256IsZero(SB),NOSPLIT,$0
1979+
// AX contains a flag that is set if the input is zero.
1980+
XORQ AX, AX
1981+
MOVQ $1, t1
1982+
1983+
// Check whether [acc4..acc7] are all zero.
1984+
MOVQ acc4, t0
1985+
ORQ acc5, t0
1986+
ORQ acc6, t0
1987+
ORQ acc7, t0
1988+
1989+
// Set the zero flag if so. (CMOV of a constant to a register doesn't
1990+
// appear to be supported in Go. Thus t1 = 1.)
1991+
CMOVQEQ t1, AX
1992+
1993+
// XOR [acc4..acc7] with P and compare with zero again.
1994+
XORQ $-1, acc4
1995+
XORQ p256const0<>(SB), acc5
1996+
XORQ p256const1<>(SB), acc7
1997+
ORQ acc5, acc4
1998+
ORQ acc6, acc4
1999+
ORQ acc7, acc4
2000+
2001+
// Set the zero flag if so.
2002+
CMOVQEQ t1, AX
2003+
RET
2004+
19752005
/* ---------------------------------------*/
19762006
#define x1in(off) (32*0 + off)(SP)
19772007
#define y1in(off) (32*1 + off)(SP)
@@ -1996,9 +2026,11 @@ TEXT ·p256PointAddAffineAsm(SB),0,$512-96
19962026
#define rsqr(off) (32*18 + off)(SP)
19972027
#define hcub(off) (32*19 + off)(SP)
19982028
#define rptr (32*20)(SP)
2029+
#define points_eq (32*20+8)(SP)
19992030

2000-
//func p256PointAddAsm(res, in1, in2 []uint64)
2001-
TEXT ·p256PointAddAsm(SB),0,$672-72
2031+
//func p256PointAddAsm(res, in1, in2 []uint64) int
2032+
TEXT ·p256PointAddAsm(SB),0,$680-80
2033+
// See https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-add-2007-bl
20022034
// Move input to stack in order to free registers
20032035
MOVQ res+0(FP), AX
20042036
MOVQ in1+24(FP), BX
@@ -2055,6 +2087,8 @@ TEXT ·p256PointAddAsm(SB),0,$672-72
20552087
LDt (s1)
20562088
CALL p256SubInternal(SB) // r = s2 - s1
20572089
ST (r)
2090+
CALL p256IsZero(SB)
2091+
MOVQ AX, points_eq
20582092

20592093
LDacc (z2sqr)
20602094
LDt (x1in)
@@ -2068,6 +2102,9 @@ TEXT ·p256PointAddAsm(SB),0,$672-72
20682102
LDt (u1)
20692103
CALL p256SubInternal(SB) // h = u2 - u1
20702104
ST (h)
2105+
CALL p256IsZero(SB)
2106+
ANDQ points_eq, AX
2107+
MOVQ AX, points_eq
20712108

20722109
LDacc (r)
20732110
CALL p256SqrInternal(SB) // rsqr = rˆ2
@@ -2135,6 +2172,9 @@ TEXT ·p256PointAddAsm(SB),0,$672-72
21352172
MOVOU X4, (16*4)(AX)
21362173
MOVOU X5, (16*5)(AX)
21372174

2175+
MOVQ points_eq, AX
2176+
MOVQ AX, ret+72(FP)
2177+
21382178
RET
21392179
#undef x1in
21402180
#undef y1in

0 commit comments

Comments
 (0)