def test_signature_hash(filename): tx, values, pk_scripts = read_json_tx(filename) correct_hashes = read_signature_hashes( filename.replace('.txn', '.sig_hashes')) n = 0 for input_index, (value, pk_script, txin) in enumerate(zip(values, pk_scripts, tx.inputs)): for sighash in range(256): sighash = SigHash(sighash) if sighash.has_forkid(): signature_hash = tx.signature_hash(input_index, value, pk_script, sighash) assert signature_hash == correct_hashes[n] n += 1
def test_issue(priv_key, receiver, new_issuer=issuer, next_tok_id=curr_token_id + 1, issued_tok_id=curr_token_id): context = scryptlib.utils.create_dummy_input_context() context.utxo.script_pubkey = token.locking_script context.utxo.value = input_sats new_data_part = b'\x23' + scryptlib.utils.get_push_int(next_tok_id)[1:] + \ new_issuer.to_bytes() + action_issue new_locking_script = Script(token.code_part.to_bytes() + new_data_part) tx_out = TxOutput(value=out_sats, script_pubkey=new_locking_script) context.tx.outputs.append(tx_out) new_data_part = b'\x23' + scryptlib.utils.get_push_int(issued_tok_id)[1:] + \ receiver.to_bytes() + action_transfer new_locking_script = Script(token.code_part.to_bytes() + new_data_part) tx_out = TxOutput(value=out_sats, script_pubkey=new_locking_script) context.tx.outputs.append(tx_out) sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) sighash = context.tx.signature_hash(0, input_sats, token.locking_script, sighash_flag) sig = priv_key.sign(sighash, hasher=None) sig = sig + pack_byte(sighash_flag) preimage = scryptlib.utils.get_preimage_from_input_context( context, sighash_flag) return token.issue(Sig(sig), PubKey(receiver), out_sats, out_sats, SigHashPreimage(preimage)).verify(context)
def preimage_hash(self, txin): input_index = self.inputs.index(txin) script_code = bytes.fromhex(self.get_preimage_script(txin)) sighash = SigHash(self.nHashType()) # Original BTC algorithm: https://en.bitcoin.it/wiki/OP_CHECKSIG # Current algorithm: https://github.com/moneybutton/bips/blob/master/bip-0143.mediawiki return self.signature_hash(input_index, txin.value, script_code, sighash=sighash)
def test_verify_scenario_2(): context = scryptlib.utils.create_dummy_input_context() context.utxo.script_pubkey = escrow.locking_script context.utxo.value = input_sats change_out = TxOutput(int(input_sats - fee), P2PKH_Address(pkh_A, Bitcoin).to_script()) context.tx.outputs.append(change_out) sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) sighash = context.tx.signature_hash(0, input_sats, escrow.locking_script, sighash_flag) sig_A = key_priv_A.sign(sighash, hasher=None) sig_A = sig_A + pack_byte(sighash_flag) sig_E = key_priv_E.sign(sighash, hasher=None) sig_E = sig_E + pack_byte(sighash_flag) preimage = scryptlib.utils.get_preimage_from_input_context( context, sighash_flag) verify_result = escrow.unlock(SigHashPreimage(preimage), PubKey(key_pub_A), Sig(sig_A), PubKey(key_pub_E), Sig(sig_E), Bytes(secret0)).verify(context) assert verify_result == True # Wrong secret with pytest.raises(bitcoinx.VerifyFailed): verify_result = escrow.unlock(SigHashPreimage(preimage), PubKey(key_pub_A), Sig(sig_A), PubKey(key_pub_E), Sig(sig_E), Bytes(secret1)).verify(context)
def get_preimage(tx, input_index, utxo_value, utxo_script, sighash_flag=None): if not sighash_flag: sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) txin = tx.inputs[input_index] hash_prevouts = hash_sequence = hash_outputs = bitcoinx.consts.ZERO sighash_not_single_none = sighash_flag.base not in (SigHash.SINGLE, SigHash.NONE) if not sighash_flag.anyone_can_pay: hash_prevouts = tx._hash_prevouts() if sighash_not_single_none: hash_sequence = tx._hash_sequence() if sighash_not_single_none: hash_outputs = tx._hash_outputs() elif (sighash_flag.base == SigHash.SINGLE and input_index < len(tx.outputs)): hash_outputs = double_sha256(tx.outputs[input_index].to_bytes()) preimage = b''.join(( pack_le_int32(tx.version), hash_prevouts, hash_sequence, txin.to_bytes_for_signature(utxo_value, utxo_script), hash_outputs, pack_le_uint32(tx.locktime), pack_le_uint32(sighash_flag), )) return preimage
def test_verify_with_change(): deposit_sats = 100000 input_sats = 100000 output_sats = deposit_sats + input_sats change_sats = 547 faucet.set_data_part(scryptlib.utils.get_push_int(1602553516)) context = scryptlib.utils.create_dummy_input_context() context.utxo.script_pubkey = faucet.locking_script context.utxo.value = input_sats tx_out_0 = TxOutput(value=output_sats, script_pubkey=faucet.locking_script) context.tx.outputs.append(tx_out_0) tx_out_1 = TxOutput(value=change_sats, script_pubkey=P2PKH_Address(pkh, Bitcoin).to_script()) context.tx.outputs.append(tx_out_1) sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) preimage = scryptlib.utils.get_preimage_from_input_context( context, sighash_flag) verify_result = faucet.deposit(SigHashPreimage(preimage), deposit_sats, Ripemd160(pkh), change_sats).verify(context) assert verify_result == True with pytest.raises(bitcoinx.VerifyFailed): faucet.deposit(SigHashPreimage(preimage), deposit_sats, Ripemd160(pkh), change_sats + 1).verify(context)
def preimage_hash(self, txin): input_index = self.inputs.index(txin) script_code = bytes.fromhex(self.get_preimage_script(txin)) sighash = SigHash(self.nHashType()) return self.signature_hash(input_index, txin.value, script_code, sighash=sighash)
def test_verify_p2pkh_correct(): context = scryptlib.utils.create_dummy_input_context() sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) sighash = context.tx.signature_hash(0, context.utxo.value, asm.locking_script, sighash_flag) sig = key_priv.sign(sighash, hasher=None) sig = sig + pack_byte(sighash_flag) verify_result = asm.p2pkh(Sig(sig), PubKey(key_pub)).verify(context) assert verify_result == True
def get_preimage_from_input_context(context, sighash_flag=None): if not sighash_flag: sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) tx = context.tx input_index = context.input_index utxo_value = context.utxo.value utxo_script = context.utxo.script_pubkey return get_preimage(tx, input_index, utxo_value, utxo_script, sighash_flag)
def increment_counter(counter_obj, prev_txid, prev_out_idx, funding_txid, funding_out_idx, unlock_key_priv, miner_fee): # Get data from previous counter tx r = requests.get('https://api.whatsonchain.com/v1/bsv/main/tx/{}'.format( prev_txid)).json() prev_locking_script = Script.from_hex( r['vout'][prev_out_idx]['scriptPubKey']['hex']) prev_counter_bytes = list(prev_locking_script.ops())[-1] prev_counter_val = int.from_bytes(prev_counter_bytes, 'little') unlocked_satoshis_counter = int(r['vout'][prev_out_idx]['value'] * 10**8) # Get data from funding tx r = requests.get('https://api.whatsonchain.com/v1/bsv/main/tx/{}'.format( funding_txid)).json() funding_locking_script = Script.from_hex( r['vout'][funding_out_idx]['scriptPubKey']['hex']) unlocked_satoshis_funding = int(r['vout'][funding_out_idx]['value'] * 10**8) # Set data for next iteration counter_obj.set_data_part( scryptlib.utils.get_push_int(prev_counter_val + 1)) ## Construct tx n_sequence = 0xffffffff # Counter input and output prev_tx_hash = hex_str_to_hash(prev_txid) counter_in = TxInput(prev_tx_hash, prev_out_idx, None, n_sequence) out_satoshis = unlocked_satoshis_counter + unlocked_satoshis_funding - miner_fee contract_out = TxOutput(out_satoshis, counter_obj.locking_script) # Funding input funding_tx_hash = hex_str_to_hash(funding_txid) funding_in = TxInput(funding_tx_hash, funding_out_idx, None, n_sequence) tx = Tx(2, [counter_in, funding_in], [contract_out], 0x00000000) # Set input script to unlock previous counter sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) preimage = scryptlib.utils.get_preimage(tx, 0, unlocked_satoshis_counter, prev_locking_script, sighash_flag) increment_func_call = counter_obj.increment(SigHashPreimage(preimage), Int(out_satoshis)) tx.inputs[0].script_sig = increment_func_call.script # Set input script to unlock funding output unlock_key_pub = unlock_key_priv.public_key sighash = tx.signature_hash(1, unlocked_satoshis_funding, funding_locking_script, sighash_flag) sig = unlock_key_priv.sign(sighash, hasher=None) sig = sig + pack_byte(sighash_flag) unlock_script = Script() << sig << unlock_key_pub.to_bytes() tx.inputs[1].script_sig = unlock_script broadcast_tx(tx)
def test_signatures(filename): tx, values, pk_scripts = read_json_tx(filename) for input_index, (value, pk_script, txin) in enumerate(zip(values, pk_scripts, tx.inputs)): signature, pubkey = txin.script_sig.ops() pubkey = PublicKey.from_bytes(pubkey) signature_hash = tx.signature_hash(input_index, value, pk_script, SigHash(signature[-1])) assert pubkey.verify_der_signature(signature[:-1], signature_hash, None)
def test_verify_wrong_output(): context = scryptlib.utils.create_dummy_input_context() context.utxo.script_pubkey = gol.locking_script new_locking_script = Script(gol.code_part.to_bytes() + wb1) tx_out = TxOutput(value=out_amount, script_pubkey=new_locking_script) context.tx.outputs.append(tx_out) sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) verify_result = gol.play(out_amount, SigHashPreimage(preimage)).verify(context) assert verify_result == False
def test_signature_hash_bad(): tx, _, _ = read_json_tx('503fd37f.txn') with pytest.raises(IndexError): tx.signature_hash(-1, 5, b'', SigHash.ALL) with pytest.raises(IndexError): tx.signature_hash(2, 5, b'', SigHash.ALL) with pytest.raises(ValueError): tx.signature_hash(0, -1, b'', SigHash.ALL) with pytest.raises(TypeError): tx.signature_hash(0, 0, b'', 1) tx.signature_hash(0, 0, b'', SigHash.NONE) tx.signature_hash(1, 0, b'', SigHash(1))
def test_merge(input_idx, balance0, balance1): context = scryptlib.utils.create_dummy_input_context() context.utxo.value = in_sats context.input_index = input_idx tx_in = TxInput(context.tx.inputs[0].prev_hash, 1, Script(), 0xffffffff) context.tx.inputs.append(tx_in) prev_txid = context.tx.inputs[0].prev_hash prevouts = prev_txid + b'\x00\x00\x00\x00' + prev_txid + b'\x01\x00\x00\x00' new_locking_script = Script(token.code_part.to_bytes() + b'\x23' + key_pub_2.to_bytes() + scryptlib.utils.get_push_int(balance0)[1:] + scryptlib.utils.get_push_int(balance1)[1:]) tx_out = TxOutput(value=out_sats, script_pubkey=new_locking_script) context.tx.outputs.append(tx_out) if input_idx == 0: balance = balance1 context.utxo.script_pubkey = locking_script_0 key_to_sign = key_priv_0 token.set_data_part(b'\x23' + data_part_0) else: balance = balance0 context.utxo.script_pubkey = locking_script_1 key_to_sign = key_priv_1 token.set_data_part(b'\x23' + data_part_1) sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) #preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) if input_idx == 0: preimage = scryptlib.utils.get_preimage(context.tx, input_idx, in_sats, locking_script_0, sighash_flag=sighash_flag) else: preimage = scryptlib.utils.get_preimage(context.tx, input_idx, in_sats, locking_script_1, sighash_flag=sighash_flag) if input_idx == 0: sighash = context.tx.signature_hash(input_idx, in_sats, locking_script_0, sighash_flag) else: sighash = context.tx.signature_hash(input_idx, in_sats, locking_script_1, sighash_flag) sig = key_to_sign.sign(sighash, hasher=None) sig = sig + pack_byte(sighash_flag) return token.merge( Sig(sig), PubKey(key_pub_2), Bytes(prevouts), balance, out_sats, SigHashPreimage(preimage) ).verify(context)
def test_verify_incorrect_sat_amount(): context = scryptlib.utils.create_dummy_input_context() context.utxo.script_pubkey = counter_obj.locking_script subsequent_counter_val_bytes = scryptlib.utils.get_push_int(COUNTER_INITIAL_VAL + 1) new_locking_script = counter_obj.code_part << Script(subsequent_counter_val_bytes) tx_out = TxOutput(value=0, script_pubkey=new_locking_script) context.tx.outputs.append(tx_out) sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) new_output_satoshis = 100 verify_result = counter_obj.increment(SigHashPreimage(preimage), Int(new_output_satoshis)).verify(context) assert verify_result == False
def test_sighash(execution_count): '''Tests the original Satoshi signature_hash on random transactions.''' hash_type = random.randrange(0, 1 << 32) sighash_type = SigHash(hash_type) tx = random_tx((hash_type & 0x1f) == SigHash.SINGLE) script_code = random_script() input_index = random.randrange(0, len(tx.inputs)) live_hash = tx._original_signature_hash(input_index, script_code, sighash_type) # ref_sighash modifies the tx so do it second ref_hash = ref_sighash(script_code, tx, input_index, hash_type) assert live_hash == ref_hash
def test_verify_correct_constructor(): context = scryptlib.utils.create_dummy_input_context() context.utxo.script_pubkey = counter_obj.locking_script new_locking_script = counter_obj.get_state_script( {"counter": COUNTER_INITIAL_VAL + 1}) tx_out = TxOutput(value=0, script_pubkey=new_locking_script) context.tx.outputs.append(tx_out) sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) preimage = scryptlib.utils.get_preimage_from_input_context( context, sighash_flag) new_output_satoshis = 0 verify_result = counter_obj.unlock( SigHashPreimage(preimage), Int(new_output_satoshis)).verify(context) assert verify_result == True
def test_split(key_priv, balance0, balance1, balance_input0=None, balance_input1=None): if not balance_input0: balance_input0 = balance0 if not balance_input1: balance_input1 = balance1 context = scryptlib.utils.create_dummy_input_context() context.utxo.script_pubkey = token.locking_script context.utxo.value = in_sats new_locking_script = Script(token.code_part.to_bytes() + b'\x23' + key_pub_1.to_bytes() + b'\x00' + scryptlib.utils.get_push_int(balance0)[1:]) tx_out = TxOutput(value=out_sats, script_pubkey=new_locking_script) context.tx.outputs.append(tx_out) if balance1 > 0: new_locking_script = Script(token.code_part.to_bytes() + b'\x23' + key_pub_2.to_bytes() + b'\x00' + scryptlib.utils.get_push_int(balance1)[1:]) tx_out = TxOutput(value=out_sats, script_pubkey=new_locking_script) context.tx.outputs.append(tx_out) sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) preimage = scryptlib.utils.get_preimage_from_input_context(context, sighash_flag) input_idx = 0 utxo_satoshis = context.utxo.value sighash = context.tx.signature_hash(input_idx, utxo_satoshis, token.locking_script, sighash_flag) sig = key_priv.sign(sighash, hasher=None) sig = sig + pack_byte(sighash_flag) return token.split( Sig(sig), PubKey(key_pub_1), balance_input0, out_sats, PubKey(key_pub_2), balance_input1, out_sats, SigHashPreimage(preimage) ).verify(context)
def initialize_counter(counter_obj, counter_initial_val, funding_txid, funding_out_idx, \ unlock_key_priv, miner_fee, contract_out_sats, change_addr): counter_obj.set_data_part( scryptlib.utils.get_push_int(counter_initial_val)) # Funding TX funding_tx_hash = hex_str_to_hash(funding_txid) unlock_key_pub = unlock_key_priv.public_key r = requests.get('https://api.whatsonchain.com/v1/bsv/main/tx/{}'.format( funding_txid)).json() funding_locking_script = Script.from_hex( r['vout'][funding_out_idx]['scriptPubKey']['hex']) unlocked_satoshis = int(r['vout'][funding_out_idx]['value'] * 10**8) n_sequence = 0xffffffff tx_input = TxInput(funding_tx_hash, funding_out_idx, None, n_sequence) # Output with counter script code contract_out = TxOutput(contract_out_sats, counter_obj.locking_script) # Change output tx_output_script = P2PKH_Address.from_string(change_addr, Bitcoin).to_script() change_out = TxOutput(unlocked_satoshis - miner_fee - contract_out_sats, tx_output_script) tx = Tx(2, [tx_input], [contract_out, change_out], 0x00000000) # Create signature for input sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) sighash = tx.signature_hash(0, unlocked_satoshis, funding_locking_script, sighash_flag) sig = unlock_key_priv.sign(sighash, hasher=None) sig = sig + pack_byte(sighash_flag) # Set script for input unlock_script = Script() << sig << unlock_key_pub.to_bytes() tx.inputs[0].script_sig = unlock_script broadcast_tx(tx)
pubkey_hash = key_pub.hash160() wrong_key_priv = PrivateKey.from_arbitrary_bytes(b'somethingelse') wrong_key_pub = wrong_key_priv.public_key contract = './test/res/p2pkh.scrypt' compiler_result = scryptlib.utils.compile_contract(contract) desc = compiler_result.to_desc() P2PKH = scryptlib.contract.build_contract_class(desc) p2pkh_obj = P2PKH(PubKeyHash(pubkey_hash)) context = scryptlib.utils.create_dummy_input_context() sighash_flag = SigHash(SigHash.ALL | SigHash.FORKID) input_idx = 0 utxo_satoshis = context.utxo.value sighash = context.tx.signature_hash(input_idx, utxo_satoshis, p2pkh_obj.locking_script, sighash_flag) def test_verify_correct_key(): sig = key_priv.sign(sighash, hasher=None) sig = sig + pack_byte(sighash_flag) verify_result = p2pkh_obj.unlock(Sig(sig), PubKey(key_pub)).verify(context) assert verify_result == True def test_verify_wrong(): sig = wrong_key_priv.sign(sighash, hasher=None)
payout_addr = pkh change_addr = pkh contract = './test/res/merkleToken.scrypt' compiler_result = scryptlib.utils.compile_contract(contract) desc = compiler_result.to_desc() input_sats = 100000 sat_price = 100 Token = scryptlib.contract.build_contract_class(desc) token = Token(sat_price) change_sats = 100 sighash_flag = SigHash(SigHash.ANYONE_CAN_PAY | SigHash.ALL | SigHash.FORKID) #def test_verify_buy_token(): # amount = 1 # new_entry = payout_addr + scryptlib.utils.get_push_int(amount)[1:] # last_entry = b'\x00' * 20 + b'\x01' # # last_entry_hash = hashlib.sha256(last_entry).digest() # new_entry_hash = hashlib.sha256(new_entry).digest() # # mixhash = hashlib.sha256(last_entry_hash + new_entry_hash).digest() # new_locking_script = token.code_part << Script(b'\x23' + scryptlib.utils.get_push_item(mixhash)) # last_merkle_path = Bytes(last_entry_hash + b'\x01') # # last_entry_double_hash = hashlib.sha256(last_entry_hash * 2).digest() # token.set_data_part(scryptlib.utils.get_push_item(last_entry_double_hash))