def sign(txdata, signatories): tx = txutil.from_hex(txdata['tx']) for i in range(wally.tx_get_num_inputs(tx)): script = wally.hex_to_bytes(txdata['prevout_scripts'][i]) script_type = txdata['prevout_script_types'][i] flags, value, sighash = 0, 0, wally.WALLY_SIGHASH_ALL if script_type == gaconstants.P2SH_P2WSH_FORTIFIED_OUT: flags = wally.WALLY_TX_FLAG_USE_WITNESS value = int(txdata['prevout_values'][i]) preimage_hash = wally.tx_get_btc_signature_hash( tx, i, script, value, sighash, flags) sigs = [s.get_signature(preimage_hash) for s in signatories] if script_type == gaconstants.P2SH_P2WSH_FORTIFIED_OUT: txutil.set_witness( tx, i, [None, _to_der(sigs[0]), _to_der(sigs[1]), script]) flags = wally.WALLY_SCRIPT_SHA256 | wally.WALLY_SCRIPT_AS_PUSH script = wally.witness_program_from_bytes(script, flags) else: sigs = sigs[0] + sigs[1] script = wally.scriptsig_multisig_from_bytes( script, sigs, [sighash, sighash], 0) wally.tx_set_input_script(tx, i, script) return tx
def test_recover_2of3(mock_bitcoincore): """Test 2of3 happy path""" mock_bitcoincore.return_value = AuthServiceProxy('testnet_txs') estimate = {'blocks': 3, 'feerate': 1, } mock_bitcoincore.return_value.estimatesmartfee.return_value = estimate destination_address = 'mynHfTyTWyGGB76NBFbfUrTnn8YWQkTJVs' args = [ '--mnemonic-file={}'.format(datafile('mnemonic_6.txt')), '--rpcuser=abc', '--rpcpassword=abc', '2of3', '--recovery-mnemonic-file={}'.format(datafile('mnemonic_7.txt')), '--rescan', '--key-search-depth={}'.format(key_depth), '--search-subaccounts={}'.format(sub_depth), '--destination-address={}'.format(destination_address), ] # Raw tx output = get_output(args).strip() assert output == open(datafile("signed_2of3_5")).read().strip() # Check replace by fee is set tx = txutil.from_hex(output) assert wally.tx_get_num_inputs(tx) == 1 assert wally.tx_get_input_sequence(tx, 0) == int(32*'1', 2) - 2 # Summary args = ['--show-summary', ] + args output = get_output(args) summary = parse_summary(output) assert len(summary) == 1 assert summary[0]['destination address'] == destination_address
def main(): parser = argparse.ArgumentParser(description="Verify Elements Commitments") parser.add_argument("--tx", type=argparse.FileType("r"), required=True) parser.add_argument("--blinded", type=argparse.FileType("r"), required=True) parser.add_argument("--input-txs", type=argparse.FileType("r"), nargs="+") args = parser.parse_args() tx = parse_tx_file(args.tx) inputs_unblinded, outputs_unblinded = parse_blinded_file(args.blinded) input_txs_map = parse_input_txs(args.input_txs) ret = { "txid": b2h_rev(wally.sha256d(wally.tx_to_bytes(tx, 0))), "inputs": [], "outputs": [], } for inp_idx in range(wally.tx_get_num_inputs(tx)): input_unblinded = inputs_unblinded.get(inp_idx) if not input_unblinded: continue prev_txid = wally.tx_get_input_txhash(tx, inp_idx) prev_vout = wally.tx_get_input_index(tx, inp_idx) prev_tx = input_txs_map.get(bytes(prev_txid)) if not prev_tx: continue asset_commitment_bin = wally.tx_get_output_asset(prev_tx, prev_vout) amount_commitment_bin = wally.tx_get_output_value(prev_tx, prev_vout) if asset_commitment_bin != input_unblinded["asset_commitment_bin"]: continue i = {"vin": inp_idx} # TODO: fill also if unblinded if input_unblinded.get("asset_id_hex"): i["asset"] = input_unblinded["asset_id_hex"] if amount_commitment_bin == input_unblinded["amount_commitment_bin"]: i["satoshi"] = input_unblinded["amount_satoshi"] ret["inputs"].append(i) for out_idx in range(wally.tx_get_num_outputs(tx)): output_unblinded = outputs_unblinded.get(out_idx) if not output_unblinded: continue asset_commitment_bin = wally.tx_get_output_asset(tx, out_idx) amount_commitment_bin = wally.tx_get_output_value(tx, out_idx) if asset_commitment_bin != output_unblinded["asset_commitment_bin"]: continue o = {"vout": out_idx} # TODO: fill also if unblinded if output_unblinded.get("asset_id_hex"): o["asset"] = output_unblinded["asset_id_hex"] if amount_commitment_bin == output_unblinded["amount_commitment_bin"]: o["satoshi"] = output_unblinded["amount_satoshi"] ret["outputs"].append(o) print(json.dumps(ret, indent=2))
def verify_txs(txs, utxos, expect_witness): txs = [txutil.from_hex(tx) for tx in txs] for tx in txs: assert wally.tx_get_num_inputs(tx) == 1 assert wally.tx_get_num_outputs(tx) >= 1 if expect_witness: assert wally.tx_get_witness_count(tx) == 1 else: assert wally.tx_get_witness_count(tx) == 0 wally.tx_get_total_output_satoshi(tx) # Throws if total overflows assert len(utxos) > 0 for idx, utxo in enumerate(utxos): tx = txs[idx] spending_tx = txutil.from_hex(''.join(utxo.split()))
def fixup_old_nlocktimes(self): """Fixup data from old format nlocktimes files Older nlocktimes files do not contain explicit prevout_signatures, prevout_scripts or prevout_script_types. Detect this and extract them from the raw transaction to make the txdata look consistent to the rest of the code. Note that segwit is not being handled here because old style nlocktimes predate segwit """ for txdata in self.txdata: if 'prevout_signatures' not in txdata: tx = txutil.from_hex(txdata['tx']) txdata['prevout_script_types'] = [] txdata['prevout_signatures'] = [] txdata['prevout_scripts'] = [] for i in range(wally.tx_get_num_inputs(tx)): inp = wally.tx_get_input_script(tx, i) ga_signature = wally.hex_from_bytes(inp[2:inp[1]+2]) redeem_script = wally.hex_from_bytes(inp[-71:]) txdata['prevout_signatures'].append(ga_signature) txdata['prevout_scripts'].append(redeem_script) txdata['prevout_script_types'].append(gaconstants.P2SH_FORTIFIED_OUT)
def test_recover_2of2_csv(mock_bitcoincore): """Test 2of2-csv happy path""" mock_bitcoincore.return_value = AuthServiceProxy('testnet_txs') estimate = { 'blocks': 3, 'feerate': decimal.Decimal('0.00001'), } mock_bitcoincore.return_value.estimatesmartfee.return_value = estimate mock_bitcoincore.return_value.getnetworkinfo = mock.Mock( return_value={'version': 190100}) mock_bitcoincore.return_value.getblockcount.return_value = 144 args = [ '--mnemonic-file={}'.format(datafile('mnemonic_1.txt')), '--rpcuser=abc', '--rpcpassword=abc', '2of2-csv', '--network=testnet', '--key-search-depth={}'.format(key_depth), '--search-subaccounts={}'.format(sub_depth), ] # Raw tx output = get_output(args).strip() assert output == open(datafile("signed_2of2_csv_1")).read().strip() tx = txutil.from_hex(output) assert wally.tx_get_num_inputs(tx) == 1 # Summary args = [ '--show-summary', ] + args output = get_output(args) summary = parse_summary(output) assert len(summary) == 1 # Use scantxoutset instead of importmulti + listunspent scantxoutset_result = { 'success': True, 'unspents': [{ 'txid': '0ab5d70ef25a601de455155fdcb8c492d21a9b3063211dc8a969568d9d0fe15b', 'vout': 0, 'scriptPubKey': 'a91458ce12e1773dd078940a9dc855b94c3c9a343b8587', 'desc': 'addr(2N1LnKRLTCWr8H9UdwoREazuFDXHMEgZj9g)#ztm9gzsm', 'amount': 0.001, 'height': 0, }], } mock_bitcoincore.return_value.scantxoutset = mock.Mock( return_value=scantxoutset_result) # output not expired yet mock_bitcoincore.return_value.getblockcount.return_value = 143 args = [ '--mnemonic-file={}'.format(datafile('mnemonic_1.txt')), '--rpcuser=abc', '--rpcpassword=abc', '2of2-csv', '--network=testnet', '--key-search-depth={}'.format(key_depth), '--search-subaccounts={}'.format(sub_depth), '--ignore-mempool', ] # Raw tx raw_tx = get_output(args).strip() assert raw_tx == '' # output expired mock_bitcoincore.return_value.getblockcount.return_value = 144 # Raw tx output = get_output(args).strip() assert output == open(datafile("signed_2of2_csv_1")).read().strip() # Check replace by fee is set tx = txutil.from_hex(output) assert wally.tx_get_num_inputs(tx) == 1 # Summary args = [ '--show-summary', ] + args output = get_output(args) summary = parse_summary(output) assert len(summary) == 1
def num_inputs(self) -> int: return wally.tx_get_num_inputs(self.tx)