main
go 378 lines 10.4 KB
Raw
1 package identity
2
3 import (
4 "crypto/sha256"
5 "encoding/hex"
6 "errors"
7 "fmt"
8 "strings"
9
10 "github.com/decred/dcrd/dcrec/secp256k1/v4"
11 "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
12 "golang.org/x/crypto/sha3"
13
14 "github.com/gosuda/portal-tunnel/v2/types"
15 )
16
17 const (
18 // compactSecp256k1SignatureSize is the byte length of a compact
19 // recoverable secp256k1 ECDSA signature.
20 compactSecp256k1SignatureSize = 65
21 // rawSecp256k1SignatureSize is the byte length of the JOSE ES256K
22 // signature form, r || s with no recovery header.
23 rawSecp256k1SignatureSize = 64
24 )
25
26 // ErrSecp256k1SignatureInvalid marks a well-formed signature that does not
27 // verify for the payload and public key.
28 var ErrSecp256k1SignatureInvalid = errors.New("signature is invalid")
29
30 type Secp256k1Signature struct {
31 compact []byte
32 }
33
34 func newSecp256k1SignatureFromCompact(compact []byte) (Secp256k1Signature, error) {
35 normalized, err := copySecp256k1CompactSignature(compact)
36 if err != nil {
37 return Secp256k1Signature{}, err
38 }
39 return Secp256k1Signature{compact: normalized}, nil
40 }
41
42 func (s Secp256k1Signature) Compact() ([]byte, error) {
43 return copySecp256k1CompactSignature(s.compact)
44 }
45
46 func (s Secp256k1Signature) Raw64() ([]byte, error) {
47 compact, err := copySecp256k1CompactSignature(s.compact)
48 if err != nil {
49 return nil, err
50 }
51
52 signature := make([]byte, rawSecp256k1SignatureSize)
53 copy(signature[:32], compact[1:33])
54 copy(signature[32:], compact[33:65])
55 return signature, nil
56 }
57
58 func (s Secp256k1Signature) DERHex() (string, error) {
59 raw, err := s.Raw64()
60 if err != nil {
61 return "", err
62 }
63
64 signature, err := secp256k1SignatureFromRaw64(raw)
65 if err != nil {
66 return "", err
67 }
68 return hex.EncodeToString(signature.Serialize()), nil
69 }
70
71 func NormalizeEVMAddress(raw string) (string, error) {
72 trimmed := strings.TrimSpace(raw)
73 if trimmed == "" {
74 return "", errors.New("address is required")
75 }
76 hexPart := trimHexPrefix(trimmed)
77 if hexPart == trimmed {
78 return "", errors.New("address must start with 0x")
79 }
80 if len(hexPart) != 40 {
81 return "", errors.New("address must be 20 bytes")
82 }
83 if _, err := hex.DecodeString(hexPart); err != nil {
84 return "", errors.New("address must be hex encoded")
85 }
86
87 lowerHex := strings.ToLower(hexPart)
88 hasher := sha3.NewLegacyKeccak256()
89 _, _ = hasher.Write([]byte(lowerHex))
90 hash := hasher.Sum(nil)
91
92 var builder strings.Builder
93 builder.Grow(len(lowerHex))
94 for idx, ch := range lowerHex {
95 if ch >= '0' && ch <= '9' {
96 builder.WriteRune(ch)
97 continue
98 }
99
100 nibble := hash[idx/2]
101 if idx%2 == 0 {
102 nibble >>= 4
103 } else {
104 nibble &= 0x0f
105 }
106 if nibble > 7 {
107 builder.WriteRune(ch - ('a' - 'A'))
108 continue
109 }
110 builder.WriteRune(ch)
111 }
112
113 checksummed := builder.String()
114 if hexPart != lowerHex && hexPart != strings.ToUpper(hexPart) && hexPart != checksummed {
115 return "", errors.New("address checksum is invalid")
116 }
117 return "0x" + checksummed, nil
118 }
119
120 func AddressFromCompressedPublicKeyHex(rawPublicKey string) (string, error) {
121 publicKey, err := ParseSecp256k1PublicKeyHex(rawPublicKey)
122 if err != nil {
123 return "", err
124 }
125
126 uncompressed := publicKey.SerializeUncompressed()
127 if len(uncompressed) != 65 || uncompressed[0] != 0x04 {
128 return "", errors.New("invalid uncompressed secp256k1 public key")
129 }
130
131 hasher := sha3.NewLegacyKeccak256()
132 _, _ = hasher.Write(uncompressed[1:])
133 hash := hasher.Sum(nil)
134
135 return NormalizeEVMAddress("0x" + hex.EncodeToString(hash[len(hash)-20:]))
136 }
137
138 func signEthereumPersonalMessage(message, privateKeyHex string) (string, error) {
139 privateKey, _, err := parseSecp256k1PrivateKeyHex(privateKeyHex, false)
140 if err != nil {
141 return "", err
142 }
143
144 data := []byte(message)
145 prefix := []byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(data)))
146 hasher := sha3.NewLegacyKeccak256()
147 _, _ = hasher.Write(prefix)
148 _, _ = hasher.Write(data)
149 hash := hasher.Sum(nil)
150
151 compactSignature := ecdsa.SignCompact(privateKey, hash, false)
152 if len(compactSignature) != 65 {
153 return "", errors.New("invalid compact signature length")
154 }
155
156 signature := make([]byte, 65)
157 copy(signature[:32], compactSignature[1:33])
158 copy(signature[32:64], compactSignature[33:65])
159 signature[64] = compactSignature[0]
160 return "0x" + hex.EncodeToString(signature), nil
161 }
162
163 func ResolveSecp256k1Identity(rawPrivateKey string) (types.Identity, error) {
164 privateKeyHex := strings.TrimSpace(rawPrivateKey)
165 if privateKeyHex == "" {
166 privateKey, err := secp256k1.GeneratePrivateKey()
167 if err != nil {
168 return types.Identity{}, fmt.Errorf("generate secp256k1 private key: %w", err)
169 }
170 privateKeyHex = hex.EncodeToString(privateKey.Serialize())
171 }
172
173 privateKey, normalizedKeyHex, err := parseSecp256k1PrivateKeyHex(privateKeyHex, true)
174 if err != nil {
175 return types.Identity{}, err
176 }
177
178 publicKeyHex := hex.EncodeToString(privateKey.PubKey().SerializeCompressed())
179 address, err := AddressFromCompressedPublicKeyHex(publicKeyHex)
180 if err != nil {
181 return types.Identity{}, err
182 }
183
184 return types.Identity{
185 Address: address,
186 PublicKey: publicKeyHex,
187 PrivateKey: normalizedKeyHex,
188 }, nil
189 }
190
191 func signSHA256Secp256k1(payload []byte, privateKey *secp256k1.PrivateKey) (Secp256k1Signature, error) {
192 if privateKey == nil {
193 return Secp256k1Signature{}, errors.New("signing key is required")
194 }
195 hash := sha256.Sum256(payload)
196 return newSecp256k1SignatureFromCompact(ecdsa.SignCompact(privateKey, hash[:], true))
197 }
198
199 // RecoverSHA256Secp256k1Compact recovers the public key from a compact
200 // recoverable signature over the SHA-256 digest of payload.
201 func RecoverSHA256Secp256k1Compact(payload, signature []byte) (*secp256k1.PublicKey, error) {
202 if len(signature) != compactSecp256k1SignatureSize {
203 return nil, errors.New("invalid compact signature length")
204 }
205
206 hash := sha256.Sum256(payload)
207 publicKey, _, err := ecdsa.RecoverCompact(signature, hash[:])
208 if err != nil {
209 return nil, err
210 }
211 if publicKey == nil {
212 return nil, ErrSecp256k1SignatureInvalid
213 }
214 return publicKey, nil
215 }
216
217 // VerifySHA256Secp256k1Raw64 verifies an ES256K raw r || s signature over the
218 // SHA-256 digest of payload.
219 func VerifySHA256Secp256k1Raw64(payload, signature []byte, publicKey *secp256k1.PublicKey) error {
220 if publicKey == nil {
221 return errors.New("verification key is required")
222 }
223 if len(signature) != rawSecp256k1SignatureSize {
224 return errors.New("invalid es256k signature length")
225 }
226
227 parsed, err := secp256k1SignatureFromRaw64(signature)
228 if err != nil {
229 return err
230 }
231
232 return verifySHA256Secp256k1Signature(payload, parsed, publicKey)
233 }
234
235 func copySecp256k1CompactSignature(compact []byte) ([]byte, error) {
236 if len(compact) != compactSecp256k1SignatureSize {
237 return nil, errors.New("invalid compact signature length")
238 }
239 if _, err := secp256k1CompactRecoveryID(compact[0]); err != nil {
240 return nil, err
241 }
242 if _, err := secp256k1SignatureFromRaw64(compact[1:]); err != nil {
243 return nil, err
244 }
245
246 normalized := make([]byte, compactSecp256k1SignatureSize)
247 copy(normalized, compact)
248 return normalized, nil
249 }
250
251 func secp256k1CompactRecoveryID(header byte) (byte, error) {
252 switch {
253 case header >= 27 && header <= 30:
254 return header - 27, nil
255 case header >= 31 && header <= 34:
256 return header - 31, nil
257 default:
258 return 0, errors.New("invalid compact signature header")
259 }
260 }
261
262 func secp256k1SignatureFromRaw64(signature []byte) (*ecdsa.Signature, error) {
263 if len(signature) != rawSecp256k1SignatureSize {
264 return nil, errors.New("invalid es256k signature length")
265 }
266
267 var r, s secp256k1.ModNScalar
268 if overflow := r.SetByteSlice(signature[:32]); overflow || r.IsZero() {
269 return nil, errors.New("invalid es256k signature r")
270 }
271 if overflow := s.SetByteSlice(signature[32:]); overflow || s.IsZero() {
272 return nil, errors.New("invalid es256k signature s")
273 }
274 return ecdsa.NewSignature(&r, &s), nil
275 }
276
277 func VerifySHA256Secp256k1DER(payload []byte, publicKeyHex, signatureHex string) error {
278 pubKey, err := ParseSecp256k1PublicKeyHex(publicKeyHex)
279 if err != nil {
280 return err
281 }
282
283 sigText := strings.TrimSpace(signatureHex)
284 if sigText == "" {
285 return errors.New("signature is required")
286 }
287 sigText = trimHexPrefix(sigText)
288
289 sigBytes, err := hex.DecodeString(sigText)
290 if err != nil {
291 return errors.New("signature must be hex encoded")
292 }
293 signature, err := ecdsa.ParseDERSignature(sigBytes)
294 if err != nil {
295 return fmt.Errorf("parse signature: %w", err)
296 }
297
298 return verifySHA256Secp256k1Signature(payload, signature, pubKey)
299 }
300
301 func verifySHA256Secp256k1Signature(payload []byte, signature *ecdsa.Signature, publicKey *secp256k1.PublicKey) error {
302 if signature == nil {
303 return errors.New("signature is required")
304 }
305 if publicKey == nil {
306 return errors.New("verification key is required")
307 }
308 hash := sha256.Sum256(payload)
309 if !signature.Verify(hash[:], publicKey) {
310 return ErrSecp256k1SignatureInvalid
311 }
312 return nil
313 }
314
315 func ParseSecp256k1PublicKeyHex(raw string) (*secp256k1.PublicKey, error) {
316 publicKeyHex := strings.TrimSpace(raw)
317 if publicKeyHex == "" {
318 return nil, errors.New("public key is required")
319 }
320 publicKeyHex = trimHexPrefix(publicKeyHex)
321
322 decoded, err := hex.DecodeString(publicKeyHex)
323 if err != nil {
324 return nil, errors.New("public key must be hex encoded")
325 }
326
327 publicKey, err := secp256k1.ParsePubKey(decoded)
328 if err != nil {
329 return nil, errors.New("invalid secp256k1 public key")
330 }
331 return publicKey, nil
332 }
333
334 func parseSecp256k1PrivateKeyHex(raw string, requireNonZero bool) (*secp256k1.PrivateKey, string, error) {
335 privateKeyHex := strings.TrimSpace(raw)
336 if privateKeyHex == "" {
337 return nil, "", errors.New("private key is required")
338 }
339 privateKeyHex = trimHexPrefix(privateKeyHex)
340
341 decoded, err := hex.DecodeString(privateKeyHex)
342 if err != nil {
343 return nil, "", errors.New("secp256k1 private key must be hex encoded")
344 }
345 if len(decoded) != secp256k1.PrivKeyBytesLen {
346 return nil, "", fmt.Errorf("secp256k1 private key must be %d bytes", secp256k1.PrivKeyBytesLen)
347 }
348 if !requireNonZero {
349 key := secp256k1.PrivKeyFromBytes(decoded)
350 if key == nil {
351 return nil, "", errors.New("invalid secp256k1 private key")
352 }
353 return key, privateKeyHex, nil
354 }
355
356 isZero := true
357 for _, b := range decoded {
358 if b != 0 {
359 isZero = false
360 break
361 }
362 }
363 if isZero {
364 return nil, "", errors.New("secp256k1 private key must not be zero")
365 }
366 key := secp256k1.PrivKeyFromBytes(decoded)
367 if key == nil {
368 return nil, "", errors.New("invalid secp256k1 private key")
369 }
370 return key, privateKeyHex, nil
371 }
372
373 func trimHexPrefix(raw string) string {
374 if len(raw) >= 2 && raw[0] == '0' && (raw[1] == 'x' || raw[1] == 'X') {
375 return raw[2:]
376 }
377 return raw
378 }