def test_mutable_tx_creation_with_immutable_parts_specified(self): tx = CMutableTransaction( vin=[CTxIn(prevout=COutPoint(hash=b'a' * 32, n=0))], vout=[CTxOut(nValue=1)], witness=CTxWitness([CTxInWitness()])) def check_mutable_parts(tx): self.assertTrue(tx.vin[0].is_mutable()) self.assertTrue(tx.vin[0].prevout.is_mutable()) self.assertTrue(tx.vout[0].is_mutable()) self.assertTrue(tx.wit.is_mutable()) self.assertTrue(tx.wit.vtxinwit[0].is_mutable()) check_mutable_parts(tx) # Test that if we deserialize with CMutableTransaction, # all the parts are mutable tx = CMutableTransaction.deserialize(tx.serialize()) check_mutable_parts(tx) # Test some parts separately, because when created via # CMutableTransaction instantiation, they are created with from_* # methods, and not directly txin = CMutableTxIn(prevout=COutPoint(hash=b'a' * 32, n=0)) self.assertTrue(txin.prevout.is_mutable()) wit = CMutableTxWitness((CTxInWitness(), )) self.assertTrue(wit.vtxinwit[0].is_mutable())
def create_test_txs(self, scriptSig, scriptPubKey, witness, nValue): txCredit = CTransaction([CTxIn(COutPoint(), CScript([OP_0, OP_0]), nSequence=0xFFFFFFFF)], [CTxOut(nValue, scriptPubKey)], witness=CTxWitness(), nLockTime=0, nVersion=1) txSpend = CTransaction([CTxIn(COutPoint(txCredit.GetTxid(), 0), scriptSig, nSequence=0xFFFFFFFF)], [CTxOut(nValue, CScript())], nLockTime=0, nVersion=1, witness=CTxWitness([CTxInWitness(witness)])) return (txCredit, txSpend)
def test_repr(self): def T(outpoint, expected): actual = repr(outpoint) self.assertEqual(actual, expected) T(COutPoint(), 'CBitcoinOutPoint()') T( COutPoint( lx('4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b' ), 0), "CBitcoinOutPoint(lx('4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b'), 0)" )
def listunspent(self, minconf=0, maxconf=9999999, addrs=None): """Return unspent transaction outputs in wallet Outputs will have between minconf and maxconf (inclusive) confirmations, optionally filtered to only include txouts paid to addresses in addrs. """ r = None if addrs is None: r = self._call('listunspent', minconf, maxconf) else: addrs = [str(addr) for addr in addrs] r = self._call('listunspent', minconf, maxconf, addrs) r2 = [] for unspent in r: unspent['outpoint'] = COutPoint(lx(unspent['txid']), unspent['vout']) del unspent['txid'] del unspent['vout'] # address isn't always available as Bitcoin Core allows scripts w/o # an address type to be imported into the wallet, e.g. non-p2sh # segwit try: unspent['address'] = CBitcoinAddress(unspent['address']) except KeyError: pass unspent['scriptPubKey'] = CScript( unhexlify(unspent['scriptPubKey'])) unspent['amount'] = int(unspent['amount'] * COIN) r2.append(unspent) return r2
def generate_transaction(amount, txin_txid, txin_vout, txout_addr, redeem_list): txin = CMutableTxIn(COutPoint(txin_txid, txin_vout)) txout = CMutableTxOut(amount * COIN, txout_addr.addr.to_scriptPubKey()) witness_script = CScriptWitness(tuple(redeem_list)) witness = CTxWitness(tuple([CTxInWitness(witness_script)])) tx = CMutableTransaction(vin=[txin], vout=[txout], witness=witness) return tx
def test_clone(self): outpoint = COutPoint( lx('4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b' ), 0) txin = CMutableTxIn(prevout=outpoint, scriptSig=CScript(b'\x03abc'), nSequence=0xffffffff) self.assertEqual(txin.serialize(), txin.clone().serialize())
def test_str(self): def T(outpoint, expected): actual = str(outpoint) self.assertEqual(actual, expected) T( COutPoint(), '0000000000000000000000000000000000000000000000000000000000000000:4294967295' ) T( COutPoint( lx('4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b' ), 0), '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0' ) T( COutPoint( lx('4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b' ), 10), '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:10' )
def create_btc_spend_tx(dst_addr, txid, vout_n, btc_contract, spend_key=None, branch_condition=True): # In real application, the fees should not be static, of course out_amount = (coins_to_satoshi(pre_agreed_amount) - coins_to_satoshi(fixed_fee_amount)) tx = CMutableTransaction( vin=[CTxIn(prevout=COutPoint(hash=lx(txid), n=vout_n))], vout=[ CTxOut(nValue=out_amount, scriptPubKey=dst_addr.to_scriptPubKey()) ]) if branch_condition is True: cond = b'\x01' else: tx.vin[0].nSequence = bitcoin_contract_timeout cond = b'' in_amount = coins_to_satoshi(pre_agreed_amount) # We used P2WSHCoinAddress to create the address that we sent bitcoin to, # so we know that we need to use SIGVERSION_WITNESS_V0 sighash = btc_contract.sighash(tx, 0, SIGHASH_ALL, amount=in_amount, sigversion=SIGVERSION_WITNESS_V0) spend_sig = spend_key.sign(sighash) + bytes([SIGHASH_ALL]) # This is script witness, not script. The condition for OP_IF # in our script is directly encoded as data in the witness. # We cannot use OP_TRUE/OP_FALSE here. We use DATA guard is to ensure that. witness = CScriptWitness([spend_sig, DATA(cond), btc_contract]) # empty scriptSig, because segwit tx.vin[0].scriptSig = CBitcoinScript([]) # all data to check the spend conditions is in the witness tx.wit.vtxinwit[0] = CTxInWitness(witness) # Cannot use VerifyScript for now, # because it does not support CHECKSEQUENCEVERIFY yet # # from_addr = P2WSHBitcoinAddress.from_redeemScript(btc_contract) # VerifyScript(tx.vin[0].scriptSig, from_addr.to_scriptPubKey(), # tx, 0, amount=in_amount) return tx
def raw_multisig(self): source = self.next_address() self.fund_address(source, 0.1) # construct transaction manually tx_ins = [CMutableTxIn(COutPoint(source.txid, source.vout))] keys = [self.next_address().key for _ in range(3)] redeem_script = CScript([OP_2, keys[0].pub, keys[1].pub, keys[2].pub, OP_3, OP_CHECKMULTISIG]) tx_outs = [ CMutableTxOut(Coin(0.1 - self.fee).satoshi(), redeem_script)] tx = CMutableTransaction(tx_ins, tx_outs) # sign and submit key = source.key script = source.address.to_scriptPubKey() sig = self._sign(script, tx, 0, Coin(source.value).satoshi(), key) tx_ins[0].scriptSig = CScript([sig, key.pub]) txid = self._send_transaction(tx, []) self.log_value("raw-multisig-tx", txid) # Redeem Transaction tx_ins = [CMutableTxIn(COutPoint(lx(txid), 0))] destination = self.next_address() tx_outs = [CMutableTxOut(Coin(0.1 - 2 * self.fee).satoshi(), destination.address.to_scriptPubKey())] tx = CMutableTransaction(tx_ins, tx_outs) # Sign with 2 out of three keys sig1 = self._sign(redeem_script, tx, 0, Coin(0.1 - self.fee).satoshi(), keys[0]) sig3 = self._sign(redeem_script, tx, 0, Coin(0.1 - self.fee).satoshi(), keys[2]) tx_ins[0].scriptSig = CScript([OP_0, sig1, sig3]) txid = self._send_transaction(tx, []) self.log_value("raw-multisig-redeem-tx", txid) self.generate_block()
def test_sighash(self) -> None: spent_amount = 1100 pub = CKey.from_secret_bytes(os.urandom(32)).pub spk_legacy = P2PKHCoinAddress.from_pubkey(pub).to_scriptPubKey() spk_segwit = P2WPKHCoinAddress.from_pubkey(pub).to_scriptPubKey() tx = CTransaction([ CTxIn( COutPoint(b'\x00' * 32, 0), CScript([]), nSequence=0xFFFFFFFF) ], [CTxOut(1000, spk_legacy)], nLockTime=0, nVersion=1) # no exceptions should be raised with these two calls spk_legacy.sighash(tx, 0, SIGHASH_ALL, amount=spent_amount, sigversion=SIGVERSION_WITNESS_V0) spk_segwit.sighash(tx, 0, SIGHASH_ALL, amount=spent_amount, sigversion=SIGVERSION_WITNESS_V0) with self.assertRaises(ValueError): # unknown sigversion spk_segwit.sighash(tx, 0, SIGHASH_ALL, amount=spent_amount, sigversion=SIGVERSION_WITNESS_V0 + 1) # type: ignore assert spk_segwit.is_witness_scriptpubkey() with self.assertRaises(ValueError): # incorect sigversion for segwit spk_segwit.sighash(tx, 0, SIGHASH_ALL, amount=spent_amount, sigversion=SIGVERSION_BASE) with self.assertRaises(ValueError): # inIdx > len(tx.vin) - non-raw sighash function should raise # ValueError (raw_sighash can return HASH_ONE) spk_legacy.sighash(tx, 10, SIGHASH_ALL, amount=spent_amount, sigversion=SIGVERSION_BASE)
def op_return(self): source = self.next_address("p2pkh") self.fund_address(source, 2 * self.fee) tx_ins = [CMutableTxIn(COutPoint(source.txid, source.vout))] tx_outs = [CMutableTxOut(Coin(self.fee).satoshi(), CScript([OP_RETURN, x("4c6f726420566f6c64656d6f7274")]))] tx = CMutableTransaction(tx_ins, tx_outs) key = source.key script = source.address.to_scriptPubKey() sig = self._sign(script, tx, 0, Coin(source.value).satoshi(), key) tx_ins[0].scriptSig = CScript([sig, key.pub]) txid = self._send_transaction(tx, []) self.log_value("op-return-tx", txid)
def create_custom_block(self, reward): txid, _ = self.fund_address(self.next_address(), 10) tx2 = self.proxy.getrawtransaction(lx(txid)) coinbase = CMutableTransaction() coinbase.vin.append(CMutableTxIn(COutPoint(), CScript([self.proxy.getblockcount() + 1]))) coinbase.vout.append(CMutableTxOut(reward * COIN, self.next_address().address.to_scriptPubKey())) prev_block_hash = self.proxy.getblockhash(self.proxy.getblockcount()) ts = self._next_timestamp() self.proxy.call("setmocktime", ts) for nonce in range(1000): block = CBlock(nBits=0x207fffff, vtx=[coinbase, tx2], hashPrevBlock=prev_block_hash, nTime=ts, nNonce=nonce) result = self.proxy.submitblock(block) if not result: self.log.debug("Chosen nonce: {}".format(nonce)) break
def load_test_vectors(name): with open(os.path.dirname(__file__) + '/data/' + name, 'r') as fd: for test_case in json.load(fd): # Comments designated by single length strings if len(test_case) == 1: continue assert len(test_case) == 3 prevouts = {} for json_prevout in test_case[0]: assert len(json_prevout) == 3 n = json_prevout[1] if n == -1: n = 0xffffffff prevout = COutPoint(lx(json_prevout[0]), n) prevouts[prevout] = parse_script(json_prevout[2]) tx_data = x(test_case[1]) tx = CTransaction.deserialize(tx_data) enforceP2SH = test_case[2] yield (prevouts, tx, tx_data, enforceP2SH)
def test_immutable_tx_creation_with_mutable_parts_specified(self) -> None: tx = CElementsTransaction( vin=[CMutableTxIn(prevout=COutPoint(hash=b'a' * 32, n=0))], vout=[CMutableTxOut()], witness=CMutableTxWitness( [CMutableTxInWitness(CScriptWitness([CScript([0])]))], [CMutableTxOutWitness()])) self.assertIsInstance(tx, CElementsTransaction) def check_immutable_parts(tx: CElementsTransaction) -> None: self.assertTrue(not tx.vin[0].is_mutable()) self.assertTrue(not tx.vin[0].prevout.is_mutable()) self.assertTrue(not tx.vout[0].is_mutable()) self.assertTrue(not tx.wit.is_mutable()) self.assertTrue(not tx.wit.vtxinwit[0].is_mutable()) self.assertTrue(not tx.wit.vtxoutwit[0].is_mutable()) check_immutable_parts(tx) # Test that if we deserialize with CTransaction, # all the parts are immutable tx = CElementsTransaction.deserialize(tx.serialize()) check_immutable_parts(tx) # Test some parts separately, because when created via # CMutableTransaction instantiation, they are created with from_* # methods, and not directly txin = CTxIn(prevout=CMutableOutPoint(hash=b'a' * 32, n=0)) self.assertTrue(not txin.prevout.is_mutable()) wit = CElementsTxWitness((CElementsMutableTxInWitness(), ), (CElementsMutableTxOutWitness(), )) self.assertTrue(not wit.vtxinwit[0].is_mutable()) self.assertTrue(not wit.vtxoutwit[0].is_mutable())
# Create the (in)famous correct brainwallet secret key. h = hashlib.sha256(b'correct horse battery staple').digest() seckey = CBitcoinKey.from_secret_bytes(h) # Same as the txid:vout the createrawtransaction RPC call requires # # lx() takes *little-endian* hex and converts it to bytes; in Bitcoin # transaction hashes are shown little-endian rather than the usual big-endian. # There's also a corresponding x() convenience function that takes big-endian # hex and converts it to bytes. txid = lx('7e195aa3de827814f172c362fcf838d92ba10e3f9fdd9c3ecaf79522b311b22d') vout = 0 # Create the txin structure, which includes the outpoint. The scriptSig # defaults to being empty. txin = CMutableTxIn(COutPoint(txid, vout)) # We also need the scriptPubKey of the output we're spending because # SignatureHash() replaces the transaction scriptSig's with it. # # Here we'll create that scriptPubKey from scratch using the pubkey that # corresponds to the secret key we generated above. txin_scriptPubKey = \ P2PKHBitcoinAddress.from_pubkey(seckey.pub).to_scriptPubKey() # Create the txout. This time we create the scriptPubKey from a Bitcoin # address. txout = CMutableTxOut( 0.001 * COIN, CBitcoinAddress('1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8').to_scriptPubKey())
def test_immutable(self): """COutPoint shall not be mutable""" outpoint = COutPoint() with self.assertRaises(AttributeError): outpoint.n = 1
def create_elt_spend_tx(dst_addr, txid, vout_n, elt_contract, die, spend_key=None, contract_key=None, blinding_key=None, blinding_factor=None, asset_blinding_factor=None, branch_condition=True): fee_satoshi = coins_to_satoshi(fixed_fee_amount) out_amount = coins_to_satoshi(pre_agreed_amount) - fee_satoshi # Single blinded output is not allowed, so we add # dummy OP_RETURN output, and we need dummy pubkey for it dummy_key = CKey.from_secret_bytes(os.urandom(32)) tx = CMutableTransaction( vin=[CTxIn(prevout=COutPoint(hash=lx(txid), n=vout_n))], vout=[ CTxOut(nValue=CConfidentialValue(out_amount), nAsset=CConfidentialAsset(bitcoin_asset), scriptPubKey=dst_addr.to_scriptPubKey(), nNonce=CConfidentialNonce(dst_addr.blinding_pubkey)), CTxOut(nValue=CConfidentialValue(0), nAsset=CConfidentialAsset(bitcoin_asset), nNonce=CConfidentialNonce(dummy_key.pub), scriptPubKey=CElementsScript([OP_RETURN])), CTxOut(nValue=CConfidentialValue(fee_satoshi), nAsset=CConfidentialAsset(bitcoin_asset)) ]) output_pubkeys = [dst_addr.blinding_pubkey, dummy_key.pub] in_amount = coins_to_satoshi(pre_agreed_amount) input_descriptors = [ BlindingInputDescriptor(asset=bitcoin_asset, amount=in_amount, blinding_factor=blinding_factor, asset_blinding_factor=asset_blinding_factor) ] blind_result = tx.blind(input_descriptors=input_descriptors, output_pubkeys=output_pubkeys) # The blinding must succeed! if blind_result.error: die('blind failed: {}'.format(blind_result.error)) if branch_condition is False: # Must set nSequence before we calculate signature hash, # because it is included in it tx.vin[0].nSequence = elements_contract_timeout # We used P2SHCoinAddress to create the address that # we sent Elements-BTC to, so we know that we need # to use SIGVERSION_BASE sighash = elt_contract.sighash(tx, 0, SIGHASH_ALL, amount=CConfidentialValue(in_amount), sigversion=SIGVERSION_BASE) spend_sig = spend_key.sign(sighash) + bytes([SIGHASH_ALL]) if branch_condition is True: prepare_elt_spend_reveal_branch(tx, elt_contract, spend_sig, contract_key, blinding_key) else: tx.vin[0].scriptSig = CElementsScript( [spend_sig, OP_FALSE, elt_contract]) return tx
def _create_transaction(self, sources: List[Address], recipients: List[Address], values, n_locktime, n_sequence): # save cospends self.cospends.union_all([str(x.address) for x in sources]) if not values: values = [recipient.value for recipient in recipients] tx_ins = [ CMutableTxIn(COutPoint(source.txid, source.vout), nSequence=n_sequence) for source in sources ] tx_outs = [] cnt = 0 for recipient, value in zip(recipients, values): if value == 0: self.log.warning("Creating output with 0 BTC") recipient.vout = cnt tx_outs.append( CMutableTxOut( Coin(value).satoshi(), recipient.address.to_scriptPubKey())) cnt += 1 tx = CMutableTransaction(tx_ins, tx_outs, nLockTime=n_locktime) in_idx = 0 witnesses = [] for txin, source in zip(tx_ins, sources): key = source.key if source.type == 'p2pkh': script = source.address.to_redeemScript() elif source.type == 'p2sh': script = CScript([key.pub, OP_CHECKSIG]) elif source.type == 'p2wpkh': script = source.address.to_redeemScript() elif source.type == 'p2wsh': script = source.witness_program else: raise UnsupportedAddressTypeError() # Create signature amount = Coin(source.value).satoshi() sig = self._sign(script, tx, in_idx, amount, key, source.type) # Add signature to input or witness if source.type == 'p2pkh': txin.scriptSig = CScript([sig, key.pub]) witnesses.append(CTxInWitness()) elif source.type == 'p2sh': txin.scriptSig = CScript([sig, script]) witnesses.append(CTxInWitness()) elif source.type == 'p2wpkh': txin.scriptSig = CScript() witnesses.append(CTxInWitness(CScriptWitness([sig, key.pub]))) elif source.type == 'p2wsh': txin.scriptSig = CScript() witnesses.append(CTxInWitness(CScriptWitness([sig, script]))) in_idx += 1 tx.wit = CTxWitness(witnesses) return tx
def test_is_null(self): self.assertTrue(COutPoint().is_null()) self.assertTrue(COutPoint(hash=b'\x00' * 32, n=0xffffffff).is_null()) self.assertFalse(COutPoint(hash=b'\x00' * 31 + b'\x01').is_null()) self.assertFalse(COutPoint(n=1).is_null())
def claim_funds_back(say, utxos, die, rpc): """Try to claim our funds by sending our UTXO to our own addresses""" # The transaction-building code here does not introduce anything new # compared to the code in participant functions, so it will not be # commented too much. input_descriptors = [] # It is better to prepare the claw-back transaction beforehand, to avoid # the possibility of unexpected problems arising at the critical time when # we need to send claw-back tx ASAP, but that would clutter the earlier # part of the example with details that are not very relevant there. tx = CMutableTransaction() for utxo in utxos: tx.vin.append( CTxIn(prevout=COutPoint(hash=lx(utxo['txid']), n=utxo['vout']))) input_descriptors.append( BlindingInputDescriptor( asset=CAsset(lx(utxo['asset'])), amount=coins_to_satoshi(utxo['amount']), blinding_factor=Uint256(lx(utxo['amountblinder'])), asset_blinding_factor=Uint256(lx(utxo['assetblinder'])))) asset_amounts = {} # If some assets are the same, we want them to be sent to one address for idesc in input_descriptors: if idesc.asset == fee_asset: amount = idesc.amount - FIXED_FEE_SATOSHI assert amount >= FIXED_FEE_SATOSHI # enforced at find_utxo_for_fee else: amount = idesc.amount asset_amounts[idesc.asset] = amount output_pubkeys = [] for asset, amount in asset_amounts.items(): dst_addr, _ = get_dst_addr(None, rpc) tx.vout.append( CTxOut(nValue=CConfidentialValue(amount), nAsset=CConfidentialAsset(asset), scriptPubKey=dst_addr.to_scriptPubKey())) output_pubkeys.append(dst_addr.blinding_pubkey) # Add the explicit fee output tx.vout.append( CTxOut(nValue=CConfidentialValue(FIXED_FEE_SATOSHI), nAsset=CConfidentialAsset(fee_asset))) # Add dummy pubkey for non-blinded fee output output_pubkeys.append(CPubKey()) # We used immutable objects for transaction components like CTxIn, # just for our convenience. Convert them all to mutable. tx = tx.to_immutable().to_mutable() # And blind the combined transaction blind_result = tx.blind(input_descriptors=input_descriptors, output_pubkeys=output_pubkeys) assert (not blind_result.error and blind_result.num_successfully_blinded == len(utxos)) for n, utxo in enumerate(utxos): sign_input(tx, n, utxo) # It is possible that Bob has actually sent the swap transaction. # We will get an error if our node has received this transaction. # In real application, we might handle this case, too, but # here we will just ignore it. txid = rpc.sendrawtransaction(b2x(tx.serialize())) rpc.generatetoaddress(1, rpc.getnewaddress()) wait_confirm(say, txid, die, rpc)
def alice(say, recv, send, die, rpc): """A function that implements the logic of the first participant of an asset atomic swap""" # Issue two asset that we are going to swap to Bob's 1 asset asset1_str, asset1_utxo = issue_asset(say, 1.0, rpc) asset2_str, asset2_utxo = issue_asset(say, 1.0, rpc) # We will need to pay a fee in an asset suitable for this fee_utxo = find_utxo_for_fee(say, die, rpc) say('Getting change address for fee asset') # We don't care for blinding key of change - the node will # have it, anyway, and we don't need to unblind the change. fee_change_addr, _ = get_dst_addr(say, rpc) say('Will use utxo {}:{} (amount: {}) for fee, change will go to {}'. format(fee_utxo['txid'], fee_utxo['vout'], fee_utxo['amount'], fee_change_addr)) say('Setting up communication with Bob') # Tell Bob that we are ready to communicate send('ready') # To avoid mempool synchronization problems, # in our example Alice is the one in charge of generating test blocks. # Bob gives alice txid of his transaction that he wants to be confirmed. bob_txid = recv('wait-txid-confirm') # Make sure asset issuance transactions are confirmed rpc.generatetoaddress(1, rpc.getnewaddress()) wait_confirm(say, asset1_utxo['txid'], die, rpc) wait_confirm(say, asset2_utxo['txid'], die, rpc) wait_confirm(say, bob_txid, die, rpc) # Make sure Bob is alive and ready to communicate, and send # him an offer for two assets say('Sending offer to Bob') my_offers = [ AtomicSwapOffer(asset=asset1_str, amount=coins_to_satoshi(asset1_utxo['amount'])), AtomicSwapOffer(asset=asset2_str, amount=coins_to_satoshi(asset2_utxo['amount'])) ] send('offer', my_offers) bob_offer = recv('offer') print_asset_balances(say, my_offers + [bob_offer], rpc) say('Bob responded with his offer: {}'.format(bob_offer)) # We unconditionally accept Bob's offer - his asset is # equally worthless as ours :-) # Generate an address for Bob to send his asset to. dst_addr, blinding_key = get_dst_addr(say, rpc) say('Sending my address and assetcommitments for my UTXOs to Bob') # Send Bob our address, and the assetcommitments of our UTXOs # (but not any other information about our UTXO), # so he can construct and blind a partial transaction that # will spend his own UTXO, to send his asset to our address. assetcommitments = [ asset1_utxo['assetcommitment'], asset2_utxo['assetcommitment'], fee_utxo['assetcommitment'] ] send('addr_and_assetcommitments', (str(dst_addr), assetcommitments)) partial_tx_bytes = recv('partial_blinded_tx') say('Got partial blinded tx of size {} bytes from Bob'.format( len(partial_tx_bytes))) partial_tx = CTransaction.deserialize(partial_tx_bytes) if len(partial_tx.vout) != 1: die('unexpected number of outputs in tx from Bob: expected 1, got {}'. format(len(partial_tx.vout))) result = partial_tx.vout[0].unblind_confidential_pair( blinding_key, partial_tx.wit.vtxoutwit[0].rangeproof) if result.error: die('cannot unblind output that should have been directed to us: {}'. format(result.error)) if result.asset.to_hex() != bob_offer.asset: die("asset in partial transaction from Bob {} is not the same " "as asset in Bob's initial offer ({})".format( result.asset.to_hex(), bob_offer.asset)) if result.amount != bob_offer.amount: die("amount in partial transaction from Bob {} is not the same " "as amount in Bob's initial offer ({})".format( result.amount, bob_offer.amount)) say("Asset and amount in partial transaction matches Bob's offer") bob_addr_list, bob_assetcommitment = recv('addr_list_and_assetcommitment') if len(bob_addr_list) != len(my_offers): die('unexpected address list lenth from Bob. expected {}, got {}'. format(len(my_offers), len(bob_addr_list))) say("Bob's addresses to receive my assets: {}".format(bob_addr_list)) # Convert Bob's addresses to address objects. # If Bob passes invalid address, we die with with exception. bob_addr_list = [CCoinAddress(a) for a in bob_addr_list] # Add our own inputs and outputs to Bob's partial tx # Create new mutable transaction from partial_tx tx = partial_tx.to_mutable() # We have assetcommitment for the first input, # other data is not needed for it. # initialize first elements of the arrays with empty/negative data. input_descriptors = [ BlindingInputDescriptor(asset=CAsset(), amount=-1, blinding_factor=Uint256(), asset_blinding_factor=Uint256()) ] # First output is already blinded, fill the slot with empty data output_pubkeys = [CPubKey()] # But assetcommitments array should start with Bob's asset commitment assetcommitments = [x(bob_assetcommitment)] # We will add our inputs for asset1 and asset2, and also an input # that will be used to pay the fee. # Note that the order is important: Bob blinded his transaction # with assetcommitments in the order we send them to him, # and we should add our inputs in the same order. utxos_to_add = (asset1_utxo, asset2_utxo, fee_utxo) # Add inputs for asset1 and asset2 and fee_asset and prepare input data # for blinding for utxo in utxos_to_add: # When we create CMutableTransaction and pass CTxIn, # it will be converted to CMutableTxIn. But if we append # to tx.vin or tx.vout, we need to use mutable versions # of the txin/txout classes, or else blinding or signing # will fail with error, unable to modify the instances. # COutPoint is not modified, though, so we can leave it # immutable. tx.vin.append( CMutableTxIn( prevout=COutPoint(hash=lx(utxo['txid']), n=utxo['vout']))) input_descriptors.append( BlindingInputDescriptor( asset=CAsset(lx(utxo['asset'])), amount=coins_to_satoshi(utxo['amount']), blinding_factor=Uint256(lx(utxo['amountblinder'])), asset_blinding_factor=Uint256(lx(utxo['assetblinder'])))) # If we are supplying asset blinders and assetblinders for # particular input, assetcommitment data for that input do # not need to be correct. But if we are supplying assetcommitments # at all (auxiliary_generators argument to tx.blind()), # then all the elements of that array must have correct # type (bytes) and length (33). This is a requirement of the original # Elements Core API, and python-elementstx requires this, too. assetcommitments.append(b'\x00' * 33) # Add outputs to give Bob all our assets, and fill output pubkeys # for blinding the outputs to Bob's addresses for n, offer in enumerate(my_offers): tx.vout.append( CMutableTxOut(nValue=CConfidentialValue(offer.amount), nAsset=CConfidentialAsset(CAsset(lx(offer.asset))), scriptPubKey=bob_addr_list[n].to_scriptPubKey())) output_pubkeys.append(bob_addr_list[n].blinding_pubkey) # Add change output for fee asset fee_change_amount = (coins_to_satoshi(fee_utxo['amount']) - FIXED_FEE_SATOSHI) tx.vout.append( CMutableTxOut(nValue=CConfidentialValue(fee_change_amount), nAsset=CConfidentialAsset(fee_asset), scriptPubKey=fee_change_addr.to_scriptPubKey())) output_pubkeys.append(fee_change_addr.blinding_pubkey) # Add fee output. # Note that while we use CConfidentialAsset and CConfidentialValue # to specify value and asset, they are not in fact confidential here # - they are explicit, because we pass explicit values at creation. # You can check if they are explicit or confidential # with nValue.is_explicit(). If they are explicit, you can access # the unblinded values with nValue.to_amount() and nAsset.to_asset() tx.vout.append( CMutableTxOut(nValue=CConfidentialValue(FIXED_FEE_SATOSHI), nAsset=CConfidentialAsset(fee_asset))) # Add dummy pubkey for non-blinded fee output output_pubkeys.append(CPubKey()) # Our transaction lacks txin witness instances for the added inputs, # and txout witness instances for added outputs. # If transaction already have witness data attached, transaction # serialization code will require in/out witness array length # to be equal to vin/vout array length # Therefore we need to add dummy txin and txout witnesses for each # input and output that we added to transaction # we added one input and one output per asset, and an additional # input/change-output for fee asset. for _ in utxos_to_add: tx.wit.vtxinwit.append(CMutableTxInWitness()) tx.wit.vtxoutwit.append(CMutableTxOutWitness()) # And one extra dummy txout witness for fee output tx.wit.vtxoutwit.append(CMutableTxOutWitness()) # And blind the combined transaction blind_result = tx.blind(input_descriptors=input_descriptors, output_pubkeys=output_pubkeys, auxiliary_generators=assetcommitments) # The blinding must succeed! if blind_result.error: die('blind failed: {}'.format(blind_result.error)) # And must blind exactly three outputs (two to Bob, one fee asset change) if blind_result.num_successfully_blinded != 3: die('blinded {} outputs, expected to be 3'.format( blind_result.num_successfully_blinded)) say('Successfully blinded the combined transaction, will now sign') # Sign two new asset inputs, and fee asset input for n, utxo in enumerate(utxos_to_add): # We specify input_index as 1+n because we skip first (Bob's) input sign_input(tx, 1 + n, utxo) say('Signed my inputs, sending partially-signed transaction to Bob') send('partially_signed_tx', tx.serialize()) # Note that at this point both participants can still opt out of the swap: # Alice by double-spending her inputs to the transaction, # and Bob by not signing or not broadcasting the transaction. # Bob still have tiny advantage, because # he can pretend to have 'difficulties' in broadcasting and try to exploit # Alice's patience. If Alice does not reclaim her funds in the case Bob's # behaviour deviates from expected, then Bob will have free option to # exectute the swap at the time convenient to him. # Get the swap transaction from Bob. # Bob is expected to broadcast this transaction, and could just send txid # here, but then there would be a period of uncertainty: if Alice do not # see the txid at her own node, she does not know if this is because Bob # did not actually broadcast, and is just taking his time watching asset # prices, or the transaction just takes long time to propagate. If the # protocol requires Bob to send the transaction, the timeout required for # Alice to wait can be defined much more certainly. try: signed_tx_raw = recv('final-signed-tx', timeout=ALICE_PATIENCE_LIMIT) signed_tx = CTransaction.deserialize(x(signed_tx_raw)) # Check that this transaction spends the same inputs as the transacton # previously agreed upon for n, vin in enumerate(signed_tx.vin): if vin.prevout != tx.vin[n].prevout: die('Inputs of transaction received from Bob do not match ' 'the agreed-upon transaction') # Send the transaction from our side txid = rpc.sendrawtransaction(b2x(signed_tx.serialize())) except Exception as e: # If there is any problem, including communication timeout or invalid # communication, or invalid transaction encoding, then Alice will try # to claim her funds back, so Bob won't have an option to execute the # swap at the time convenient to him. He should execute it immediately. say('Unexpected problem on receiving final signed transaction ' 'from Bob: {}'.format(e)) say('This is suspicious. I will try to reclaim my funds now') claim_funds_back(say, utxos_to_add, die, rpc) say("Claimed my funds back. Screw Bob!") sys.exit(0) # Make sure the final transaction is confirmed rpc.generatetoaddress(1, rpc.getnewaddress()) wait_confirm(say, txid, die, rpc) # Check that everything went smoothly balance = coins_to_satoshi(rpc.getbalance("*", 1, False, bob_offer.asset)) if balance != bob_offer.amount: die('something went wrong, balance of Bob\'s asset after swap ' 'should be {} satoshi, but it is {} satoshi'.format( balance, bob_offer.amount)) print_asset_balances(say, my_offers + [bob_offer], rpc) # Wait for alice to politely end the conversation send('thanks-goodbye') say('Asset atomic swap completed successfully')
def bob(say, recv, send, die, rpc): """A function that implements the logic of the second participant of an asset atomic swap""" # Issue an asset that we are going to swap asset_str, asset_utxo = issue_asset(say, 1.0, rpc) asset_amount_satoshi = coins_to_satoshi(asset_utxo['amount']) say('Setting up communication with Alice') # Wait for Alice to start communication recv('ready') # To avoid mempool synchronization problems in two-node regtest setup, # in our example Alice is the one in charge of generating test blocks. # Send txid of asset issuance to alice so she can ensure it is confirmed. send('wait-txid-confirm', asset_utxo['txid']) say('Waiting for Alice to send us an offer array') alice_offers = recv('offer') # We unconditionally accept Alice's offer - her assets are # equally worthless as our asset :-) say("Alice's offers are {}, sending my offer".format(alice_offers)) my_offer = AtomicSwapOffer(amount=asset_amount_satoshi, asset=asset_str) send('offer', my_offer) say('Waiting for Alice\'s address and assetcommitments') alice_addr_str, alice_assetcommitments = recv('addr_and_assetcommitments') print_asset_balances(say, alice_offers + [my_offer], rpc) # Convert Alice's address to address object. # If Alice passes invalid address, we die with we die with exception. alice_addr = CCoinAddress(alice_addr_str) say('Alice\'s address: {}'.format(alice_addr)) say('Alice\'s assetcommitments: {}'.format(alice_assetcommitments)) # Create asset commitments array. First goes our own asset commitment, # because our UTXO will be first. assetcommitments = [x(asset_utxo['assetcommitment'])] for ac in alice_assetcommitments: # If Alice sends non-hex data, we will die while converting. assetcommitments.append(x(ac)) # Let's create our part of the transaction. We need to create # mutable transaction, because blind() method only works for mutable. partial_tx = CMutableTransaction( vin=[ CTxIn(prevout=COutPoint(hash=lx(asset_utxo['txid']), n=asset_utxo['vout'])) ], vout=[ CTxOut(nValue=CConfidentialValue(asset_amount_satoshi), nAsset=CConfidentialAsset(CAsset(lx(asset_str))), scriptPubKey=alice_addr.to_scriptPubKey()) ]) # Blind our part of transaction, specifying assetcommitments # (Incliding those received from Alice) as auxiliary_generators. # Note that we could get the blinding factors if we retrieve # the transaction that we spend from, deserialize it, and unblind # the output that we are going to spend. # We could do everything here (besides issuing the asset and sending # the transactions) without using Elements RPC, if we get our data # from files or database, etc. But to simplify our demonstration, # we will use the values we got from RPC. # See 'spend-to-confidential-address.py' example for the code # that does the unblinding itself, and uses the unblinded values # to create a spending transaction. blind_result = partial_tx.blind( input_descriptors=[ BlindingInputDescriptor( asset=CAsset(lx(asset_utxo['asset'])), amount=asset_amount_satoshi, blinding_factor=Uint256(lx(asset_utxo['amountblinder'])), asset_blinding_factor=Uint256(lx(asset_utxo['assetblinder']))) ], output_pubkeys=[alice_addr.blinding_pubkey], auxiliary_generators=assetcommitments) # The blinding must succeed! if blind_result.error: die('blind failed: {}'.format(blind_result.error)) # And must blind exactly one output if blind_result.num_successfully_blinded != 1: die('blinded {} outputs, expected to be 1'.format( blind_result.num_successfully_blinded)) say('Successfully blinded partial transaction, sending it to Alice') send('partial_blinded_tx', partial_tx.serialize()) say("Generating addresses to receive Alice's assets") # Generate as many destination addresses as there are assets # in Alice's offer. Record blinding keys for the addresses. our_addrs = [] blinding_keys = [] for _ in alice_offers: addr, blinding_key = get_dst_addr(say, rpc) our_addrs.append(str(addr)) blinding_keys.append(blinding_key) say("Sending my addresses and assetcommitment to Alice") send('addr_list_and_assetcommitment', (our_addrs, asset_utxo['assetcommitment'])) semi_signed_tx_bytes = recv('partially_signed_tx') say('Got partially signed tx of size {} bytes from Alice'.format( len(semi_signed_tx_bytes))) semi_signed_tx = CTransaction.deserialize(semi_signed_tx_bytes) # Transaction should have 3 extra outputs - one output to Alice, # fee output, and fee asset change output if len(semi_signed_tx.vout) != len(alice_offers) + 3: die('unexpected number of outputs in tx from Alice: ' 'expected {}, got {}'.format( len(alice_offers) + 3, len(semi_signed_tx.vout))) if not semi_signed_tx.vout[-1].is_fee(): die('Last output in tx from Alice ' 'is expected to be fee output, but it is not') # Unblind outputs that should be directed to us and check # that they match the offer. We use n+1 as output index # because we skip our own output, which is at index 0. for n, offer in enumerate(alice_offers): result = semi_signed_tx.vout[n + 1].unblind_confidential_pair( blinding_keys[n], semi_signed_tx.wit.vtxoutwit[n + 1].rangeproof) if result.error: die('cannot unblind output {} that should have been ' 'directed to us: {}'.format(n + 1, result.error)) if result.asset.to_hex() != offer.asset: die("asset at position {} (vout {}) in partial transaction " "from Alice {} is not the same as asset in Alice's " "initial offer ({})".format(n, n + 1, result.asset.to_hex(), offer.asset)) if result.amount != offer.amount: die("amount at position {} (vout {}) in partial transaction " "from Alice {} is not the same as amount in Alice's " "initial offer ({})".format(n, n + 1, result.amount, offer.amount)) say("Assets and amounts in partially signed transaction " "match Alice's offer") # Signing will change the tx, so i tx = semi_signed_tx.to_mutable() # Our input is at index 0 sign_input(tx, 0, asset_utxo) # Note that at this point both participants can still opt out of the swap: # Bob by not broadcasting the transaction, and Alice by double-spending # her inputs to the transaction. Bob still have tiny advantage, because # he can pretend to have 'difficulties' in broadcasting and try to exploit # Alice's patience say('Signed the transaction from my side, ready to send') tx_hex = b2x(tx.serialize()) if bob_be_sneaky: say('Hey! I am now in control of the final transaction. ' 'I have the option to exectue the swap or abort. ') say('Why not wait a bit and watch asset prices, and execute ' 'the swap only if it is profitable') say('I will reduce my risk a bit by doing that.') # Bob takes his time and is not sending the final # transaction to Alice for some time... time.sleep(ALICE_PATIENCE_LIMIT + 2) say('OK, I am willing to execute the swap now') # Send the final transaction to Alice, so she can be sure that # we is not cheating send('final-signed-tx', tx_hex) txid = rpc.sendrawtransaction(tx_hex) say('Sent with txid {}'.format(txid)) # Wait for alice to politely end the conversation recv('thanks-goodbye') print_asset_balances(say, alice_offers + [my_offer], rpc) for i, offer in enumerate(alice_offers): balance = coins_to_satoshi(rpc.getbalance("*", 1, False, offer.asset)) if balance != offer.amount: die('something went wrong, asset{} balance after swap should be ' '{} satoshi, but it is {} satoshi'.format( i, balance, offer.amount)) say('Asset atomic swap completed successfully')
# An array of blinding pubkeys that we will supply to tx.blind() # It should cover all the outputs of the resulting transaction. output_pubkeys = [] if isinstance(dst_addr, CCoinConfidentialAddress): output_pubkeys.append(dst_addr.blinding_pubkey) else: output_pubkeys.append(CPubKey()) # Construct a transaction that spends the output we found # to the given destination address. # Note that the CTransaction is just a frontend for convenience, # and the created object will be the instance of the # CElementsTransaction class. The same with CTxIn, CTxOut, etc. tx = CElementsTransaction( vin=[CTxIn(prevout=COutPoint(hash=input_tx.GetTxid(), n=utxo_n))], vout=[ CElementsTxOut(nValue=CConfidentialValue(dst_value), nAsset=CConfidentialAsset(asset_to_spend), scriptPubKey=dst_addr.to_scriptPubKey()), # Fee output must be explicit in Elements CElementsTxOut(nValue=CConfidentialValue(fee_value), nAsset=fee_asset) ]) # Add empty pubkey for fee output output_pubkeys.append(CPubKey()) # We cannot blind an immutable transaction. Make it mutable. tx = tx.to_mutable()
} prevouts_by_scriptPubKey = None if not args.dryrun: txid = rpc.sendmany( '', { adr: float(float(amount) / CoreCoinParams.COIN) for adr, amount in payments.items() }, 0) logging.info('Sent pre-pub tx: %s' % txid) tx = CTransaction.deserialize(x(rpc.getrawtransaction(txid))) prevouts_by_scriptPubKey = { txout.scriptPubKey: COutPoint(lx(txid), i) for i, txout in enumerate(tx.vout) } else: prevouts_by_scriptPubKey = { redeemScript.to_p2sh_scriptPubKey(): COutPoint(b'\x00' * 32, i) for i, (scriptSig, redeemScript) in enumerate(scripts) } logging.debug('Payments: %r' % payments) logging.info( 'Total cost: %s BTC' % str_money_value(sum(amount for addr, amount in payments.items()))) # Create unsigned tx for SignatureHash vout = [CTxOut(0, CScript([OP_RETURN]))]
except FileNotFoundError as exp: if len(f) / 2 in (20, 32): digests.append(x(f)) else: raise exp except IOError as exp: print(exp, file=sys.stderr) continue for digest in digests: txouts = [] unspent = sorted(rpc.listunspent(0), key=lambda x: hash(x['amount'])) txins = [ CTxIn(COutPoint(lx(unspent[-1]['txid']), int(unspent[-1]['vout']))) ] value_in = coins_to_satoshi(unspent[-1]['amount']) change_addr = rpc.getnewaddress() change_pubkey_hex = rpc.getaddressinfo(change_addr)['pubkey'] change_out = CMutableTxOut( CoreCoinParams.MAX_MONEY, CScript([x(change_pubkey_hex), OP_CHECKSIG])) digest_outs = [CMutableTxOut(0, CScript([OP_RETURN, digest]))] txouts = [change_out] + digest_outs tx = CTransaction(txins, txouts).to_mutable()
def test_clone(self): outpoint = COutPoint( lx('4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b' ), 0) self.assertEqual(outpoint.serialize(), outpoint.clone().serialize())