def issuance_test(self, sighash_ty): tx, prev_vout, spk, sec, pub, tweak = self.create_taproot_utxo() blind_addr = self.nodes[0].getnewaddress() nonblind_addr = self.nodes[0].validateaddress( blind_addr)['unconfidential'] raw_tx = self.nodes[0].createrawtransaction([], [{nonblind_addr: 1}]) raw_tx = FromHex(CTransaction(), raw_tx) # Need to taproot outputs later because fundrawtransaction cannot estimate fees # prev out has value 1.2 btc in_total = tx.vout[prev_vout].nValue.getAmount() fees = 100 raw_tx.vin.append(CTxIn(COutPoint(tx.sha256, prev_vout))) raw_tx.vout.append( CTxOut(nValue=CTxOutValue(in_total - fees - 10**8), scriptPubKey=spk)) # send back to self raw_tx.vout.append(CTxOut(nValue=CTxOutValue(fees))) # issued_tx = raw_tx.serialize().hex() blind_addr = self.nodes[0].getnewaddress() issue_addr = self.nodes[0].validateaddress( blind_addr)['unconfidential'] issued_tx = self.nodes[0].rawissueasset( raw_tx.serialize().hex(), [{ "asset_amount": 2, "asset_address": issue_addr, "blind": False }])[0]["hex"] # blind_tx = self.nodes[0].blindrawtransaction(issued_tx) # This is a no-op genesis_hash = uint256_from_str( bytes.fromhex(self.nodes[0].getblockhash(0))[::-1]) issued_tx = FromHex(CTransaction(), issued_tx) issued_tx.wit.vtxoutwit = [CTxOutWitness()] * len(issued_tx.vout) issued_tx.wit.vtxinwit = [CTxInWitness()] * len(issued_tx.vin) msg = TaprootSignatureHash(issued_tx, [tx.vout[prev_vout]], sighash_ty, genesis_hash, 0) # compute the tweak tweak_sk = tweak_add_privkey(sec, tweak) sig = sign_schnorr(tweak_sk, msg) issued_tx.wit.vtxinwit[0].scriptWitness.stack = [ taproot_pad_sighash_ty(sig, sighash_ty) ] pub_tweak = tweak_add_pubkey(pub, tweak)[0] assert (verify_schnorr(pub_tweak, sig, msg)) # Since we add in/outputs the min feerate is no longer maintained. self.nodes[0].sendrawtransaction(hexstring=issued_tx.serialize().hex()) self.nodes[0].generate(1) last_blk = self.nodes[0].getblock(self.nodes[0].getbestblockhash()) issued_tx.rehash() assert (issued_tx.hash in last_blk['tx'])
def pegin_test(self, sighash_ty): # Peg-in prep: # Hack: since we're not validating peg-ins in parent chain, just make # both the funding and claim tx on same chain (printing money) fund_info = self.nodes[0].getpeginaddress() peg_id = self.nodes[0].sendtoaddress(fund_info["mainchain_address"], 1) raw_peg_tx = self.nodes[0].gettransaction(peg_id)["hex"] peg_txid = self.nodes[0].sendrawtransaction(raw_peg_tx) self.nodes[0].generate(101) peg_prf = self.nodes[0].gettxoutproof([peg_txid]) claim_script = fund_info["claim_script"] # Create a pegin transaction # We have to manually supply claim script, otherwise the wallet will pick raw_claim = self.nodes[0].createrawpegin(raw_peg_tx, peg_prf, claim_script) raw_claim = FromHex(CTransaction(), raw_claim['hex']) # Create a taproot utxo tx, prev_vout, spk, sec, pub, tweak = self.create_taproot_utxo() # Spend the pegin and taproot tx together raw_claim.vin.append(CTxIn(COutPoint(tx.sha256, prev_vout))) raw_claim.vout.append( CTxOut(nValue=CTxOutValue(12 * 10**7), scriptPubKey=spk)) # send back to self signed = self.nodes[0].signrawtransactionwithwallet( raw_claim.serialize().hex()) raw_claim = FromHex(CTransaction(), signed['hex']) genesis_hash = uint256_from_str( bytes.fromhex(self.nodes[0].getblockhash(0))[::-1]) peg_utxo = CTxOut() peg_utxo.from_pegin_witness_data( raw_claim.wit.vtxinwit[0].peginWitness) msg = TaprootSignatureHash(raw_claim, [peg_utxo, tx.vout[prev_vout]], sighash_ty, genesis_hash, 1) # compute the tweak tweak_sk = tweak_add_privkey(sec, tweak) sig = sign_schnorr(tweak_sk, msg) raw_claim.wit.vtxinwit[1].scriptWitness.stack = [ taproot_pad_sighash_ty(sig, sighash_ty) ] pub_tweak = tweak_add_pubkey(pub, tweak)[0] assert (verify_schnorr(pub_tweak, sig, msg)) # Since we add in/outputs the min feerate is no longer maintained. self.nodes[0].sendrawtransaction(hexstring=raw_claim.serialize().hex()) self.nodes[0].generate(1) last_blk = self.nodes[0].getblock(self.nodes[0].getbestblockhash()) raw_claim.rehash() assert (raw_claim.hash in last_blk['tx'])
def make_utxo(node, amount, confirmed=True, scriptPubKey=CScript([1])): """Create a txout with a given amount and scriptPubKey Mines coins as needed. confirmed - txouts created will be confirmed in the blockchain; unconfirmed otherwise. """ fee = 1 * COIN while node.getbalance()['bitcoin'] < satoshi_round((amount + fee) / COIN): node.generate(100) new_addr = node.getnewaddress() unblinded_addr = node.validateaddress(new_addr)["unconfidential"] txidstr = node.sendtoaddress(new_addr, satoshi_round( (amount + fee) / COIN)) tx1 = node.getrawtransaction(txidstr, 1) txid = int(txidstr, 16) i = None for i, txout in enumerate(tx1['vout']): if txout['scriptPubKey']['type'] == "fee": continue # skip fee outputs if txout['scriptPubKey']['addresses'] == [unblinded_addr]: break assert i is not None tx2 = CTransaction() tx2.vin = [CTxIn(COutPoint(txid, i))] tx1raw = CTransaction() tx1raw.deserialize( BytesIO(hex_str_to_bytes(node.getrawtransaction(txidstr)))) feeout = CTxOut(CTxOutValue(tx1raw.vout[i].nValue.getAmount() - amount)) tx2.vout = [CTxOut(amount, scriptPubKey), feeout] tx2.rehash() signed_tx = node.signrawtransactionwithwallet(txToHex(tx2)) txid = node.sendrawtransaction(signed_tx['hex'], True) # If requested, ensure txouts are confirmed. if confirmed: mempool_size = len(node.getrawmempool()) while mempool_size > 0: node.generate(1) new_size = len(node.getrawmempool()) # Error out if we have something stuck in the mempool, as this # would likely be a bug. assert (new_size < mempool_size) mempool_size = new_size return COutPoint(int(txid, 16), 0)
def create_taproot_utxo(self, scripts=None, blind=False): # modify the transaction to add one output that should spend previous taproot # Create a taproot prevout addr = self.nodes[0].getnewaddress() sec = generate_privkey() pub = compute_xonly_pubkey(sec)[0] tap = taproot_construct(pub, scripts) spk = tap.scriptPubKey # No need to test blinding in this unit test unconf_addr = self.nodes[0].getaddressinfo(addr)['unconfidential'] raw_tx = self.nodes[0].createrawtransaction([], [{unconf_addr: 1.2}]) # edit spk directly, no way to get new address. # would need to implement bech32m in python tx = FromHex(CTransaction(), raw_tx) tx.vout[0].scriptPubKey = spk tx.vout[0].nValue = CTxOutValue(12 * 10**7) raw_hex = tx.serialize().hex() fund_tx = self.nodes[0].fundrawtransaction( raw_hex, False, )["hex"] fund_tx = FromHex(CTransaction(), fund_tx) # Createrawtransaction might rearrage txouts prev_vout = None for i, out in enumerate(fund_tx.vout): if spk == out.scriptPubKey: prev_vout = i tx = self.nodes[0].blindrawtransaction(fund_tx.serialize().hex()) signed_raw_tx = self.nodes[0].signrawtransactionwithwallet(tx) _txid = self.nodes[0].sendrawtransaction(signed_raw_tx['hex']) tx = FromHex(CTransaction(), signed_raw_tx['hex']) tx.rehash() self.nodes[0].generate(1) last_blk = self.nodes[0].getblock(self.nodes[0].getbestblockhash()) assert (tx.hash in last_blk['tx']) return tx, prev_vout, spk, sec, pub, tap
def run_test(self): node0 = self.nodes[0] node1 = self.nodes[1] node0.importprivkey(mandatory_privkey) self.log.info( "generatetoaddress: Making blocks of various kinds, checking for rejection" ) # Create valid blocks to get out of IBD and get some funds (subsidy goes to permitted addr) node0.generatetoaddress(101, mandatory_address) # Generating for another address will not work assert_raises_rpc_error( -1, "CreateNewBlock: TestBlockValidity failed: bad-coinbase-txos", node0.generatetoaddress, 1, node0.getnewaddress()) # Have non-mandatory node make a template self.sync_all() tmpl = node1.getblocktemplate() # We make a block with OP_TRUE coinbase output that will fail on node0 coinbase_tx = create_coinbase(height=int(tmpl["height"])) # sequence numbers must not be max for nLockTime to have effect coinbase_tx.vin[0].nSequence = 2**32 - 2 coinbase_tx.rehash() block = CBlock() block.nVersion = tmpl["version"] block.hashPrevBlock = int(tmpl["previousblockhash"], 16) block.nTime = tmpl["curtime"] block.nBits = int(tmpl["bits"], 16) block.nNonce = 0 block.proof = CProof(bytearray.fromhex('51')) block.vtx = [coinbase_tx] block.block_height = int(tmpl["height"]) block.hashMerkleRoot = block.calc_merkle_root() self.log.info("getblocktemplate: Test block on both nodes") assert_template(node1, block, None) assert_template(node0, block, 'bad-coinbase-txos') self.log.info("getblocktemplate: Test non-subsidy block on both nodes") # Without block reward anything goes, this allows commitment outputs like segwit coinbase_tx.vout[0].nValue = CTxOutValue(0) coinbase_tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'\xff']) coinbase_tx.rehash() block.vtx = [coinbase_tx] assert_template(node0, block, None) assert_template(node1, block, None) # # Also test that coinbases can't have fees. self.sync_all() tmpl = node1.getblocktemplate() coinbase_tx = create_coinbase(height=int(tmpl["height"])) # sequence numbers must not be max for nLockTime to have effect coinbase_tx.vin[0].nSequence = 2**32 - 2 # Add fee output. coinbase_tx.vout[0].nValue.setToAmount( coinbase_tx.vout[0].nValue.getAmount() - 1) coinbase_tx.vout.append(CTxOut(1)) coinbase_tx.rehash() block = CBlock() block.nVersion = tmpl["version"] block.hashPrevBlock = int(tmpl["previousblockhash"], 16) block.nTime = tmpl["curtime"] block.nBits = int(tmpl["bits"], 16) block.nNonce = 0 block.proof = CProof(bytearray.fromhex('51')) block.vtx = [coinbase_tx] block.block_height = int(tmpl["height"]) block.hashMerkleRoot = block.calc_merkle_root() # should not be accepted assert_template(node0, block, "bad-cb-fee") assert_template(node1, block, "bad-cb-fee")
def run_test(self): # Block will have 10 satoshi output, node 1 will ban addr = self.nodes[0].getnewaddress() sub_block = self.nodes[0].generatetoaddress(1, addr) raw_coinbase = self.nodes[0].getrawtransaction( self.nodes[0].getblock(sub_block[0])["tx"][0], False, sub_block[0]) decoded_coinbase = self.nodes[0].decoderawtransaction(raw_coinbase) found_ten = False for vout in decoded_coinbase["vout"]: if vout["value"] == Decimal('0.00000010') and found_ten == False: found_ten = True elif vout["value"] == 0: continue else: raise Exception("Invalid output amount in coinbase") assert (found_ten) # Block will have 0 satoshis outputs only at height 1 no_sub_block = self.nodes[1].generatetoaddress(1, addr) raw_coinbase = self.nodes[1].getrawtransaction( self.nodes[1].getblock(no_sub_block[0])["tx"][0], False, no_sub_block[0]) decoded_coinbase = self.nodes[1].decoderawtransaction(raw_coinbase) for vout in decoded_coinbase["vout"]: if vout["value"] != 0: raise Exception("Invalid output amount in coinbase") tmpl = self.nodes[0].getblocktemplate({"rules": ["segwit"]}) # Template with invalid amount(50*COIN) will be invalid in both coinbase_tx = create_coinbase(height=int(tmpl["height"])) block = CBlock() block.nVersion = tmpl["version"] block.hashPrevBlock = int(tmpl["previousblockhash"], 16) block.nTime = tmpl["curtime"] block.nBits = int(tmpl["bits"], 16) block.nNonce = 0 block.block_height = int(tmpl["height"]) block.proof = CProof(bytearray.fromhex('51')) block.vtx = [coinbase_tx] assert_template(self.nodes[0], block, "bad-cb-amount") # Set to proper value, resubmit block.vtx[0].vout[0].nValue = CTxOutValue(10) block.vtx[0].sha256 = None assert_template(self.nodes[0], block, None) # No subsidy also allowed block.vtx[0].vout[0].nValue = CTxOutValue(0) block.vtx[0].sha256 = None #assert_template(self.nodes[0], block, None) # ELEMENTS: 0-value outputs not allowed # Change previous blockhash to other nodes' genesis block and reward to 1, test again block.hashPrevBlock = int( self.nodes[1].getblockhash(self.nodes[1].getblockcount()), 16) block.vtx[0].vout[0].nValue = CTxOutValue(1) block.vtx[0].sha256 = None assert_template(self.nodes[1], block, "bad-cb-amount") block.vtx[0].vout[0].nValue = CTxOutValue(0) block.vtx[0].sha256 = None
def run_test(self): node = self.nodes[0] self.log.info('Start with empty mempool, and 200 blocks') self.mempool_size = 0 wait_until(lambda: node.getblockcount() == 200) assert_equal(node.getmempoolinfo()['size'], self.mempool_size) coins = node.listunspent() self.log.info('Should not accept garbage to testmempoolaccept') assert_raises_rpc_error( -3, 'Expected type array, got string', lambda: node.testmempoolaccept(rawtxs='ff00baar')) assert_raises_rpc_error( -8, 'Array must contain exactly one raw transaction for now', lambda: node.testmempoolaccept(rawtxs=['ff00baar', 'ff22'])) assert_raises_rpc_error( -22, 'TX decode failed', lambda: node.testmempoolaccept(rawtxs=['ff00baar'])) self.log.info('A transaction already in the blockchain') coin = coins.pop() # Pick a random coin(base) to spend raw_tx_in_block = node.signrawtransactionwithwallet( node.createrawtransaction( inputs=[{ 'txid': coin['txid'], 'vout': coin['vout'] }], outputs=[{ node.getnewaddress(): 0.3 }, { node.getnewaddress(): 49 }, { "fee": coin["amount"] - Decimal('49.3') }], ))['hex'] txid_in_block = node.sendrawtransaction(hexstring=raw_tx_in_block, allowhighfees=True) node.generate(1) self.mempool_size = 0 self.check_mempool_result( result_expected=[{ 'txid': txid_in_block, 'allowed': False, 'reject-reason': '18: txn-already-known' }], rawtxs=[raw_tx_in_block], ) self.log.info('A transaction not in the mempool') fee = 0.00000700 raw_tx_0 = node.signrawtransactionwithwallet( node.createrawtransaction( inputs=[{ "txid": txid_in_block, "vout": 0, "sequence": BIP125_SEQUENCE_NUMBER }], # RBF is used later outputs=[{ node.getnewaddress(): 0.3 - fee }, { "fee": fee }], ))['hex'] tx = CTransaction() tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) txid_0 = tx.rehash() self.check_mempool_result( result_expected=[{ 'txid': txid_0, 'allowed': True }], rawtxs=[raw_tx_0], ) self.log.info('A final transaction not in the mempool') coin = coins.pop() # Pick a random coin(base) to spend raw_tx_final = node.signrawtransactionwithwallet( node.createrawtransaction( inputs=[{ 'txid': coin['txid'], 'vout': coin['vout'], "sequence": 0xffffffff }], # SEQUENCE_FINAL outputs=[{ node.getnewaddress(): 0.025 }, { "fee": coin["amount"] - Decimal("0.025") }], locktime=node.getblockcount() + 2000, # Can be anything ))['hex'] tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_final))) self.check_mempool_result( result_expected=[{ 'txid': tx.rehash(), 'allowed': True }], rawtxs=[bytes_to_hex_str(tx.serialize())], allowhighfees=True, ) node.sendrawtransaction(hexstring=raw_tx_final, allowhighfees=True) self.mempool_size += 1 self.log.info('A transaction in the mempool') node.sendrawtransaction(hexstring=raw_tx_0) self.mempool_size += 1 self.check_mempool_result( result_expected=[{ 'txid': txid_0, 'allowed': False, 'reject-reason': '18: txn-already-in-mempool' }], rawtxs=[raw_tx_0], ) self.log.info('A transaction that replaces a mempool transaction') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) tx.vout[0].nValue.setToAmount(tx.vout[0].nValue.getAmount() - int(fee * COIN)) # Double the fee txid_0_out = tx.vout[0].nValue.getAmount() tx.vout[1].nValue.setToAmount(tx.vout[1].nValue.getAmount() + int(fee * COIN)) tx.vin[0].nSequence = BIP125_SEQUENCE_NUMBER + 1 # Now, opt out of RBF raw_tx_0 = node.signrawtransactionwithwallet( bytes_to_hex_str(tx.serialize()))['hex'] tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) txid_0 = tx.rehash() self.check_mempool_result( result_expected=[{ 'txid': txid_0, 'allowed': True }], rawtxs=[raw_tx_0], ) self.log.info('A transaction that conflicts with an unconfirmed tx') # Send the transaction that replaces the mempool transaction and opts out of replaceability node.sendrawtransaction(hexstring=bytes_to_hex_str(tx.serialize()), allowhighfees=True) # take original raw_tx_0 tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) tx.vout[0].nValue.setToAmount(tx.vout[0].nValue.getAmount() - int(4 * fee * COIN)) # Set more fee tx.vout[1].nValue.setToAmount(tx.vout[1].nValue.getAmount() + int(4 * fee * COIN)) # skip re-signing the tx self.check_mempool_result( result_expected=[{ 'txid': tx.rehash(), 'allowed': False, 'reject-reason': '18: txn-mempool-conflict' }], rawtxs=[bytes_to_hex_str(tx.serialize())], allowhighfees=True, ) self.log.info('A transaction with missing inputs, that never existed') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) tx.vin[0].prevout = COutPoint(hash=int('ff' * 32, 16), n=14) # skip re-signing the tx self.check_mempool_result( result_expected=[{ 'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'missing-inputs' }], rawtxs=[bytes_to_hex_str(tx.serialize())], ) self.log.info( 'A transaction with missing inputs, that existed once in the past') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_0))) tx.vin[ 0].prevout.n = 1 # Set vout to 1, to spend the other outpoint (49 coins) of the in-chain-tx we want to double spend tx.vout[1].nValue.setToAmount(49 * COIN - tx.vout[0].nValue.getAmount()) # fee txid_1_out = tx.vout[0].nValue.getAmount() raw_tx_1 = node.signrawtransactionwithwallet( bytes_to_hex_str(tx.serialize()))['hex'] txid_1 = node.sendrawtransaction(hexstring=raw_tx_1, allowhighfees=True) # Now spend both to "clearly hide" the outputs, ie. remove the coins from the utxo set by spending them raw_tx_spend_both = node.signrawtransactionwithwallet( node.createrawtransaction( inputs=[ { 'txid': txid_0, 'vout': 0 }, { 'txid': txid_1, 'vout': 0 }, ], outputs=[{ node.getnewaddress(): 0.1 }, { "fee": Decimal(txid_0_out + txid_1_out) / Decimal(COIN) - Decimal('0.1') }]))['hex'] txid_spend_both = node.sendrawtransaction(hexstring=raw_tx_spend_both, allowhighfees=True) node.generate(1) self.mempool_size = 0 # Now see if we can add the coins back to the utxo set by sending the exact txs again self.check_mempool_result( result_expected=[{ 'txid': txid_0, 'allowed': False, 'reject-reason': 'missing-inputs' }], rawtxs=[raw_tx_0], ) self.check_mempool_result( result_expected=[{ 'txid': txid_1, 'allowed': False, 'reject-reason': 'missing-inputs' }], rawtxs=[raw_tx_1], ) self.log.info('Create a signed "reference" tx for later use') raw_tx_reference = node.signrawtransactionwithwallet( node.createrawtransaction( inputs=[{ 'txid': txid_spend_both, 'vout': 0 }], outputs=[{ node.getnewaddress(): 0.05 }, { "fee": Decimal('0.1') - Decimal('0.05') }], ))['hex'] tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) # Reference tx should be valid on itself self.check_mempool_result( result_expected=[{ 'txid': tx.rehash(), 'allowed': True }], rawtxs=[bytes_to_hex_str(tx.serialize())], ) self.log.info('A transaction with no outputs') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout = [] # Skip re-signing the transaction for context independent checks from now on # tx.deserialize(BytesIO(hex_str_to_bytes(node.signrawtransactionwithwallet(bytes_to_hex_str(tx.serialize()))['hex']))) self.check_mempool_result( result_expected=[{ 'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-vout-empty' }], rawtxs=[bytes_to_hex_str(tx.serialize())], ) self.log.info('A really large transaction') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vin = [tx.vin[0]] * math.ceil( MAX_BLOCK_BASE_SIZE / len(tx.vin[0].serialize())) self.check_mempool_result( result_expected=[{ 'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-oversize' }], rawtxs=[bytes_to_hex_str(tx.serialize())], ) self.log.info('A transaction with negative output value') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout[0].nValue.setToAmount(tx.vout[0].nValue.getAmount() * -1) self.check_mempool_result( result_expected=[{ 'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-vout-negative' }], rawtxs=[bytes_to_hex_str(tx.serialize())], ) self.log.info('A transaction with too large output value') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout[0].nValue = CTxOutValue(21000000 * COIN + 1) self.check_mempool_result( result_expected=[{ 'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-vout-toolarge' }], rawtxs=[bytes_to_hex_str(tx.serialize())], ) self.log.info('A transaction with too large sum of output values') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout = [tx.vout[0]] * 2 tx.vout[0].nValue = CTxOutValue(21000000 * COIN) self.check_mempool_result( result_expected=[{ 'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-txouttotal-toolarge' }], rawtxs=[bytes_to_hex_str(tx.serialize())], ) self.log.info('A transaction with duplicate inputs') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vin = [tx.vin[0]] * 2 self.check_mempool_result( result_expected=[{ 'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: bad-txns-inputs-duplicate' }], rawtxs=[bytes_to_hex_str(tx.serialize())], ) self.log.info('A coinbase transaction') # Pick the input of the first tx we signed, so it has to be a coinbase tx raw_tx_coinbase_spent = node.getrawtransaction( txid=node.decoderawtransaction( hexstring=raw_tx_in_block)['vin'][0]['txid']) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_coinbase_spent))) self.check_mempool_result( result_expected=[{ 'txid': tx.rehash(), 'allowed': False, 'reject-reason': '16: coinbase' }], rawtxs=[bytes_to_hex_str(tx.serialize())], ) self.log.info('Some nonstandard transactions') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.nVersion = 3 # A version currently non-standard self.check_mempool_result( result_expected=[{ 'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: version' }], rawtxs=[bytes_to_hex_str(tx.serialize())], ) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout[0].scriptPubKey = CScript([OP_0]) # Some non-standard script self.check_mempool_result( result_expected=[{ 'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: scriptpubkey' }], rawtxs=[bytes_to_hex_str(tx.serialize())], ) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vin[0].scriptSig = CScript([OP_HASH160 ]) # Some not-pushonly scriptSig self.check_mempool_result( result_expected=[{ 'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: scriptsig-not-pushonly' }], rawtxs=[bytes_to_hex_str(tx.serialize())], ) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) output_p2sh_burn = CTxOut(nValue=540, scriptPubKey=CScript( [OP_HASH160, hash160(b'burn'), OP_EQUAL])) num_scripts = 100000 // len(output_p2sh_burn.serialize( )) # Use enough outputs to make the tx too large for our policy tx.vout = [output_p2sh_burn] * num_scripts self.check_mempool_result( result_expected=[{ 'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: tx-size' }], rawtxs=[bytes_to_hex_str(tx.serialize())], ) tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout[0] = output_p2sh_burn tx.vout[0].nValue.setToAmount( tx.vout[0].nValue.getAmount() - 1) # Make output smaller, such that it is dust for our policy self.check_mempool_result( result_expected=[{ 'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: dust' }], rawtxs=[bytes_to_hex_str(tx.serialize())], ) # Elements: We allow multi op_return outputs by default. This still fails because relay fee isn't met tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'\xff']) tx.vout = [tx.vout[0]] * 2 self.check_mempool_result( result_expected=[{ 'txid': tx.rehash(), 'allowed': False, 'reject-reason': '66: min relay fee not met' }], rawtxs=[bytes_to_hex_str(tx.serialize())], ) self.log.info('A timelocked transaction') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vin[ 0].nSequence -= 1 # Should be non-max, so locktime is not ignored tx.nLockTime = node.getblockcount() + 1 self.check_mempool_result( result_expected=[{ 'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: non-final' }], rawtxs=[bytes_to_hex_str(tx.serialize())], ) self.log.info('A transaction that is locked by BIP68 sequence logic') tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx_reference))) tx.vin[ 0].nSequence = 2 # We could include it in the second block mined from now, but not the very next one # Can skip re-signing the tx because of early rejection self.check_mempool_result( result_expected=[{ 'txid': tx.rehash(), 'allowed': False, 'reject-reason': '64: non-BIP68-final' }], rawtxs=[bytes_to_hex_str(tx.serialize())], allowhighfees=True, )
def run_test(self): print("Testing wallet secret recovery") self.test_wallet_recovery() print("General Confidential tests") # Running balances node0 = self.nodes[0].getbalance()["bitcoin"] assert_equal(node0, 21000000) # just making sure initialfreecoins is working node1 = 0 node2 = 0 self.nodes[0].generate(101) txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), node0, "", "", True) self.nodes[0].generate(101) self.sync_all() assert_equal(self.nodes[0].getbalance()["bitcoin"], node0) assert_equal(self.nodes[1].getbalance("*", 1, False, "bitcoin"), node1) assert_equal(self.nodes[2].getbalance("*", 1, False, "bitcoin"), node2) # Send 3 BTC from 0 to a new unconfidential address of 2 with # the sendtoaddress call address = self.nodes[2].getnewaddress() unconfidential_address = self.nodes[2].validateaddress( address)["unconfidential"] value0 = 3 self.nodes[0].sendtoaddress(unconfidential_address, value0) self.nodes[0].generate(101) self.sync_all() node0 = node0 - value0 node2 = node2 + value0 assert_equal(self.nodes[0].getbalance()["bitcoin"], node0) assert_equal(self.nodes[1].getbalance("*", 1, False, "bitcoin"), node1) assert_equal(self.nodes[2].getbalance()["bitcoin"], node2) # Send 5 BTC from 0 to a new address of 2 with the sendtoaddress call address2 = self.nodes[2].getnewaddress() unconfidential_address2 = self.nodes[2].validateaddress( address2)["unconfidential"] value1 = 5 confidential_tx_id = self.nodes[0].sendtoaddress(address2, value1) self.nodes[0].generate(101) self.sync_all() node0 = node0 - value1 node2 = node2 + value1 assert_equal(self.nodes[0].getbalance()["bitcoin"], node0) assert_equal(self.nodes[1].getbalance("*", 1, False, "bitcoin"), node1) assert_equal(self.nodes[2].getbalance()["bitcoin"], node2) # Send 7 BTC from 0 to the unconfidential address of 2 and 11 BTC to the # confidential address using the raw transaction interface change_address = self.nodes[0].getnewaddress() value2 = 7 value3 = 11 value23 = value2 + value3 unspent = self.nodes[0].listunspent(1, 9999999, [], True, {"asset": "bitcoin"}) unspent = [i for i in unspent if i['amount'] > value23] assert_equal(len(unspent), 1) fee = Decimal('0.0001') tx = self.nodes[0].createrawtransaction( [{ "txid": unspent[0]["txid"], "vout": unspent[0]["vout"], "nValue": unspent[0]["amount"] }], { unconfidential_address: value2, address2: value3, change_address: unspent[0]["amount"] - value2 - value3 - fee, "fee": fee }) tx = self.nodes[0].blindrawtransaction(tx) tx_signed = self.nodes[0].signrawtransactionwithwallet(tx) raw_tx_id = self.nodes[0].sendrawtransaction(tx_signed['hex']) self.nodes[0].generate(101) self.sync_all() node0 -= (value2 + value3) node2 += value2 + value3 assert_equal(self.nodes[0].getbalance()["bitcoin"], node0) assert_equal(self.nodes[1].getbalance("*", 1, False, "bitcoin"), node1) assert_equal(self.nodes[2].getbalance()["bitcoin"], node2) # Check 2's listreceivedbyaddress received_by_address = self.nodes[2].listreceivedbyaddress( 0, False, False, "", "bitcoin") validate_by_address = [(address2, { "bitcoin": value1 + value3 }), (address, { "bitcoin": value0 + value2 })] assert_equal( sorted([(ele['address'], ele['amount']) for ele in received_by_address], key=lambda t: t[0]), sorted(validate_by_address, key=lambda t: t[0])) # Give an auditor (node 1) a blinding key to allow her to look at # transaction values self.nodes[1].importaddress(address2) received_by_address = self.nodes[1].listreceivedbyaddress( 1, False, True) #Node sees nothing unless it understands the values assert_equal(len(received_by_address), 0) assert_equal( len(self.nodes[1].listunspent(1, 9999999, [], True, {"asset": "bitcoin"})), 0) # Import the blinding key blindingkey = self.nodes[2].dumpblindingkey(address2) self.nodes[1].importblindingkey(address2, blindingkey) # Check the auditor's gettransaction and listreceivedbyaddress # Needs rescan to update wallet txns conf_tx = self.nodes[1].gettransaction(confidential_tx_id, True) assert_equal(conf_tx['amount']["bitcoin"], value1) # Make sure wallet can now deblind part of transaction deblinded_tx = self.nodes[1].unblindrawtransaction( conf_tx['hex'])['hex'] for output in self.nodes[1].decoderawtransaction(deblinded_tx)["vout"]: if "value" in output and output["scriptPubKey"]["type"] != "fee": assert_equal( output["scriptPubKey"]["addresses"][0], self.nodes[1].validateaddress(address2)['unconfidential']) found_unblinded = True assert (found_unblinded) assert_equal( self.nodes[1].gettransaction(raw_tx_id, True)['amount']["bitcoin"], value3) list_unspent = self.nodes[1].listunspent(1, 9999999, [], True, {"asset": "bitcoin"}) assert_equal(list_unspent[0]['amount'] + list_unspent[1]['amount'], value1 + value3) received_by_address = self.nodes[1].listreceivedbyaddress( 1, False, True) assert_equal(len(received_by_address), 1) assert_equal((received_by_address[0]['address'], received_by_address[0]['amount']['bitcoin']), (unconfidential_address2, value1 + value3)) # Spending a single confidential output and sending it to a # unconfidential output is not possible with CT. Test the # correct behavior of blindrawtransaction. unspent = self.nodes[0].listunspent(1, 9999999, [], True, {"asset": "bitcoin"}) unspent = [i for i in unspent if i['amount'] > value23] assert_equal(len(unspent), 1) tx = self.nodes[0].createrawtransaction( [{ "txid": unspent[0]["txid"], "vout": unspent[0]["vout"], "nValue": unspent[0]["amount"] }], { unconfidential_address: unspent[0]["amount"] - fee, "fee": fee }) # Test that blindrawtransaction adds an OP_RETURN output to balance blinders temptx = self.nodes[0].blindrawtransaction(tx) decodedtx = self.nodes[0].decoderawtransaction(temptx) assert_equal(decodedtx["vout"][-1]["scriptPubKey"]["asm"], "OP_RETURN") assert_equal(len(decodedtx["vout"]), 3) # Create same transaction but with a change/dummy output. # It should pass the blinding step. value4 = 17 change_address = self.nodes[0].getrawchangeaddress() tx = self.nodes[0].createrawtransaction( [{ "txid": unspent[0]["txid"], "vout": unspent[0]["vout"], "nValue": unspent[0]["amount"] }], { unconfidential_address: value4, change_address: unspent[0]["amount"] - value4 - fee, "fee": fee }) tx = self.nodes[0].blindrawtransaction(tx) tx_signed = self.nodes[0].signrawtransactionwithwallet(tx) txid = self.nodes[0].sendrawtransaction(tx_signed['hex']) decodedtx = self.nodes[0].decoderawtransaction(tx_signed["hex"]) self.nodes[0].generate(101) self.sync_all() unblindfound = False for i in range(len(decodedtx["vout"])): txout = self.nodes[0].gettxout(txid, i) if txout is not None and "asset" in txout: unblindfound = True if unblindfound == False: raise Exception( "No unconfidential output detected when one should exist") node0 -= value4 node2 += value4 assert_equal(self.nodes[0].getbalance()["bitcoin"], node0) assert_equal(self.nodes[1].getbalance("*", 1, False, "bitcoin"), node1) assert_equal(self.nodes[2].getbalance()["bitcoin"], node2) # Testing wallet's ability to deblind its own outputs addr = self.nodes[0].getnewaddress() addr2 = self.nodes[0].getnewaddress() # We add two to-blind outputs, fundraw adds an already-blinded change output # If we only add one, the newly blinded will be 0-blinded because input = -output raw = self.nodes[0].createrawtransaction([], { addr: Decimal('1.1'), addr2: 1 }) funded = self.nodes[0].fundrawtransaction(raw) # fund again to make sure no blinded outputs were created (would fail) funded = self.nodes[0].fundrawtransaction(funded["hex"]) blinded = self.nodes[0].blindrawtransaction(funded["hex"]) # blind again to make sure we know output blinders blinded2 = self.nodes[0].blindrawtransaction(blinded) # then sign and send signed = self.nodes[0].signrawtransactionwithwallet(blinded2) self.nodes[0].sendrawtransaction(signed["hex"]) # Aside: Check all outputs after fundraw are properly marked for blinding fund_decode = self.nodes[0].decoderawtransaction(funded["hex"]) for output in fund_decode["vout"][:-1]: assert "asset" in output assert "value" in output assert output["scriptPubKey"]["type"] != "fee" assert output["commitmentnonce_fully_valid"] assert fund_decode["vout"][-1]["scriptPubKey"]["type"] == "fee" assert not fund_decode["vout"][-1]["commitmentnonce_fully_valid"] # Also check that all fundraw outputs marked for blinding are blinded later for blind_tx in [blinded, blinded2]: blind_decode = self.nodes[0].decoderawtransaction(blind_tx) for output in blind_decode["vout"][:-1]: assert "asset" not in output assert "value" not in output assert output["scriptPubKey"]["type"] != "fee" assert output["commitmentnonce_fully_valid"] assert blind_decode["vout"][-1]["scriptPubKey"]["type"] == "fee" assert "asset" in blind_decode["vout"][-1] assert "value" in blind_decode["vout"][-1] assert not blind_decode["vout"][-1]["commitmentnonce_fully_valid"] # Check createblindedaddress functionality blinded_addr = self.nodes[0].getnewaddress() validated_addr = self.nodes[0].validateaddress(blinded_addr) blinding_pubkey = self.nodes[0].validateaddress( blinded_addr)["confidential_key"] blinding_key = self.nodes[0].dumpblindingkey(blinded_addr) assert_equal( blinded_addr, self.nodes[1].createblindedaddress( validated_addr["unconfidential"], blinding_pubkey)) # If a blinding key is over-ridden by a newly imported one, funds may be unaccounted for new_addr = self.nodes[0].getnewaddress() new_validated = self.nodes[0].validateaddress(new_addr) self.nodes[2].sendtoaddress(new_addr, 1) self.sync_all() diff_blind = self.nodes[1].createblindedaddress( new_validated["unconfidential"], blinding_pubkey) assert_equal( len(self.nodes[0].listunspent(0, 0, [new_validated["unconfidential"]])), 1) self.nodes[0].importblindingkey(diff_blind, blinding_key) # CT values for this wallet transaction have been cached via importblindingkey # therefore result will be same even though we change blinding keys assert_equal( len(self.nodes[0].listunspent(0, 0, [new_validated["unconfidential"]])), 1) # Confidential Assets Tests print("Assets tests...") # Bitcoin is the first issuance assert_equal(self.nodes[0].listissuances()[0]["assetlabel"], "bitcoin") assert_equal(len(self.nodes[0].listissuances()), 1) # Unblinded issuance of asset issued = self.nodes[0].issueasset(1, 1, False) self.nodes[0].reissueasset(issued["asset"], 1) # Compare resulting fields with getrawtransaction raw_details = self.nodes[0].getrawtransaction(issued["txid"], 1) assert_equal( issued["entropy"], raw_details["vin"][issued["vin"]]["issuance"]["assetEntropy"]) assert_equal(issued["asset"], raw_details["vin"][issued["vin"]]["issuance"]["asset"]) assert_equal(issued["token"], raw_details["vin"][issued["vin"]]["issuance"]["token"]) self.nodes[0].generate(1) self.sync_all() issued2 = self.nodes[0].issueasset(2, 1) test_asset = issued2["asset"] assert_equal(self.nodes[0].getwalletinfo()['balance'][test_asset], Decimal(2)) assert (test_asset not in self.nodes[1].getwalletinfo()['balance']) # Assets balance checking, note that accounts are completely ignored because # balance queries with accounts are horrifically broken upstream assert_equal(self.nodes[0].getbalance("*", 0, False, "bitcoin"), self.nodes[0].getbalance("*", 0, False, "bitcoin")) assert_equal(self.nodes[0].getwalletinfo()['balance']['bitcoin'], self.nodes[0].getbalance("*", 0, False, "bitcoin")) # Send some bitcoin and other assets over as well to fund wallet addr = self.nodes[2].getnewaddress() self.nodes[0].sendtoaddress(addr, 5) self.nodes[0].sendmany("", { addr: 1, self.nodes[2].getnewaddress(): 13 }, 0, "", [], False, 1, "UNSET", {addr: test_asset}) self.sync_all() # Should have exactly 1 in change(trusted, though not confirmed) after sending one off assert_equal(self.nodes[0].getbalance("*", 0, False, test_asset), 1) assert_equal(self.nodes[2].getunconfirmedbalance()[test_asset], Decimal(1)) b_utxos = self.nodes[2].listunspent(0, 0, [], True, {"asset": "bitcoin"}) t_utxos = self.nodes[2].listunspent(0, 0, [], True, {"asset": test_asset}) assert_equal(len(self.nodes[2].listunspent(0, 0, [])), len(b_utxos) + len(t_utxos)) # Now craft a blinded transaction via raw api rawaddrs = [] for i in range(2): rawaddrs.append(self.nodes[1].getnewaddress()) raw_assets = self.nodes[2].createrawtransaction( [{ "txid": b_utxos[0]['txid'], "vout": b_utxos[0]['vout'], "nValue": b_utxos[0]['amount'] }, { "txid": b_utxos[1]['txid'], "vout": b_utxos[1]['vout'], "nValue": b_utxos[1]['amount'], "asset": b_utxos[1]['asset'] }, { "txid": t_utxos[0]['txid'], "vout": t_utxos[0]['vout'], "nValue": t_utxos[0]['amount'], "asset": t_utxos[0]['asset'] }], { rawaddrs[1]: Decimal(t_utxos[0]['amount']), rawaddrs[0]: Decimal(b_utxos[0]['amount'] + b_utxos[1]['amount'] - Decimal("0.01")), "fee": Decimal("0.01") }, 0, False, { rawaddrs[0]: b_utxos[0]['asset'], rawaddrs[1]: t_utxos[0]['asset'], "fee": b_utxos[0]['asset'] }) # Sign unblinded, then blinded signed_assets = self.nodes[2].signrawtransactionwithwallet(raw_assets) blind_assets = self.nodes[2].blindrawtransaction(raw_assets) signed_assets = self.nodes[2].signrawtransactionwithwallet( blind_assets) # And finally send self.nodes[2].sendrawtransaction(signed_assets['hex']) self.nodes[2].generate(101) self.sync_all() issuancedata = self.nodes[2].issueasset( 0, Decimal('0.00000006')) #0 of asset, 6 reissuance token # Node 2 will send node 1 a reissuance token, both will generate assets self.nodes[2].sendtoaddress(self.nodes[1].getnewaddress(), Decimal('0.00000001'), "", "", False, False, 1, "UNSET", issuancedata["token"]) # node 1 needs to know about a (re)issuance to reissue itself self.nodes[1].importaddress(self.nodes[2].gettransaction( issuancedata["txid"])["details"][0]["address"]) # also send some bitcoin self.nodes[2].generate(1) self.sync_all() self.nodes[1].reissueasset(issuancedata["asset"], Decimal('0.05')) self.nodes[2].reissueasset(issuancedata["asset"], Decimal('0.025')) self.nodes[1].generate(1) self.sync_all() # Check for value accounting when asset issuance is null but token not, ie unblinded # HACK: Self-send to sweep up bitcoin inputs into blinded output. # We were hitting https://github.com/ElementsProject/elements/issues/473 for the following issuance self.nodes[0].sendtoaddress( self.nodes[0].getnewaddress(), self.nodes[0].getwalletinfo()["balance"]["bitcoin"], "", "", True) issued = self.nodes[0].issueasset(0, 1, False) walletinfo = self.nodes[0].getwalletinfo() assert (issued["asset"] not in walletinfo["balance"]) assert_equal(walletinfo["balance"][issued["token"]], Decimal(1)) assert (issued["asset"] not in walletinfo["unconfirmed_balance"]) assert (issued["token"] not in walletinfo["unconfirmed_balance"]) # Check for value when receiving different assets by same address. self.nodes[0].sendtoaddress(unconfidential_address2, Decimal('0.00000001'), "", "", False, False, 1, "UNSET", test_asset) self.nodes[0].sendtoaddress(unconfidential_address2, Decimal('0.00000002'), "", "", False, False, 1, "UNSET", test_asset) self.nodes[0].generate(1) self.sync_all() received_by_address = self.nodes[1].listreceivedbyaddress( 0, False, True) multi_asset_amount = [ x for x in received_by_address if x['address'] == unconfidential_address2 ][0]['amount'] assert_equal(multi_asset_amount['bitcoin'], value1 + value3) assert_equal(multi_asset_amount[test_asset], Decimal('0.00000003')) # Check blinded multisig functionality and partial blinding functionality # Get two pubkeys blinded_addr = self.nodes[0].getnewaddress() pubkey = self.nodes[0].getaddressinfo(blinded_addr)["pubkey"] blinded_addr2 = self.nodes[1].getnewaddress() pubkey2 = self.nodes[1].getaddressinfo(blinded_addr2)["pubkey"] pubkeys = [pubkey, pubkey2] # Add multisig address unconfidential_addr = self.nodes[0].addmultisigaddress( 2, pubkeys)["address"] self.nodes[1].addmultisigaddress(2, pubkeys) self.nodes[0].importaddress(unconfidential_addr) self.nodes[1].importaddress(unconfidential_addr) # Use blinding key from node 0's original getnewaddress call blinding_pubkey = self.nodes[0].getaddressinfo( blinded_addr)["confidential_key"] blinding_key = self.nodes[0].dumpblindingkey(blinded_addr) # Create blinded address from p2sh address and import corresponding privkey blinded_multisig_addr = self.nodes[0].createblindedaddress( unconfidential_addr, blinding_pubkey) self.nodes[0].importblindingkey(blinded_multisig_addr, blinding_key) # Issue new asset, to use different assets in one transaction when doing # partial blinding. Just to make these tests a bit more elaborate :-) issued3 = self.nodes[2].issueasset(1, 0) self.nodes[2].generate(1) self.sync_all() node2_balance = self.nodes[2].getbalance() assert (issued3['asset'] in node2_balance) assert_equal(node2_balance[issued3['asset']], Decimal(1)) # Send asset to blinded multisig address and check that it was received self.nodes[2].sendtoaddress(address=blinded_multisig_addr, amount=1, assetlabel=issued3['asset']) self.sync_all() # We will use this multisig UTXO in our partially-blinded transaction, # and will also check that multisig UTXO can be successfully spent # after the transaction is signed by node1 and node0 in succession. unspent_asset = self.nodes[0].listunspent(0, 0, [unconfidential_addr], True, {"asset": issued3['asset']}) assert_equal(len(unspent_asset), 1) assert (issued3['asset'] not in self.nodes[2].getbalance()) # Create new UTXO on node0 to be used in our partially-blinded transaction blinded_addr = self.nodes[0].getnewaddress() addr = self.nodes[0].validateaddress(blinded_addr)["unconfidential"] self.nodes[0].sendtoaddress(blinded_addr, 0.1) unspent = self.nodes[0].listunspent(0, 0, [addr]) assert_equal(len(unspent), 1) # Create new UTXO on node1 to be used in our partially-blinded transaction blinded_addr2 = self.nodes[1].getnewaddress() addr2 = self.nodes[1].validateaddress(blinded_addr2)["unconfidential"] self.nodes[1].sendtoaddress(blinded_addr2, 0.11) unspent2 = self.nodes[1].listunspent(0, 0, [addr2]) assert_equal(len(unspent2), 1) # The transaction will have three non-fee outputs dst_addr = self.nodes[0].getnewaddress() dst_addr2 = self.nodes[1].getnewaddress() dst_addr3 = self.nodes[2].getnewaddress() # Inputs are selected up front inputs = [{ "txid": unspent2[0]["txid"], "vout": unspent2[0]["vout"] }, { "txid": unspent[0]["txid"], "vout": unspent[0]["vout"] }, { "txid": unspent_asset[0]["txid"], "vout": unspent_asset[0]["vout"] }] # Create one part of the transaction to partially blind rawtx = self.nodes[0].createrawtransaction( inputs, {dst_addr2: Decimal("0.01")}) # Create another part of the transaction to partially blind rawtx2 = self.nodes[0].createrawtransaction( inputs, { dst_addr: Decimal("0.1"), dst_addr3: Decimal("1.0") }, 0, False, { dst_addr: unspent[0]['asset'], dst_addr3: unspent_asset[0]['asset'] }) sum_i = unspent2[0]["amount"] + unspent[0]["amount"] sum_o = 0.01 + 0.10 + 0.1 assert_equal(int(round(sum_i * COIN)), int(round(sum_o * COIN))) # Blind the first part of the transaction - we need to supply the # assetcommmitments for all of the inputs, for the surjectionproof # to be valid after we combine the transactions blindtx = self.nodes[1].blindrawtransaction(rawtx, True, [ unspent2[0]['assetcommitment'], unspent[0]['assetcommitment'], unspent_asset[0]['assetcommitment'] ]) # Combine the transactions # Blinded, but incomplete transaction. # 3 inputs and 1 output, but no fee output, and # it was blinded with 3 asset commitments, that means # the final transaction should have 3 inputs. btx = CTransaction() btx.deserialize(io.BytesIO(hex_str_to_bytes(blindtx))) # Unblinded transaction, with 3 inputs and 2 outputs. # We will add them to the other transaction to make it complete. ubtx = CTransaction() ubtx.deserialize(io.BytesIO(hex_str_to_bytes(rawtx2))) # We will add outputs of unblinded transaction # on top of inputs and outputs of the blinded, but incomplete transaction. # We also append empty witness instances to make witness arrays match # vin/vout arrays btx.wit.vtxinwit.append(CTxInWitness()) btx.vout.append(ubtx.vout[0]) btx.wit.vtxoutwit.append(CTxOutWitness()) btx.wit.vtxinwit.append(CTxInWitness()) btx.vout.append(ubtx.vout[1]) btx.wit.vtxoutwit.append(CTxOutWitness()) # Add explicit fee output btx.vout.append( CTxOut(nValue=CTxOutValue(10000000), nAsset=CTxOutAsset(BITCOIN_ASSET_OUT))) btx.wit.vtxoutwit.append(CTxOutWitness()) # Input 0 is bitcoin asset (already blinded) # Input 1 is also bitcoin asset # Input 2 is our new asset # Blind with wrong order of assetcommitments - such transaction should be rejected blindtx = self.nodes[0].blindrawtransaction( bytes_to_hex_str(btx.serialize()), True, [ unspent_asset[0]['assetcommitment'], unspent[0]['assetcommitment'], unspent2[0]['assetcommitment'] ]) stx2 = self.nodes[1].signrawtransactionwithwallet(blindtx) stx = self.nodes[0].signrawtransactionwithwallet(stx2['hex']) self.sync_all() assert_raises_rpc_error(-26, "bad-txns-in-ne-out", self.nodes[2].sendrawtransaction, stx['hex']) # Blind with correct order of assetcommitments blindtx = self.nodes[0].blindrawtransaction( bytes_to_hex_str(btx.serialize()), True, [ unspent2[0]['assetcommitment'], unspent[0]['assetcommitment'], unspent_asset[0]['assetcommitment'] ]) stx2 = self.nodes[1].signrawtransactionwithwallet(blindtx) stx = self.nodes[0].signrawtransactionwithwallet(stx2['hex']) txid = self.nodes[2].sendrawtransaction(stx['hex']) self.nodes[2].generate(1) assert self.nodes[2].getrawtransaction(txid, 1)['confirmations'] == 1 self.sync_all() # Check that the sent asset has reached its destination unconfidential_dst_addr3 = self.nodes[2].validateaddress( dst_addr3)["unconfidential"] unspent_asset2 = self.nodes[2].listunspent(1, 1, [unconfidential_dst_addr3], True, {"asset": issued3['asset']}) assert_equal(len(unspent_asset2), 1) assert_equal(unspent_asset2[0]['amount'], Decimal(1)) # And that the balance was correctly updated assert_equal(self.nodes[2].getbalance()[issued3['asset']], Decimal(1)) # Basic checks of rawblindrawtransaction functionality blinded_addr = self.nodes[0].getnewaddress() addr = self.nodes[0].validateaddress(blinded_addr)["unconfidential"] self.nodes[0].sendtoaddress(blinded_addr, 1) self.nodes[0].sendtoaddress(blinded_addr, 3) unspent = self.nodes[0].listunspent(0, 0) rawtx = self.nodes[0].createrawtransaction( [{ "txid": unspent[0]["txid"], "vout": unspent[0]["vout"] }, { "txid": unspent[1]["txid"], "vout": unspent[1]["vout"] }], { addr: unspent[0]["amount"] + unspent[1]["amount"] - Decimal("0.2"), "fee": Decimal("0.2") }) # Blinding will fail with 2 blinded inputs and 0 blinded outputs # since it has no notion of a wallet to fill in a 0-value OP_RETURN output try: self.nodes[0].rawblindrawtransaction( rawtx, [unspent[0]["amountblinder"], unspent[1]["amountblinder"]], [unspent[0]["amount"], unspent[1]["amount"]], [unspent[0]["asset"], unspent[1]["asset"]], [unspent[0]["assetblinder"], unspent[1]["assetblinder"]]) raise AssertionError( "Shouldn't be able to blind 2 input 0 output transaction via rawblindraw" ) except JSONRPCException: pass # Blinded destination added, can blind, sign and send rawtx = self.nodes[0].createrawtransaction( [{ "txid": unspent[0]["txid"], "vout": unspent[0]["vout"] }, { "txid": unspent[1]["txid"], "vout": unspent[1]["vout"] }], { blinded_addr: unspent[0]["amount"] + unspent[1]["amount"] - Decimal("0.002"), "fee": Decimal("0.002") }) signtx = self.nodes[0].signrawtransactionwithwallet(rawtx) try: self.nodes[0].sendrawtransaction(signtx["hex"]) raise AssertionError( "Shouldn't be able to send unblinded tx with emplaced pubkey in output without additional argument" ) except JSONRPCException: pass blindtx = self.nodes[0].rawblindrawtransaction( rawtx, [unspent[0]["amountblinder"], unspent[1]["amountblinder"]], [unspent[0]["amount"], unspent[1]["amount"]], [unspent[0]["asset"], unspent[1]["asset"]], [unspent[0]["assetblinder"], unspent[1]["assetblinder"]]) signtx = self.nodes[0].signrawtransactionwithwallet(blindtx) txid = self.nodes[0].sendrawtransaction(signtx["hex"]) for output in self.nodes[0].decoderawtransaction(blindtx)["vout"]: if "asset" in output and output["scriptPubKey"]["type"] != "fee": raise AssertionError("An unblinded output exists") # Test fundrawtransaction with multiple assets issue = self.nodes[0].issueasset(1, 0) assetaddr = self.nodes[0].getnewaddress() rawtx = self.nodes[0].createrawtransaction( [], { assetaddr: 1, self.nodes[0].getnewaddress(): 2 }, 0, False, {assetaddr: issue["asset"]}) funded = self.nodes[0].fundrawtransaction(rawtx) blinded = self.nodes[0].blindrawtransaction(funded["hex"]) signed = self.nodes[0].signrawtransactionwithwallet(blinded) txid = self.nodes[0].sendrawtransaction(signed["hex"]) # Test fundrawtransaction with multiple inputs, creating > vout.size change rawtx = self.nodes[0].createrawtransaction( [{ "txid": txid, "vout": 0 }, { "txid": txid, "vout": 1 }], {self.nodes[0].getnewaddress(): 5}) funded = self.nodes[0].fundrawtransaction(rawtx) blinded = self.nodes[0].blindrawtransaction(funded["hex"]) signed = self.nodes[0].signrawtransactionwithwallet(blinded) txid = self.nodes[0].sendrawtransaction(signed["hex"]) # Test corner case where wallet appends a OP_RETURN output, yet doesn't blind it # due to the fact that the output value is 0-value and input pedersen commitments # self-balance. This is rare corner case, but ok. unblinded = self.nodes[0].validateaddress( self.nodes[0].getnewaddress())["unconfidential"] self.nodes[0].sendtoaddress(unblinded, self.nodes[0].getbalance()["bitcoin"], "", "", True) # Make tx with blinded destination and change outputs only self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), self.nodes[0].getbalance()["bitcoin"] / 2) # Send back again, this transaction should have 3 outputs, all unblinded txid = self.nodes[0].sendtoaddress( unblinded, self.nodes[0].getbalance()["bitcoin"], "", "", True) outputs = self.nodes[0].getrawtransaction(txid, 1)["vout"] assert_equal(len(outputs), 3) assert ("value" in outputs[0] and "value" in outputs[1] and "value" in outputs[2]) assert_equal(outputs[2]["scriptPubKey"]["type"], 'nulldata') # Test burn argument in createrawtransaction raw_burn1 = self.nodes[0].createrawtransaction( [], { self.nodes[0].getnewaddress(): 1, "burn": 2 }) decode_burn1 = self.nodes[0].decoderawtransaction(raw_burn1) assert_equal(len(decode_burn1["vout"]), 2) found_pay = False found_burn = False for output in decode_burn1["vout"]: if output["scriptPubKey"]["asm"] == "OP_RETURN": found_burn = True if output["asset"] != self.nodes[0].dumpassetlabels( )["bitcoin"]: raise Exception( "Burn should have been bitcoin(policyAsset)") if output["scriptPubKey"]["type"] == "scripthash": found_pay = True assert (found_pay and found_burn) raw_burn2 = self.nodes[0].createrawtransaction( [], { self.nodes[0].getnewaddress(): 1, "burn": 2 }, 101, False, {"burn": "deadbeef" * 8}) decode_burn2 = self.nodes[0].decoderawtransaction(raw_burn2) assert_equal(len(decode_burn2["vout"]), 2) found_pay = False found_burn = False for output in decode_burn2["vout"]: if output["scriptPubKey"]["asm"] == "OP_RETURN": found_burn = True if output["asset"] != "deadbeef" * 8: raise Exception("Burn should have been deadbeef") if output["scriptPubKey"]["type"] == "scripthash": found_pay = True assert (found_pay and found_burn)
def run_test(self): # Add p2p connection to node0 node = self.nodes[0] # convenience reference to the node node.add_p2p_connection(P2PDataStore()) best_block = node.getblock(node.getbestblockhash()) tip = int(node.getbestblockhash(), 16) height = best_block["height"] + 1 block_time = best_block["time"] + 1 self.log.info("Create a new block with an anyone-can-spend coinbase") height = 1 block = create_block(tip, create_coinbase(height), block_time) block.solve() # Save the coinbase for later block1 = block tip = block.sha256 node.p2p.send_blocks_and_test([block1], node, success=True) self.log.info("Mature the block.") node.generatetoaddress(100, node.get_deterministic_priv_key().address) best_block = node.getblock(node.getbestblockhash()) tip = int(node.getbestblockhash(), 16) height = best_block["height"] + 1 block_time = best_block["time"] + 1 # Use merkle-root malleability to generate an invalid block with # same blockheader. # Manufacture a block with 3 transactions (coinbase, spend of prior # coinbase, spend of that spend). Duplicate the 3rd transaction to # leave merkle root and blockheader unchanged but invalidate the block. self.log.info("Test merkle root malleability.") block2 = create_block(tip, create_coinbase(height), block_time) block_time += 1 # b'0x51' is OP_TRUE tx1 = create_tx_with_script(block1.vtx[0], 0, script_sig=b'\x51', amount=50 * COIN) tx2 = create_tx_with_script(tx1, 0, script_sig=b'\x51', amount=50 * COIN) block2.vtx.extend([tx1, tx2]) block2.hashMerkleRoot = block2.calc_merkle_root() block2.rehash() block2.solve() orig_hash = block2.sha256 block2_orig = copy.deepcopy(block2) # Mutate block 2 block2.vtx.append(tx2) assert_equal(block2.hashMerkleRoot, block2.calc_merkle_root()) assert_equal(orig_hash, block2.rehash()) assert block2_orig.vtx != block2.vtx node.p2p.send_blocks_and_test([block2], node, success=False, reject_reason='bad-txns-duplicate') # Check transactions for duplicate inputs self.log.info("Test duplicate input block.") block2_orig.vtx[2].vin.append(block2_orig.vtx[2].vin[0]) block2_orig.vtx[2].rehash() block2_orig.hashMerkleRoot = block2_orig.calc_merkle_root() block2_orig.rehash() block2_orig.solve() node.p2p.send_blocks_and_test([block2_orig], node, success=False, reject_reason='bad-txns-inputs-duplicate') # Check transactions for duplicate inputs self.log.info("Test duplicate input block.") block2_orig.vtx[2].vin.append(block2_orig.vtx[2].vin[0]) block2_orig.vtx[2].rehash() block2_orig.hashMerkleRoot = block2_orig.calc_merkle_root() block2_orig.rehash() block2_orig.solve() node.p2p.send_blocks_and_test([block2_orig], node, success=False, reject_reason='bad-txns-inputs-duplicate') self.log.info("Test very broken block.") block3 = create_block(tip, create_coinbase(height), block_time) block_time += 1 block3.vtx[0].vout[0].nValue = CTxOutValue(100 * COIN) # Too high! block3.vtx[0].sha256 = None block3.vtx[0].calc_sha256() block3.hashMerkleRoot = block3.calc_merkle_root() block3.rehash() block3.solve() node.p2p.send_blocks_and_test([block3], node, success=False, reject_reason='bad-cb-amount')
def run_test(self): # Add p2p connection to node0 node = self.nodes[0] # convenience reference to the node peer = node.add_p2p_connection(P2PDataStore()) best_block = node.getblock(node.getbestblockhash()) tip = int(node.getbestblockhash(), 16) height = best_block["height"] + 1 block_time = best_block["time"] + 1 self.log.info("Create a new block with an anyone-can-spend coinbase") height = 1 block = create_block(tip, create_coinbase(height), block_time) block.solve() # Save the coinbase for later block1 = block tip = block.sha256 peer.send_blocks_and_test([block1], node, success=True) self.log.info("Mature the block.") node.generatetoaddress(100, node.get_deterministic_priv_key().address) best_block = node.getblock(node.getbestblockhash()) tip = int(node.getbestblockhash(), 16) height = best_block["height"] + 1 block_time = best_block["time"] + 1 # Use merkle-root malleability to generate an invalid block with # same blockheader (CVE-2012-2459). # Manufacture a block with 3 transactions (coinbase, spend of prior # coinbase, spend of that spend). Duplicate the 3rd transaction to # leave merkle root and blockheader unchanged but invalidate the block. # For more information on merkle-root malleability see src/consensus/merkle.cpp. self.log.info("Test merkle root malleability.") block2 = create_block(tip, create_coinbase(height), block_time) block_time += 1 # b'0x51' is OP_TRUE # ELEMENTS: scriptpubkeys can't be empty or else we interpret them as fee outputs, # so we modify the Core test to move the OP_TRUEs from scriptSig to scriptPubKey tx1 = create_tx_with_script(block1.vtx[0], 0, script_pub_key=b'\x51', amount=50 * COIN) tx2 = create_tx_with_script(tx1, 0, script_pub_key=b'\x51', amount=50 * COIN) block2.vtx.extend([tx1, tx2]) block2.hashMerkleRoot = block2.calc_merkle_root() block2.rehash() block2.solve() orig_hash = block2.sha256 block2_orig = copy.deepcopy(block2) # Mutate block 2 block2.vtx.append(tx2) assert_equal(block2.hashMerkleRoot, block2.calc_merkle_root()) assert_equal(orig_hash, block2.rehash()) assert block2_orig.vtx != block2.vtx peer.send_blocks_and_test([block2], node, success=False, reject_reason='bad-txns-duplicate') # Check transactions for duplicate inputs (CVE-2018-17144) self.log.info("Test duplicate input block.") block2_dup = copy.deepcopy(block2_orig) block2_dup.vtx[2].vin.append(block2_dup.vtx[2].vin[0]) block2_dup.vtx[2].rehash() block2_dup.hashMerkleRoot = block2_dup.calc_merkle_root() block2_dup.rehash() block2_dup.solve() peer.send_blocks_and_test([block2_dup], node, success=False, reject_reason='bad-txns-inputs-duplicate') self.log.info("Test very broken block.") block3 = create_block(tip, create_coinbase(height), block_time) block_time += 1 block3.vtx[0].vout[0].nValue = CTxOutValue(100 * COIN) # Too high! block3.vtx[0].sha256 = None block3.vtx[0].calc_sha256() block3.hashMerkleRoot = block3.calc_merkle_root() block3.rehash() block3.solve() peer.send_blocks_and_test([block3], node, success=False, reject_reason='bad-cb-amount') # Complete testing of CVE-2012-2459 by sending the original block. # It should be accepted even though it has the same hash as the mutated one. self.log.info("Test accepting original block after rejecting its mutated version.") peer.send_blocks_and_test([block2_orig], node, success=True, timeout=5) # Update tip info height += 1 block_time += 1 tip = int(block2_orig.hash, 16) # Complete testing of CVE-2018-17144, by checking for the inflation bug. # Create a block that spends the output of a tx in a previous block. block4 = create_block(tip, create_coinbase(height), block_time) tx3 = create_tx_with_script(tx2, 0, script_sig=b'\x51', amount=50 * COIN) # Duplicates input tx3.vin.append(tx3.vin[0]) tx3.rehash() block4.vtx.append(tx3) block4.hashMerkleRoot = block4.calc_merkle_root() block4.rehash() block4.solve() self.log.info("Test inflation by duplicating input") peer.send_blocks_and_test([block4], node, success=False, reject_reason='bad-txns-inputs-duplicate')
def tapscript_satisfy_test(self, script, inputs=[], add_issuance=False, add_pegin=False, fail=None, add_prevout=False, add_asset=False, add_value=False, add_spk=False, seq=0, add_out_spk=None, add_out_asset=None, add_out_value=None, add_out_nonce=None, ver=2, locktime=0, add_num_outputs=False, add_weight=False, blind=False): # Create a taproot utxo scripts = [("s0", script)] prev_tx, prev_vout, spk, sec, pub, tap = self.create_taproot_utxo( scripts) if add_pegin: fund_info = self.nodes[0].getpeginaddress() peg_id = self.nodes[0].sendtoaddress( fund_info["mainchain_address"], 1) raw_peg_tx = self.nodes[0].gettransaction(peg_id)["hex"] peg_txid = self.nodes[0].sendrawtransaction(raw_peg_tx) self.nodes[0].generate(101) peg_prf = self.nodes[0].gettxoutproof([peg_txid]) claim_script = fund_info["claim_script"] raw_claim = self.nodes[0].createrawpegin(raw_peg_tx, peg_prf, claim_script) tx = FromHex(CTransaction(), raw_claim['hex']) else: tx = CTransaction() tx.nVersion = ver tx.nLockTime = locktime # Spend the pegin and taproot tx together in_total = prev_tx.vout[prev_vout].nValue.getAmount() fees = 1000 tap_in_pos = 0 if blind: # Add an unrelated output key = ECKey() key.generate() tx.vout.append( CTxOut(nValue=CTxOutValue(10000), scriptPubKey=spk, nNonce=CTxOutNonce(key.get_pubkey().get_bytes()))) tx_hex = self.nodes[0].fundrawtransaction(tx.serialize().hex()) tx = FromHex(CTransaction(), tx_hex['hex']) tx.vin.append( CTxIn(COutPoint(prev_tx.sha256, prev_vout), nSequence=seq)) tx.vout.append( CTxOut(nValue=CTxOutValue(in_total - fees), scriptPubKey=spk)) # send back to self tx.vout.append(CTxOut(CTxOutValue(fees))) if add_issuance: blind_addr = self.nodes[0].getnewaddress() issue_addr = self.nodes[0].validateaddress( blind_addr)['unconfidential'] # Issuances only require one fee output and that output must the last # one. However the way, the current code is structured, it is not possible # to this in a super clean without special casing. if add_pegin: tx.vout.pop() tx.vout.pop() tx.vout.insert(0, CTxOut(nValue=CTxOutValue(in_total), scriptPubKey=spk)) # send back to self) issued_tx = self.nodes[0].rawissueasset( tx.serialize().hex(), [{ "asset_amount": 2, "asset_address": issue_addr, "blind": False }])[0]["hex"] tx = FromHex(CTransaction(), issued_tx) # Sign inputs if add_pegin: signed = self.nodes[0].signrawtransactionwithwallet( tx.serialize().hex()) tx = FromHex(CTransaction(), signed['hex']) tap_in_pos += 1 else: # Need to create empty witness when not deserializing from rpc tx.wit.vtxinwit.append(CTxInWitness()) if blind: tx.vin[0], tx.vin[1] = tx.vin[1], tx.vin[0] utxo = self.get_utxo(tx, 1) zero_str = "0" * 64 blinded_raw = self.nodes[0].rawblindrawtransaction( tx.serialize().hex(), [zero_str, utxo["amountblinder"]], [1.2, utxo['amount']], [utxo['asset'], utxo['asset']], [zero_str, utxo['assetblinder']]) tx = FromHex(CTransaction(), blinded_raw) signed_raw_tx = self.nodes[0].signrawtransactionwithwallet( tx.serialize().hex()) tx = FromHex(CTransaction(), signed_raw_tx['hex']) suffix_annex = [] control_block = bytes([ tap.leaves["s0"].version + tap.negflag ]) + tap.inner_pubkey + tap.leaves["s0"].merklebranch # Add the prevout to the top of inputs. The witness script will check for equality. if add_prevout: inputs = [ prev_vout.to_bytes(4, 'little'), ser_uint256(prev_tx.sha256) ] if add_asset: assert blind # only used with blinding in testing utxo = self.nodes[0].gettxout( ser_uint256(tx.vin[1].prevout.hash)[::-1].hex(), tx.vin[1].prevout.n) if "assetcommitment" in utxo: asset = bytes.fromhex(utxo["assetcommitment"]) else: asset = b"\x01" + bytes.fromhex(utxo["asset"])[::-1] inputs = [asset[0:1], asset[1:33]] if add_value: utxo = self.nodes[0].gettxout( ser_uint256(tx.vin[1].prevout.hash)[::-1].hex(), tx.vin[1].prevout.n) if "valuecommitment" in utxo: value = bytes.fromhex(utxo["valuecommitment"]) inputs = [value[0:1], value[1:33]] else: value = b"\x01" + int( satoshi_round(utxo["value"]) * COIN).to_bytes(8, 'little') inputs = [value[0:1], value[1:9]] if add_spk: ver = CScriptOp.decode_op_n(int.from_bytes(spk[0:1], 'little')) inputs = [CScriptNum.encode(CScriptNum(ver))[1:], spk[2:len(spk)]] # always segwit # Add witness for outputs if add_out_asset is not None: asset = tx.vout[add_out_asset].nAsset.vchCommitment inputs = [asset[0:1], asset[1:33]] if add_out_value is not None: value = tx.vout[add_out_value].nValue.vchCommitment if len(value) == 9: inputs = [value[0:1], value[1:9][::-1]] else: inputs = [value[0:1], value[1:33]] if add_out_nonce is not None: nonce = tx.vout[add_out_nonce].nNonce.vchCommitment if len(nonce) == 1: inputs = [b''] else: inputs = [nonce] if add_out_spk is not None: out_spk = tx.vout[add_out_spk].scriptPubKey if len(out_spk) == 0: # Python upstream encoding CScriptNum interesting behaviour where it also encodes the length # This assumes the implicit wallet behaviour of using segwit outputs. # This is useful while sending scripts, but not while using CScriptNums in constructing scripts inputs = [ CScriptNum.encode(CScriptNum(-1))[1:], sha256(out_spk) ] else: ver = CScriptOp.decode_op_n( int.from_bytes(out_spk[0:1], 'little')) inputs = [ CScriptNum.encode(CScriptNum(ver))[1:], out_spk[2:len(out_spk)] ] # always segwit if add_num_outputs: num_outs = len(tx.vout) inputs = [CScriptNum.encode(CScriptNum(num_outs))[1:]] if add_weight: # Add a dummy input and check the overall weight inputs = [int(5).to_bytes(8, 'little')] wit = inputs + [bytes(tap.leaves["s0"].script), control_block ] + suffix_annex tx.wit.vtxinwit[tap_in_pos].scriptWitness.stack = wit exp_weight = self.nodes[0].decoderawtransaction( tx.serialize().hex())["weight"] inputs = [exp_weight.to_bytes(8, 'little')] wit = inputs + [bytes(tap.leaves["s0"].script), control_block ] + suffix_annex tx.wit.vtxinwit[tap_in_pos].scriptWitness.stack = wit if fail: assert_raises_rpc_error(-26, fail, self.nodes[0].sendrawtransaction, tx.serialize().hex()) return self.nodes[0].sendrawtransaction(hexstring=tx.serialize().hex()) self.nodes[0].generate(1) last_blk = self.nodes[0].getblock(self.nodes[0].getbestblockhash()) tx.rehash() assert (tx.hash in last_blk['tx'])