From 9b38b0a7f8cef7f001fe9126a1cfcb4990f7b996 Mon Sep 17 00:00:00 2001 From: Evgeniy Kulikov Date: Wed, 23 Oct 2019 16:13:20 +0300 Subject: [PATCH] crypto/elliptic: Implement marshal / unmarshal for compressed format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First byte can be 0x2 or 0x3 (compressed form). To unmarshal used formula y² = x³ - 3x + b. Reuse code from `IsOnCurve`. closes #34105 issue --- src/crypto/elliptic/elliptic.go | 56 +++++++++++++++++++++++++--- src/crypto/elliptic/elliptic_test.go | 33 ++++++++++++++++ 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/src/crypto/elliptic/elliptic.go b/src/crypto/elliptic/elliptic.go index c84657c5e36777..59e2f8ddb5b70a 100644 --- a/src/crypto/elliptic/elliptic.go +++ b/src/crypto/elliptic/elliptic.go @@ -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) @@ -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 @@ -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. @@ -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 diff --git a/src/crypto/elliptic/elliptic_test.go b/src/crypto/elliptic/elliptic_test.go index 09c5483520ee5c..7e2720a86c35b8 100644 --- a/src/crypto/elliptic/elliptic_test.go +++ b/src/crypto/elliptic/elliptic_test.go @@ -5,6 +5,7 @@ package elliptic import ( + "bytes" "crypto/rand" "encoding/hex" "fmt" @@ -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") + } +}