tMerge pull request #704 from badmofo/ecies-encryption - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit eb981926ebc0844aa6eb5daf1346efe42c7a235c DIR parent 680fbf1d3e57a8bd6e1f3d4d1d7b6f5dc195f73b HTML 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(-) --- DIR diff --git a/lib/bitcoin.py b/lib/bitcoin.py t@@ -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() ) t@@ -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) t@@ -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")