Skip to content

Commit

Permalink
crypto/elliptic: Implement marshal / unmarshal for compressed format
Browse files Browse the repository at this point in the history
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
  • Loading branch information
im-kulikov committed Feb 13, 2020
1 parent 9fc41cd commit 9b38b0a
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 6 deletions.
56 changes: 50 additions & 6 deletions src/crypto/elliptic/elliptic.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,8 @@ func (curve *CurveParams) Params() *CurveParams {
return curve
}

func (curve *CurveParams) IsOnCurve(x, y *big.Int) bool {
// y² = x³ - 3x + b
y2 := new(big.Int).Mul(y, y)
y2.Mod(y2, curve.P)

// polynomial returns x³ - 3x + b
func (curve *CurveParams) polynomial(x *big.Int) *big.Int {
x3 := new(big.Int).Mul(x, x)
x3.Mul(x3, x)

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

return x3.Cmp(y2) == 0
return x3
}

func (curve *CurveParams) IsOnCurve(x, y *big.Int) bool {
// y² = x³ - 3x + b
y2 := new(big.Int).Mul(y, y)
y2.Mod(y2, curve.P)

return curve.polynomial(x).Cmp(y2) == 0
}

// zForAffine returns a Jacobian Z value for the affine point (x, y). If x and
Expand Down Expand Up @@ -316,6 +321,19 @@ func Marshal(curve Curve, x, y *big.Int) []byte {
return ret
}

// MarshalCompressed converts a point into the compressed form specified in section 4.3.6 of ANSI X9.62.
func MarshalCompressed(_ Curve, x, y *big.Int) []byte {
byteLen := (p256.Params().BitSize + 7) >> 3
compressed := make([]byte, 1+byteLen)
compressed[0] = 3
if y.Bit(0) == 0 {
compressed[0] = 2
}
i := byteLen + 1 - len(x.Bytes())
copy(compressed[i:], x.Bytes())
return compressed
}

// Unmarshal converts a point, serialized by Marshal, into an x, y pair.
// It is an error if the point is not in uncompressed form or is not on the curve.
// On error, x = nil.
Expand All @@ -339,6 +357,32 @@ func Unmarshal(curve Curve, data []byte) (x, y *big.Int) {
return
}

// UnmarshalCompressed converts a point, serialized by MarshalCompressed, into an x, y pair.
// It is an error if the point is not in compressed form or is not on the curve.
// On error, x = nil.
func UnmarshalCompressed(curve Curve, data []byte) (x, y *big.Int) {
byteLen := (curve.Params().BitSize + 7) >> 3
if len(data) != 1+byteLen {
return
}
if data[0] != 2 && data[0] != 3 { // compressed form
return
}
x = new(big.Int).SetBytes(data[1:])
x3 := curve.Params().polynomial(x)
y = new(big.Int).ModSqrt(x3, curve.Params().P)
// Jacobi(x, p) == -1
if y == nil {
return nil, nil
} else if y.Bit(0) != uint(data[0]&0x1) {
y.Neg(y)
y.Mod(y, curve.Params().P)
} else if !curve.IsOnCurve(x, y) {
return nil, nil
}
return
}

var initonce sync.Once
var p384 *CurveParams
var p521 *CurveParams
Expand Down
33 changes: 33 additions & 0 deletions src/crypto/elliptic/elliptic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package elliptic

import (
"bytes"
"crypto/rand"
"encoding/hex"
"fmt"
Expand Down Expand Up @@ -628,3 +629,35 @@ func TestUnmarshalToLargeCoordinates(t *testing.T) {
t.Errorf("Unmarshal accepts invalid Y coordinate")
}
}

// See https://golang.org/issues/34105
func TestUnmarshalCompressed(t *testing.T) {
p256 := P256()
data, _ := hex.DecodeString("031e3987d9f9ea9d7dd7155a56a86b2009e1e0ab332f962d10d8beb6406ab1ad79")
expectX := "13671033352574878777044637384712060483119675368076128232297328793087057702265"
expectY := "66200849279091436748794323380043701364391950689352563629885086590854940586447"

x, y := UnmarshalCompressed(p256, data)
if x == nil || y == nil {
t.Error("P256 failed unmarshal point")
} else if !p256.IsOnCurve(x, y) {
t.Error("P256 failed to validate a correct point")
} else if x.String() != expectX || y.String() != expectY {
t.Errorf("Unmarshalled wrong point:\n\tX: expect %s, actual %s\n\tY: expect %s, actual %s",
expectX, x.String(),
expectY, y.String())
}
}

// See https://golang.org/issues/34105
func TestMarshalCompressed(t *testing.T) {
p256 := P256()
expect, _ := hex.DecodeString("031e3987d9f9ea9d7dd7155a56a86b2009e1e0ab332f962d10d8beb6406ab1ad79")
x, _ := new(big.Int).SetString("13671033352574878777044637384712060483119675368076128232297328793087057702265", 10)
y, _ := new(big.Int).SetString("66200849279091436748794323380043701364391950689352563629885086590854940586447", 10)

actual := MarshalCompressed(p256, x, y)
if !bytes.Equal(expect, actual) {
t.Error("P256 failed correctly marshal point")
}
}

0 comments on commit 9b38b0a

Please sign in to comment.