Skip to content

Commit c6915d1

Browse files
committed
test: use Wycheproof ECDSA SHA256 vectors
1 parent cd568a4 commit c6915d1

File tree

4 files changed

+5021
-9
lines changed

4 files changed

+5021
-9
lines changed

libevm/precompiles/p256verify/p256verify.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,15 @@ func Sign(priv *ecdsa.PrivateKey, hash [32]byte) ([]byte, error) {
9191
if err != nil {
9292
return nil, err
9393
}
94+
return Pack(hash, r, s, &priv.PublicKey), nil
95+
}
9496

97+
func Pack(hash [32]byte, r, s *big.Int, key *ecdsa.PublicKey) []byte {
9598
var in input
9699
copy(in.word(0), hash[:])
97100
r.FillBytes(in.word(1))
98101
s.FillBytes(in.word(2))
99-
priv.X.FillBytes(in.word(3))
100-
priv.Y.FillBytes(in.word(4))
101-
return in[:], nil
102+
key.X.FillBytes(in.word(3))
103+
key.Y.FillBytes(in.word(4))
104+
return in[:]
102105
}

libevm/precompiles/p256verify/p256verify_test.go

Lines changed: 117 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,14 @@ import (
2020
"crypto/ecdsa"
2121
"crypto/elliptic"
2222
"crypto/rand"
23+
"crypto/sha256"
24+
"encoding/asn1"
25+
"encoding/hex"
26+
"encoding/json"
27+
"fmt"
28+
"math/big"
2329
"slices"
30+
"strings"
2431
"testing"
2532

2633
"github.com/holiman/uint256"
@@ -33,6 +40,8 @@ import (
3340
"github.com/ava-labs/libevm/libevm/ethtest"
3441
"github.com/ava-labs/libevm/libevm/hookstest"
3542
"github.com/ava-labs/libevm/params"
43+
44+
_ "embed"
3645
)
3746

3847
// ulerdoganTestCase is the test case from
@@ -41,15 +50,18 @@ import (
4150
// information.
4251
const ulerdoganTestCase = `4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e`
4352

53+
//go:embed testdata/ecdsa_secp256r1_sha256_test.json
54+
var wycheproofECDSASHA256 []byte
55+
56+
type testCase struct {
57+
name string
58+
in []byte
59+
wantSuccess bool
60+
}
61+
4462
func TestPrecompile(t *testing.T) {
4563
assert.Equal(t, params.P256VerifyGas, Precompile{}.RequiredGas(nil), "RequiredGas()")
4664

47-
type testCase struct {
48-
name string
49-
in []byte
50-
wantSuccess bool
51-
}
52-
5365
tests := []testCase{
5466
{
5567
name: "empty_input",
@@ -102,6 +114,11 @@ func TestPrecompile(t *testing.T) {
102114
}
103115
}
104116

117+
tests = append(tests, wycheproofTestCases(t)...)
118+
if t.Failed() {
119+
return
120+
}
121+
105122
for _, tt := range tests {
106123
t.Run(tt.name, func(t *testing.T) {
107124
got, err := Precompile{}.Run(tt.in)
@@ -116,6 +133,100 @@ func TestPrecompile(t *testing.T) {
116133
}
117134
}
118135

136+
type jsonHex []byte
137+
138+
var _ json.Unmarshaler = (*jsonHex)(nil)
139+
140+
func (j *jsonHex) UnmarshalJSON(data []byte) error {
141+
var s string
142+
if err := json.Unmarshal(data, &s); err != nil {
143+
return err
144+
}
145+
b, err := hex.DecodeString(s)
146+
if err != nil {
147+
return err
148+
}
149+
*j = b
150+
return nil
151+
}
152+
153+
func wycheproofTestCases(t *testing.T) []testCase {
154+
t.Helper()
155+
156+
var raw struct {
157+
Groups []struct {
158+
Key struct {
159+
X jsonHex `json:"wx"`
160+
Y jsonHex `json:"wy"`
161+
}
162+
Tests []struct {
163+
ID int `json:"tcId"`
164+
Comment string
165+
Preimage jsonHex `json:"msg"`
166+
ASNSig jsonHex `json:"sig"`
167+
Result string
168+
} `json:"tests"`
169+
} `json:"testGroups"`
170+
}
171+
require.NoError(t, json.Unmarshal(wycheproofECDSASHA256, &raw))
172+
173+
var cases []testCase
174+
for _, group := range raw.Groups {
175+
key := &ecdsa.PublicKey{
176+
Curve: elliptic.P256(),
177+
X: new(big.Int).SetBytes(group.Key.X),
178+
Y: new(big.Int).SetBytes(group.Key.Y),
179+
}
180+
181+
for _, test := range group.Tests {
182+
t.Run(fmt.Sprintf("parse_test_%d", test.ID), func(t *testing.T) {
183+
// Many of the invalid cases are due to ASN1-specific problems,
184+
// which aren't of concern to us.
185+
include := test.Result == "valid" ||
186+
strings.Contains(test.Comment, "r or s") ||
187+
strings.Contains(test.Comment, "r and s") ||
188+
slices.Contains(
189+
[]int{
190+
// Special cases of r and/or s.
191+
286, 294, 295, 303, 304, 340, 341,
192+
342, 343, 356, 357, 358, 359,
193+
},
194+
test.ID,
195+
)
196+
197+
include = include && !slices.Contains(
198+
// These cases have negative r or s value(s) with the same
199+
// absolute value(s) as valid signatures. Packing and then
200+
// unpacking via [big.Int.Bytes] therefore converts them to
201+
// the valid, positive values that pass verification and
202+
// raise false-positive test errors.
203+
[]int{133, 139, 140},
204+
test.ID,
205+
)
206+
if !include {
207+
return
208+
}
209+
210+
var rs [2]*big.Int
211+
rest, err := asn1.Unmarshal(test.ASNSig, &rs)
212+
if err != nil || len(rest) > 0 {
213+
return
214+
}
215+
if rs[0].BitLen() > 256 || rs[1].BitLen() > 256 {
216+
return
217+
}
218+
cases = append(cases, testCase{
219+
name: fmt.Sprintf("wycheproof_ecdsa_secp256r1_sha256_%d", test.ID),
220+
in: Pack(sha256.Sum256(test.Preimage), rs[0], rs[1], key),
221+
wantSuccess: test.Result == "valid",
222+
})
223+
})
224+
}
225+
}
226+
t.Logf("%d Wycheproof cases", len(cases))
227+
return cases
228+
}
229+
119230
func BenchmarkPrecompile(b *testing.B) {
120231
in := common.Hex2Bytes(ulerdoganTestCase)
121232
var p Precompile
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Test vectors from Project Wycheproof; see [original source](https://github.com/C2SP/wycheproof/tree/4a6c2bf5dc4c0b67c770233ad33961ee653996a0) for license and copyright. No changes made.

0 commit comments

Comments
 (0)