commit eb981926ebc0844aa6eb5daf1346efe42c7a235c
parent 680fbf1d3e57a8bd6e1f3d4d1d7b6f5dc195f73b
Author: ThomasV <thomasv1@gmx.de>
Date: Wed, 28 May 2014 11:14:17 +0200
Merge pull request #704 from badmofo/ecies-encryption
replaced jackjack encryption with corrected ecies implementation
Diffstat:
M | lib/bitcoin.py | | | 135 | +++++++++++++++++++++++++++++++------------------------------------------------ |
1 file changed, 52 insertions(+), 83 deletions(-)
diff --git a/lib/bitcoin.py b/lib/bitcoin.py
@@ -383,21 +383,6 @@ def ECC_YfromX(x,curved=curve_secp256k1, odd=True):
return [_p-My,offset]
raise Exception('ECC_YfromX: No Y found')
-def private_header(msg,v):
- assert v<1, "Can't write version %d private header"%v
- r = ''
- if v==0:
- r += ('%08x'%len(msg)).decode('hex')
- r += sha256(msg)[:2]
- return ('%02x'%v).decode('hex') + ('%04x'%len(r)).decode('hex') + r
-
-def public_header(pubkey,v):
- assert v<1, "Can't write version %d public header"%v
- r = ''
- if v==0:
- r = sha256(pubkey)[:2]
- return '\x6a\x6a' + ('%02x'%v).decode('hex') + ('%04x'%len(r)).decode('hex') + r
-
def negative_point(P):
return Point( P.curve(), P.x(), -P.y(), P.order() )
@@ -493,83 +478,67 @@ class EC_KEY(object):
raise Exception("Bad signature")
- # ecdsa encryption/decryption methods
- # credits: jackjack, https://github.com/jackjack-jj/jeeq
+ # ecies encryption/decryption methods; aes-256-cbc is used as the cipher; hmac-sha256 is used as the mac
@classmethod
def encrypt_message(self, message, pubkey):
- generator = generator_secp256k1
- curved = curve_secp256k1
- r = ''
- msg = private_header(message,0) + message
- msg = msg + ('\x00'*( 32-(len(msg)%32) ))
- msgs = chunks(msg,32)
-
- _r = generator.order()
- str_to_long = string_to_number
-
- P = generator
+
pk = ser_to_point(pubkey)
+ if not ecdsa.ecdsa.point_is_valid(generator_secp256k1, pk.x(), pk.y()):
+ raise Exception('invalid pubkey')
+
+ ephemeral_exponent = number_to_string(ecdsa.util.randrange(pow(2,256)), generator_secp256k1.order())
+ ephemeral = EC_KEY(ephemeral_exponent)
+
+ ecdh_key = (pk * ephemeral.privkey.secret_multiplier).x()
+ ecdh_key = ('%064x' % ecdh_key).decode('hex')
+ key = hashlib.sha512(ecdh_key).digest()
+ key_e, key_m = key[:32], key[32:]
+
+ iv_ciphertext = aes.encryptData(key_e, message)
- for i in range(len(msgs)):
- n = ecdsa.util.randrange( pow(2,256) )
- Mx = str_to_long(msgs[i])
- My, xoffset = ECC_YfromX(Mx, curved)
- M = Point( curved, Mx+xoffset, My, _r )
- T = P*n
- U = pk*n + M
- toadd = point_to_ser(T) + point_to_ser(U)
- toadd = chr(ord(toadd[0])-2 + 2*xoffset) + toadd[1:]
- r += toadd
+ ephemeral_pubkey = ephemeral.get_public_key(compressed=True).decode('hex')
+ encrypted = 'BIE1' + hash_160(pubkey) + ephemeral_pubkey + iv_ciphertext
+ mac = hmac.new(key_m, encrypted, hashlib.sha256).digest()
- return base64.b64encode(public_header(pubkey,0) + r)
+ return base64.b64encode(encrypted + mac)
- def decrypt_message(self, enc):
- G = generator_secp256k1
- curved = curve_secp256k1
- pvk = self.secret
- pubkeys = [point_to_ser(G*pvk,True), point_to_ser(G*pvk,False)]
- enc = base64.b64decode(enc)
- str_to_long = string_to_number
-
- assert enc[:2]=='\x6a\x6a'
-
- phv = str_to_long(enc[2])
- assert phv==0, "Can't read version %d public header"%phv
- hs = str_to_long(enc[3:5])
- public_header=enc[5:5+hs]
- checksum_pubkey=public_header[:2]
- address=filter(lambda x:sha256(x)[:2]==checksum_pubkey, pubkeys)
- assert len(address)>0, 'Bad private key'
- address=address[0]
- enc=enc[5+hs:]
- r = ''
- for Tser,User in map(lambda x:[x[:33],x[33:]], chunks(enc,66)):
- ots = ord(Tser[0])
- xoffset = ots>>1
- Tser = chr(2+(ots&1))+Tser[1:]
- T = ser_to_point(Tser)
- U = ser_to_point(User)
- V = T*pvk
- Mcalc = U + negative_point(V)
- r += ('%064x'%(Mcalc.x()-xoffset)).decode('hex')
-
- pvhv = str_to_long(r[0])
- assert pvhv==0, "Can't read version %d private header"%pvhv
- phs = str_to_long(r[1:3])
- private_header = r[3:3+phs]
- size = str_to_long(private_header[:4])
- checksum = private_header[4:6]
- r = r[3+phs:]
-
- msg = r[:size]
- hashmsg = sha256(msg)[:2]
- checksumok = hashmsg==checksum
-
- return [msg, checksumok, address]
+ def decrypt_message(self, encrypted):
+
+ encrypted = base64.b64decode(encrypted)
+
+ if len(encrypted) < 105:
+ raise Exception('invalid ciphertext: length')
+
+ magic = encrypted[:4]
+ recipient_pubkeyhash = encrypted[4:24]
+ ephemeral_pubkey = encrypted[24:57]
+ iv_ciphertext = encrypted[57:-32]
+ mac = encrypted[-32:]
+
+ if magic != 'BIE1':
+ raise Exception('invalid ciphertext: invalid magic bytes')
+
+ if hash_160(self.get_public_key().decode('hex')) != recipient_pubkeyhash:
+ raise Exception('invalid ciphertext: invalid key')
+
+ try:
+ ephemeral_pubkey = ser_to_point(ephemeral_pubkey)
+ except AssertionError, e:
+ raise Exception('invalid ciphertext: invalid ephemeral pubkey')
+
+ if not ecdsa.ecdsa.point_is_valid(generator_secp256k1, ephemeral_pubkey.x(), ephemeral_pubkey.y()):
+ raise Exception('invalid ciphertext: invalid ephemeral pubkey')
+ ecdh_key = (ephemeral_pubkey * self.privkey.secret_multiplier).x()
+ ecdh_key = ('%064x' % ecdh_key).decode('hex')
+ key = hashlib.sha512(ecdh_key).digest()
+ key_e, key_m = key[:32], key[32:]
+ if mac != hmac.new(key_m, encrypted[:-32], hashlib.sha256).digest():
+ raise Exception('invalid ciphertext: invalid mac')
+ return aes.decryptData(key_e, iv_ciphertext)
@@ -779,7 +748,7 @@ def test_crypto():
if __name__ == '__main__':
- #test_crypto()
+ test_crypto()
test_bip32("000102030405060708090a0b0c0d0e0f", "m/0'/1/2'/2/1000000000")
test_bip32("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542","m/0/2147483647'/1/2147483646'/2")