documentation for offline wallets, release notes
[electrum-nvc.git] / lib / bitcoin.py
index f84bd58..1ae9503 100644 (file)
@@ -18,7 +18,7 @@
 
 
 import hashlib, base64, ecdsa, re
-
+from util import print_error
 
 def rev_hex(s):
     return s.decode('hex')[::-1].encode('hex')
@@ -39,6 +39,17 @@ def var_int(i):
     else:
         return "ff"+int_to_hex(i,8)
 
+def op_push(i):
+    if i<0x4c:
+        return int_to_hex(i)
+    elif i<0xff:
+        return '4c' + int_to_hex(i)
+    elif i<0xffff:
+        return '4d' + int_to_hex(i,2)
+    else:
+        return '4e' + int_to_hex(i,4)
+    
+
 
 Hash = lambda x: hashlib.sha256(hashlib.sha256(x).digest()).digest()
 hash_encode = lambda x: x[::-1].encode('hex')
@@ -262,15 +273,85 @@ generator_secp256k1 = ecdsa.ellipticcurve.Point( curve_secp256k1, _Gx, _Gy, _r )
 oid_secp256k1 = (1,3,132,0,10)
 SECP256k1 = ecdsa.curves.Curve("SECP256k1", curve_secp256k1, generator_secp256k1, oid_secp256k1 ) 
 
+from ecdsa.util import string_to_number, number_to_string
+
+def msg_magic(message):
+    return "\x18Bitcoin Signed Message:\n" + chr( len(message) ) + message
+
+
 class EC_KEY(object):
     def __init__( self, secret ):
         self.pubkey = ecdsa.ecdsa.Public_key( generator_secp256k1, generator_secp256k1 * secret )
         self.privkey = ecdsa.ecdsa.Private_key( self.pubkey, secret )
         self.secret = secret
 
+    def sign_message(self, message, compressed, address):
+        private_key = ecdsa.SigningKey.from_secret_exponent( self.secret, curve = SECP256k1 )
+        public_key = private_key.get_verifying_key()
+        signature = private_key.sign_digest( Hash( msg_magic(message) ), sigencode = ecdsa.util.sigencode_string )
+        assert public_key.verify_digest( signature, Hash( msg_magic(message) ), sigdecode = ecdsa.util.sigdecode_string)
+        for i in range(4):
+            sig = base64.b64encode( chr(27 + i + (4 if compressed else 0)) + signature )
+            try:
+                self.verify_message( address, sig, message)
+                return sig
+            except:
+                continue
+        else:
+            raise BaseException("error: cannot sign message")
+
+    @classmethod
+    def verify_message(self, address, signature, message):
+        """ See http://www.secg.org/download/aid-780/sec1-v2.pdf for the math """
+        from ecdsa import numbertheory, ellipticcurve, util
+        import msqr
+        curve = curve_secp256k1
+        G = generator_secp256k1
+        order = G.order()
+        # extract r,s from signature
+        sig = base64.b64decode(signature)
+        if len(sig) != 65: raise BaseException("Wrong encoding")
+        r,s = util.sigdecode_string(sig[1:], order)
+        nV = ord(sig[0])
+        if nV < 27 or nV >= 35:
+            raise BaseException("Bad encoding")
+        if nV >= 31:
+            compressed = True
+            nV -= 4
+        else:
+            compressed = False
+
+        recid = nV - 27
+        # 1.1
+        x = r + (recid/2) * order
+        # 1.3
+        alpha = ( x * x * x  + curve.a() * x + curve.b() ) % curve.p()
+        beta = msqr.modular_sqrt(alpha, curve.p())
+        y = beta if (beta - recid) % 2 == 0 else curve.p() - beta
+        # 1.4 the constructor checks that nR is at infinity
+        R = ellipticcurve.Point(curve, x, y, order)
+        # 1.5 compute e from message:
+        h = Hash( msg_magic(message) )
+        e = string_to_number(h)
+        minus_e = -e % order
+        # 1.6 compute Q = r^-1 (sR - eG)
+        inv_r = numbertheory.inverse_mod(r,order)
+        Q = inv_r * ( s * R + minus_e * G )
+        public_key = ecdsa.VerifyingKey.from_public_point( Q, curve = SECP256k1 )
+        # check that Q is the public key
+        public_key.verify_digest( sig[1:], h, sigdecode = ecdsa.util.sigdecode_string)
+        # check that we get the original signing address
+        addr = public_key_to_bc_address( encode_point(public_key, compressed) )
+        if address != addr:
+            raise BaseException("Bad signature")
+
 
 ###################################### BIP32 ##############################
 
