Skip to content

Commit 10475f4

Browse files
committed
Merge pull request #1984 from fjl/secp256k1-recover-id-verify
crypto/secp256k1: verify recovery ID before calling libsecp256k1
2 parents 9422eec + e344e1d commit 10475f4

File tree

3 files changed

+89
-81
lines changed

3 files changed

+89
-81
lines changed

crypto/secp256k1/panic_cb.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2015 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package secp256k1
18+
19+
import "C"
20+
import "unsafe"
21+
22+
// Callbacks for converting libsecp256k1 internal faults into
23+
// recoverable Go panics.
24+
25+
//export secp256k1GoPanicIllegal
26+
func secp256k1GoPanicIllegal(msg *C.char, data unsafe.Pointer) {
27+
panic("illegal argument: " + C.GoString(msg))
28+
}
29+
30+
//export secp256k1GoPanicError
31+
func secp256k1GoPanicError(msg *C.char, data unsafe.Pointer) {
32+
panic("internal error: " + C.GoString(msg))
33+
}

crypto/secp256k1/secp256.go

Lines changed: 45 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ package secp256k1
2020

2121
/*
2222
#cgo CFLAGS: -I./libsecp256k1
23-
#cgo darwin CFLAGS: -I/usr/local/include -I/opt/pkg/include
23+
#cgo darwin CFLAGS: -I/usr/local/include
2424
#cgo freebsd CFLAGS: -I/usr/local/include
2525
#cgo linux,arm CFLAGS: -I/usr/local/arm/include
2626
#cgo LDFLAGS: -lgmp
27-
#cgo darwin LDFLAGS: -L/usr/local/lib -L/opt/pkg/lib
27+
#cgo darwin LDFLAGS: -L/usr/local/lib
2828
#cgo freebsd LDFLAGS: -L/usr/local/lib
2929
#cgo linux,arm LDFLAGS: -L/usr/local/arm/lib
3030
#define USE_NUM_GMP
@@ -35,11 +35,14 @@ package secp256k1
3535
#define NDEBUG
3636
#include "./libsecp256k1/src/secp256k1.c"
3737
#include "./libsecp256k1/src/modules/recovery/main_impl.h"
38+
39+
typedef void (*callbackFunc) (const char* msg, void* data);
40+
extern void secp256k1GoPanicIllegal(const char* msg, void* data);
41+
extern void secp256k1GoPanicError(const char* msg, void* data);
3842
*/
3943
import "C"
4044

4145
import (
42-
"bytes"
4346
"errors"
4447
"unsafe"
4548

@@ -62,8 +65,16 @@ var context *C.secp256k1_context
6265
func init() {
6366
// around 20 ms on a modern CPU.
6467
context = C.secp256k1_context_create(3) // SECP256K1_START_SIGN | SECP256K1_START_VERIFY
68+
C.secp256k1_context_set_illegal_callback(context, C.callbackFunc(C.secp256k1GoPanicIllegal), nil)
69+
C.secp256k1_context_set_error_callback(context, C.callbackFunc(C.secp256k1GoPanicError), nil)
6570
}
6671

72+
var (
73+
ErrInvalidMsgLen = errors.New("invalid message length for signature recovery")
74+
ErrInvalidSignatureLen = errors.New("invalid signature length")
75+
ErrInvalidRecoveryID = errors.New("invalid signature recovery id")
76+
)
77+
6778
func GenerateKeyPair() ([]byte, []byte) {
6879
var seckey []byte = randentropy.GetEntropyCSPRNG(32)
6980
var seckey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&seckey[0]))
@@ -177,69 +188,20 @@ func VerifySeckeyValidity(seckey []byte) error {
177188
return nil
178189
}
179190

