URI: 
       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")