-
Notifications
You must be signed in to change notification settings - Fork 288
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
code example please for signing ethereum transactions #2889
Comments
You'd follow the example of the existing no-cgo Sign function here https://github.com/ethereum/go-ethereum/blob/8fddf27a989e246659fd018ea9be37b2b4f55326/crypto/signature_nocgo.go#L61 So to use dcrd's |
Oh, the only real "trick" you have to address is using |
This is not polished by any means, but should be the broad strokes. Run it on the playground: https://go.dev/play/p/gIbvbly7n9h package main
import (
"errors"
"fmt"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
)
var (
ErrInvalidMsgLen = errors.New("invalid message length, need 32 bytes")
ErrInvalidKey = errors.New("invalid private key")
ErrInvalidSignatureLen = errors.New("invalid signature length")
ErrInvalidRecoveryID = errors.New("invalid signature recovery id")
ErrRecoverFailed = errors.New("recovery failed")
)
func Sign(msg []byte, seckey []byte) ([]byte, error) {
if len(msg) != 32 {
return nil, ErrInvalidMsgLen
}
if len(seckey) != 32 {
return nil, ErrInvalidKey
}
var privKey secp256k1.PrivateKey
if overflow := privKey.Key.SetByteSlice(seckey); overflow || privKey.Key.IsZero() {
return nil, ErrInvalidKey
}
sig := ecdsa.SignCompact(&privKey, msg, false)
privKey.Zero()
// Convert to Ethereum signature format with 'recovery id' v at the end.
v := sig[0] - 27
copy(sig, sig[1:])
sig[64] = v
return sig, nil
}
func checkSignature(sig []byte) error {
if len(sig) != 65 {
return ErrInvalidSignatureLen
}
if sig[64] >= 4 {
return ErrInvalidRecoveryID
}
return nil
}
func RecoverPubkey(msg []byte, sig []byte) ([]byte, error) {
if len(msg) != 32 {
return nil, ErrInvalidMsgLen
}
if err := checkSignature(sig); err != nil {
return nil, err
}
// Convert from Ethereum signature format with 'recovery id' v at the end.
var convertedSig [65]byte
copy(convertedSig[1:], sig)
convertedSig[0] = sig[64] + 27
pub, _, err := ecdsa.RecoverCompact(convertedSig[:], msg)
if err != nil {
return nil, ErrRecoverFailed
}
return pub.SerializeUncompressed(), nil
}
func main() {
var msg [32]byte // Ordinarily would be the hash to sign...
var seckey = [32]byte{1} // Ordinarily would come from somewhere else...
sig, err := Sign(msg[:], seckey[:])
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("sig: %x\n", sig)
pubkey, err := RecoverPubkey(msg[:], sig)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("recovered pubkey: %x\n", pubkey)
} |
excellent, works |
I should also note that my original example code was not verifying the provided private key is within the valid range for compact signing with Ethereum, which is [1, N-1], so you would need to implement that for compatibility purposes as well. Namely, as the documentation for secp256k1.PrivKeyFromBytes calls out:
Notice that the range it accepts includes Thus, you would really want to check the incoming raw private key bytes to ensure they are neither a 0 nor overflow (are >= N) for full compatibility. I updated the example code to do that. |
Is this necessary when signing with a For example, is this code safe to use? func Sign(msg []byte, privKey *secp256k1.PrivateKey) ([]byte, error) {
if len(msg) != 32 {
return nil, ErrInvalidMsgLen
}
sig := ecdsa.SignCompact(privKey, msg, false)
// Convert to Ethereum signature format with 'recovery id' v at the end.
v := sig[0] - 27
copy(sig, sig[1:])
sig[64] = v
return sig, nil
} |
Mostly. A That said, realistically speaking, callers should never create a private key that is 0 because that is always invalid in all ECC crypto given public keys are generated by performing a scalar multiplication by the group generator and anything multiplied by 0 is obviously never a valid point on the curve. Moreover, a private key of zero would lead to never being able to create a valid signature which would result in an infinite loop in the signing code. Since your proposed function has an error return, and if it is an exported function that is a part of a public API, you could be extra paranoid and do something like |
@davecgh This makes sense. Thank you for taking the time to explain the details. I have a much better understanding now. Also, I've been following your development through various projects --including this project and btcd-- and I have learned a lot from your code and your excellent writing. Thank you so much! Legend! |
as a follow up to #2883 (thank you folks) , I'm looking for dcrd v4 example code that can be a drop in replacement for https://github.com/ethereum/go-ethereum/blob/master/crypto/secp256k1/secp256.go#L70
The text was updated successfully, but these errors were encountered: