-
Notifications
You must be signed in to change notification settings - Fork 16
/
ethereum.go
260 lines (233 loc) · 7.4 KB
/
ethereum.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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
// Package ethereum provides cryptographic operations used in go-dvote related
// to ethereum.
package ethereum
import (
"bytes"
"crypto/ecdsa"
"encoding/hex"
"errors"
"fmt"
"sync"
ethcommon "github.com/ethereum/go-ethereum/common"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"go.vocdoni.io/dvote/types"
"go.vocdoni.io/dvote/util"
)
const (
// SignatureLength is the size of an ECDSA signature in hexString format
SignatureLength = ethcrypto.SignatureLength
// PubKeyLengthBytes is the size of a Public Key
PubKeyLengthBytes = 33
// PubKeyLengthBytesUncompressed is the size of a uncompressed Public Key
PubKeyLengthBytesUncompressed = 65
// SigningPrefix is the prefix added when hashing
SigningPrefix = "\u0019Ethereum Signed Message:\n"
// DefaultSIKPayload conatains the default seed to sing during
// SIK generation
DefaultSIKPayload = "vocdoni-sik-payload"
)
// SignKeys represents an ECDSA pair of keys for signing.
// Authorized addresses is a list of Ethereum like addresses which are checked on Verify
type SignKeys struct {
Public ecdsa.PublicKey
Private ecdsa.PrivateKey
Authorized map[ethcommon.Address]bool
Lock sync.RWMutex
}
// Address represents an Ethereum like address
type Address ethcommon.Address
// NewSignKeys creates an ECDSA pair of keys for signing
// and initializes the map for authorized keys
func NewSignKeys() *SignKeys {
return &SignKeys{
Private: ecdsa.PrivateKey{},
Authorized: make(map[ethcommon.Address]bool),
}
}
// NewSignKeysBatch creates a set of eth random signing keys
func NewSignKeysBatch(n int) []*SignKeys {
s := make([]*SignKeys, n)
for i := 0; i < n; i++ {
s[i] = NewSignKeys()
if err := s[i].Generate(); err != nil {
panic(err)
}
}
return s
}
// Generate generates new keys
func (k *SignKeys) Generate() error {
key, err := ethcrypto.GenerateKey()
if err != nil {
return err
}
k.Private = *key
k.Public = key.PublicKey
return nil
}
// AddHexKey imports a private hex key
func (k *SignKeys) AddHexKey(privHex string) error {
key, err := ethcrypto.HexToECDSA(util.TrimHex(privHex))
if err != nil {
return err
}
k.Private = *key
k.Public = key.PublicKey
return nil
}
// AddAuthKey adds a new authorized address key
func (k *SignKeys) AddAuthKey(address ethcommon.Address) {
k.Lock.Lock()
k.Authorized[address] = true
k.Lock.Unlock()
}
// HexString returns the public compressed and private keys as hex strings
func (k *SignKeys) HexString() (string, string) {
pubHexComp := fmt.Sprintf("%x", ethcrypto.CompressPubkey(&k.Public))
privHex := fmt.Sprintf("%x", ethcrypto.FromECDSA(&k.Private))
return pubHexComp, privHex
}
// PublicKey returns the compressed public key
func (k *SignKeys) PublicKey() types.HexBytes {
return ethcrypto.CompressPubkey(&k.Public)
}
// PrivateKey returns the private key
func (k *SignKeys) PrivateKey() types.HexBytes {
return ethcrypto.FromECDSA(&k.Private)
}
// DecompressPubKey takes a compressed public key and returns it descompressed. If already decompressed, returns the same key.
func DecompressPubKey(pubComp types.HexBytes) (types.HexBytes, error) {
if len(pubComp) > PubKeyLengthBytes {
return pubComp, nil
}
pub, err := ethcrypto.DecompressPubkey(pubComp)
if err != nil {
return nil, fmt.Errorf("decompress pubKey %w", err)
}
return ethcrypto.FromECDSAPub(pub), nil
}
// CompressPubKey returns the compressed public key in hexString format
func CompressPubKey(pubHexDec string) (string, error) {
pubHexDec = util.TrimHex(pubHexDec)
if len(pubHexDec) < PubKeyLengthBytesUncompressed*2 {
return pubHexDec, nil
}
pubBytes, err := hex.DecodeString(pubHexDec)
if err != nil {
return "", err
}
pub, err := ethcrypto.UnmarshalPubkey(pubBytes)
if err != nil {
return "", err
}
return fmt.Sprintf("%x", ethcrypto.CompressPubkey(pub)), nil
}
// Address returns the SignKeys ethereum address
func (k *SignKeys) Address() ethcommon.Address {
return ethcrypto.PubkeyToAddress(k.Public)
}
// AddressString returns the ethereum Address as string
func (k *SignKeys) AddressString() string {
return ethcrypto.PubkeyToAddress(k.Public).String()
}
// SignEthereum signs a message. Message is a normal string (no HexString nor a Hash)
func (k *SignKeys) SignEthereum(message []byte) ([]byte, error) {
if k.Private.D == nil {
return nil, errors.New("no private key available")
}
signature, err := ethcrypto.Sign(Hash(message), &k.Private)
if err != nil {
return nil, err
}
return signature, nil
}
// Sign signs a raw message. TxData is the full transaction payload (no HexString nor a Hash)
func (k *SignKeys) Sign(txData []byte) ([]byte, error) {
if k.Private.D == nil {
return nil, errors.New("no private key available")
}
signature, err := ethcrypto.Sign(Hash(txData), &k.Private)
if err != nil {
return nil, err
}
return signature, nil
}
// VerifySender verifies if a message is sent by some Authorized address key
func (k *SignKeys) VerifySender(message, signature []byte) (bool, ethcommon.Address, error) {
recoveredAddr, err := AddrFromSignature(message, signature)
if err != nil {
return false, ethcommon.Address{}, err
}
k.Lock.RLock()
defer k.Lock.RUnlock()
if k.Authorized[recoveredAddr] {
return true, recoveredAddr, nil
}
return false, recoveredAddr, nil
}
// AddrFromPublicKey standaolone function to obtain the Ethereum address from a ECDSA public key
func AddrFromPublicKey(pub []byte) (ethcommon.Address, error) {
var err error
if len(pub) <= PubKeyLengthBytes {
pub, err = DecompressPubKey(pub)
if err != nil {
return ethcommon.Address{}, err
}
}
pubkey, err := ethcrypto.UnmarshalPubkey(pub)
if err != nil {
return ethcommon.Address{}, err
}
return ethcrypto.PubkeyToAddress(*pubkey), nil
}
// AddrFromBytes returns the Ethereum address from a byte array
func AddrFromBytes(addr []byte) ethcommon.Address {
return ethcommon.BytesToAddress(addr)
}
// PubKeyFromPrivateKey returns the hex public key given a hex private key
func PubKeyFromPrivateKey(privHex string) (string, error) {
s := NewSignKeys()
if err := s.AddHexKey(privHex); err != nil {
return "", err
}
pub, _ := s.HexString()
return pub, nil
}
// PubKeyFromSignature recovers the ECDSA public key that created the signature of a message
// public key is hex encoded
func PubKeyFromSignature(message, signature []byte) ([]byte, error) {
if len(signature) < SignatureLength || len(signature) > SignatureLength+12 {
// TODO: investigate the exact size (and if a marging is required)
return nil, fmt.Errorf("signature length not correct (%d)", len(signature))
}
if signature[64] > 1 {
signature[64] -= 27
}
if signature[64] > 1 {
return nil, errors.New("bad recover ID byte")
}
pubKey, err := ethcrypto.SigToPub(Hash(message), signature)
if err != nil {
return nil, fmt.Errorf("sigToPub %w", err)
}
// Temporary until the client side changes to compressed keys
return ethcrypto.CompressPubkey(pubKey), nil
}
// AddrFromSignature recovers the Ethereum address that created the signature of a message
func AddrFromSignature(message, signature []byte) (ethcommon.Address, error) {
pub, err := PubKeyFromSignature(message, signature)
if err != nil {
return ethcommon.Address{}, err
}
return AddrFromPublicKey(pub)
}
// Hash data adding Ethereum prefix
func Hash(data []byte) []byte {
var buf bytes.Buffer
fmt.Fprintf(&buf, "%s%d%s", SigningPrefix, len(data), data)
return HashRaw(buf.Bytes())
}
// HashRaw hashes data with no prefix
func HashRaw(data []byte) []byte {
return ethcrypto.Keccak256(data)
}