Skip to content

Commit c8343f2

Browse files
committed
update ecies, support electrum and bitcore. remove redundant encryption.go. update examples.
1 parent 7876bc5 commit c8343f2

File tree

18 files changed

+1020
-500
lines changed

18 files changed

+1020
-500
lines changed

compat/ecies/ecies.go

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
package compat
2+
3+
import (
4+
"crypto/hmac"
5+
"log"
6+
"math/big"
7+
"reflect"
8+
9+
"encoding/base64"
10+
"errors"
11+
12+
ecies "github.com/bitcoin-sv/go-sdk/primitives/aescbc"
13+
ec "github.com/bitcoin-sv/go-sdk/primitives/ec"
14+
c "github.com/bitcoin-sv/go-sdk/primitives/hash"
15+
)
16+
17+
//
18+
// ECIES encryption/decryption methods; AES-128-CBC with PKCS7 is used as the cipher; hmac-sha256 is used as the mac
19+
//
20+
21+
func EncryptSingle(message string, privateKey *ec.PrivateKey) (string, error) {
22+
messageBytes := []byte(message)
23+
return ElectrumEncrypt(messageBytes, privateKey.PubKey(), privateKey, false)
24+
}
25+
26+
func DecryptSingle(encryptedData string, privateKey *ec.PrivateKey) (string, error) {
27+
plainBytes, err := ElectrumDecrypt(encryptedData, privateKey, nil)
28+
if err != nil {
29+
return "", err
30+
}
31+
return string(plainBytes), nil
32+
}
33+
34+
func EncryptShared(message string, toPublicKey *ec.PublicKey, fromPrivateKey *ec.PrivateKey) (string, error) {
35+
messageBytes := []byte(message)
36+
return ElectrumEncrypt(messageBytes, toPublicKey, nil, false)
37+
}
38+
39+
func DecryptShared(encryptedData string, toPrivateKey *ec.PrivateKey, fromPublicKey *ec.PublicKey) (string, error) {
40+
plainBytes, err := ElectrumDecrypt(encryptedData, toPrivateKey, fromPublicKey)
41+
if err != nil {
42+
return "", err
43+
}
44+
return string(plainBytes), nil
45+
}
46+
47+
func ElectrumEncrypt(message []byte, toPublicKey *ec.PublicKey, fromPrivateKey *ec.PrivateKey, noKey bool) (string, error) {
48+
// Generate an ephemeral EC private key if fromPrivateKey is nil
49+
var ephemeralPrivateKey *ec.PrivateKey
50+
if fromPrivateKey == nil {
51+
ephemeralPrivateKey, _ = ec.NewPrivateKey()
52+
} else {
53+
ephemeralPrivateKey = fromPrivateKey
54+
}
55+
56+
// Derive ECDH key
57+
x, y := toPublicKey.Curve.ScalarMult(toPublicKey.X, toPublicKey.Y, ephemeralPrivateKey.D.Bytes())
58+
ecdhKey := (&ec.PublicKey{X: x, Y: y}).SerializeCompressed()
59+
60+
// SHA512(ECDH_KEY)
61+
key := c.Sha512(ecdhKey)
62+
iv, keyE, keyM := key[0:16], key[16:32], key[32:]
63+
64+
// AES encryption
65+
cipherText, err := ecies.AESCBCEncrypt(message, keyE, iv, false)
66+
if err != nil {
67+
return "", err
68+
}
69+
70+
ephemeralPublicKey := ephemeralPrivateKey.PubKey()
71+
var encrypted []byte
72+
if noKey {
73+
// encrypted = magic_bytes(4 bytes) + cipher
74+
encrypted = append([]byte("BIE1"), cipherText...)
75+
} else {
76+
// encrypted = magic_bytes(4 bytes) + ephemeral_public_key(33 bytes) + cipher
77+
encrypted = append(append([]byte("BIE1"), ephemeralPublicKey.SerializeCompressed()...), cipherText...)
78+
}
79+
80+
mac := c.Sha256HMAC(encrypted, keyM)
81+
82+
return base64.StdEncoding.EncodeToString(append(encrypted, mac...)), nil
83+
}
84+
func ElectrumDecrypt(encryptedData string, toPrivateKey *ec.PrivateKey, fromPublicKey *ec.PublicKey) ([]byte, error) {
85+
encrypted, err := base64.StdEncoding.DecodeString(encryptedData)
86+
if err != nil {
87+
return nil, err
88+
}
89+
if len(encrypted) < 52 { // Minimum length: 4 (magic) + 16 (min cipher) + 32 (mac)
90+
return nil, errors.New("invalid encrypted text: length")
91+
}
92+
magic := encrypted[:4]
93+
if string(magic) != "BIE1" {
94+
return nil, errors.New("invalid cipher text: invalid magic bytes")
95+
}
96+
97+
var sharedSecret []byte
98+
var cipherText []byte
99+
var ephemeralPublicKey *ec.PublicKey
100+
101+
if fromPublicKey != nil {
102+
// Use counterparty public key to derive shared secret
103+
x, y := toPrivateKey.Curve.ScalarMult(fromPublicKey.X, fromPublicKey.Y, toPrivateKey.D.Bytes())
104+
sharedSecret = (&ec.PublicKey{X: x, Y: y}).SerializeCompressed()
105+
if len(encrypted) > 69 { // 4 (magic) + 33 (pubkey) + 32 (mac)
106+
cipherText = encrypted[37 : len(encrypted)-32]
107+
} else {
108+
cipherText = encrypted[4 : len(encrypted)-32]
109+
}
110+
} else {
111+
// Use ephemeral public key to derive shared secret
112+
ephemeralPublicKey, err = ec.ParsePubKey(encrypted[4:37])
113+
if err != nil {
114+
return nil, err
115+
}
116+
x, y := ephemeralPublicKey.Curve.ScalarMult(ephemeralPublicKey.X, ephemeralPublicKey.Y, toPrivateKey.D.Bytes())
117+
sharedSecret = (&ec.PublicKey{X: x, Y: y}).SerializeCompressed()
118+
cipherText = encrypted[37 : len(encrypted)-32]
119+
}
120+
121+
// Derive key_e, iv and key_m
122+
key := c.Sha512(sharedSecret)
123+
iv, keyE, keyM := key[0:16], key[16:32], key[32:]
124+
125+
// Verify mac
126+
mac := encrypted[len(encrypted)-32:]
127+
macRecalculated := c.Sha256HMAC(encrypted[:len(encrypted)-32], keyM)
128+
if !reflect.DeepEqual(mac, macRecalculated) {
129+
return nil, errors.New("incorrect password")
130+
}
131+
132+
// AES decryption
133+
plain, err := ecies.AESCBCDecrypt(cipherText, keyE, iv)
134+
if err != nil {
135+
return nil, err
136+
}
137+
log.Printf("IV: %x, plain text: %x", iv, plain)
138+
return plain, nil
139+
}
140+
141+
// func BitcoreEncrypt(message []byte, publicKey *ec.PublicKey) (string, error) {
142+
// // Generate an ephemeral EC private key
143+
// ephemeralPrivateKey, err := ec.NewPrivateKey()
144+
// if err != nil {
145+
// return "", err
146+
// }
147+
148+
// // Derive shared secret
149+
// x, _ := publicKey.Curve.ScalarMult(publicKey.X, publicKey.Y, ephemeralPrivateKey.D.Bytes())
150+
// sharedSecret := x.Bytes()
151+
152+
// // Key derivation
153+
// keyMaterial := c.Sha512(sharedSecret)
154+
155+
// keyE := keyMaterial[:32] // AES-256 key
156+
// keyM := keyMaterial[32:] // HMAC key
157+
// iv := make([]byte, 16) // IV for AES (all zeros in Bitcore)
158+
159+
// // Encrypt the message
160+
// cipherText, err := aescbc.AESEncryptWithIV(message, keyE, iv)
161+
// if err != nil {
162+
// return "", err
163+
// }
164+
165+
// // Prepare the output
166+
// ephemeralPublicKey := ephemeralPrivateKey.PubKey().SerializeCompressed()
167+
// encryptedData := append(ephemeralPublicKey, cipherText...)
168+
169+
// // Calculate HMAC
170+
// hmacSum := c.Sha256HMAC(encryptedData, keyM)
171+
172+
// // Combine all parts
173+
// result := append(encryptedData, hmacSum...)
174+
175+
// return base64.StdEncoding.EncodeToString(result), nil
176+
// }
177+
178+
func BitcoreEncrypt(message []byte, toPublicKey *ec.PublicKey, fromPrivateKey *ec.PrivateKey, iv []byte) (string, error) {
179+
180+
// JS Implementation
181+
// if (!fromPrivateKey) {
182+
// fromPrivateKey = PrivateKey.fromRandom()
183+
// }
184+
// const r = fromPrivateKey
185+
// const RPublicKey = fromPrivateKey.toPublicKey()
186+
// const RBuf = RPublicKey.encode(true) as number[]
187+
// const KB = toPublicKey
188+
// const P = KB.mul(r)
189+
// const S = P.getX()
190+
// const Sbuf = S.toArray('be', 32)
191+
// const kEkM = Hash.sha512(Sbuf)
192+
// const kE = kEkM.slice(0, 32)
193+
// const kM = kEkM.slice(32, 64)
194+
// const c = AESCBC.encrypt(messageBuf, kE, ivBuf)
195+
// const d = Hash.sha256hmac(kM, [...c])
196+
// const encBuf = [...RBuf, ...c, ...d]
197+
// return encBuf
198+
// If IV is not provided, generate a random one
199+
200+
if iv == nil {
201+
iv = make([]byte, 16)
202+
}
203+
204+
// If fromPrivateKey is not provided, generate a random one
205+
if fromPrivateKey == nil {
206+
var err error
207+
fromPrivateKey, err = ec.NewPrivateKey()
208+
if err != nil {
209+
return "", err
210+
}
211+
}
212+
213+
RBuf := fromPrivateKey.PubKey().ToDERBytes()
214+
P := toPublicKey.Mul(fromPrivateKey.D)
215+
216+
Sbuf := P.X.Bytes()
217+
kEkM := c.Sha512(Sbuf)
218+
kE := kEkM[:32]
219+
kM := kEkM[32:]
220+
cc, err := ecies.AESCBCEncrypt(message, kE, iv, true)
221+
if err != nil {
222+
return "", err
223+
}
224+
d := c.Sha256HMAC(cc, kM)
225+
encBuf := append(RBuf, cc...)
226+
encBuf = append(encBuf, d...)
227+
228+
log.Printf("encBuf: %x", encBuf)
229+
result := base64.StdEncoding.EncodeToString(encBuf)
230+
return result, nil
231+
}
232+
233+
func BitcoreDecrypt(encryptedMessage string, toPrivatKey *ec.PrivateKey) ([]byte, error) {
234+
// const kB = toPrivateKey
235+
// const fromPublicKey = PublicKey.fromString(toHex(encBuf.slice(0, 33)))
236+
// const R = fromPublicKey
237+
// const P = R.mul(kB)
238+
// if (P.eq(new Point(0, 0))) {
239+
// throw new Error('P equals 0')
240+
// }
241+
// const S = P.getX()
242+
// const Sbuf = S.toArray('be', 32)
243+
// const kEkM = Hash.sha512(Sbuf)
244+
// const kE = kEkM.slice(0, 32)
245+
// const kM = kEkM.slice(32, 64)
246+
// const c = encBuf.slice(33, encBuf.length - 32)
247+
// const d = encBuf.slice(encBuf.length - 32, encBuf.length)
248+
// const d2 = Hash.sha256hmac(kM, c)
249+
// if (toHex(d) !== toHex(d2)) {
250+
// throw new Error('Invalid checksum')
251+
// }
252+
// const messageBuf = AESCBC.decrypt(c, kE)
253+
// return [...messageBuf]
254+
255+
data, err := base64.StdEncoding.DecodeString(encryptedMessage)
256+
if err != nil {
257+
return nil, err
258+
}
259+
260+
fromPublicKey, err := ec.ParsePubKey(data[:33])
261+
if err != nil {
262+
return nil, err
263+
}
264+
265+
P := fromPublicKey.Mul(toPrivatKey.D)
266+
if P.X.Cmp(big.NewInt(0)) == 0 && P.Y.Cmp(big.NewInt(0)) == 0 {
267+
return nil, errors.New("p equals 0")
268+
}
269+
270+
Sbuf := P.X.Bytes()
271+
kEkM := c.Sha512(Sbuf)
272+
kE := kEkM[:32]
273+
kM := kEkM[32:]
274+
275+
cipherText := data[33 : len(data)-32]
276+
mac := data[len(data)-32:]
277+
expectedMAC := c.Sha256HMAC(cipherText, kM)
278+
if !hmac.Equal(mac, expectedMAC) {
279+
return nil, errors.New("invalid ciphertext: HMAC mismatch")
280+
}
281+
iv := cipherText[:16]
282+
return ecies.AESCBCDecrypt(cipherText[16:], kE, iv)
283+
284+
// Derive shared secret
285+
// x, _ := privateKey.Curve.ScalarMult(fromPublicKey.X, fromPublicKey.Y, privateKey.D.Bytes())
286+
// sharedSecret := x.Bytes()
287+
288+
// Key derivation
289+
// keyMaterial := c.Sha512(sharedSecret)
290+
291+
// keyE := keyMaterial[:32] // AES-256 key
292+
// keyM := keyMaterial[32:] // HMAC key
293+
294+
// // Verify HMAC
295+
// mac := data[len(data)-32:]
296+
// encryptedData := data[:len(data)-32]
297+
// expectedMAC := c.Sha256HMAC(encryptedData, keyM)
298+
299+
// if !hmac.Equal(mac, expectedMAC) {
300+
// return nil, errors.New("invalid ciphertext: HMAC mismatch")
301+
// }
302+
303+
// // Decrypt
304+
// iv := make([]byte, 16) // In Bitcore, IV is usually all zeros
305+
// cipherText := encryptedData[33:]
306+
// plainText, err := ecies.AESCBCDecrypt(cipherText, keyE, iv)
307+
// if err != nil {
308+
// return nil, err
309+
// }
310+
311+
// return plainText, nil
312+
}

0 commit comments

Comments
 (0)