def test_immutable_tx_creation_with_mutable_parts_specified(self): tx = CTransaction( vin=[CMutableTxIn(prevout=COutPoint(hash=b'a' * 32, n=0))], vout=[CMutableTxOut(nValue=1)], witness=CMutableTxWitness( [CMutableTxInWitness(CScriptWitness([CScript([0])]))])) def check_immutable_parts(tx): self.assertTrue(tx.vin[0].is_immutable()) self.assertTrue(tx.vin[0].is_immutable()) self.assertTrue(tx.vout[0].is_immutable()) self.assertTrue(tx.wit.is_immutable()) self.assertTrue(tx.wit.vtxinwit[0].is_immutable()) check_immutable_parts(tx) # Test that if we deserialize with CTransaction, # all the parts are immutable tx = CTransaction.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(txin.prevout.is_immutable()) wit = CTxWitness((CMutableTxInWitness(), )) self.assertTrue(wit.vtxinwit[0].is_immutable())
def test_repr(self): def T(txout, expected): actual = repr(txout) self.assertEqual(actual, expected) T(CMutableTxInWitness(CScriptWitness([1])), "CBitcoinMutableTxInWitness(CScriptWitness([x('01')]))")
def test_repr(self) -> None: def T(txinwitness: CTxInWitness, expected: str) -> None: actual = repr(txinwitness) self.assertEqual(actual, expected) T(CMutableTxInWitness(CScriptWitness([1])), "CBitcoinMutableTxInWitness(CScriptWitness([x('01')]))")
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 test_clone(self): txinwit = CMutableTxInWitness(CScriptWitness([1])) self.assertEqual(txinwit.serialize(), txinwit.clone().serialize())