def test_MoneyRangeCustomParams(self): class CoreHighMaxClassDispatcher(CoreBitcoinClassDispatcher): ... class CoreHighMaxParams(CoreBitcoinParams, CoreBitcoinClass): @classgetter def MAX_MONEY(self): return 22000000 * self.COIN class WalletHighMaxClassDispatcher(WalletBitcoinClassDispatcher): ... class HighMaxParams(BitcoinMainnetParams): NAME = 'high_maxmoney' WALLET_DISPATCHER = WalletHighMaxClassDispatcher with ChainParams(HighMaxParams): self.assertFalse(MoneyRange(-1)) with self.assertRaises(ValueError): coins_to_satoshi(-1) with self.assertRaises(ValueError): satoshi_to_coins(-1) self.assertTrue(MoneyRange(0)) self.assertTrue(MoneyRange(100000)) max_satoshi = coins_to_satoshi(22000000) self.assertTrue( MoneyRange(max_satoshi)) # Maximum money on Bitcoin network self.assertFalse(MoneyRange(max_satoshi + 1)) with self.assertRaises(ValueError): coins_to_satoshi(max_satoshi + 1) with self.assertRaises(ValueError): satoshi_to_coins(max_satoshi + 1)
def test_p2wsh_signaturehash2(self): unsigned_tx = x( '0100000002e9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff80e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffff0280969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac80969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac00000000' ) value1 = coins_to_satoshi(0.16777215) scriptcode1 = CScript( x('0063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac' )) value2 = coins_to_satoshi(0.16777215) scriptcode2 = CScript( x('68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac' )) self.assertEqual( SignatureHash(scriptcode1, CTransaction.deserialize(unsigned_tx), 0, SIGHASH_SINGLE | SIGHASH_ANYONECANPAY, value1, SIGVERSION_WITNESS_V0), x('e9071e75e25b8a1e298a72f0d2e9f4f95a0f5cdf86a533cda597eb402ed13b3a' )) self.assertEqual( SignatureHash(scriptcode2, CTransaction.deserialize(unsigned_tx), 1, SIGHASH_SINGLE | SIGHASH_ANYONECANPAY, value2, SIGVERSION_WITNESS_V0), x('cd72f1f1a433ee9df816857fad88d8ebd97e09a75cd481583eb841c330275e54' ))
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 find_utxo_for_fee(say, die, rpc): """Find suitable utxo to pay the fee. Retrieve thekey to spend this utxo and add it to the returned dict.""" # Find utxo to use for fee. In our simple example, only Alice pays the fee. # To be on a safe side, include only transactions # that are confirmed (1 as 'minconf' argument of listunspent) # and safe to spend (False as 'include_unsafe' # argument of listunspent) say('Searching for utxo for fee asset') utxo_list = rpc.listunspent(1, 9999999, [], False, {'asset': fee_asset.to_hex()}) utxo_list.sort(key=lambda u: u['amount']) for utxo in utxo_list: # To not deal with possibility of dust outputs, # just require fee utxo to be big enough if coins_to_satoshi(utxo['amount']) >= FIXED_FEE_SATOSHI * 2: utxo['key'] = CCoinKey(rpc.dumpprivkey(utxo['address'])) if 'assetcommitment' not in utxo: # If UTXO is not blinded, Elements daemon will not # give us assetcommitment, so we need to generate it ourselves. asset = CAsset(lx(utxo['asset'])) utxo['assetcommitment'] = b2x(asset.to_commitment()) return utxo else: die('Cannot find utxo for fee that is >= {} satoshi'.format( FIXED_FEE_SATOSHI * 2))
def test_MoneyRange(self): self.assertFalse(MoneyRange(-1)) with self.assertRaises(ValueError): coins_to_satoshi(-1) with self.assertRaises(ValueError): satoshi_to_coins(-1) self.assertTrue(MoneyRange(0)) self.assertTrue(MoneyRange(100000)) max_satoshi = coins_to_satoshi(21000000) self.assertTrue( MoneyRange(max_satoshi)) # Maximum money on Bitcoin network self.assertFalse(MoneyRange(max_satoshi + 1)) with self.assertRaises(ValueError): coins_to_satoshi(max_satoshi + 1) with self.assertRaises(ValueError): satoshi_to_coins(max_satoshi + 1)
def check_unblind(self, unblinded_tx, unblinded_tx_raw, blinded_tx, blinded_tx_raw, bundle, blinding_derivation_key): for n, bvout in enumerate(blinded_tx.vout): uvout = unblinded_tx.vout[n] if not uvout.nValue.is_explicit(): # skip confidential vouts of partially-blinded txs continue self.assertEqual(bvout.scriptPubKey, uvout.scriptPubKey) if bvout.nAsset.is_explicit(): self.assertTrue(bvout.nValue.is_explicit()) self.assertEqual(bvout.nValue.to_amount(), uvout.nValue.to_amount()) self.assertEqual(bvout.nAsset.to_asset().data, uvout.nAsset.to_asset().data) self.assertEqual(bvout.nNonce.commitment, uvout.nNonce.commitment) else: self.assertFalse(bvout.nValue.is_explicit()) for fbk, spk_set in bundle['foreign_blinding_keys'].items(): if b2x(bvout.scriptPubKey) in spk_set: blinding_key = uvout.scriptPubKey.derive_blinding_key( CKey(lx(fbk))) break else: blinding_key = uvout.scriptPubKey.derive_blinding_key( blinding_derivation_key) unblind_result = bvout.unblind_confidential_pair( blinding_key=blinding_key, rangeproof=blinded_tx.wit.vtxoutwit[n].rangeproof) self.assertFalse(unblind_result.error) self.assertEqual(uvout.nValue.to_amount(), unblind_result.amount) self.assertEqual(uvout.nAsset.to_asset().data, unblind_result.asset.data) descr = unblind_result.get_descriptor() self.assertIsInstance(descr, BlindingInputDescriptor) self.assertEqual(descr.amount, unblind_result.amount) self.assertEqual(descr.asset, unblind_result.asset) self.assertEqual(descr.blinding_factor, unblind_result.blinding_factor) self.assertEqual(descr.asset_blinding_factor, unblind_result.asset_blinding_factor) ub_info = bundle['unblinded_vout_info'][n] if len(ub_info): self.assertEqual(coins_to_satoshi(ub_info['amount']), unblind_result.amount) self.assertEqual(ub_info['asset'], unblind_result.asset.to_hex()) self.assertEqual(ub_info['blinding_factor'], unblind_result.blinding_factor.to_hex()) self.assertEqual( ub_info['asset_blinding_factor'], unblind_result.asset_blinding_factor.to_hex())
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 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 test_p2wpkh_signaturehash(self): unsigned_tx = x( '0100000002fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f0000000000eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac11000000' ) scriptpubkey = CScript( x('00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1')) value = coins_to_satoshi(6) address = CBech32BitcoinAddress.from_scriptPubKey(scriptpubkey) self.assertEqual( SignatureHash(address.to_redeemScript(), CTransaction.deserialize(unsigned_tx), 1, SIGHASH_ALL, value, SIGVERSION_WITNESS_V0), x('c37af31116d1b27caf68aae9e3ac82f1477929014d5b917657d0eb49478cb670' ))
def test_p2sh_p2wpkh_signaturehash(self): unsigned_tx = x( '0100000001db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a54770100000000feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac92040000' ) scriptpubkey = CScript( x('001479091972186c449eb1ded22b78e40d009bdf0089')) value = coins_to_satoshi(10) address = CCoinAddress.from_scriptPubKey(scriptpubkey) self.assertEqual( SignatureHash(address.to_redeemScript(), CTransaction.deserialize(unsigned_tx), 0, SIGHASH_ALL, value, SIGVERSION_WITNESS_V0), x('64f3b0f4dd2bb3aa1ce8566d220cc74dda9df97d8490cc81d89d735c92e59fb6' ))
def test_p2sh_p2wsh_signaturehash(self): unsigned_tx = x( '010000000136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000000ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac00000000' ) value = coins_to_satoshi(9.87654321) witnessscript = CScript( x('56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae' )) self.assertEqual( SignatureHash(witnessscript, CTransaction.deserialize(unsigned_tx), 0, SIGHASH_ALL, value, SIGVERSION_WITNESS_V0), x('185c0be5263dce5b4bb50a047973c1b6272bfbd0103a89444597dc40b248ee7c' )) self.assertEqual( SignatureHash(witnessscript, CTransaction.deserialize(unsigned_tx), 0, SIGHASH_NONE, value, SIGVERSION_WITNESS_V0), x('e9733bc60ea13c95c6527066bb975a2ff29a925e80aa14c213f686cbae5d2f36' )) self.assertEqual( SignatureHash(witnessscript, CTransaction.deserialize(unsigned_tx), 0, SIGHASH_SINGLE, value, SIGVERSION_WITNESS_V0), x('1e1f1c303dc025bd664acb72e583e933fae4cff9148bf78c157d1e8f78530aea' )) self.assertEqual( SignatureHash(witnessscript, CTransaction.deserialize(unsigned_tx), 0, SIGHASH_ALL | SIGHASH_ANYONECANPAY, value, SIGVERSION_WITNESS_V0), x('2a67f03e63a6a422125878b40b82da593be8d4efaafe88ee528af6e5a9955c6e' )) self.assertEqual( SignatureHash(witnessscript, CTransaction.deserialize(unsigned_tx), 0, SIGHASH_NONE | SIGHASH_ANYONECANPAY, value, SIGVERSION_WITNESS_V0), x('781ba15f3779d5542ce8ecb5c18716733a5ee42a6f51488ec96154934e2c890a' )) self.assertEqual( SignatureHash(witnessscript, CTransaction.deserialize(unsigned_tx), 0, SIGHASH_SINGLE | SIGHASH_ANYONECANPAY, value, SIGVERSION_WITNESS_V0), x('511e8e52ed574121fc1b654970395502128263f62662e076dc6baf05c2e6a99b' ))
def test_MoneyRangeCustomParams(self) -> None: class CoreHighMaxClassDispatcher(CoreBitcoinClassDispatcher): ... class CoreHighMaxClass(CoreBitcoinClass, metaclass=CoreHighMaxClassDispatcher): ... class CoreHighMaxParams(CoreBitcoinParams, CoreHighMaxClass): @classgetter def MAX_MONEY(self) -> int: return 10**100 * self.COIN class WalletHighMaxClassDispatcher(WalletBitcoinClassDispatcher, depends=[ CoreHighMaxClassDispatcher ]): ... class HighMaxParams(BitcoinMainnetParams): NAME = 'high_maxmoney' WALLET_DISPATCHER = WalletHighMaxClassDispatcher with self.assertRaises(ValueError): coins_to_satoshi(10**100) with ChainParams(HighMaxParams): self.assertFalse(MoneyRange(-1)) with self.assertRaises(ValueError): coins_to_satoshi(-1) with self.assertRaises(ValueError): satoshi_to_coins(-1) self.assertTrue(MoneyRange(0)) self.assertTrue(MoneyRange(100000)) max_satoshi = coins_to_satoshi(10**100) self.assertEqual(satoshi_to_coins(max_satoshi), 10**100) self.assertTrue(MoneyRange(max_satoshi)) self.assertFalse(MoneyRange(max_satoshi + 1)) with self.assertRaises(ValueError): coins_to_satoshi(max_satoshi + 1) with self.assertRaises(ValueError): satoshi_to_coins(max_satoshi + 1)
def test_p2wsh_signaturehash1(self): unsigned_tx = x( '0100000002fe3dc9208094f3ffd12645477b3dc56f60ec4fa8e6f5d67c565d1c6b9216b36e0000000000ffffffff0815cf020f013ed6cf91d29f4202e8a58726b1ac6c79da47c23d1bee0a6925f80000000000ffffffff0100f2052a010000001976a914a30741f8145e5acadf23f751864167f32e0963f788ac00000000' ) value2 = coins_to_satoshi(49) scriptcode1 = CScript( x('21026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac' )) # This is the same script with everything up to the last executed OP_CODESEPARATOR, including that # OP_CODESEPARATOR removed scriptcode2 = CScript( x('210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac' )) self.assertEqual( SignatureHash(scriptcode1, CTransaction.deserialize(unsigned_tx), 1, SIGHASH_SINGLE, value2, SIGVERSION_WITNESS_V0), x('82dde6e4f1e94d02c2b7ad03d2115d691f48d064e9d52f58194a6637e4194391' )) self.assertEqual( SignatureHash(scriptcode2, CTransaction.deserialize(unsigned_tx), 1, SIGHASH_SINGLE, value2, SIGVERSION_WITNESS_V0), x('fef7bd749cce710c5c052bd796df1af0d935e59cea63736268bcbe2d2134fc47' ))
# 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( coins_to_satoshi(0.001), CBitcoinAddress('1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8').to_scriptPubKey()) # Create the unsigned transaction. tx = CMutableTransaction([txin], [txout]) # Calculate the signature hash for that transaction. sighash = SignatureHash(txin_scriptPubKey, tx, 0, SIGHASH_ALL) # Now sign it. We have to append the type of signature we want to the end, in # this case the usual SIGHASH_ALL. sig = seckey.sign(sighash) + bytes([SIGHASH_ALL]) # Set the scriptSig of our transaction input appropriately. txin.scriptSig = CScript([sig, seckey.pub])
import sys PATH_TO_FUNCTIONAL = "~/bitcoin/test/functional" PATH_TO_BITCOINTX = "~/python-bitcointx" TEST_DATADIR = "tests/data" sys.path.insert(0, PATH_TO_BITCOINTX) sys.path.insert(0, PATH_TO_FUNCTIONAL) from bitcointx import select_chain_params from bitcointx.core import coins_to_satoshi select_chain_params("bitcoin/regtest") MIN_FEE = coins_to_satoshi(0.00000169) TIMELOCK = 3 PORTION_TO_VAULT = 0.1
def alice(say, recv, send, die, btc_rpc, elt_rpc): """A function that implements the logic of the Elements-side participant of confidential cross-chain atomic swap""" global last_wish_func # Default chain for Alice will be Elements # To handle bitcoin-related objects, either # `with ChainParams(bitcoin_chain_name):` have to be used, or # concrete classes, like CBitcoinAddress, CBitcoinTransaction, etc. select_chain_params(elements_chain_name) # Let's create the shared blinding key blinding_key = CKey.from_secret_bytes(os.urandom(32)) # And the key for btc spend alice_btc_key = CKey.from_secret_bytes(os.urandom(32)) # And the key for the 'timeout' branch of the contract alice_elt_exit_key = CKey.from_secret_bytes(os.urandom(32)) say('Sending pubkeys to Bob') send('pubkeys', (alice_btc_key.pub, alice_elt_exit_key.pub)) say('Sending the blinding key to Bob') send('blinding_key', blinding_key.secret_bytes) (contract_pubkey_raw, bob_elt_pubkey_raw, bob_btc_exit_pub_raw) = recv('pubkeys') say("Pubkey of the key to be revealed: {}".format( b2x(contract_pubkey_raw))) say("Bob's Elements-side pubkey: {}".format(b2x(bob_elt_pubkey_raw))) contract_pubkey = CPubKey(contract_pubkey_raw) key_to_reveal_pub = CPubKey.add(contract_pubkey, blinding_key.pub) elt_contract = make_elt_cntract(key_to_reveal_pub, bob_elt_pubkey_raw, alice_elt_exit_key.pub) elt_contract_addr = P2SHCoinAddress.from_redeemScript(elt_contract) confidential_contract_addr = P2SHCoinConfidentialAddress.from_unconfidential( elt_contract_addr, blinding_key.pub) assert isinstance(confidential_contract_addr, CElementsConfidentialAddress) say("Created Elemets-side swap contract, size: {}".format( len(elt_contract))) say("Contract address:\n\tconfidential: {}\n\tunconfidential: {}".format( confidential_contract_addr, elt_contract_addr)) btc_txid = recv('btc_txid') combined_btc_spend_pubkey = CPubKey.add(contract_pubkey, alice_btc_key.pub) btc_contract = make_btc_contract(combined_btc_spend_pubkey, bob_btc_exit_pub_raw) tx_json = btc_rpc.getrawtransaction(btc_txid, 1) if tx_json['confirmations'] < 6: die('Transaction does not have enough confirmations') # We use ChainParams, and not P2WSHBitcoinAddress here, # because bitcoin_chain_name might be 'bitcoin/regtest', for example, # and then the address would need to be P2WSHBitcoinRegtestAddress. # with ChainParams we leverage the 'frontend class' magic, P2WSHCoinAddress # will give us appropriate instance. with ChainParams(bitcoin_chain_name): btc_contract_addr = P2WSHCoinAddress.from_redeemScript(btc_contract) say('Looking for this address in transaction {} in Bitcoin'.format( btc_txid)) # CTransaction subclasses do not change between mainnet/testnet/regtest, # so we can directly use CBitcoinTransaction. # That might not be true for other chains, though. # You might also want to use CTransaction within `with ChainParams(...):` btc_tx = CBitcoinTransaction.deserialize(x(tx_json['hex'])) for n, vout in enumerate(btc_tx.vout): if vout.scriptPubKey == btc_contract_addr.to_scriptPubKey(): say("Found the address at output {}".format(n)) btc_vout_n = n break else: die('Did not find contract address in transaction') if vout.nValue != coins_to_satoshi(pre_agreed_amount): die('the amount {} found at the output in the offered transaction ' 'does not match the expected amount {}'.format( satoshi_to_coins(vout.nValue), pre_agreed_amount)) say('Bitcoin amount match expected values') say('Sending {} to {}'.format(pre_agreed_amount, confidential_contract_addr)) contract_txid = elt_rpc.sendtoaddress(str(confidential_contract_addr), pre_agreed_amount) def alice_last_wish_func(): try_reclaim_elt(say, elt_rpc, contract_txid, elt_contract, alice_elt_exit_key, blinding_key, die) last_wish_func = alice_last_wish_func wait_confirm(say, 'Elements', contract_txid, die, elt_rpc, num_confirms=2) send('elt_txid', contract_txid) sr_txid = wait_spend_reveal_transaction(say, contract_txid, die, elt_rpc) say('Got txid for spend-reveal transaction from Bob ({})'.format(sr_txid)) tx_json = elt_rpc.getrawtransaction(sr_txid, 1) wait_confirm(say, 'Elements', sr_txid, die, elt_rpc, num_confirms=2) sr_tx = CTransaction.deserialize(x(tx_json['hex'])) for n, vin in enumerate(sr_tx.vin): if vin.prevout.hash == lx(contract_txid)\ and vin.scriptSig[-(len(elt_contract)):] == elt_contract: say('Transaction input {} seems to contain a script ' 'we can recover the key from'.format(n)) reveal_script_iter = iter(vin.scriptSig) break else: die('Spend-reveal transaction does not have input that spends ' 'the contract output') next(reveal_script_iter) # skip Bob's spend signature try: # 2 skipped bytes are tag and len sig_s = ecdsa.util.string_to_number(next(reveal_script_iter)[2:]) except (ValueError, StopIteration): die('Reveal script is invalid') k, r = get_known_k_r() order = ecdsa.SECP256k1.order mhash = ecdsa.util.string_to_number(hashlib.sha256(b'\x01').digest()) r_inverse = ecdsa.numbertheory.inverse_mod(r, order) for s in (-sig_s, sig_s): secret_exponent = (((s * k - mhash) % order) * r_inverse) % order recovered_key = CKey.from_secret_bytes( ecdsa.util.number_to_string(secret_exponent, order)) if recovered_key.pub == key_to_reveal_pub: break else: die('Key recovery failed. Should not happen - the sig was already ' 'verified when transaction was accepted into mempool. ' 'Must be a bug.') say('recovered key pubkey: {}'.format(b2x(recovered_key.pub))) contract_key = CKey.sub(recovered_key, blinding_key) say('recovered unblined key pubkey: {}'.format(b2x(contract_key.pub))) combined_btc_spend_key = CKey.add(contract_key, alice_btc_key) say('Successfully recovered the key. Can now spend Bitcoin from {}'.format( btc_contract_addr)) with ChainParams(bitcoin_chain_name): dst_addr = CCoinAddress(btc_rpc.getnewaddress()) btc_claim_tx = create_btc_spend_tx(dst_addr, btc_txid, btc_vout_n, btc_contract, spend_key=combined_btc_spend_key) say('Sending my Bitcoin-claim transaction') btc_claim_txid = btc_rpc.sendrawtransaction(b2x(btc_claim_tx.serialize())) wait_confirm(say, 'Bitcoin', btc_claim_txid, die, btc_rpc, num_confirms=3) say('Got my Bitcoin. Swap successful!')
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, btc_rpc, elt_rpc): """A function that implements the logic of the Bitcoin-side participant of confidential cross-chain atomic swap""" global last_wish_func # Default chain for Bob will be Bitcoin # To handle bitcoin-related objects, either # `with ChainParams(elements_chain_name):` have to be used, or # concrete classes, like CElementsAddress, CElementsTransaction, etc. select_chain_params(bitcoin_chain_name) say('Waiting for blinding key from Alice') alice_btc_pub_raw, alice_elt_exit_pub_raw = recv('pubkeys') blinding_key = CKey.from_secret_bytes(recv('blinding_key')) say("Pubkey for blinding key: {}".format(b2x(blinding_key.pub))) # Let's create the key that would lock the coins on Bitcoin side contract_key = CKey.from_secret_bytes(os.urandom(32)) # And the key for Elements side bob_elt_spend_key = CKey.from_secret_bytes(os.urandom(32)) # And the key for 'timeout' case on btc side bob_btc_exit_key = CKey.from_secret_bytes(os.urandom(32)) key_to_reveal_pub = CPubKey.add(contract_key.pub, blinding_key.pub) say("The pubkey of the combined key to be revealed: {}".format( b2x(key_to_reveal_pub))) say('Sending my pubkeys to Alice') send('pubkeys', (contract_key.pub, bob_elt_spend_key.pub, bob_btc_exit_key.pub)) combined_btc_spend_pubkey = CPubKey.add(contract_key.pub, CPubKey(alice_btc_pub_raw)) say('combined_btc_spend_pubkey: {}'.format(b2x(combined_btc_spend_pubkey))) btc_contract = make_btc_contract(combined_btc_spend_pubkey, bob_btc_exit_key.pub) btc_contract_addr = P2WSHCoinAddress.from_redeemScript(btc_contract) say("Created Bitcoin-side swap contract, size: {}".format( len(btc_contract))) say("Contract address: {}".format(btc_contract_addr)) say('Sending {} to {}'.format(pre_agreed_amount, btc_contract_addr)) btc_txid = btc_rpc.sendtoaddress(str(btc_contract_addr), pre_agreed_amount) def bob_last_wish_func(): try_reclaim_btc(say, btc_rpc, btc_txid, btc_contract, bob_btc_exit_key, die) last_wish_func = bob_last_wish_func wait_confirm(say, 'Bitcoin', btc_txid, die, btc_rpc, num_confirms=6) send('btc_txid', btc_txid) elt_txid = recv('elt_txid') elt_contract = make_elt_cntract(key_to_reveal_pub, bob_elt_spend_key.pub, alice_elt_exit_pub_raw) with ChainParams(elements_chain_name): elt_contract_addr = P2SHCoinAddress.from_redeemScript(elt_contract) say('Got Elements contract address from Alice: {}'.format( elt_contract_addr)) say('Looking for this address in transaction {} in Elements'.format( elt_txid)) tx_json = elt_rpc.getrawtransaction(elt_txid, 1) if tx_json['confirmations'] < 2: die('Transaction does not have enough confirmations') elt_commit_tx = CElementsTransaction.deserialize(x(tx_json['hex'])) vout_n, unblind_result = find_and_unblind_vout(say, elt_commit_tx, elt_contract_addr, blinding_key, die) if unblind_result.amount != coins_to_satoshi(pre_agreed_amount): die('the amount {} found at the output in the offered transaction ' 'does not match the expected amount {}'.format( satoshi_to_coins(unblind_result.amount), pre_agreed_amount)) say('The asset and amount match expected values. lets spend it.') with ChainParams(elements_chain_name): dst_addr = CCoinAddress(elt_rpc.getnewaddress()) assert isinstance(dst_addr, CCoinConfidentialAddress) say('I will claim my Elements-BTC to {}'.format(dst_addr)) elt_claim_tx = create_elt_spend_tx( dst_addr, elt_txid, vout_n, elt_contract, die, spend_key=bob_elt_spend_key, contract_key=contract_key, blinding_key=blinding_key, blinding_factor=unblind_result.blinding_factor, asset_blinding_factor=unblind_result.asset_blinding_factor) # Cannot use VerifyScript for now, # because it does not support CHECKSIGFROMSTACK yet # # VerifyScript(tx.vin[0].scriptSig, # elt_contract_addr.to_scriptPubKey(), # tx, 0, amount=amount) say('Sending my spend-reveal transaction') sr_txid = elt_rpc.sendrawtransaction(b2x(elt_claim_tx.serialize())) wait_confirm(say, 'Elements', sr_txid, die, elt_rpc, num_confirms=2) say('Got my Elements-BTC. Swap successful (at least for me :-)')
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 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
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() FEE_PER_VBYTE = 0.00025 * CoreCoinParams.COIN / 1000 while True:
def select_coins(self, address: ExternalAddress): target_value = coins_to_satoshi(self.spend_amount, False) return self.controller.select_coins(target_value, address, self.priority_fee_selected)
def check_serialize_deserialize(self, tx, tx_bytes, tx_decoded): self.assertEqual(tx_bytes, tx.serialize()) self.assertEqual(tx_bytes, CTransaction.deserialize(tx.serialize()).serialize()) self.assertEqual(tx_bytes, tx.to_mutable().to_immutable().serialize()) self.assertEqual(tx_decoded['version'], tx.nVersion) self.assertEqual(tx_decoded['locktime'], tx.nLockTime) # we ignore withash field - we do not have ComputeWitnessHash() function # as it is only relevant for blocks, not transactions self.assertEqual(tx_decoded['hash'], b2lx(tx.GetHash())) self.assertEqual(tx_decoded['txid'], b2lx(tx.GetTxid())) for n, vout in enumerate(tx_decoded['vout']): if 'amountcommitment' in vout: self.assertEqual(x(vout['amountcommitment']), tx.vout[n].nValue.commitment) if 'assetcommitment' in vout: self.assertEqual(x(vout['assetcommitment']), tx.vout[n].nAsset.commitment) if 'asset' in vout: self.assertEqual(vout['asset'], tx.vout[n].nAsset.to_asset().to_hex()) if 'scriptPubKey' in vout: spk = vout['scriptPubKey'] self.assertEqual(x(spk['hex']), tx.vout[n].scriptPubKey) if 'pegout_type' in spk: self.assertEqual(spk['type'], 'nulldata') self.assertTrue(tx.vout[n].scriptPubKey.is_pegout()) genesis_hash, pegout_scriptpubkey = tx.vout[ n].scriptPubKey.get_pegout_data() if spk['pegout_type'] != 'nonstandard': assert spk['pegout_type'] in ('pubkeyhash', 'scripthash') addr = CCoinAddress.from_scriptPubKey( pegout_scriptpubkey) self.assertEqual(len(spk['pegout_addresses']), 1) self.assertEqual(spk['pegout_addresses'][0], str(addr)) self.assertEqual(spk['pegout_hex'], b2x(pegout_scriptpubkey)) self.assertEqual(spk['pegout_chain'], b2lx(genesis_hash)) if spk['type'] in ('pubkeyhash', 'scripthash'): self.assertEqual(len(spk['addresses']), 1) addr = CCoinAddress.from_scriptPubKey( tx.vout[n].scriptPubKey) self.assertEqual(spk['addresses'][0], str(addr)) elif spk['type'] == 'nulldata': self.assertEqual(tx.vout[n].scriptPubKey, x(spk['hex'])) else: self.assertEqual(spk['type'], 'fee') self.assertEqual(len(tx.vout[n].scriptPubKey), 0) if secp256k1_has_zkp: if tx.wit.is_null(): rpinfo = None else: rpinfo = tx.wit.vtxoutwit[n].get_rangeproof_info() if 'value-minimum' in vout: self.assertIsNotNone(rpinfo) self.assertEqual(vout['ct-exponent'], rpinfo.exp) self.assertEqual(vout['ct-bits'], rpinfo.mantissa) self.assertEqual( coins_to_satoshi(vout['value-minimum'], check_range=False), rpinfo.value_min) self.assertEqual( coins_to_satoshi(vout['value-maximum'], check_range=False), rpinfo.value_max) else: self.assertTrue(rpinfo is None or rpinfo.exp == -1) if rpinfo is None: value = tx.vout[n].nValue.to_amount() else: value = rpinfo.value_min self.assertEqual(coins_to_satoshi(vout['value']), value) else: warn_zkp_unavailable() if 'value' in vout and tx.vout[n].nValue.is_explicit(): self.assertEqual(coins_to_satoshi(vout['value']), tx.vout[n].nValue.to_amount()) for n, vin in enumerate(tx_decoded['vin']): if 'scripSig' in vin: self.assertEqual( x(vin['scriptSig']['hex'], tx.vin[n].scriptSig)) if 'txid' in vin: self.assertEqual(vin['txid'], b2lx(tx.vin[n].prevout.hash)) if 'vout' in vin: self.assertEqual(vin['vout'], tx.vin[n].prevout.n) if 'is_pegin' in vin: self.assertEqual(vin['is_pegin'], tx.vin[n].is_pegin) if vin['is_pegin'] is False: if 'scriptWitness' in vin: self.assertTrue( tx.wit.vtxinwit[n].scriptWitness.is_null()) if 'pegin_witness' in vin: self.assertTrue( tx.wit.vtxinwit[n].pegin_witness.is_null()) else: for stack_index, stack_item in enumerate( vin['scriptWitness']): self.assertTrue( stack_item, b2x(tx.wit.vtxinwit[n].scriptWitness. stack[stack_index])) for stack_index, stack_item in enumerate( vin['pegin_witness']): self.assertTrue( stack_item, b2x(tx.wit.vtxinwit[n].pegin_witness. stack[stack_index])) if 'sequence' in vin: self.assertEqual(vin['sequence'], tx.vin[n].nSequence) if 'coinbase' in vin: self.assertTrue(tx.is_coinbase()) if 'issuance' in vin: iss = vin['issuance'] self.assertEqual( iss['assetBlindingNonce'], tx.vin[n].assetIssuance.assetBlindingNonce.to_hex()) if 'asset' in iss: if iss['isreissuance']: self.assertTrue(not tx.vin[n].assetIssuance. assetBlindingNonce.is_null()) self.assertEqual( iss['assetEntropy'], tx.vin[n].assetIssuance.assetEntropy.to_hex()) asset = calculate_asset( tx.vin[n].assetIssuance.assetEntropy) else: entropy = generate_asset_entropy( tx.vin[n].prevout, tx.vin[n].assetIssuance.assetEntropy) self.assertEqual(iss['assetEntropy'], entropy.to_hex()) asset = calculate_asset(entropy) reiss_token = calculate_reissuance_token( entropy, tx.vin[n].assetIssuance.nAmount.is_commitment()) self.assertEqual(iss['token'], reiss_token.to_hex()) self.assertEqual(iss['asset'], asset.to_hex()) if 'assetamount' in iss: self.assertEqual( coins_to_satoshi(iss['assetamount']), tx.vin[n].assetIssuance.nAmount.to_amount()) elif 'assetamountcommitment' in iss: self.assertEqual( iss['assetamountcommitment'], b2x(tx.vin[n].assetIssuance.nAmount.commitment)) if 'tokenamount' in iss: self.assertEqual( coins_to_satoshi(iss['tokenamount']), tx.vin[n].assetIssuance.nInflationKeys.to_amount()) elif 'tokenamountcommitment' in iss: self.assertEqual( iss['tokenamountcommitment'], b2x(tx.vin[n].assetIssuance.nInflationKeys.commitment))
# # 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('bff785da9f8169f49be92fa95e31f0890c385bfb1bd24d6b94d7900057c617ae') vout = 0 # Create the txin structure, which includes the outpoint. The scriptSig # defaults to being empty. txin = CMutableTxIn(COutPoint(txid, vout)) # Create the txout. This time we create the scriptPubKey from a Bitcoin # address. txout = CMutableTxOut( coins_to_satoshi(0.0005), CBitcoinAddress('323uf9MgLaSn9T7vDaK1cGAZ2qpvYUuqSp').to_scriptPubKey()) # Create the unsigned transaction. tx = CMutableTransaction([txin], [txout]) # Calculate the signature hash for that transaction. Note how the script we use # is the redeemScript, not the scriptPubKey. That's because when the CHECKSIG # operation happens EvalScript() will be evaluating the redeemScript, so the # corresponding SignatureHash() function will use that same script when it # replaces the scriptSig in the transaction being hashed with the script being # executed. sighash = SignatureHash(txin_redeemScript, tx, 0, SIGHASH_ALL) # Now sign it. We have to append the type of signature we want to the end, in # this case the usual SIGHASH_ALL.
def check_blind(self, unblinded_tx, unblinded_tx_raw, blinded_tx, blinded_tx_raw, bundle, blinding_derivation_key, asset_commitments=()): input_descriptors = [] for utxo in bundle['vin_utxo']: amount = -1 if utxo['amount'] == -1 else coins_to_satoshi( utxo['amount']) input_descriptors.append( BlindingInputDescriptor( amount=amount, asset=CAsset(lx(utxo['asset'])), blinding_factor=Uint256(lx(utxo['blinder'])), asset_blinding_factor=Uint256(lx(utxo['assetblinder'])))) num_to_blind = 0 output_pubkeys = [] for vout in unblinded_tx.vout: if not vout.nNonce.is_null() and vout.nValue.is_explicit(): output_pubkeys.append(CPubKey(vout.nNonce.commitment)) num_to_blind += 1 else: output_pubkeys.append(CPubKey()) tx_to_blind = unblinded_tx.to_mutable() blind_issuance_asset_keys = [] blind_issuance_token_keys = [] for vin in blinded_tx.vin: issuance = vin.assetIssuance if not issuance.is_null(): issuance_blinding_script = CScript( [OP_RETURN, vin.prevout.hash, vin.prevout.n]) blind_issuance_key = issuance_blinding_script.derive_blinding_key( blinding_derivation_key) if issuance.nAmount.is_commitment(): blind_issuance_asset_keys.append(blind_issuance_key) num_to_blind += 1 else: blind_issuance_asset_keys.append(None) if issuance.nInflationKeys.is_commitment(): blind_issuance_token_keys.append(blind_issuance_key) num_to_blind += 1 else: blind_issuance_token_keys.append(None) else: blind_issuance_asset_keys.append(None) blind_issuance_token_keys.append(None) # Deterministic random was used when generating test transactions, # to have reproducible results. We need to set the random seed # to the same value that was used when test data was generated. # (see note below on that supplying _rand_func parameter to blind() # is intended only for testing code, not for production) random.seed(bundle['rand_seed']) def rand_func(n): return bytes([random.randint(0, 255) for _ in range(n)]) # Auxiliary generators will be be non-empty only for the case # when we are blinding different transaction templates that is # then combined into one common transaction, that is done in # test_split_blinding_multi_sign(). # In this case, you need to supply the asset commitments for # all of the inputs of the final transaction, even if currently # blinded transaction template does not contain these inputs. blind_result = tx_to_blind.blind( input_descriptors=input_descriptors, output_pubkeys=output_pubkeys, blind_issuance_asset_keys=blind_issuance_asset_keys, blind_issuance_token_keys=blind_issuance_token_keys, auxiliary_generators=asset_commitments, # IMPORTANT NOTE: # Specifying custom _rand_func is only required for testing. # Here we use it to supply deterministically generated # pseudo-random bytes, so that blinding results will match the test # data that was generated using deterministically generated random # bytes, with seed values that are saved in 'rand_seed' fields of # test data bunldes. # # In normal code you do should NOT specify _rand_func: # os.urandom will be used by default (os.urandom is suitable for cryptographic use) _rand_func=rand_func) self.assertFalse(blind_result.error) if all(_k is None for _k in blind_issuance_asset_keys): random.seed(bundle['rand_seed']) tx_to_blind2 = unblinded_tx.to_mutable() blind_result2 = tx_to_blind2.blind( input_descriptors=input_descriptors, output_pubkeys=output_pubkeys, blind_issuance_asset_keys=blind_issuance_asset_keys, blind_issuance_token_keys=blind_issuance_token_keys, auxiliary_generators=asset_commitments, _rand_func=rand_func) self.assertFalse(blind_result2.error) self.assertEqual(blind_result, blind_result2) self.assertEqual(tx_to_blind.serialize(), tx_to_blind2.serialize()) self.assertEqual(blind_result.num_successfully_blinded, num_to_blind) self.assertNotEqual(unblinded_tx_raw, tx_to_blind.serialize()) self.assertEqual(blinded_tx_raw, tx_to_blind.serialize())