Skip to content

Commit

Permalink
extract msg manipulate and hash algo to Signer and Signature.Verify
Browse files Browse the repository at this point in the history
  • Loading branch information
Yaiba committed Aug 16, 2023
1 parent 428e536 commit d1dc77f
Show file tree
Hide file tree
Showing 7 changed files with 322 additions and 21 deletions.
4 changes: 2 additions & 2 deletions pkg/crypto/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ type KeyType string
type PrivateKey interface {
Bytes() []byte
Type() KeyType
Sign(msg []byte) (*Signature, error)
Sign(msg []byte) ([]byte, error)
PubKey() PublicKey
Hex() string
}

type PublicKey interface {
Bytes() []byte
Type() KeyType
Verify(sig *Signature, msg []byte) error
Verify(sig []byte, hash []byte) error
Address() Address
}

Expand Down
29 changes: 18 additions & 11 deletions pkg/crypto/secp256k1.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package crypto
import (
"crypto/ecdsa"
"encoding/hex"
"fmt"
ethAccount "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
ethCrypto "github.com/ethereum/go-ethereum/crypto"
Expand All @@ -29,19 +28,25 @@ func (s *Secp256k1PrivateKey) PubKey() PublicKey {
}
}

// Sign signs the given message using EIP-191 personal sign.
func (s *Secp256k1PrivateKey) Sign(msg []byte) (*Signature, error) {
// SignMsg signs the given message(not hashed) according to EIP-191 personal_sign.
// This is default signature type for sec256k1.
func (s *Secp256k1PrivateKey) SignMsg(msg []byte) (*Signature, error) {
hash := ethAccount.TextHash(msg)
sig, err := ethCrypto.Sign(hash, s.privateKey)
sig, err := s.Sign(hash)
if err != nil {
return nil, err
}
return &Signature{
Signature: sig,
Type: SIGNATURE_TYPE_SECP256K1,
Type: SIGNATURE_TYPE_SECP256K1_PERSONAL,
}, nil
}

// Sign signs the given hash utilizing go-ethereum's Sign function.
func (s *Secp256k1PrivateKey) Sign(hash []byte) ([]byte, error) {
return ethCrypto.Sign(hash, s.privateKey)
}

func (s *Secp256k1PrivateKey) Type() KeyType {
return Secp256k1
}
Expand All @@ -66,12 +71,14 @@ func (s *Secp256k1PublicKey) Type() KeyType {

// Verify verifies the given signature against the given message according to EIP-191
// personal sign.
func (s *Secp256k1PublicKey) Verify(sig *Signature, msg []byte) error {
hash := ethAccount.TextHash(msg)
// Remove recovery ID
signature := sig.Signature[:len(sig.Signature)-1]
if !ethCrypto.VerifySignature(s.Bytes(), hash, signature) {
return fmt.Errorf("invalid signature")
func (s *Secp256k1PublicKey) Verify(sig []byte, hash []byte) error {
if len(sig) != 64 {
return errInvalidSignature
}

// signature should have the 64 byte [R || S] format
if !ethCrypto.VerifySignature(s.Bytes(), hash, sig) {
return errVerifySignatureFailed
}

return nil
Expand Down
77 changes: 73 additions & 4 deletions pkg/crypto/secp256k1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package crypto

import (
"encoding/hex"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)
Expand All @@ -12,12 +13,80 @@ func TestSecp256k1PrivateKey_Sign(t *testing.T) {
require.NoError(t, err, "error parse private key")

msg := []byte("foo")
hash := Sha256(msg)

sig, err := pk.Sign(msg)
sig, err := pk.Sign(hash)
require.NoError(t, err, "error sign")

err = pk.PubKey().Verify(sig, msg)
require.NoError(t, err, "error verify")
expectSig := "19a4aced02d5b9142b4f622b06442b1904445e16bd25409e6b0ff357ccc021d001d0e7824654b695b4b6e0991cb7507f487b82be4b2ed713d1e3e2cbc3d2518d01"
require.Equal(t, SIGNATURE_SECP256K1_PERSONAL_LENGTH, len(sig), "invalid signature length")
require.EqualValues(t, hex.EncodeToString(sig), expectSig, "invalid signature")
}

func TestSecp256k1PrivateKey_SignMsg(t *testing.T) {
key := "f1aa5a7966c3863ccde3047f6a1e266cdc0c76b399e256b8fede92b1c69e4f4e"
pk, err := loadSecp256k1PrivateKeyFromHex(key)
require.NoError(t, err, "error parse private key")

msg := []byte("foo")

sig, err := pk.SignMsg(msg)
require.NoError(t, err, "error sign msg")

expectSignature := "cb3fed7f6ff36e59054c04a831b215e514052753ee353e6fe31d4b4ef736acd6155127db555d3006ba14fcb4c79bbad56c8e63b81a9896319bb053a9e253475800"
expectSignatureBytes, _ := hex.DecodeString(expectSignature)

expectSig := &Signature{
Signature: expectSignatureBytes,
Type: SIGNATURE_TYPE_SECP256K1_PERSONAL,
}

assert.EqualValues(t, expectSig, sig, "unexpect signature")
}

func TestSecp256k1PublicKey_Verify(t *testing.T) {
key := "04812bef44f6e7b2a19c0b01c2dca5e54ba1935a1890ffdcb93abd0c534b209c21e4f6176823fef493f7b5afaa456f31d5293363d8f801c540ebcc061812890cba"
keyBytes, err := hex.DecodeString(key)
require.NoError(t, err, "error decode key")

pubKey, err := loadSecp256k1PublicKeyFromByte(keyBytes)
require.NoError(t, err, "error parse public key")

msg := []byte("foo")
hash := Sha256(msg)

sig := "19a4aced02d5b9142b4f622b06442b1904445e16bd25409e6b0ff357ccc021d001d0e7824654b695b4b6e0991cb7507f487b82be4b2ed713d1e3e2cbc3d2518d01"
sigBytes, _ := hex.DecodeString(sig)
require.Equal(t, SIGNATURE_SECP256K1_PERSONAL_LENGTH, len(sigBytes), "invalid signature length")

tests := []struct {
name string
sigBytes []byte
wantErr error
}{
{
name: "verify success",
sigBytes: sigBytes[:len(sigBytes)-1],
wantErr: nil,
},
{
name: "invalid signature length",
sigBytes: sigBytes,
wantErr: errInvalidSignature,
},
{
name: "invalid signature",
sigBytes: sigBytes[1:],
wantErr: errVerifySignatureFailed,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := pubKey.Verify(tt.sigBytes, hash)
assert.ErrorIs(t, err, tt.wantErr)
})
}
}

func TestSecp256k1PublicKey_Address(t *testing.T) {
Expand All @@ -26,7 +95,7 @@ func TestSecp256k1PublicKey_Address(t *testing.T) {
require.NoError(t, err, "error decode key")

pubKey, err := loadSecp256k1PublicKeyFromByte(keyBytes)
require.NoError(t, err, "error parse private key")
require.NoError(t, err, "error parse public key")

eq := pubKey.Address().String() == "0xc89D42189f0450C2b2c3c61f58Ec5d628176A1E7"
require.True(t, eq, "mismatch address")
Expand Down
41 changes: 37 additions & 4 deletions pkg/crypto/signature.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,35 @@ package crypto

import (
"fmt"
ethAccount "github.com/ethereum/go-ethereum/accounts"
)

type SignatureType int32

const (
SIGNATURE_TYPE_INVALID SignatureType = iota
SIGNATURE_TYPE_EMPTY
SIGNATURE_TYPE_SECP256K1
SIGNATURE_TYPE_SECP256K1_COMETBFT
SIGNATURE_TYPE_SECP256K1_PERSONAL // ethereum EIP-191 personal_sign
SIGNATURE_TYPE_ED25519
END_SIGNATURE_TYPE
)

const (
SIGNATURE_SECP256K1_COMETBFT_LENGTH = 64
SIGNATURE_SECP256K1_PERSONAL_LENGTH = 65
)

var (
errInvalidSignature = fmt.Errorf("invalid signature")
errVerifySignatureFailed = fmt.Errorf("verify signature failed")
errNotSupportedSignatureType = fmt.Errorf("not supported signature type")
)

// IsValid returns an error if the signature type is invalid.
func (s *SignatureType) IsValid() error {
if *s < SIGNATURE_TYPE_INVALID || *s >= END_SIGNATURE_TYPE {
return fmt.Errorf("invalid signature type '%d'", *s)
return fmt.Errorf("%w: %d", errNotSupportedSignatureType, *s)
}
return nil
}
Expand All @@ -34,6 +47,26 @@ type Signature struct {
}

// Verify verifies the signature against the given public key and data.
func (s *Signature) Verify(publicKey PublicKey, data []byte) error {
return publicKey.Verify(s, data)
func (s *Signature) Verify(publicKey PublicKey, msg []byte) error {
switch s.Type {
case SIGNATURE_TYPE_SECP256K1_PERSONAL:
if len(s.Signature) != SIGNATURE_SECP256K1_PERSONAL_LENGTH {
return errInvalidSignature
}
hash := ethAccount.TextHash(msg)
// Remove recovery ID
sig := s.Signature[:len(s.Signature)-1]
return publicKey.Verify(sig, hash)
case SIGNATURE_TYPE_SECP256K1_COMETBFT:
if len(s.Signature) != SIGNATURE_SECP256K1_COMETBFT_LENGTH {
return errInvalidSignature
}
// cometbft using sha256 and 64 bytes signature(no recovery ID 'v')
hash := Sha256(msg)
return publicKey.Verify(s.Signature, hash)
case SIGNATURE_TYPE_ED25519:
panic("not implemented")
default:
return fmt.Errorf("%w: %d", errNotSupportedSignatureType, s.Type)
}
}
133 changes: 133 additions & 0 deletions pkg/crypto/signature_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package crypto

import (
"encoding/hex"
"github.com/stretchr/testify/assert"
"testing"
)

func TestSignature_Verify(t *testing.T) {
msg := []byte("foo")
anotherMsg := []byte("bar")

secp256k1PubKeyHex := "04812bef44f6e7b2a19c0b01c2dca5e54ba1935a1890ffdcb93abd0c534b209c21e4f6176823fef493f7b5afaa456f31d5293363d8f801c540ebcc061812890cba"
secp256k1PubKeyBytes, _ := hex.DecodeString(secp256k1PubKeyHex)
secp256k1PublicKey, _ := loadSecp256k1PublicKeyFromByte(secp256k1PubKeyBytes)

personalSignSig := "cb3fed7f6ff36e59054c04a831b215e514052753ee353e6fe31d4b4ef736acd6155127db555d3006ba14fcb4c79bbad56c8e63b81a9896319bb053a9e253475800"
personalSignSigBytes, _ := hex.DecodeString(personalSignSig)

cometbftSecp256k1Sig := "19a4aced02d5b9142b4f622b06442b1904445e16bd25409e6b0ff357ccc021d001d0e7824654b695b4b6e0991cb7507f487b82be4b2ed713d1e3e2cbc3d2518d"
cometbftSecp256k1SigBytes, _ := hex.DecodeString(cometbftSecp256k1Sig)

type fields struct {
Signature []byte
Type SignatureType
}
type args struct {
publicKey PublicKey
msg []byte
}

tests := []struct {
name string
fields fields
args args
wantErr error
}{
{
name: "test secp256k1 personal_sign",
fields: fields{
Signature: personalSignSigBytes,
Type: SIGNATURE_TYPE_SECP256K1_PERSONAL,
},
args: args{
publicKey: secp256k1PublicKey,
msg: msg,
},
wantErr: nil,
},
{
name: "test secp256k1 personal_sign invalid signature",
fields: fields{
Signature: personalSignSigBytes[1:],
Type: SIGNATURE_TYPE_SECP256K1_PERSONAL,
},
args: args{
publicKey: secp256k1PublicKey,
msg: msg,
},
wantErr: errInvalidSignature,
},
{
name: "test secp256k1 personal_sign wrong signature",
fields: fields{
Signature: personalSignSigBytes,
Type: SIGNATURE_TYPE_SECP256K1_PERSONAL,
},
args: args{
publicKey: secp256k1PublicKey,
msg: anotherMsg,
},
wantErr: errVerifySignatureFailed,
},
{
name: "test secp256k1 cometbft",
fields: fields{
Signature: cometbftSecp256k1SigBytes,
Type: SIGNATURE_TYPE_SECP256K1_COMETBFT,
},
args: args{
publicKey: secp256k1PublicKey,
msg: msg,
},
wantErr: nil,
},
{
name: "test secp256k1 cometbft invalid signature",
fields: fields{
Signature: cometbftSecp256k1SigBytes[1:],
Type: SIGNATURE_TYPE_SECP256K1_COMETBFT,
},
args: args{
publicKey: secp256k1PublicKey,
msg: msg,
},
wantErr: errInvalidSignature,
},
{
name: "test secp256k1 cometbft wrong signature",
fields: fields{
Signature: cometbftSecp256k1SigBytes,
Type: SIGNATURE_TYPE_SECP256K1_COMETBFT,
},
args: args{
publicKey: secp256k1PublicKey,
msg: anotherMsg,
},
wantErr: errVerifySignatureFailed,
},
{
name: "unsupported signature type",
fields: fields{
Signature: nil,
Type: SIGNATURE_TYPE_INVALID,
},
args: args{
publicKey: secp256k1PublicKey,
msg: msg,
},
wantErr: errNotSupportedSignatureType,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &Signature{
Signature: tt.fields.Signature,
Type: tt.fields.Type,
}
err := s.Verify(tt.args.publicKey, tt.args.msg)
assert.ErrorIs(t, err, tt.wantErr)
})
}
}
Loading

0 comments on commit d1dc77f

Please sign in to comment.