+random_seed = lambda n: "%032x"%ecdsa.util.randrange( pow(2,n) )
+
+
+
 def bip32_init(seed):
     import hmac
         
@@ -321,115 +402,64 @@ def CKD_prime(K, c, n):
 
 
 
+class DeterministicSequence:
+    """  Privatekey(type,n) = Master_private_key + H(n|S|type)  """
 
+    def __init__(self, master_public_key):
+        self.master_public_key = master_public_key
 
-################################## transactions
-
+    @classmethod
+    def from_seed(klass, seed):
+        curve = SECP256k1
+        secexp = klass.stretch_key(seed)
+        master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
+        master_public_key = master_private_key.get_verifying_key().to_string().encode('hex')
+        self = klass(master_public_key)
+        return self
 
-def tx_filter(s): 
-    out = re.sub('( [^\n]*|)\n','',s)
-    out = out.replace(' ','')
-    out = out.replace('\n','')
-    return out
-
-def raw_tx( inputs, outputs, for_sig = None ):
-
-    s  = int_to_hex(1,4)                                         # version
-    s += var_int( len(inputs) )                                  # number of inputs
-    for i in range(len(inputs)):
-        txin = inputs[i]
-        s += txin['tx_hash'].decode('hex')[::-1].encode('hex')            # prev hash
-        s += int_to_hex(txin['index'],4)                               # prev index
-
-        if for_sig is None:
-            pubkeysig = txin['pubkeysig']
-            if len(pubkeysig) == 1:
-                pubkey, sig = pubkeysig[0]
-                sig = sig + chr(1)                               # hashtype
-                script  = int_to_hex( len(sig))
-                script += sig.encode('hex')
-                script += int_to_hex( len(pubkey))
-                script += pubkey.encode('hex')
-            else:
-                n = txin['multisig_num']
-                pubkeys = map(lambda x:x[0], pubkeysig)
-                script = '00'                                    # op_0
-                for item in pubkeysig:
-                    pubkey, sig = item
-                    sig = sig + chr(1)
-                    script += int_to_hex(len(sig))
-                    script += sig.encode('hex')
-                inner_script = multisig_script(pubkeys)
-                script += var_int(len(inner_script)/2)
-                script += inner_script
-
-        elif for_sig==i:
-            pubkeys = txin.get('pubkeys')
-            if pubkeys:
-                num = txin['p2sh_num']
-                script = multisig_script(pubkeys, num)           # p2sh uses the inner script
-            else:
-                script = txin['raw_output_script']               # scriptsig
-        else:
-            script=''
-        s += var_int( len(tx_filter(script))/2 )                 # script length
-        s += script
-        s += "ffffffff"                                          # sequence
-
-    s += var_int( len(outputs) )                                 # number of outputs
-    for output in outputs:
-        addr, amount = output
-        s += int_to_hex( amount, 8)                              # amount
-        addrtype, hash_160 = bc_address_to_hash_160(addr)
-        if addrtype == 0:
-            script = '76a9'                                      # op_dup, op_hash_160
-            script += '14'                                       # push 0x14 bytes
-            script += hash_160.encode('hex')
-            script += '88ac'                                     # op_equalverify, op_checksig
-        elif addrtype == 5:
-            script = 'a9'                                        # op_hash_160
-            script += '14'                                       # push 0x14 bytes
-            script += hash_160.encode('hex')
-            script += '87'                                       # op_equal
-        else:
-            raise
-            
-        s += var_int( len(tx_filter(script))/2 )                #  script length
-        s += script                                             #  script
-    s += int_to_hex(0,4)                                        #  lock time
-    if for_sig is not None and for_sig != 1: s += int_to_hex(1, 4)               #  hash type
-    return tx_filter(s)
+    @classmethod
+    def stretch_key(self,seed):
+        oldseed = seed
+        for i in range(100000):
+            seed = hashlib.sha256(seed + oldseed).digest()
+        return string_to_number( seed )
+
+    def get_sequence(self,n,for_change):
+        return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.master_public_key.decode('hex') ) )
+
+    def get_pubkey(self, n, for_change):
+        curve = SECP256k1
+        z = self.get_sequence(n, for_change)
+        master_public_key = ecdsa.VerifyingKey.from_string( self.master_public_key.decode('hex'), curve = SECP256k1 )
+        pubkey_point = master_public_key.pubkey.point + z*curve.generator
+        public_key2 = ecdsa.VerifyingKey.from_public_point( pubkey_point, curve = SECP256k1 )
+        return '04' + public_key2.to_string().encode('hex')
+
+    def get_private_key(self, n, for_change, seed):
+        order = generator_secp256k1.order()
+        secexp = self.stretch_key(seed)
+        secexp = ( secexp + self.get_sequence(n,for_change) ) % order
+        pk = number_to_string( secexp, generator_secp256k1.order() )
+        compressed = False
+        return SecretToASecret( pk, compressed )
+
+    def check_seed(self, seed):
+        curve = SECP256k1
+        secexp = self.stretch_key(seed)
+        master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
+        master_public_key = master_private_key.get_verifying_key().to_string().encode('hex')
+        if master_public_key != self.master_public_key:
+            print_error('invalid password (mpk)')
+            raise BaseException('Invalid password')
+
+        return True
 