180-
func VerifySignatureValidity(sig []byte) bool {
181-
//64+1
182-
if len(sig) != 65 {
183-
return false
184-
}
185-
//malleability check, highest bit must be 1
186-
if (sig[32] & 0x80) == 0x80 {
187-
return false
188-
}
189-
//recovery id check
190-
if sig[64] >= 4 {
191-
return false
192-
}
193-
194-
return true
195-
}
196-
197-
//for compressed signatures, does not need pubkey
198-
func VerifySignature(msg []byte, sig []byte, pubkey1 []byte) error {
199-
if msg == nil || sig == nil || pubkey1 == nil {
200-
return errors.New("inputs must be non-nil")
201-
}
202-
if len(sig) != 65 {
203-
return errors.New("invalid signature length")
204-
}
205-
if len(pubkey1) != 65 {
206-
return errors.New("Invalid public key length")
207-
}
208-
209-
//to enforce malleability, highest bit of S must be 0
210-
//S starts at 32nd byte
211-
if (sig[32] & 0x80) == 0x80 { //highest bit must be 1
212-
return errors.New("Signature not malleable")
213-
}
214-
215-
if sig[64] >= 4 {
216-
return errors.New("Recover byte invalid")
217-
}
218-
219-
// if pubkey recovered, signature valid
220-
pubkey2, err := RecoverPubkey(msg, sig)
221-
if err != nil {
222-
return err
223-
}
224-
if len(pubkey2) != 65 {
225-
return errors.New("Invalid recovered public key length")
226-
}
227-
if !bytes.Equal(pubkey1, pubkey2) {
228-
return errors.New("Public key does not match recovered public key")
229-
}
230-
231-
return nil
232-
}
233-
234-
// recovers a public key from the signature
191+
// RecoverPubkey returns the the public key of the signer.
192+
// msg must be the 32-byte hash of the message to be signed.
193+
// sig must be a 65-byte compact ECDSA signature containing the
194+
// recovery id as the last element.
235195
func RecoverPubkey(msg []byte, sig []byte) ([]byte, error) {
236-
if len(sig) != 65 {
237-
return nil, errors.New("Invalid signature length")
196+
if len(msg) != 32 {
197+
return nil, ErrInvalidMsgLen
198+
}
199+
if err := checkSignature(sig); err != nil {
200+
return nil, err
238201
}
239202

240203
msg_ptr := (*C.uchar)(unsafe.Pointer(&msg[0]))
241204
sig_ptr := (*C.uchar)(unsafe.Pointer(&sig[0]))
242-
243205
pubkey := make([]byte, 64)
244206
/*
245207
this slice is used for both the recoverable signature and the
@@ -248,17 +210,15 @@ func RecoverPubkey(msg []byte, sig []byte) ([]byte, error) {
248210
pubkey recovery is one bottleneck during load in Ethereum
249211
*/
250212
bytes65 := make([]byte, 65)
251-
252213
pubkey_ptr := (*C.secp256k1_pubkey)(unsafe.Pointer(&pubkey[0]))
253214
recoverable_sig_ptr := (*C.secp256k1_ecdsa_recoverable_signature)(unsafe.Pointer(&bytes65[0]))
254-
255215
recid := C.int(sig[64])
216+
256217
ret := C.secp256k1_ecdsa_recoverable_signature_parse_compact(
257218
context,
258219
recoverable_sig_ptr,
259220
sig_ptr,
260221
recid)
261-
262222
if ret == C.int(0) {
263223
return nil, errors.New("Failed to parse signature")
264224
}
@@ -269,20 +229,28 @@ func RecoverPubkey(msg []byte, sig []byte) ([]byte, error) {
269229
recoverable_sig_ptr,
270230
msg_ptr,
271231
)
272-
273232
if ret == C.int(0) {
274233
return nil, errors.New("Failed to recover public key")
275-
} else {
276-
serialized_pubkey_ptr := (*C.uchar)(unsafe.Pointer(&bytes65[0]))
277-
278-
var output_len C.size_t
279-
C.secp256k1_ec_pubkey_serialize( // always returns 1
280-
context,
281-
serialized_pubkey_ptr,
282-
&output_len,
283-
pubkey_ptr,
284-
0, // SECP256K1_EC_COMPRESSED
285-
)
286-
return bytes65, nil
287234
}
235+
236+
serialized_pubkey_ptr := (*C.uchar)(unsafe.Pointer(&bytes65[0]))
237+
var output_len C.size_t
238+
C.secp256k1_ec_pubkey_serialize( // always returns 1
239+
context,
240+
serialized_pubkey_ptr,
241+
&output_len,
242+
pubkey_ptr,
243+
0, // SECP256K1_EC_COMPRESSED
244+
)
245+
return bytes65, nil
246+
}
247+
248+
func checkSignature(sig []byte) error {
249+
if len(sig) != 65 {
250+
return ErrInvalidSignatureLen
251+
}
252+
if sig[64] >= 4 {
253+
return ErrInvalidRecoveryID
254+
}
255+
return nil
288256
}

crypto/secp256k1/secp256_test.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,17 @@ func TestSignatureValidity(t *testing.T) {
5656
}
5757
}
5858

59+
func TestInvalidRecoveryID(t *testing.T) {
60+
_, seckey := GenerateKeyPair()
61+
msg := randentropy.GetEntropyCSPRNG(32)
62+
sig, _ := Sign(msg, seckey)
63+
sig[64] = 99
64+
_, err := RecoverPubkey(msg, sig)
65+
if err != ErrInvalidRecoveryID {
66+
t.Fatalf("got %q, want %q", err, ErrInvalidRecoveryID)
67+
}
68+
}
69+
5970
func TestSignAndRecover(t *testing.T) {
6071
pubkey1, seckey := GenerateKeyPair()
6172
msg := randentropy.GetEntropyCSPRNG(32)
@@ -70,10 +81,6 @@ func TestSignAndRecover(t *testing.T) {
7081
if !bytes.Equal(pubkey1, pubkey2) {
7182
t.Errorf("pubkey mismatch: want: %x have: %x", pubkey1, pubkey2)
7283
}
73-
err = VerifySignature(msg, sig, pubkey1)
74-
if err != nil {
75-
t.Errorf("signature verification error: %s", err)
76-
}
7784
}
7885

7986
func TestRandomMessagesWithSameKey(t *testing.T) {

0 commit comments

Comments
 (0)