def syncWallet(result, wallet, gaitwallet, path): hexprivkey = wallet.subkey(1).subkey(result['pointer']).wif() hexpubkey = wallet.subkey(1).subkey(result['pointer']).public_copy().sec_as_hex() tproxy = Proxy() GAKey = gaitwallet.subkey(1).subkey_for_path(path).subkey(result['pointer']).sec_as_hex() addrfromapi = P2SHBitcoinAddress.from_redeemScript(CScript(binascii.unhexlify(result['script']))) print(addrfromapi) #tproxy.call("importprivkey", hexprivkey, "", False) print(tproxy.call("createmultisig", 2, [GAKey, hexpubkey])['address'])
def bitcoinrpc(*args): host = BITCOIN_RPC_HOST if BITCOIN_RPC_USER and BITCOIN_RPC_PASSWORD: host = "%s:%s@%s" % ( quote(BITCOIN_RPC_USER), quote(BITCOIN_RPC_PASSWORD), host, ) if BITCOIN_RPC_PORT: host = "%s:%s" % (host, BITCOIN_RPC_PORT) service_url = "%s://%s" % (BITCOIN_RPC_SCHEME, host) proxy = Proxy(service_url=service_url) result = proxy.call(*args) return result
class BitcoinNode(Node): __slots__ = [ '_path', '_spent_to', '_rpc_connection', '_current_tx_chain_index', '_tx_chains' ] def __init__(self, name, group, ip, docker_image, path): super().__init__(name, group, ip, docker_image) self._path = path self._spent_to = None self._rpc_connection = None self._current_tx_chain_index = 0 self._tx_chains = [] def exec_event_cmd(self, cmd: str, args: List[str] = []): if cmd == 'tx': self.generate_tx elif cmd == 'block': self.generate_blocks(amount=1) else: raise Exception("Unknown cmd {cmd} for node {node}", cmd, self) def exec_event_cmd_string(self, cmd: str, args: List[str] = []) -> str: if cmd == 'tx': # TODO fix transactions #return self.generate_tx_string() return "" elif cmd == 'block': return self.generate_blocks_string(amount=1) else: raise Exception("Unknown cmd {cmd} for node {node}", cmd, self) def create_conf_file(self): # file is needed for RPC connection with open(config.btc_conf_file.format(self.name), 'w') as file: file.write('rpcconnect={}\n'.format(self._ip)) file.write('rpcport={}\n'.format(config.rpc_port)) file.write('rpcuser={}\n'.format(config.rpc_user)) file.write('rpcpassword={}\n'.format(config.rpc_password)) def run(self, connect_to_ips): bash.check_output( bitcoincmd.start(self._name, str(self._ip), self._docker_image, self._path, connect_to_ips)) def is_running(self): return bash.check_output(dockercmd.check_if_running( self._name)) == 'true' def close_rpc_connection(self): if self._rpc_connection is not None: self._rpc_connection.__dict__['_BaseProxy__conn'].close() logging.debug('Closed rpc connection to node={}'.format( self._name)) def stop(self): # self.execute_rpc('stop') # bash.check_output(self.execute_rpc_string(('stop', ''))) bash.check_output( f"docker exec simcoin-{self._name} bitcoin-cli -regtest -rpcuser=admin -rpcpassword=admin stop" ) logging.info('Send stop to node={}'.format(self.name)) def get_log_file(self): return self._path + config.bitcoin_log_file_name def wait_until_rpc_ready(self): logging.debug("# Waiting for docker instances to become ready") while True: try: bash.check_output("nc -z -w1 {} {}".format( self._ip, config.rpc_port), purpose="Wait for port beeing open") logging.info(f"Success for {self.ip}") break except Exception: logging.debug("Waiting with netcat until port is open") while True: try: self.execute_rpc('getnetworkinfo') break except JSONRPCError: logging.debug('Waiting until RPC of node={} is ready.'.format( self._name)) utils.sleep(1) def connect_to_rpc(self): self._rpc_connection = Proxy(btc_conf_file=config.btc_conf_file.format( self.name), timeout=config.rpc_timeout) def rm_peers_file(self): return bash.check_output(bitcoincmd.rm_peers(self._name)) def execute_rpc(self, *args): retry = 30 while retry > 0: try: return self._rpc_connection.call(args[0], *args[1:]) except (IOError, CannotSendRequest) as error: logging.exception( 'Could not execute RPC-call={} on node={} because of error={}.' ' Reconnecting and retrying, {} retries left'.format( args[0], self._name, error, retry)) retry -= 1 self.connect_to_rpc() raise Exception('Could not execute RPC-call={} on node {}'.format( args[0], self._name)) def transfer_coinbases_to_normal_tx(self): for tx_chain in self._tx_chains: tx_chain.amount /= 2 logging.info( f'transfer_coinbase_to_normal_tx tx_chain.amount = {tx_chain.amount}' ) tx_chain.amount -= int(config.transaction_fee / 2) logging.info( f'transfer_coinbase_to_normal_tx tx_chain.amount = {tx_chain.amount}' ) tx_chain.amount = math.floor( tx_chain.amount ) # TODO find better fix for 'Invalid amount' json_rpc error raw_transaction = self.execute_rpc( 'createrawtransaction', [{ 'txid': tx_chain.current_unspent_tx, 'vout': 0, }], OrderedDict([ (tx_chain.address, str(tx_chain.amount / 100000000)), (self._spent_to.address, str(tx_chain.amount / 100000000)) ])) signed_raw_transaction = self.execute_rpc('signrawtransaction', raw_transaction)['hex'] tx_chain.current_unspent_tx = self.execute_rpc( 'sendrawtransaction', signed_raw_transaction) def generate_blocks(self, amount=1): logging.debug('{} trying to generate block'.format(self._name)) block_hash = self.execute_rpc('generate', amount) logging.info('{} generated block with hash={}'.format( self._name, block_hash)) def generate_blocks_string(self, amount=1) -> str: return self.execute_rpc_string('generate ', amount) # TODO fix amount def generate_tx(self): tx_chain = self.get_next_tx_chain() txid = lx(tx_chain.current_unspent_tx) txins = [ CMutableTxIn(COutPoint(txid, 0)), CMutableTxIn(COutPoint(txid, 1)) ] txin_seckeys = [tx_chain.seckey, self._spent_to.seckey] amount_in = tx_chain.amount tx_chain.amount -= int(config.transaction_fee / 2) txout1 = CMutableTxOut( tx_chain.amount, CBitcoinAddress(tx_chain.address).to_scriptPubKey()) txout2 = CMutableTxOut( tx_chain.amount, CBitcoinAddress(self._spent_to.address).to_scriptPubKey()) tx = CMutableTransaction(txins, [txout1, txout2], nVersion=2) for i, txin in enumerate(txins): txin_scriptPubKey = CScript([ OP_DUP, OP_HASH160, Hash160(txin_seckeys[i].pub), OP_EQUALVERIFY, OP_CHECKSIG ]) sighash = SignatureHash(txin_scriptPubKey, tx, i, SIGHASH_ALL) sig = txin_seckeys[i].sign(sighash) + bytes([SIGHASH_ALL]) txin.scriptSig = CScript([sig, txin_seckeys[i].pub]) tx_serialized = tx.serialize() logging.debug('{} trying to sendrawtransaction' ' (in=2x{} out=2x{} fee={} bytes={})' ' using tx_chain number={}'.format( self._name, amount_in, txout1.nValue, (amount_in * 2) - (txout1.nValue * 2), len(tx_serialized), self._current_tx_chain_index)) tx_hash = self.execute_rpc('sendrawtransaction', b2x(tx_serialized)) tx_chain.current_unspent_tx = tx_hash logging.info( '{} sendrawtransaction was successful; tx got hash={}'.format( self._name, tx_hash)) def generate_spent_to_address(self): address = self.execute_rpc('getnewaddress') seckey = CBitcoinSecret(self.execute_rpc('dumpprivkey', address)) self._spent_to = SpentToAddress(address, seckey) def create_tx_chains(self): for unspent_tx in self.execute_rpc('listunspent'): seckey = CBitcoinSecret( self.execute_rpc('dumpprivkey', unspent_tx['address'])) tx_chain = TxChain(unspent_tx['txid'], unspent_tx['address'], seckey, unspent_tx['amount'] * 100000000) self._tx_chains.append(tx_chain) def get_next_tx_chain(self): tx_chain = self._tx_chains[self._current_tx_chain_index] self._current_tx_chain_index = ((self._current_tx_chain_index + 1) % len(self._tx_chains)) return tx_chain
class BitcoinNode(Node): __slots__ = [ '_path', '_spent_to', '_rpc_connection', '_current_tx_chain_index', '_tx_chains' ] def __init__(self, name, group, ip, path): super().__init__(name, group, ip) self._path = path self._spent_to = None self._rpc_connection = None self._current_tx_chain_index = 0 self._tx_chains = [] def create_conf_file(self): # file is needed for RPC connection # reading not possible at each node # create own file and push with open(config.btc_conf_file.format(self.name), 'w') as file: file.write('rpcconnect={}\n'.format(self._ip)) file.write('rpcport={}\n'.format(config.rpc_port)) file.write('rpcuser={}\n'.format(config.rpc_user)) file.write('rpcpassword={}\n'.format(config.rpc_password)) def run(self, connect_to_ips): bash.check_output( bitcoincmd.start(self._name, str(self._ip), self._path, connect_to_ips)) def is_running(self): return bash.check_output(bitcoincmd.check_if_running( self._name)) == 'true' def close_rpc_connection(self): if self._rpc_connection is not None: self._rpc_connection.__dict__['_BaseProxy__conn'].close() logging.debug('Closed rpc') def stop(self): self.execute_rpc('stop') logging.info('stopping node {}'.format(self.name)) def get_log_file(self): return self._path + config.bitcoin_log_file_name def wait_until_rpc_ready(self): while True: try: bash.check_output("nc -z -w1 {} {}".format( self._ip, config.rpc_port)) break except Exception: logging.debug("Port not open") while True: try: self.execute_rpc('getnetworkinfo') break except JSONRPCError: logging.debug('RPC not ready yet, sleeping for 2') utils.sleep(2) def connect_to_rpc(self): self._rpc_connection = Proxy(btc_conf_file=config.btc_conf_file.format( self.name), timeout=config.rpc_timeout) def rm_peers_file(self): return bash.check_output(bitcoincmd.rm_peers(self._name)) def execute_rpc(self, *args): # No clue whats the optimal retry = 10 while retry > 0: try: return self._rpc_connection.call(args[0], *args[1:]) except (IOError, CannotSendRequest) as error: logging.exception( 'Error: {}.' ' Reconnecting and retrying, {} more tries left'.format( error, retry)) retry -= 1 self.connect_to_rpc() def transfer_coinbases_to_normal_tx(self): # Readable format convert for tx_chain in self._tx_chains: tx_chain.amount /= 2 tx_chain.amount -= int(config.transaction_fee / 2) raw_transaction = self.execute_rpc( 'createrawtransaction', [{ 'txid': tx_chain.current_unspent_tx, 'vout': 0, }], OrderedDict([ (tx_chain.address, str(tx_chain.amount / 100000000)), (self._spent_to.address, str(tx_chain.amount / 100000000)) ])) signed_raw_transaction = self.execute_rpc('signrawtransaction', raw_transaction)['hex'] tx_chain.current_unspent_tx = self.execute_rpc( 'sendrawtransaction', signed_raw_transaction) def generate_blocks(self, amount=1): logging.debug('{} trying to generate mine'.format(self._name)) block_hash = self.execute_rpc('generate', amount) logging.info('{} mined a block {}'.format(self._name, block_hash)) def generate_tx(self): # Normal transaction signing and raw processing tx_chain = self.get_next_tx_chain() txid = lx(tx_chain.current_unspent_tx) txins = [ CMutableTxIn(COutPoint(txid, 0)), CMutableTxIn(COutPoint(txid, 1)) ] txin_seckeys = [tx_chain.seckey, self._spent_to.seckey] amount_in = tx_chain.amount tx_chain.amount -= int(config.transaction_fee / 2) txout1 = CMutableTxOut( tx_chain.amount, CBitcoinAddress(tx_chain.address).to_scriptPubKey()) txout2 = CMutableTxOut( tx_chain.amount, CBitcoinAddress(self._spent_to.address).to_scriptPubKey()) tx = CMutableTransaction(txins, [txout1, txout2], nVersion=2) for i, txin in enumerate(txins): txin_scriptPubKey = CScript([ OP_DUP, OP_HASH160, Hash160(txin_seckeys[i].pub), OP_EQUALVERIFY, OP_CHECKSIG ]) sighash = SignatureHash(txin_scriptPubKey, tx, i, SIGHASH_ALL) sig = txin_seckeys[i].sign(sighash) + bytes([SIGHASH_ALL]) txin.scriptSig = CScript([sig, txin_seckeys[i].pub]) tx_serialized = tx.serialize() logging.debug('{} is sending raw transaction' ' (input {} output {} fee {} bytes {})' ' using tx_chain id {}'.format( self._name, amount_in, txout1.nValue, (amount_in * 2) - (txout1.nValue * 2), len(tx_serialized), self._current_tx_chain_index)) tx_hash = self.execute_rpc('sendrawtransaction', b2x(tx_serialized)) tx_chain.current_unspent_tx = tx_hash #print(tx_chain.current_unspent_tx) logging.info('{} send was ok; hash={}'.format(self._name, tx_hash)) def generate_spent_to_address(self): # Get from wallet the list of address or generate one address = self.execute_rpc('getnewaddress') seckey = CBitcoinSecret(self.execute_rpc('dumpprivkey', address)) self._spent_to = SpentToAddress(address, seckey) def create_tx_chains(self): for unspent_tx in self.execute_rpc('listunspent'): seckey = CBitcoinSecret( self.execute_rpc('dumpprivkey', unspent_tx['address'])) tx_chain = TxChain(unspent_tx['txid'], unspent_tx['address'], seckey, unspent_tx['amount'] * 100000000) self._tx_chains.append(tx_chain) def get_next_tx_chain(self): tx_chain = self._tx_chains[self._current_tx_chain_index] self._current_tx_chain_index = ((self._current_tx_chain_index + 1) % len(self._tx_chains)) return tx_chain
class TX(object): def __init__(self, logger=None, test=False): self.logger = logger or logging.getLogger(__name__) # Setup logging file handler self.logger.setLevel(logging.DEBUG) self.handler = logging.FileHandler(__name__ + '.log') self.handler.setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s - %(name)s - ' + '%(levelname)s:\n\t %(message)s') self.handler.setFormatter(formatter) self.logger.addHandler(self.handler) self.test = test # if test: # SelectParams('testnet') if not test: self.proxy = Proxy() def __del__(self): self.handler.close() ########################################################################### ## General TX Functions ########################################################################### def get_tx(self, redeem_script, address, amount, funding_tx, lock_time=0, vout=0): ''' Returns a raw transaction and it's signature hash that pays to address from funding_tx Options: vout -> which vout of funding_tx nLockTime -> set's transaction locktime ''' # Set P2SH funding tx in little-endian fx = lx(funding_tx) if lock_time > 0: nlock_time = lock_time else: nlock_time = 0 # nSequence must be any number less than 0xffffffff to enable nLockTime if (nlock_time != 0): txin = CMutableTxIn(COutPoint(fx, vout), nSequence=0) else: txin = CMutableTxIn(COutPoint(fx, vout)) # Convert amount to Satoshi's amount *= COIN # Create the txout to address script_pubkey = CBitcoinAddress(address).to_scriptPubKey() txout = CMutableTxOut(amount, script_pubkey) # Create the unsigned transaction. tx = CMutableTransaction([txin], [txout], nLockTime=nlock_time) # Calculte TX sig hash sighash = SignatureHash(CScript(redeem_script), tx, 0, SIGHASH_ALL) self.logger.info("get_tx: TX SIGHASH is %s", b2x(sighash)) return (tx.serialize(), sighash) def refund_tx(self, payer_sig, serial_tx, redeem_script): ''' Sends a transaction refunding the funder of the P2SH address. ''' # Read in transaction temp_tx = CTransaction.deserialize(serial_tx) tx = CMutableTransaction.from_tx(temp_tx) txin = tx.vin[0] # Set script sig txin.scriptSig = CScript([payer_sig + '\x01', OP_FALSE, redeem_script]) # Verify script redeem_script = CScript(redeem_script) VerifyScript(txin.scriptSig, redeem_script.to_p2sh_scriptPubKey(), tx, 0, [SCRIPT_VERIFY_P2SH]) serial_tx = tx.serialize() if not self.test: # txid = self.self.proxy.sendrawtransaction(tx) txid = b2lx(Hash(serial_tx)) else: txid = b2lx(Hash(serial_tx)) self.logger.info("refund_tx: TXID is %s", txid) self.logger.info("refund_tx: RAW TX is %s", b2x(serial_tx)) return serial_tx ########################################################################### ## Serialization related ########################################################################### def serialize_list(self, l): ''' Serializes a python list ''' serial = "" for i in range(len(l)): serial += l[i] return serial def get_keys_from_tx(self, serial_tx, n_keys=15): '''Extracts n_keys from tx in serial form''' # Read in transaction temp_tx = CTransaction.deserialize(serial_tx) tx = CMutableTransaction.from_tx(temp_tx) # Keys are in txin.scriptSig txin = tx.vin[0] script = txin.scriptSig # Extract keys from script keys = [] for i, op in enumerate(script): if i in range(1, n_keys + 1): keys += [op] # Serialize keys in correct order serial_keys = "" for op in reversed(keys): serial_keys += op return serial_keys def get_keys_from_serial(self, serial, n_keys=15, key_len=16): ''' Returns a list of n_keys of key_len extracted from serial''' expected = (n_keys * key_len) if len(serial) != expected: self.logger.error( "get_keys_from_serial: serial len is %d " + "expected %d", len(serial), expected) return [] keys = [] for i in range(n_keys): keys += [serial[i * key_len:key_len * (i + 1)]] return keys def get_hashes_from_serial(self, serial, n_hashes, hash_len): ''' Returns a list of n_hashes of hash_len extracted from serial''' expected = (n_hashes * hash_len) if len(serial) != expected: self.logger.error( "get_hashes_from_serial: serial len is %d " + "expected %d", len(serial), expected) return [] hashes = [] for i in range(n_hashes): hashes += [serial[i * hash_len:hash_len * (i + 1)]] return hashes ########################################################################### ## Preimage P2SH wth Refund ########################################################################### def create_hash_script(self, redeemer_pubkey, hashes): ''' Creates part of the redeem script that deals with the hashes ''' script = [] for h in hashes: script += [OP_RIPEMD160, h, OP_EQUALVERIFY] script += [redeemer_pubkey, OP_CHECKSIG] return script def setup_preimage(self, payer_pubkey, redeemer_pubkey, hashes, amount, lock_time): ''' Setups a P2SH that can only be redeemed if the redeemer is able to provide the hash preimages. Also, sends a tx funding the escrow (Assumes payer calls the setup) ''' # Set locktime relative to current block if not self.test: lock = self.proxy.getblockcount() + lock_time else: lock = lock_time script = self.create_hash_script(redeemer_pubkey, hashes) redeem_script = CScript([OP_IF] + script + [ OP_ELSE, lock, OP_CHECKLOCKTIMEVERIFY, OP_DROP, payer_pubkey, OP_CHECKSIG, OP_ENDIF ]) redeem = b2x(redeem_script) self.logger.info("setup_preimage: Redeem script is %s", redeem) # Get P2SH address # 1. Get public key script_pub_key = redeem_script.to_p2sh_scriptPubKey() # 2. Get bitcoin address p2sh_address = CBitcoinAddress.from_scriptPubKey(script_pub_key) self.logger.info("setup_preimage: P2SH is %s", str(p2sh_address)) # 3. Fund address if not self.test: # funding_tx = self.proxy.call("sendtoaddress", str(p2sh_address), # amount) funding_tx = FUNDING_TX self.logger.info("setup_preimage: P2SH Fund TX is %s", funding_tx) else: funding_tx = FUNDING_TX return (redeem_script, str(funding_tx), str(p2sh_address), str(lock)) def spend_preimage(self, preimages, redeemer_sig, serial_tx, redeem_script): ''' Sends a transaction fulfilling the redeem script of the preimage P2SH ''' # Read in transaction temp_tx = CTransaction.deserialize(serial_tx) tx = CMutableTransaction.from_tx(temp_tx) txin = tx.vin[0] # Setup preimages in reverse order script = [] for p in reversed(preimages): script += [p] # Create script sig txin.scriptSig = CScript([redeemer_sig + '\x01'] + script + [OP_TRUE, redeem_script]) # Verify script redeem_script = CScript(redeem_script) VerifyScript(txin.scriptSig, redeem_script.to_p2sh_scriptPubKey(), tx, 0, [SCRIPT_VERIFY_P2SH]) serial_tx = tx.serialize() if not self.test: # txid = self.proxy.sendrawtransaction(tx) txid = b2lx(Hash(serial_tx)) else: txid = b2lx(Hash(serial_tx)) self.logger.info("spend_preimage: TXID is %s", txid) self.logger.info("spend_preimage: RAW TX is %s", b2x(serial_tx)) return serial_tx ########################################################################### ## Two Party Escrow with Refund ########################################################################### def setup_escrow(self, payer_pubkey, redeemer_pubkey, amount, lock_time): ''' Setups a 2of2 escrow with payer and redeemer Also, sends a tx funding the escrow (Assumes payer calls the setup) ''' # Set locktime relative to current block if not self.test: lock = self.proxy.getblockcount() + lock_time self.logger.info("setup_escrow: Locktime is %d", lock) else: lock = lock_time redeem_script = CScript([ OP_IF, OP_2, payer_pubkey, redeemer_pubkey, OP_2, OP_CHECKMULTISIG, OP_ELSE, lock, OP_CHECKLOCKTIMEVERIFY, OP_DROP, payer_pubkey, OP_CHECKSIG, OP_ENDIF ]) redeem = b2x(redeem_script) self.logger.info("setup_escrow: Redeem script is %s", redeem) # Get P2SH address # 1. Get public key script_pub_key = redeem_script.to_p2sh_scriptPubKey() # 2. Get bitcoin address p2sh_address = CBitcoinAddress.from_scriptPubKey(script_pub_key) self.logger.info("setup_escrow: P2SH is %s", str(p2sh_address)) # 3. Fund address if not self.test: funding_tx = self.proxy.call("sendtoaddress", str(p2sh_address), amount) self.logger.info("setup_escrow: P2SH Fund TX is %s", funding_tx) else: funding_tx = FUNDING_TX return (redeem_script, str(funding_tx), str(p2sh_address), str(lock)) def spend_escrow(self, payer_sig, redeemer_sig, serial_tx, redeem_script): ''' Sends a transaction fulfilling the redeem script of escrow tx ''' # Read in transaction temp_tx = CTransaction.deserialize(serial_tx) tx = CMutableTransaction.from_tx(temp_tx) txin = tx.vin[0] # Set script sig txin.scriptSig = CScript([ OP_FALSE, payer_sig + '\x01', redeemer_sig + '\x01', OP_TRUE, redeem_script ]) # Verify script redeem_script = CScript(redeem_script) serial_tx = tx.serialize() VerifyScript(txin.scriptSig, redeem_script.to_p2sh_scriptPubKey(), tx, 0, [SCRIPT_VERIFY_P2SH]) serial_tx = tx.serialize() if not self.test: # txid = self.proxy.sendrawtransaction(tx) txid = b2lx(Hash(serial_tx)) else: txid = b2lx(Hash(serial_tx)) self.logger.info("spend_escrow: TXID is %s", txid) self.logger.info("spend_escrow: RAW TX is %s", b2x(serial_tx)) return serial_tx