def sign_input(tx, input_index, utxo): """Sign an input of transaction. Single-signature signing with SIGHASH_ALL""" key = utxo['key'] src_addr = CCoinAddress(utxo['address']) script_for_sighash = CScript( [OP_DUP, OP_HASH160, Hash160(key.pub), OP_EQUALVERIFY, OP_CHECKSIG]) assert isinstance(src_addr, (P2PKHCoinAddress, P2SHCoinAddress, P2WPKHCoinAddress)),\ 'only p2pkh, p2wpkh and p2sh_p2wpkh addresses are supported' if isinstance(src_addr, P2PKHCoinAddress): sigversion = SIGVERSION_BASE else: sigversion = SIGVERSION_WITNESS_V0 if 'amountcommitment' in utxo: amountcommitment = CConfidentialValue(x(utxo['amountcommitment'])) else: amountcommitment = CConfidentialValue(coins_to_satoshi(utxo['amount'])) sighash = script_for_sighash.sighash(tx, input_index, SIGHASH_ALL, amount=amountcommitment, sigversion=sigversion) sig = key.sign(sighash) + bytes([SIGHASH_ALL]) if isinstance(src_addr, P2PKHCoinAddress): tx.vin[input_index].scriptSig = CScript( [CScript(sig), CScript(key.pub)]) scriptpubkey = src_addr.to_scriptPubKey() elif isinstance(src_addr, P2WPKHCoinAddress): tx.vin[input_index].scriptSig = CScript() tx.wit.vtxinwit[input_index] = CTxInWitness( CScriptWitness([CScript(sig), CScript(key.pub)])) scriptpubkey = src_addr.to_scriptPubKey() else: # Assume that this is p2sh-wrapped p2wpkh address inner_scriptPubKey = CScript([0, Hash160(key.pub)]) tx.vin[input_index].scriptSig = CScript([inner_scriptPubKey]) tx.wit.vtxinwit[input_index] = CTxInWitness( CScriptWitness([CScript(sig), CScript(key.pub)])) scriptpubkey = inner_scriptPubKey.to_p2sh_scriptPubKey() VerifyScript(tx.vin[input_index].scriptSig, scriptpubkey, tx, input_index, amount=amountcommitment, flags=(SCRIPT_VERIFY_P2SH, ))
def payment_request(): """Generates a http PaymentRequest object""" bc = RPCCaller(allow_default_conf=True) btc = CCoinAddress(bc.getnewaddress()) # Setting the 'amount' field to 0 (zero) should prompt the user to enter # the amount for us but a bug in bitcoin core qt version 0.9.1 (at time of # writing) wrongly informs us that the value is too small and aborts. # https://github.com/bitcoin/bitcoin/issues/3095 # Also there can be no leading 0's (zeros). btc_amount = 100000 serialized_pubkey = btc.to_scriptPubKey() pdo = o.PaymentDetails(network="regtest") # pdo.network = 'test' pdo.outputs.add(amount=btc_amount, script=serialized_pubkey) pdo.time = int(time()) pdo.memo = 'String shown to user before confirming payment' pdo.payment_url = 'http://{}:{}/{}'.format(listen_on['host'], listen_on['port'], ack_url_path) pro = o.PaymentRequest() pro.serialized_payment_details = pdo.SerializeToString() sds_pr = pro.SerializeToString() headers = { 'Content-Type': 'application/bitcoin-payment', 'Accept': 'application/bitcoin-paymentrequest' } return sds_pr, headers
def check_sign(self, blinded_tx: CTransaction, signed_tx: CTransaction, bundle: Dict[str, Any]) -> None: tx_to_sign = blinded_tx.to_mutable() for n, vin in enumerate(tx_to_sign.vin): utxo = bundle['vin_utxo'][n] amount = -1 if utxo['amount'] == -1 else coins_to_satoshi( utxo['amount']) scriptPubKey = CScript(x(utxo['scriptPubKey'])) a = CCoinAddress(utxo['address']) if 'privkey' in utxo: privkey = CCoinKey(utxo['privkey']) assert isinstance(a, P2PKHCoinAddress),\ "only P2PKH is supported for single-sig" assert a == P2PKHElementsAddress.from_pubkey(privkey.pub) assert scriptPubKey == a.to_scriptPubKey() sighash = SignatureHash(scriptPubKey, tx_to_sign, n, SIGHASH_ALL, amount=amount, sigversion=SIGVERSION_BASE) sig = privkey.sign(sighash) + bytes([SIGHASH_ALL]) tx_to_sign.vin[n].scriptSig = CScript( [CScript(sig), CScript(privkey.pub)]) else: pk_list = [CCoinKey(pk) for pk in utxo['privkey_list']] redeem_script_data = [utxo['num_p2sh_participants']] redeem_script_data.extend([pk.pub for pk in pk_list]) redeem_script_data.extend([len(pk_list), OP_CHECKMULTISIG]) redeem_script = CScript(redeem_script_data) assert isinstance(a, P2SHCoinAddress),\ "only P2SH is supported for multi-sig." assert scriptPubKey == redeem_script.to_p2sh_scriptPubKey() assert a == P2SHElementsAddress.from_scriptPubKey( redeem_script.to_p2sh_scriptPubKey()) sighash = SignatureHash(redeem_script, tx_to_sign, n, SIGHASH_ALL, amount=amount, sigversion=SIGVERSION_BASE) sigs = [ pk.sign(sighash) + bytes([SIGHASH_ALL]) for pk in pk_list ] tx_to_sign.vin[n].scriptSig = CScript([b''] + sigs + [redeem_script]) VerifyScript(tx_to_sign.vin[n].scriptSig, scriptPubKey, tx_to_sign, n, amount=amount) self.assertEqual(tx_to_sign.serialize(), signed_tx.serialize())
def get_utxos_by_address(self, address: Address) -> List[Utxo]: raw_utxos = self.query_api(self.Query.GET_UNSPENT_TX, str(address))["txs"] utxos: List[Utxo] = [] for raw_utxo in raw_utxos: value = int(float(raw_utxo["value"]) * SATOSHIS_PER_COIN) tx_hash = raw_utxo["txid"] block = self.get_block_by_tx(tx_hash) vout = raw_utxo["output_no"] tx_in = TxIn(OutPoint(lx(tx_hash), vout)) tx_out = TxOut(value, address.to_scriptPubKey()) utxo = Utxo(block, tx_in, tx_out) utxos.append(utxo) return utxos
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')
def assemble_transaction_spend_output(self, recipient: ExternalAddress, selection: CoinSelection): return TxOut(selection.target_value, recipient.to_scriptPubKey())