Skip to content

crypto/elliptic: implement unmarshal for compressed format #35110

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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")
}
}