+################################## transactions
 
 
 
-def multisig_script(public_keys, num=None):
-    # supports only "2 of 2", and "2 of 3" transactions
-    n = len(public_keys)
 
-    if num is None:
-        num = n
 
-    assert num <= n and n <= 3 and n >= 2
-    
-    if num==2:
-        s = '52'
-    elif num == 3:
-        s = '53'
-    else:
-        raise
-    
-    for k in public_keys:
-        s += var_int(len(k)/2)
-        s += k
-    if n==2:
-        s += '52'
-    elif n==3:
-        s += '53'
-    else:
-        raise
-    s += 'ae'
-    return s
 
 
 
@@ -444,40 +474,193 @@ class Transaction:
         
     @classmethod
     def from_io(klass, inputs, outputs):
-        raw = raw_tx(inputs, outputs, for_sig = -1) # for_sig=-1 means do not sign
+        raw = klass.serialize(inputs, outputs, for_sig = -1) # for_sig=-1 means do not sign
         self = klass(raw)
+        self.is_complete = False
         self.inputs = inputs
         self.outputs = outputs
+        extras = []
+        for i in self.inputs:
+            e = { 'txid':i['tx_hash'], 'vout':i['index'],'scriptPubKey':i['raw_output_script'] }
+            extras.append(e)
+        self.input_info = extras
         return self
 
     def __str__(self):
         return self.raw
 
