From 94fbad2965651d0c3374027e55c0b71b72560ecb Mon Sep 17 00:00:00 2001 From: thomasv Date: Sat, 23 Feb 2013 10:00:33 +0100 Subject: [PATCH 01/11] remove debugging messages --- lib/bitcoin.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/bitcoin.py b/lib/bitcoin.py index 032a6d88..5cb240ff 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -598,9 +598,6 @@ class Transaction: def has_address(self, addr): - print self.inputs - print self.outputs - found = False for txin in self.inputs: if addr == txin.get('address'): From 03e216050365a39dc9c1aeb52ff964c88509ce93 Mon Sep 17 00:00:00 2001 From: thomasv Date: Sat, 23 Feb 2013 11:35:46 +0100 Subject: [PATCH 02/11] create separate class for deterministic key generation. add pubkeys to validateaddress --- electrum | 19 +++++-- lib/bitcoin.py | 50 ++++++++++++++++++ lib/gui_qt.py | 4 +- lib/wallet.py | 138 ++++++++++++++++++++++--------------------------- 4 files changed, 127 insertions(+), 84 deletions(-) diff --git a/electrum b/electrum index 46ef786b..7d5bcce5 100755 --- a/electrum +++ b/electrum @@ -247,7 +247,7 @@ if __name__ == '__main__': wallet.gap_limit = gap if len(seed) == 128: wallet.seed = '' - wallet.master_public_key = seed + wallet.sequence.master_public_key = seed else: wallet.init_seed(str(seed)) @@ -332,7 +332,7 @@ if __name__ == '__main__': if len(seed) == 128: wallet.seed = None - wallet.master_public_key = seed + wallet.sequence.master_public_key = seed else: wallet.seed = str(seed) wallet.init_mpk( wallet.seed ) @@ -488,12 +488,12 @@ if __name__ == '__main__': except: sys.exit("Error: Error with seed file") - mpk = wallet.master_public_key + mpk = wallet.get_master_public_key() wallet.seed = seed wallet.imported_keys = imported_keys wallet.use_encryption = False wallet.init_mpk(seed) - if mpk == wallet.master_public_key: + if mpk == wallet.get_master_public_key(): wallet.save() print_msg("Done: " + wallet.config.path) else: @@ -501,7 +501,16 @@ if __name__ == '__main__': elif cmd == 'validateaddress': addr = args[1] - print_msg(wallet.is_valid(addr)) + is_valid = wallet.is_valid(addr) + out = { 'isvalid':is_valid } + if is_valid: + is_mine = wallet.is_mine(addr) + out['address'] = addr + out['ismine'] = is_mine + if is_mine: + out['pubkey'] = wallet.get_public_key(addr) + + print_json(out) elif cmd == 'balance': try: diff --git a/lib/bitcoin.py b/lib/bitcoin.py index 5cb240ff..1fd840d1 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -398,7 +398,57 @@ 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 + + @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 + + @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 diff --git a/lib/gui_qt.py b/lib/gui_qt.py index 68bcb866..859564b3 100644 --- a/lib/gui_qt.py +++ b/lib/gui_qt.py @@ -1319,10 +1319,10 @@ class ElectrumWindow(QMainWindow): dialog.setWindowTitle(_("Master Public Key")) main_text = QTextEdit() - main_text.setText(self.wallet.master_public_key) + main_text.setText(self.wallet.get_master_public_key()) main_text.setReadOnly(True) main_text.setMaximumHeight(170) - qrw = QRCodeWidget(self.wallet.master_public_key, 6) + qrw = QRCodeWidget(self.wallet.get_master_public_key(), 6) ok_button = QPushButton(_("OK")) ok_button.setDefault(True) diff --git a/lib/wallet.py b/lib/wallet.py index bf26257d..ebe290a3 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -32,7 +32,7 @@ import Queue import time from ecdsa.util import string_to_number, number_to_string -from util import print_error, user_dir, format_satoshis +from util import print_msg, print_error, user_dir, format_satoshis from bitcoin import * # URL decode @@ -43,6 +43,27 @@ urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x) EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s)) DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e)) +def pw_encode(s, password): + if password: + secret = Hash(password) + return EncodeAES(secret, s) + else: + return s + +def pw_decode(s, password): + if password is not None: + secret = Hash(password) + try: + d = DecodeAES(secret, s) + except: + raise BaseException('Invalid password') + return d + else: + return s + + + + from version import ELECTRUM_VERSION, SEED_VERSION @@ -60,7 +81,6 @@ class Wallet: self.use_change = config.get('use_change',True) self.fee = int(config.get('fee',100000)) self.num_zeros = int(config.get('num_zeros',0)) - self.master_public_key = config.get('master_public_key','') self.use_encryption = config.get('use_encryption', False) self.addresses = config.get('addresses', []) # receiving addresses visible for user self.change_addresses = config.get('change_addresses', []) # addresses used as change @@ -76,6 +96,9 @@ class Wallet: self.history = config.get('addr_history',{}) # address -> list(txid, height) self.tx_height = config.get('tx_height',{}) + master_public_key = config.get('master_public_key','') + self.sequence = DeterministicSequence(master_public_key) + self.transactions = {} tx = config.get('transactions',{}) try: @@ -122,19 +145,15 @@ class Wallet: while not self.is_up_to_date(): time.sleep(0.1) def import_key(self, sec, password): - # try password - try: - seed = self.decode_seed(password) - except: - raise BaseException("Invalid password") - + # check password + seed = self.decode_seed(password) address = address_from_private_key(sec) if address in self.all_addresses(): raise BaseException('Address already in wallet') # store the originally requested keypair into the imported keys table - self.imported_keys[address] = self.pw_encode(sec, password ) + self.imported_keys[address] = pw_encode(sec, password ) return address @@ -149,11 +168,8 @@ class Wallet: def init_mpk(self,seed): # public key - curve = SECP256k1 - secexp = self.stretch_key(seed) - master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 ) - self.master_public_key = master_private_key.get_verifying_key().to_string().encode('hex') - self.config.set_key('master_public_key', self.master_public_key, True) + self.sequence = DeterministicSequence.from_seed(seed) + self.config.set_key('master_public_key', self.sequence.master_public_key, True) def all_addresses(self): return self.addresses + self.change_addresses + self.imported_keys.keys() @@ -173,23 +189,35 @@ class Wallet: return False return addr == hash_160_to_bc_address(h, addrtype) - 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_master_public_key(self): + return self.sequence.master_public_key - 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_public_key(self, address): + if address in self.imported_keys.keys(): + raise BaseException("imported key") + if address in self.addresses: + n = self.addresses.index(address) + for_change = False + elif address in self.change_addresses: + n = self.change_addresses.index(address) + for_change = True + + return self.sequence.get_pubkey(n, for_change) + + + def decode_seed(self, password): + seed = pw_decode(self.seed, password) + self.sequence.check_seed(seed) + return seed + def get_private_key(self, address, password): - """ Privatekey(type,n) = Master_private_key + H(n|S|type) """ - # decode seed in any case, in order to make test the password + # decode seed in any case, in order to test the password seed = self.decode_seed(password) if address in self.imported_keys.keys(): - return self.pw_decode( self.imported_keys[address], password ) + return pw_decode( self.imported_keys[address], password ) else: if address in self.addresses: n = self.addresses.index(address) @@ -199,13 +227,8 @@ class Wallet: for_change = True else: raise BaseException("unknown address", address) - - 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 ) + + return self.sequence.get_private_key(n, for_change, seed) def sign_message(self, address, message, password): @@ -225,16 +248,10 @@ class Wallet: return address def get_new_address(self, n, for_change): - """ Publickey(type,n) = Master_public_key + H(n|S|type)*point """ - 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 ) - address = public_key_to_bc_address( '04'.decode('hex') + public_key2.to_string() ) - print address + pubkey = self.sequence.get_pubkey(n, for_change) + address = public_key_to_bc_address( pubkey.decode('hex') ) + print_msg( address ) return address - def change_gap_limit(self, value): if value >= self.gap_limit: @@ -303,7 +320,7 @@ class Wallet: def synchronize(self): - if not self.master_public_key: + if not self.sequence.master_public_key: return [] new_addresses = [] new_addresses += self.synchronize_sequence(self.addresses, self.gap_limit, False) @@ -516,39 +533,6 @@ class Wallet: return outputs - def pw_encode(self, s, password): - if password: - secret = Hash(password) - return EncodeAES(secret, s) - else: - return s - - def pw_decode(self, s, password): - if password is not None: - secret = Hash(password) - try: - d = DecodeAES(secret, s) - except: - raise BaseException('Invalid password') - return d - else: - return s - - def decode_seed(self, password): - seed = self.pw_decode(self.seed, password) - - # check decoded seed with master public key - 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 seed - - def get_history(self, address): with self.lock: return self.history.get(address) @@ -791,12 +775,12 @@ class Wallet: def update_password(self, seed, old_password, new_password): if new_password == '': new_password = None self.use_encryption = (new_password != None) - self.seed = self.pw_encode( seed, new_password) + self.seed = pw_encode( seed, new_password) self.config.set_key('seed', self.seed, True) for k in self.imported_keys.keys(): a = self.imported_keys[k] - b = self.pw_decode(a, old_password) - c = self.pw_encode(b, new_password) + b = pw_decode(a, old_password) + c = pw_encode(b, new_password) self.imported_keys[k] = c self.save() From 03d36d91e2b0544851b36b90c28240d7b80b5315 Mon Sep 17 00:00:00 2001 From: thomasv Date: Sat, 23 Feb 2013 11:50:09 +0100 Subject: [PATCH 03/11] remove unused method --- lib/wallet.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/lib/wallet.py b/lib/wallet.py index ebe290a3..31138960 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -613,16 +613,6 @@ class Wallet: return result - def get_transactions_at_height(self, height): - with self.lock: - values = self.transactions.values()[:] - - out = [] - for tx in values: - if tx['height'] == height: - out.append(tx['tx_hash']) - return out - def get_label(self, tx_hash): label = self.labels.get(tx_hash) @@ -630,6 +620,7 @@ class Wallet: if is_default: label = self.get_default_label(tx_hash) return label, is_default + def get_default_label(self, tx_hash): tx = self.transactions.get(tx_hash) default_label = '' From 9cfb73365b5a9f5eaab303296f531c41a62a0b1e Mon Sep 17 00:00:00 2001 From: thomasv Date: Sat, 23 Feb 2013 12:07:46 +0100 Subject: [PATCH 04/11] fix: use deserialized tx outputs in choose_tx_inputs --- lib/wallet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/wallet.py b/lib/wallet.py index 31138960..b618d549 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -484,7 +484,7 @@ class Wallet: if h == ['*']: continue for tx_hash, tx_height in h: tx = self.transactions.get(tx_hash) - for output in tx.get('outputs'): + for output in tx.d.get('outputs'): if output.get('address') != addr: continue key = tx_hash + ":%d" % output.get('index') if key in self.spent_outputs: continue @@ -497,7 +497,7 @@ class Wallet: if h == ['*']: continue for tx_hash, tx_height in h: tx = self.transactions.get(tx_hash) - for output in tx.get('outputs'): + for output in tx.d.get('outputs'): if output.get('address') != addr: continue key = tx_hash + ":%d" % output.get('index') if key in self.spent_outputs: continue From 780c5d01d5b066c42398eafd03b5021bc9bcfa5d Mon Sep 17 00:00:00 2001 From: thomasv Date: Sat, 23 Feb 2013 12:11:32 +0100 Subject: [PATCH 05/11] fix get_tx_details --- lib/wallet.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/wallet.py b/lib/wallet.py index b618d549..296e51b9 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -358,7 +358,7 @@ class Wallet: import datetime if not tx_hash: return '' tx = self.transactions.get(tx_hash) - is_mine, v, fee = self.get_tx_value(tx_hash) + is_mine, v, fee = self.get_tx_value(tx) conf, timestamp = self.verifier.get_confirmations(tx_hash) if timestamp: @@ -366,8 +366,8 @@ class Wallet: else: time_str = 'pending' - inputs = map(lambda x: x.get('address'), tx['inputs']) - outputs = map(lambda x: x.get('address'), tx['outputs']) + inputs = map(lambda x: x.get('address'), tx.inputs) + outputs = map(lambda x: x.get('address'), tx.d['outputs']) tx_details = "Transaction Details" +"\n\n" \ + "Transaction ID:\n" + tx_hash + "\n\n" \ + "Status: %d confirmations\n"%conf From 2642fa0f7d74e59231564ffa9ae27e3e60e9328d Mon Sep 17 00:00:00 2001 From: thomasv Date: Sat, 23 Feb 2013 13:18:15 +0100 Subject: [PATCH 06/11] fix listunspent, add method wallet.get_unspent_coins --- electrum | 3 +-- lib/wallet.py | 45 ++++++++++++++++++++------------------------- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/electrum b/electrum index 7d5bcce5..12cdb142 100755 --- a/electrum +++ b/electrum @@ -792,8 +792,7 @@ if __name__ == '__main__': elif cmd == 'listunspent': - unspent = map(lambda x: {"txid":x[0].split(':')[0],"vout":x[0].split(':')[1],"amount":x[1]*1.e-8}, wallet.prevout_values.items() ) - print_json(unspent) + print_json(wallet.get_unspent_coins()) if cmd not in offline_commands and not options.offline: diff --git a/lib/wallet.py b/lib/wallet.py index 296e51b9..c6492283 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -465,6 +465,24 @@ class Wallet: return conf, unconf + def get_unspent_coins(self, domain=None): + coins = [] + if domain is None: domain = self.all_addresses() + for addr in domain: + h = self.history.get(addr, []) + if h == ['*']: continue + for tx_hash, tx_height in h: + tx = self.transactions.get(tx_hash) + for output in tx.d.get('outputs'): + if output.get('address') != addr: continue + key = tx_hash + ":%d" % output.get('index') + if key in self.spent_outputs: continue + output['tx_hash'] = tx_hash + coins.append(output) + return coins + + + def choose_tx_inputs( self, amount, fixed_fee, from_addr = None ): """ todo: minimize tx size """ total = 0 @@ -479,31 +497,8 @@ class Wallet: for i in self.prioritized_addresses: if i in domain: domain.remove(i) - for addr in domain: - h = self.history.get(addr, []) - if h == ['*']: continue - for tx_hash, tx_height in h: - tx = self.transactions.get(tx_hash) - for output in tx.d.get('outputs'): - if output.get('address') != addr: continue - key = tx_hash + ":%d" % output.get('index') - if key in self.spent_outputs: continue - output['tx_hash'] = tx_hash - coins.append(output) - - - for addr in self.prioritized_addresses: - h = self.history.get(addr, []) - if h == ['*']: continue - for tx_hash, tx_height in h: - tx = self.transactions.get(tx_hash) - for output in tx.d.get('outputs'): - if output.get('address') != addr: continue - key = tx_hash + ":%d" % output.get('index') - if key in self.spent_outputs: continue - output['tx_hash'] = tx_hash - prioritized_coins.append(output) - + coins = self.get_unspent_coins(domain) + prioritized_coins = self.get_unspent_coins(self.prioritized_addresses) inputs = [] coins = prioritized_coins + coins From 0c5f42cc29b782539dbf55844e96228a4d5a4a27 Mon Sep 17 00:00:00 2001 From: thomasv Date: Sat, 23 Feb 2013 13:29:03 +0100 Subject: [PATCH 07/11] fix history sorting function --- lib/wallet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/wallet.py b/lib/wallet.py index c6492283..8b5ec2d5 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -584,7 +584,7 @@ class Wallet: def get_tx_history(self): with self.lock: history = self.transactions.items() - history.sort(key = lambda x: self.tx_height.get(x[0],1e12) ) + history.sort(key = lambda x: self.tx_height.get(x[0]) if self.tx_height.get(x[0]) else 1e12) result = [] balance = 0 From 67669909318a923d2762bf9035948cfadcae3630 Mon Sep 17 00:00:00 2001 From: thomasv Date: Sat, 23 Feb 2013 14:41:55 +0100 Subject: [PATCH 08/11] fix deserialize: OP_0 is not OP_PUSHDATA --- lib/deserialize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/deserialize.py b/lib/deserialize.py index b8e9b780..f0ceafa2 100644 --- a/lib/deserialize.py +++ b/lib/deserialize.py @@ -315,7 +315,7 @@ def match_decoded(decoded, to_match): if len(decoded) != len(to_match): return False; for i in range(len(decoded)): - if to_match[i] == opcodes.OP_PUSHDATA4 and decoded[i][0] <= opcodes.OP_PUSHDATA4: + if to_match[i] == opcodes.OP_PUSHDATA4 and decoded[i][0] <= opcodes.OP_PUSHDATA4 and decoded[i][0]>0: continue # Opcodes below OP_PUSHDATA4 all just push data onto stack, and are equivalent. if to_match[i] != decoded[i][0]: return False From a56220f0507fb50b4de894d52af8c0be0a5ffcc0 Mon Sep 17 00:00:00 2001 From: thomasv Date: Sat, 23 Feb 2013 14:42:31 +0100 Subject: [PATCH 09/11] multisig: raise exception if no signature can be added --- lib/bitcoin.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/bitcoin.py b/lib/bitcoin.py index 1fd840d1..a70c9b57 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -599,10 +599,11 @@ class Transaction: pubkey = GetPubKey(pkey.pubkey, compressed) keypairs[ pubkey.encode('hex') ] = sec - # list of signatures + # list of already existing signatures signatures = txin.get("signatures",[]) # check if we have a key corresponding to the redeem script + found = False for pubkey, privkey in keypairs.items(): if pubkey in redeem_pubkeys: # add signature @@ -616,6 +617,10 @@ class Transaction: 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) signatures.append( sig.encode('hex') ) + found = True + + if not found: + raise BaseException("public key not found", keypairs.keys(), redeem_pubkeys) # for p2sh, pubkeysig is a tuple (may be incomplete) self.inputs[i]["signatures"] = signatures From bd8dbbf79b2f13045b53234a093be04dd648d3a9 Mon Sep 17 00:00:00 2001 From: thomasv Date: Sat, 23 Feb 2013 15:11:41 +0100 Subject: [PATCH 10/11] check if multisig transaction is complete, add is_complete to json output --- electrum | 2 +- lib/bitcoin.py | 44 +++++++++++++++++++++++++++++--------------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/electrum b/electrum index 12cdb142..07aa6fb5 100755 --- a/electrum +++ b/electrum @@ -788,7 +788,7 @@ if __name__ == '__main__': private_keys = pk tx.sign( private_keys ) - print_msg(tx) + print_json({ "hex":str(tx),"complete":tx.is_complete}) elif cmd == 'listunspent': diff --git a/lib/bitcoin.py b/lib/bitcoin.py index a70c9b57..5f32bc54 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -601,29 +601,42 @@ class Transaction: # list of already existing signatures signatures = txin.get("signatures",[]) - - # check if we have a key corresponding to the redeem script found = False - for pubkey, privkey in keypairs.items(): - if pubkey in redeem_pubkeys: - # add signature - 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() + complete = True - 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) - signatures.append( sig.encode('hex') ) - found = True + # check if we have a key corresponding to the redeem script + for pubkey in redeem_pubkeys: + public_key = ecdsa.VerifyingKey.from_string(pubkey[2:].decode('hex'), curve = SECP256k1) + tx_for_sig = raw_tx( self.inputs, self.outputs, for_sig = i ) + 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: + 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') ) + found = True + else: + complete = False + if not found: raise BaseException("public key not found", keypairs.keys(), redeem_pubkeys) # for p2sh, pubkeysig is a tuple (may be incomplete) self.inputs[i]["signatures"] = signatures + self.is_complete = complete else: sec = private_keys[txin['address']] @@ -640,6 +653,7 @@ class Transaction: assert public_key.verify_digest( sig, Hash( tx.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der) self.inputs[i]["pubkeysig"] = [(pubkey, sig)] + self.is_complete = True self.raw = raw_tx( self.inputs, self.outputs ) From 04a5739979a6d4cc87ca0d31e101929797e2a345 Mon Sep 17 00:00:00 2001 From: thomasv Date: Sat, 23 Feb 2013 15:15:16 +0100 Subject: [PATCH 11/11] simplification: tx_for_sig --- lib/bitcoin.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/bitcoin.py b/lib/bitcoin.py index 5f32bc54..2886961c 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -585,6 +585,7 @@ class Transaction: for i in range(len(self.inputs)): txin = self.inputs[i] + tx_for_sig = raw_tx( self.inputs, self.outputs, for_sig = i ) if txin.get('redeemScript'): # 1 parse the redeem script @@ -607,7 +608,6 @@ class Transaction: # check if we have a key corresponding to the redeem script for pubkey in redeem_pubkeys: public_key = ecdsa.VerifyingKey.from_string(pubkey[2:].decode('hex'), curve = SECP256k1) - tx_for_sig = raw_tx( self.inputs, self.outputs, for_sig = i ) for s in signatures: try: @@ -648,9 +648,8 @@ class Transaction: 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) + 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.inputs[i]["pubkeysig"] = [(pubkey, sig)] self.is_complete = True