-
Notifications
You must be signed in to change notification settings - Fork 36
/
k1util.go
168 lines (131 loc) · 5.26 KB
/
k1util.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// Package k1util provides functions to sign and verify Ethereum RSV style signatures.
package k1util
import (
stdecdsa "crypto/ecdsa"
"github.com/omni-network/omni/lib/errors"
"github.com/cometbft/cometbft/crypto"
k1 "github.com/cometbft/cometbft/crypto/secp256k1"
cryptopb "github.com/cometbft/cometbft/proto/tendermint/crypto"
"github.com/ethereum/go-ethereum/common"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
cosmosk1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
cosmoscrypto "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
)
// privKeyLen is the length of a secp256k1 private key.
const privKeyLen = 32
// pubkeyCompressedLen is the length of a secp256k1 compressed public key.
const pubkeyCompressedLen = 33
// pubkeyUncompressedLen is the length of a secp256k1 uncompressed public key.
const pubkeyUncompressedLen = 65
// Sign returns a signature from input data.
//
// The produced signature is 65 bytes in the [R || S || V] format where V is 27 or 28.
func Sign(key crypto.PrivKey, input [32]byte) ([65]byte, error) {
bz := key.Bytes()
if len(bz) != privKeyLen {
return [65]byte{}, errors.New("invalid private key length")
}
sig := ecdsa.SignCompact(secp256k1.PrivKeyFromBytes(bz), input[:], false)
// Convert signature from "compact" into "Ethereum R S V" format.
return [65]byte(append(sig[1:], sig[0])), nil
}
// Verify returns whether the 65 byte signature is valid for the provided hash
// and Ethereum address.
//
// Note the signature MUST be 65 bytes in the Ethereum [R || S || V] format.
func Verify(address common.Address, hash [32]byte, sig [65]byte) (bool, error) {
// Adjust V from Ethereum 27/28 to secp256k1 0/1
const vIdx = 64
if v := sig[vIdx]; v != 27 && v != 28 {
return false, errors.New("invalid recovery id (V) format, must be 27 or 28")
}
sig[vIdx] -= 27
pubkey, err := ethcrypto.SigToPub(hash[:], sig[:])
if err != nil {
return false, errors.Wrap(err, "recover public key")
}
actual := ethcrypto.PubkeyToAddress(*pubkey)
return actual == address, nil
}
// PubKeyToAddress returns the Ethereum address for the given k1 public key.
func PubKeyToAddress(pubkey crypto.PubKey) (common.Address, error) {
pubkeyBytes := pubkey.Bytes()
if len(pubkeyBytes) != pubkeyCompressedLen {
return common.Address{}, errors.New("invalid pubkey length", "length", len(pubkeyBytes))
}
ethPubKey, err := ethcrypto.DecompressPubkey(pubkeyBytes)
if err != nil {
return common.Address{}, errors.Wrap(err, "decompress pubkey")
}
return ethcrypto.PubkeyToAddress(*ethPubKey), nil
}
func StdPrivKeyToComet(privkey *stdecdsa.PrivateKey) (crypto.PrivKey, error) {
bz := ethcrypto.FromECDSA(privkey)
if len(bz) != privKeyLen {
return nil, errors.New("invalid private key length")
}
return k1.PrivKey(bz), nil
}
func StdPrivKeyFromComet(privkey crypto.PrivKey) (*stdecdsa.PrivateKey, error) {
bz := privkey.Bytes()
if len(bz) != privKeyLen {
return nil, errors.New("invalid private key length")
}
resp, err := ethcrypto.ToECDSA(bz)
if err != nil {
return nil, errors.Wrap(err, "convert to ECDSA")
}
return resp, nil
}
func StdPubKeyToCosmos(pubkey *stdecdsa.PublicKey) (cosmoscrypto.PubKey, error) {
return PubKeyBytesToCosmos(ethcrypto.CompressPubkey(pubkey))
}
func PubKeyToCosmos(pubkey crypto.PubKey) (cosmoscrypto.PubKey, error) {
return PubKeyBytesToCosmos(pubkey.Bytes())
}
func PubKeyBytesToCosmos(pubkey []byte) (cosmoscrypto.PubKey, error) {
if len(pubkey) != pubkeyCompressedLen {
return nil, errors.New("invalid pubkey length", "length", len(pubkey))
}
return &cosmosk1.PubKey{
Key: pubkey,
}, nil
}
func PBPubKeyFromBytes(pubkey []byte) (cryptopb.PublicKey, error) {
if len(pubkey) != pubkeyCompressedLen {
return cryptopb.PublicKey{}, errors.New("invalid pubkey length", "length", len(pubkey))
}
return cryptopb.PublicKey{Sum: &cryptopb.PublicKey_Secp256K1{Secp256K1: pubkey}}, nil
}
// PubKeyPBToAddress returns the Ethereum address for the given k1 public key.
func PubKeyPBToAddress(pubkey cryptopb.PublicKey) (common.Address, error) {
pubkeyBytes := pubkey.GetSecp256K1()
if len(pubkeyBytes) != pubkeyCompressedLen {
return common.Address{}, errors.New("invalid pubkey length", "length", len(pubkeyBytes))
}
ethPubKey, err := ethcrypto.DecompressPubkey(pubkeyBytes)
if err != nil {
return common.Address{}, errors.Wrap(err, "decompress pubkey")
}
return ethcrypto.PubkeyToAddress(*ethPubKey), nil
}
// PubKeyToBytes64 returns the 64 byte uncompressed version of the public key, by removing the prefix (0x04 for uncompressed keys).
func PubKeyToBytes64(pubkey *stdecdsa.PublicKey) []byte {
return ethcrypto.FromECDSAPub(pubkey)[1:]
}
// PubKeyFromBytes64 returns the public key from the 64 byte uncompressed version.
// It adds the prefix (0x04 for uncompressed keys) to the input bytes.
func PubKeyFromBytes64(pubkey []byte) (*stdecdsa.PublicKey, error) {
if len(pubkey) != pubkeyUncompressedLen-1 {
return nil, errors.New("invalid pubkey length", "length", len(pubkey))
}
const prefix = 0x04
// TODO(corver): Fix possible panics if the pubkey is not on the curve
resp, err := ethcrypto.UnmarshalPubkey(append([]byte{prefix}, pubkey...))
if err != nil {
return nil, errors.Wrap(err, "unmarshal pubkey")
}
return resp, nil
}