+    @classmethod
+    def multisig_script(klass, public_keys, num=None):
+        n = len(public_keys)
+        if num is None: num = n
+        # supports only "2 of 2", and "2 of 3" transactions
+        assert num <= n and n in [2,3]
+    
+        if num==2:
+            s = '52'
+        elif num == 3:
+            s = '53'
+        else:
+            raise
+    
+        for k in public_keys:
+            s += var_int(len(k)/2)
+            s += k
+        if n==2:
+            s += '52'
+        elif n==3:
+            s += '53'
+        else:
+            raise
+        s += 'ae'
+
+        out = { "address": hash_160_to_bc_address(hash_160(s.decode('hex')), 5), "redeemScript":s }
+        return out
+
+    @classmethod
+    def serialize( klass, inputs, outputs, for_sig = None ):
+
+        s  = int_to_hex(1,4)                                         # version
+        s += var_int( len(inputs) )                                  # number of inputs
+        for i in range(len(inputs)):
+            txin = inputs[i]
+            s += txin['tx_hash'].decode('hex')[::-1].encode('hex')   # prev hash
+            s += int_to_hex(txin['index'],4)                         # prev index
+
+            if for_sig is None:
+                pubkeysig = txin.get('pubkeysig')
+                if pubkeysig:
+                    pubkey, sig = pubkeysig[0]
+                    sig = sig + chr(1)                               # hashtype
+                    script  = op_push( len(sig))
+                    script += sig.encode('hex')
+                    script += op_push( len(pubkey))
+                    script += pubkey.encode('hex')
+                else:
+                    signatures = txin['signatures']
+                    pubkeys = txin['pubkeys']
+                    script = '00'                                    # op_0
+                    for sig in signatures:
+                        sig = sig + '01'
+                        script += op_push(len(sig)/2)
+                        script += sig
+
+                    redeem_script = klass.multisig_script(pubkeys,2).get('redeemScript')
+                    script += op_push(len(redeem_script)/2)
+                    script += redeem_script
+
+            elif for_sig==i:
+                if txin.get('redeemScript'):
+                    script = txin['redeemScript']                    # p2sh uses the inner script
+                else:
+                    script = txin['raw_output_script']               # scriptsig
+            else:
+                script=''
+            s += var_int( len(script)/2 )                            # script length
+            s += script
+            s += "ffffffff"                                          # sequence
+
+        s += var_int( len(outputs) )                                 # number of outputs
+        for output in outputs:
+            addr, amount = output
+            s += int_to_hex( amount, 8)                              # amount
+            addrtype, hash_160 = bc_address_to_hash_160(addr)
+            if addrtype == 0:
+                script = '76a9'                                      # op_dup, op_hash_160
+                script += '14'                                       # push 0x14 bytes
+                script += hash_160.encode('hex')
+                script += '88ac'                                     # op_equalverify, op_checksig
+            elif addrtype == 5:
+                script = 'a9'                                        # op_hash_160
+                script += '14'                                       # push 0x14 bytes
+                script += hash_160.encode('hex')
+                script += '87'                                       # op_equal
+            else:
+                raise
+            
+            s += var_int( len(script)/2 )                           #  script length
+            s += script                                             #  script
+        s += int_to_hex(0,4)                                        #  lock time
+        if for_sig is not None and for_sig != -1:
+            s += int_to_hex(1, 4)                                   #  hash type
+        return s
+
+
     def for_sig(self,i):
-        return raw_tx(self.inputs, self.outputs, for_sig = i)
+        return self.serialize(self.inputs, self.outputs, for_sig = i)
+
 
     def hash(self):
         return Hash(self.raw.decode('hex') )[::-1].encode('hex')
 
     def sign(self, private_keys):
+        import deserialize
 
         for i in range(len(self.inputs)):
             txin = self.inputs[i]
