Skip to content

Commit 6388039

Browse files
committed
fix pub key decoding after downgrading go-eth crypto version and improve tests
1 parent ab90dbf commit 6388039

7 files changed

Lines changed: 68 additions & 39 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ All signature schemes use the generic interfaces of `PrivateKey` and `PublicKey`
6565

6666
* ECDSA
6767
* public keys are compressed or uncompressed.
68-
* ephemeral key is derived from the private key, hash and the system entropy (based on https://golang.org/pkg/crypto/ecdsa/).
6968
* supports NIST P-256 (secp256r1) and secp256k1 curves.
69+
* For NIST P-256, ephemeral key is derived from the private key, hash and the system entropy (based on https://golang.org/pkg/crypto/ecdsa/). For secp256k1, ephemeral key is deterministically formed following RFC 6979 (based on github.com/ethereum/go-ethereum/crypto/secp256k1)
7070

7171
* BLS
7272
* supports [BLS12-381](https://electriccoin.co/blog/new-snark-curve/) curve.

blst_src/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ The folder contains:
2121
To upgrade the BLST version:
2222
- [ ] audit all BLST updates, with focus on `<blst>/src`: https://github.com/supranational/blst/compare/v0.3.14...<new_version>
2323
- [ ] delete all files in this folder `./blst_src/` but `blst_src.c` and `README.md`.
24-
- [ ] delete all files in `./internal/blst/` but `non_cgo.go`.
24+
- [ ] delete all files in `./internal/blst/`.
2525
- [ ] open BLST repository on the new version.
2626
- [ ] copy all `.c` and `.h` files from `<blst>/src/` into `./blst_src/`.
2727
- [ ] delete newly copied `./blst_src/server.c`.

ecdsa.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ type ecdsaContext struct {
4141
curveP *big.Int
4242
// curve order
4343
curveN *big.Int
44-
// curve order minus 1 divided by 2 (used for signature malleability annalysis)
44+
// curve order minus 1 divided by 2 (used for signature malleability analysis)
4545
curveNdiv2 *big.Int
4646
}
4747

@@ -122,7 +122,7 @@ func (a *ecdsaContext) mapToPrivateKey(seed []byte) (PrivateKey, error) {
122122
// privateKey returns an ECDSA private key using the
123123
// input scalar.
124124
//
125-
// Input scalar d is assumed to be satisfy 0 < d < n before calling this function.
125+
// Input scalar d is assumed to satisfy 0 < d < n before calling this function.
126126
//
127127
// The function returns:
128128
// - (nil, invalidInputsError) if the curve is not supported
@@ -217,7 +217,7 @@ func (a *ecdsaContext) decodePrivateKey(der []byte) (PrivateKey, error) {
217217
func (a *ecdsaContext) rawDecodePublicKey(input []byte) (PublicKey, error) {
218218
switch a.algo {
219219
case ECDSAP256:
220-
return publicKeyECDSAP256(a, input)
220+
return publicKeyECDSAP256(input)
221221
case ECDSASecp256k1:
222222
return publicKeyECDSASecp256k1(a, input)
223223
default:
@@ -330,7 +330,9 @@ func (a *ecdsaContext) isLowS(s *big.Int) bool {
330330
// (same slice is returned if S is already normalized)
331331
// It assumes len(sig) == 2*nLen where nLen is the byte-length of the curve order.
332332
// This is needed when the underlying signature verification requires S to be in the lower range (to avoid signature malleability). In this package, verification allows high S signatures to be accepted.
333-
// The function checks that S is in the correct range [0, n-1] before normalizing it. If S is not in the correct range, the function returns a false boolean. (S will be checked against 0 in the verification function - check against N is inlcuded here)
333+
// The function checks that S is in the correct range [0, n-1] before normalizing it.
334+
// If S is not in the correct range, the function returns a false boolean.
335+
// (S will be checked against 0 in the verification function - check against N is inlcuded here)
334336
// returns:
335337
// - newSig, true if S is in the valid range and was normalized to low S
336338
// - nil, false if S was not in the correct range

ecdsa_p256.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ type pubKeyECDSAP256 struct {
8282

8383
var _ PublicKey = (*pubKeyECDSAP256)(nil)
8484

85+
// Input scalar d is assumed to satisfy 0 < d < n before calling this function.
8586
func privateKeyECDSAP256(a *ecdsaContext, dBytes []byte) (*prKeyECDSAP256, error) {
8687
internalSK, err := ecdsa.ParseRawPrivateKey(elliptic.P256(), dBytes)
8788
if err != nil {
@@ -128,12 +129,13 @@ func (sk *prKeyECDSAP256) String() string {
128129
}
129130

130131
// returns a publicKeyECDSAP256 from (bytes(x) || bytes(y)) bytes
131-
func publicKeyECDSAP256(a *ecdsaContext, XYBytes []byte) (*pubKeyECDSAP256, error) {
132+
func publicKeyECDSAP256(XYBytes []byte) (*pubKeyECDSAP256, error) {
132133
// deserialization uses SEC1 version 2 (https://www.secg.org/sec1-v2.pdf section 2.3.3)
133134
// and includes on curve check.
134135
// The bytes serialization for non-infinity points is `0x04 || X || Y` and infinity point should be rejected anyway
135136
parsingBytes := append([]byte{ecEncodingUncompressed}, XYBytes...)
136137

138+
// ParseUncompressedPublicKey includes x<p and y<p checks, and on curve checks
137139
internalPK, err := ecdsa.ParseUncompressedPublicKey(elliptic.P256(), parsingBytes)
138140
if err != nil {
139141
return nil, invalidInputsErrorf("input point has invalid coordinates or is not on curve: %w", err)

ecdsa_secp256k1.go

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,14 @@ var _ PrivateKey = (*prKeyECDSASecp256k1)(nil)
9090
type pubKeyECDSASecp256k1 struct {
9191
// ECDSA generic public key
9292
*pubKeyCommonECDSA
93-
// 0x4 || bytes(x) || bytes(y) (65 bytes) where x and y are the coordinates of the public key point, padded to the field size (32 bytes)
93+
// 0x4 || bytes(x) || bytes(y) (65 bytes) where x and y are the coordinates of the public key point, padded to the field size (32 bytes).
94+
// This is the form required by go-ethereum/crypto/secp256k1.
9495
pkBytes []byte
9596
}
9697

9798
var _ PublicKey = (*pubKeyECDSASecp256k1)(nil)
9899

100+
// Input scalar d is assumed to be satisfy 0 < d < n before calling this function.
99101
func privateKeyECDSASecp256k1(a *ecdsaContext, dBytes []byte) *prKeyECDSASecp256k1 {
100102
sk := &prKeyECDSASecp256k1{
101103
prKeyCommonECDSA: &prKeyCommonECDSA{a},
@@ -148,14 +150,18 @@ func publicKeyECDSASecp256k1(a *ecdsaContext, XYBytes []byte) (*pubKeyECDSASecp2
148150

149151
x, y := readTwoBigInts(XYBytes, pLen)
150152

151-
// `IsOnCurve` includes checks for x<p and y<p
153+
// check the coordinates are valid field elements (required for go-ethereum versions prior or equal to v1.16.8)
154+
if x.Cmp(a.curveP) >= 0 || y.Cmp(a.curveP) >= 0 {
155+
return nil, invalidInputsErrorf("at least one coordinate is larger than the field prime for %s", a.algo)
156+
}
157+
158+
// `IsOnCurve` includes checks for x<p and y<p (in go-ethereum versions later than v1.16.9)
152159
if !secp256k1.S256().IsOnCurve(x, y) {
153160
return nil, invalidInputsErrorf("input point has invalid coordinates or is not on curve")
154161
}
155162
return &pubKeyECDSASecp256k1{
156163
&pubKeyCommonECDSA{secp256k1Instance},
157-
append([]byte{ecEncodingUncompressed}, XYBytes...),
158-
// XYBytes is already the raw uncompressed encoding `bytes(x) || bytes(y)`
164+
secp256k1PkBytes(x, y),
159165
}, nil
160166
}
161167

@@ -164,18 +170,27 @@ func (pk *pubKeyECDSASecp256k1) String() string {
164170
return pubKeyCommonECDSAString(pk)
165171
}
166172

173+
// 0x4 || bytes(x) || bytes(y) (65 bytes) where x and y are the coordinates of the public key point, padded to the field size (32 bytes).
174+
// This is the form required by the underlying go-ethereum/crypto/secp256k1.
175+
//
176+
// The function assumes x and y are valid field elements and the point (x,y) is on curve
177+
func secp256k1PkBytes(x, y *big.Int) []byte {
178+
pkBytes := make([]byte, 1+2*pLenSecp256k1)
179+
pkBytes[0] = ecEncodingUncompressed
180+
// pad x and y to the field size and concatenate them
181+
padToSizeAndConcat(pkBytes[1:], x, y, pLenSecp256k1)
182+
return pkBytes
183+
}
184+
167185
// PublicKey returns the public key associated to the private key
168186
func (sk *prKeyECDSASecp256k1) PublicKey() PublicKey {
169187
// construct the public key once
170188
if sk.pubKey == nil {
171189
x, y := secp256k1.S256().ScalarBaseMult(sk.dBytes)
172-
pkBytes := make([]byte, 1+2*pLenSecp256k1)
173-
pkBytes[0] = ecEncodingUncompressed
174-
// pad x and y to the field size and concatenate them
175-
padToSizeAndConcat(pkBytes[1:], x, y, pLenSecp256k1)
190+
176191
sk.pubKey = &pubKeyECDSASecp256k1{
177192
pubKeyCommonECDSA: &pubKeyCommonECDSA{secp256k1Instance},
178-
pkBytes: pkBytes,
193+
pkBytes: secp256k1PkBytes(x, y),
179194
}
180195
}
181196
return sk.pubKey
@@ -279,12 +294,9 @@ func secp256k1DecodePublicKeyCompressed(pkBytes []byte) (*pubKeyECDSASecp256k1,
279294
if x == nil || y == nil {
280295
return nil, invalidInputsErrorf("input %x isn't a compressed serialization of a point on secp256k1", pkBytes)
281296
}
282-
uncompressedPkBytes := make([]byte, 1+2*pLenSecp256k1)
283-
uncompressedPkBytes[0] = ecEncodingUncompressed
284-
padToSizeAndConcat(uncompressedPkBytes[1:], x, y, pLenSecp256k1)
285297

286298
return &pubKeyECDSASecp256k1{
287299
&pubKeyCommonECDSA{secp256k1Instance},
288-
uncompressedPkBytes,
300+
secp256k1PkBytes(x, y),
289301
}, nil
290302
}

ecdsa_test.go

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -185,24 +185,40 @@ func TestECDSAEncodeDecode(t *testing.T) {
185185
// - public key decoding handles input x-coordinates with x and y larger than p (doesn't result in an exception)
186186
// - public key decoding only accepts reduced x and y
187187
t.Run("public key with non-reduced coordinates", func(t *testing.T) {
188-
invalidPK1s := map[SigningAlgorithm]string{
189-
ECDSASecp256k1: "0000000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30",
190-
ECDSAP256: "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000000000000000000000000000",
188+
onflowCryptoErr := "at least one coordinate is larger than the field prime"
189+
goCryptoErr := "invalid P256 element encoding"
190+
191+
invalidPKs := []struct {
192+
signin SigningAlgorithm
193+
pk string
194+
errorMsg string
195+
}{
196+
{
197+
// x >= p
198+
ECDSASecp256k1, "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F0000000000000000000000000000000000000000000000000000000000000000",
199+
onflowCryptoErr,
200+
}, {
201+
ECDSAP256, "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000000000000000000000000000",
202+
goCryptoErr,
203+
}, {
204+
// y >= p
205+
ECDSASecp256k1, "0000000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30",
206+
onflowCryptoErr,
207+
}, {
208+
ECDSAP256, "0000000000000000000000000000000000000000000000000000000000000000FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF",
209+
goCryptoErr,
210+
},
191211
}
192-
invalidPK2s := map[SigningAlgorithm]string{
193-
ECDSASecp256k1: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F0000000000000000000000000000000000000000000000000000000000000000",
194-
ECDSAP256: "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000000000000000000000000000",
212+
213+
for _, invalidPK := range invalidPKs {
214+
pkBytes, err := hex.DecodeString(invalidPK.pk)
215+
require.NoError(t, err)
216+
pk, err := DecodePublicKey(invalidPK.signin, pkBytes)
217+
require.Error(t, err)
218+
assert.True(t, IsInvalidInputsError(err))
219+
assert.ErrorContains(t, err, invalidPK.errorMsg)
220+
assert.Nil(t, pk)
195221
}
196-
// invalidpk1 with x >= p
197-
invalidPk1, err := hex.DecodeString(invalidPK1s[curve])
198-
require.NoError(t, err)
199-
_, err = DecodePublicKey(curve, invalidPk1)
200-
assert.Error(t, err)
201-
// invalidpk2 with y >= p
202-
invalidPk2, err := hex.DecodeString(invalidPK2s[curve])
203-
require.NoError(t, err)
204-
_, err = DecodePublicKey(curve, invalidPk2)
205-
assert.Error(t, err)
206222
})
207223
}
208224
}

internal/blst/non_cgo.go

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)