Skip to content

Commit a158b95

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 a158b95

File tree

2 files changed

+66
-20
lines changed

2 files changed

+66
-20
lines changed

src/crypto/elliptic/elliptic.go

Lines changed: 43 additions & 20 deletions
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

@@ -67,7 +64,15 @@ func (curve *CurveParams) IsOnCurve(x, y *big.Int) bool {
6764
x3.Add(x3, curve.B)
6865
x3.Mod(x3, curve.P)
6966

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

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

src/crypto/elliptic/elliptic_test.go

Lines changed: 23 additions & 0 deletions
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)