-            sec = private_keys[txin['address']]
-            compressed = is_compressed(sec)
-            pkey = regenerate_key(sec)
-            secexp = pkey.secret
+            tx_for_sig = self.serialize( self.inputs, self.outputs, for_sig = i )
+
+            if txin.get('redeemScript'):
+                # 1 parse the redeem script
+                num, redeem_pubkeys = deserialize.parse_redeemScript(txin.get('redeemScript'))
+                self.inputs[i]["pubkeys"] = redeem_pubkeys
+
+                # build list of public/private keys
+                keypairs = {}
+                for sec in private_keys.values():
+                    compressed = is_compressed(sec)
+                    pkey = regenerate_key(sec)
+                    pubkey = GetPubKey(pkey.pubkey, compressed)
+                    keypairs[ pubkey.encode('hex') ] = sec
+
+                # list of already existing signatures
+                signatures = txin.get("signatures",[])
+                print_error("signatures",signatures)
+
+                for pubkey in redeem_pubkeys:
+                    public_key = ecdsa.VerifyingKey.from_string(pubkey[2:].decode('hex'), curve = SECP256k1)
+                    for s in signatures:
+                        try:
+                            public_key.verify_digest( s.decode('hex')[:-1], Hash( tx_for_sig.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der)
+                            break
+                        except ecdsa.keys.BadSignatureError:
+                            continue
+                    else:
+                        # check if we have a key corresponding to the redeem script
+                        if pubkey in keypairs.keys():
+                            # add signature
+                            sec = keypairs[pubkey]
+                            compressed = is_compressed(sec)
+                            pkey = regenerate_key(sec)
+                            secexp = pkey.secret
+                            private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
+                            public_key = private_key.get_verifying_key()
+                            sig = private_key.sign_digest( Hash( tx_for_sig.decode('hex') ), sigencode = ecdsa.util.sigencode_der )
+                            assert public_key.verify_digest( sig, Hash( tx_for_sig.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der)
+                            signatures.append( sig.encode('hex') )
+                        
+                # for p2sh, pubkeysig is a tuple (may be incomplete)
+                self.inputs[i]["signatures"] = signatures
+                print_error("signatures",signatures)
+                self.is_complete = len(signatures) == num
+
+            else:
+                sec = private_keys[txin['address']]
+                compressed = is_compressed(sec)
+                pkey = regenerate_key(sec)
+                secexp = pkey.secret
 
-            private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
-            public_key = private_key.get_verifying_key()
-            pkey = EC_KEY(secexp)
-            pubkey = GetPubKey(pkey.pubkey, compressed)
-            tx = raw_tx( self.inputs, self.outputs, for_sig = i )
-            sig = private_key.sign_digest( Hash( tx.decode('hex') ), sigencode = ecdsa.util.sigencode_der )
-            assert public_key.verify_digest( sig, Hash( tx.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der)
-            self.inputs[i]["pubkeysig"] = [(pubkey, sig)]
+                private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
+                public_key = private_key.get_verifying_key()
+                pkey = EC_KEY(secexp)
+                pubkey = GetPubKey(pkey.pubkey, compressed)
+                sig = private_key.sign_digest( Hash( tx_for_sig.decode('hex') ), sigencode = ecdsa.util.sigencode_der )
+                assert public_key.verify_digest( sig, Hash( tx_for_sig.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der)
 
-        self.raw = raw_tx( self.inputs, self.outputs )
+                self.inputs[i]["pubkeysig"] = [(pubkey, sig)]
+                self.is_complete = True
+
+        self.raw = self.serialize( self.inputs, self.outputs )
 
 
     def deserialize(self):
@@ -488,6 +671,59 @@ class Transaction:
         return self.d
     
 
+    def has_address(self, addr):
+        found = False
+        for txin in self.inputs:
+            if addr == txin.get('address'): 
+                found = True
+                break
+        for txout in self.outputs:
+            if addr == txout[0]:
+                found = True
+                break
+        return found
+
+
+    def get_value(self, addresses, prevout_values):
+        # return the balance for that tx
+        is_send = False
+        is_pruned = False
+        v_in = v_out = v_out_mine = 0
+
+        for item in self.inputs:
+            addr = item.get('address')
+            if addr in addresses:
+                is_send = True
+                key = item['prevout_hash']  + ':%d'%item['prevout_n']
+                value = prevout_values.get( key )
+                if value is None:
+                    is_pruned = True
+                else:
+                    v_in += value
+            else:
+                is_pruned = True
+                    
+        for item in self.outputs:
+            addr, value = item
+            v_out += value
+            if addr in addresses:
+                v_out_mine += value
+
+        if not is_pruned:
+            # all inputs are mine:
+            fee = v_out - v_in
+            v = v_out_mine - v_in
+        else:
+            # some inputs are mine:
+            fee = None
+            if is_send:
+                v = v_out_mine - v_out
+            else:
+                # no input is mine
+                v = v_out_mine
+            
+        return is_send, v, fee
+
 
 
 def test_bip32():
@@ -525,52 +761,8 @@ def test_bip32():
     print "address", hash_160_to_bc_address(hash_160(K0137_compressed))
         
 
-def test_p2sh():
-
-    print "2 of 2"
-    pubkeys = ["04e89a79651522201d756f14b1874ae49139cc984e5782afeca30ffe84e5e6b2cfadcfe9875c490c8a1a05a4debd715dd57471af8886ab5dfbb3959d97f087f77a",
-               "0455cf4a3ab68a011b18cb0a86aae2b8e9cad6c6355476de05247c57a9632d127084ac7630ad89893b43c486c5a9f7ec6158fb0feb708fa9255d5c4d44bc0858f8"]
-    s = multisig_script(pubkeys)
-    print "address", hash_160_to_bc_address(hash_160(s.decode('hex')), 5)
-
-
-    print "Gavin's tutorial: redeem p2sh:  http://blockchain.info/tx-index/30888901"
-    pubkey1 = "0491bba2510912a5bd37da1fb5b1673010e43d2c6d812c514e91bfa9f2eb129e1c183329db55bd868e209aac2fbc02cb33d98fe74bf23f0c235d6126b1d8334f86"
-    pubkey2 = "04865c40293a680cb9c020e7b1e106d8c1916d3cef99aa431a56d253e69256dac09ef122b1a986818a7cb624532f062c1d1f8722084861c5c3291ccffef4ec6874"
-    pubkey3 = "048d2455d2403e08708fc1f556002f1b6cd83f992d085097f9974ab08a28838f07896fbab08f39495e15fa6fad6edbfb1e754e35fa1c7844c41f322a1863d46213"
-    pubkeys = [pubkey1, pubkey2, pubkey3]
-
-    tx = Transaction.from_io(
-        [{'tx_hash':'3c9018e8d5615c306d72397f8f5eef44308c98fb576a88e030c25456b4f3a7ac', 'index':0,
-          'raw_output_script':'a914f815b036d9bbbce5e9f2a00abd1bf3dc91e9551087', 'pubkeys':pubkeys, 'p2sh_num':2}],
-        [('1GtpSrGhRGY5kkrNz4RykoqRQoJuG2L6DS',1000000)])
-
-    tx_for_sig = tx.for_sig(0)
-    print "tx for sig", tx_for_sig
-
-    signature1 = "304502200187af928e9d155c4b1ac9c1c9118153239aba76774f775d7c1f9c3e106ff33c0221008822b0f658edec22274d0b6ae9de10ebf2da06b1bbdaaba4e50eb078f39e3d78"
-    signature2 = "30440220795f0f4f5941a77ae032ecb9e33753788d7eb5cb0c78d805575d6b00a1d9bfed02203e1f4ad9332d1416ae01e27038e945bc9db59c732728a383a6f1ed2fb99da7a4"
-
-    for pubkey in pubkeys:
-        import traceback, sys
-
-        public_key = ecdsa.VerifyingKey.from_string(pubkey[2:].decode('hex'), curve = SECP256k1)
-
-        try:
-            public_key.verify_digest( signature1.decode('hex'), Hash( tx_for_sig.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der)
-            print True
-        except ecdsa.keys.BadSignatureError:
-            #traceback.print_exc(file=sys.stdout)
-            print False
-
-        try:
-            public_key.verify_digest( signature2.decode('hex'), Hash( tx_for_sig.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der)
-            print True
-        except ecdsa.keys.BadSignatureError:
-            #traceback.print_exc(file=sys.stdout)
-            print False
 
 if __name__ == '__main__':
-    #test_bip32()
-    test_p2sh()
+    test_bip32()
+