Skip to content

Commit d9a339d

Browse files
committed
Implement unmarshal for compressed format for crypto/elliptic
First byte can be 0x2 or 0x3 (compressed form). To unmarshal used formula y² = x³ - 3x + b. Reuse code from `IsOnCurve`. Closes golang#34105 issue
1 parent 9fc41cd commit d9a339d

File tree

2 files changed

+64
-20
lines changed

2 files changed

+64
-20
lines changed

src/crypto/elliptic/elliptic.go

+41-20
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,8 @@ func (curve *CurveParams) Params() *CurveParams {
5252
return curve
5353
}
5454

55-
func (curve *CurveParams) IsOnCurve(x, y *big.Int) bool {
56-
// y² = x³ - 3x + b
57-
y2 := new(big.Int).Mul(y, y)
58-
y2.Mod(y2, curve.P)
59-
55+
// polynomial returns x³ - 3x + b
56+
func (curve *CurveParams) polynomial(x *big.Int) *big.Int {
6057
x3 := new(big.Int).Mul(x, x)
6158
x3.Mul(x3, x)
6259

@@ -66,8 +63,14 @@ func (curve *CurveParams) IsOnCurve(x, y *big.Int) bool {
6663
x3.Sub(x3, threeX)
6764
x3.Add(x3, curve.B)
6865
x3.Mod(x3, curve.P)
66+
return x3
67+
}
6968

70-
return x3.Cmp(y2) == 0
69+
func (curve *CurveParams) IsOnCurve(x, y *big.Int) bool {
70+
// y² = x³ - 3x + b
71+
y2 := new(big.Int).Mul(y, y)
72+
y2.Mod(y2, curve.P)
73+
return curve.polynomial(x).Cmp(y2) == 0
7174
}
7275

7376
// zForAffine returns a Jacobian Z value for the affine point (x, y). If x and
@@ -321,21 +324,39 @@ func Marshal(curve Curve, x, y *big.Int) []byte {
321324
// On error, x = nil.
322325
func Unmarshal(curve Curve, data []byte) (x, y *big.Int) {
323326
byteLen := (curve.Params().BitSize + 7) >> 3
324-
if len(data) != 1+2*byteLen {
325-
return
326-
}
327-
if data[0] != 4 { // uncompressed form
328-
return
329-
}
330-
p := curve.Params().P
331-
x = new(big.Int).SetBytes(data[1 : 1+byteLen])
332-
y = new(big.Int).SetBytes(data[1+byteLen:])
333-
if x.Cmp(p) >= 0 || y.Cmp(p) >= 0 {
334-
return nil, nil
335-
}
336-
if !curve.IsOnCurve(x, y) {
337-
return nil, nil
327+
328+
switch data[0] {
329+
case 2, 3: // compressed form
330+
if len(data) != 1+byteLen {
331+
return
332+
}
333+
334+
x = new(big.Int).SetBytes(data[1:])
335+
x3 := curve.Params().polynomial(x)
336+
y = new(big.Int).ModSqrt(x3, curve.Params().P)
337+
338+
// Jacobi(x, p) == -1
339+
if y == nil {
340+
return nil, nil
341+
} else if y.Bit(0) != uint(data[0]&0x1) {
342+
y.Neg(y)
343+
y.Mod(y, curve.Params().P)
344+
}
345+
case 4: // uncompressed form
346+
if len(data) != 1+2*byteLen {
347+
return
348+
}
349+
p := curve.Params().P
350+
x = new(big.Int).SetBytes(data[1 : 1+byteLen])
351+
y = new(big.Int).SetBytes(data[1+byteLen:])
352+
if x.Cmp(p) >= 0 || y.Cmp(p) >= 0 {
353+
return nil, nil
354+
}
355+
if !curve.IsOnCurve(x, y) {
356+
return nil, nil
357+
}
338358
}
359+
339360
return
340361
}
341362

src/crypto/elliptic/elliptic_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -628,3 +628,26 @@ func TestUnmarshalToLargeCoordinates(t *testing.T) {
628628
t.Errorf("Unmarshal accepts invalid Y coordinate")
629629
}
630630
}
631+
632+
// See https://golang.org/issues/34105
633+
func TestUnmarshalCompressed(t *testing.T) {
634+
p256 := P256()
635+
_, x, y, err := GenerateKey(p256, rand.Reader)
636+
if err != nil {
637+
t.Error(err)
638+
return
639+
}
640+
byteLen := (p256.Params().BitSize + 7) >> 3
641+
compressed := make([]byte, 1+byteLen)
642+
compressed[0] = 3
643+
if y.Bit(0) == 0 {
644+
compressed[0] = 2
645+
}
646+
copy(compressed[1:], x.Bytes())
647+
newX, newY := Unmarshal(p256, compressed)
648+
if !p256.IsOnCurve(newX, newY) {
649+
t.Error("P256 failed to validate a correct point")
650+
} else if x.Cmp(newX) != 0 || y.Cmp(newY) != 0 {
651+
t.Error("P256 failed to correctly unmarshal compressed point")
652+
}
653+
}

0 commit comments

Comments
 (0)