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 create_script_sig(script_type: ScriptType, threshold: int, x_pubkeys: List[XPublicKey], signatures: List[bytes]) -> Script: if script_type == ScriptType.P2PK: return Script(push_item(signatures[0])) elif script_type == ScriptType.P2PKH: return Script( push_item(signatures[0]) + push_item(x_pubkeys[0].to_bytes())) elif script_type == ScriptType.MULTISIG_P2SH: parts = [pack_byte(Ops.OP_0)] parts.extend(push_item(signature) for signature in signatures) nested_script = multisig_script(x_pubkeys, threshold) parts.append(push_item(nested_script)) return Script(b''.join(parts)) elif script_type == ScriptType.MULTISIG_BARE: parts = [pack_byte(Ops.OP_0)] parts.extend(push_item(signature) for signature in signatures) return Script(b''.join(parts)) elif script_type == ScriptType.MULTISIG_ACCUMULATOR: parts = [] for i, signature in enumerate(signatures): if signature == NO_SIGNATURE: parts.append([pack_byte(Ops.OP_FALSE)]) else: parts.append([ push_item(signature), push_item(x_pubkeys[i].to_bytes()), pack_byte(Ops.OP_TRUE), ]) parts.reverse() return Script(b''.join([value for l in parts for value in l])) raise ValueError(f"unable to realize script {script_type}")
def test_from_WIF_bad_suffix_byte(self, value): payload = pack_byte(Bitcoin.WIF_byte) + global_privkey.to_bytes() + pack_byte(value) WIF = base58_encode_check(payload) if value == 0x01: PrivateKey.from_WIF(WIF) else: with pytest.raises(ValueError): PrivateKey.from_WIF(WIF)
def test_from_template(self, threshold, count): good_output = P2MultiSig_Output(MS_PUBKEYS[:count], threshold) public_keys = [public_key.to_bytes() for public_key in MS_PUBKEYS[:count]] output = P2MultiSig_Output.from_template(pack_byte(threshold), *public_keys, pack_byte(count)) assert list(output.public_keys) == MS_PUBKEYS[:count] assert output.threshold == threshold assert output == good_output
def test_P2PKH(self): script_hex = '76a914a6dbba870185ab6689f386a40522ae6cb5c7b61a88ac' s = Script.from_hex(script_hex) sc = classify_output_script(s, Bitcoin) assert isinstance(sc, P2PKH_Address) prefix = push_item(b'foobar') + pack_byte(OP_DROP) + pack_byte(OP_NOP) s2 = Script.from_hex(prefix.hex() + script_hex) sc2 = classify_output_script(s2, Bitcoin) assert s2 != s assert isinstance(sc2, P2PKH_Address)
def test_P2SH(self): script_hex = 'a9143e4501f9f212cb6813b3815edbc7013d6a3f0f1087' s = Script.from_hex(script_hex) sc = classify_output_script(s, Bitcoin) assert isinstance(sc, P2SH_Address) suffix = push_item(b'foobar') + pack_byte(OP_DROP) + pack_byte(OP_NOP) s2 = Script.from_hex(script_hex + suffix.hex()) sc2 = classify_output_script(s2, Bitcoin) assert s2 != s assert isinstance(sc2, P2SH_Address)
def test_from_template_bad(self): public_keys = [ PrivateKey.from_random().public_key.to_bytes() for n in range(2) ] with pytest.raises(ValueError): script = P2MultiSig_Output.from_template(pack_byte(1), *public_keys, pack_byte(1)) with pytest.raises(ValueError): script = P2MultiSig_Output.from_template(pack_byte(1), *public_keys, pack_byte(3))
def get_push_item(item_bytes): ''' Returns script bytes to push item on the stack. ALL data is length prefixed. ''' dlen = len(item_bytes) if dlen < Ops.OP_PUSHDATA1: return pack_byte(dlen) + item_bytes elif dlen <= 0xff: return pack_byte(Ops.OP_PUSHDATA1) + pack_byte(dlen) + item_bytes elif dlen <= 0xffff: return pack_byte(Ops.OP_PUSHDATA2) + pack_le_uint16(dlen) + item_bytes return pack_byte(Ops.OP_PUSHDATA4) + pack_le_uint32(dlen) + item_bytes
def test_OP_RETURN(self): s = Script(pack_byte(OP_RETURN)) sc = classify_output_script(s, Bitcoin) assert isinstance(sc, OP_RETURN_Output) s = Script(pack_byte(OP_RETURN) + push_item(b'BitcoinSV')) sc = classify_output_script(s, Bitcoin) assert isinstance(sc, OP_RETURN_Output) # Truncated OP_RETURN script s = Script(pack_byte(OP_RETURN) + pack_byte(1)) sc = classify_output_script(s, Bitcoin) assert isinstance(sc, OP_RETURN_Output)
def get_push_int(value): '''Returns script bytes to push a numerical value to the stack. Stack values are stored as signed-magnitude little-endian numbers. ''' if value == 0: return b'\x01\x00' item = int_to_le_bytes(abs(value)) if item[-1] & 0x80: item += pack_byte(0x80 if value < 0 else 0x00) elif value < 0: item = item[:-1] + pack_byte(item[-1] | 0x80) return get_push_item(item)
def _test_op_return(self, old=False): prefix = b'' if old else pack_byte(OP_0) s = Script(prefix + pack_byte(OP_RETURN)) sc = classify_output_script(s, Bitcoin) assert isinstance(sc, OP_RETURN_Output) s = Script(prefix + pack_byte(OP_RETURN) + push_item(b'BitcoinSV')) sc = classify_output_script(s, Bitcoin) assert isinstance(sc, OP_RETURN_Output) # Truncated OP_RETURN script s = Script(prefix + pack_byte(OP_RETURN) + pack_byte(1)) sc = classify_output_script(s, Bitcoin) assert isinstance(sc, OP_RETURN_Output)
def test_to_script_bytes(self): pubkey_hex = '0363f75554e05e05a04551e59d78d78965ec6789f42199f7cbaa9fa4bd2df0a4b4' pubkey = PublicKey.from_hex(pubkey_hex) output = P2PK_Output(pubkey, Bitcoin) raw = output.to_script_bytes() assert isinstance(raw, bytes) assert raw == push_item(bytes.fromhex(pubkey_hex)) + pack_byte(OP_CHECKSIG)
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 test_der_signature(self): s = Signature(MISSING_SIG_BYTES) with pytest.raises(InvalidSignatureError): s.der_signature der_sig = serialization_testcases[0][0] s = Signature(der_sig + pack_byte(0x41)) assert s.der_signature == der_sig
def test_finish(key_priv, pkh_B, action_A, action_B, total_sats, input_sats, out_sats, change_sats): rps.set_data_part(player_A_data + pkh_B + scryptlib.utils.get_push_int(action_B)[1:]) context = scryptlib.utils.create_dummy_input_context() context.utxo.script_pubkey = rps.locking_script context.utxo.value = total_sats change_out = TxOutput(change_sats, P2PKH_Address(pkh_A, Bitcoin).to_script()) context.tx.outputs.append(change_out) if out_sats > 0: pay_out = TxOutput(out_sats, P2PKH_Address(pkh_B, Bitcoin).to_script()) context.tx.outputs.append(pay_out) 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, rps.locking_script, sighash_flag) sig = key_priv.sign(sighash, hasher=None) sig = sig + pack_byte(sighash_flag) return rps.finish(SigHashPreimage(preimage), action_A, Sig(sig), PubKey(key_pub_A), change_sats).verify(context)
def test_verify_wrong_key(): MultiSig = scryptlib.contract.build_contract_class(desc) multisig = MultiSig([Ripemd160(pkh_0), Ripemd160(pkh_1), Ripemd160(pkh_2)]) context = scryptlib.utils.create_dummy_input_context() sighash = context.tx.signature_hash(input_idx, context.utxo.value, multisig.locking_script, sighash_flag) sig_0 = key_priv_0.sign(sighash, hasher=None) + pack_byte(sighash_flag) sig_1 = key_priv_1.sign(sighash, hasher=None) + pack_byte(sighash_flag) sig_2 = key_priv_1.sign(sighash, hasher=None) + pack_byte(sighash_flag) with pytest.raises(bitcoinx.NullFailError): verify_result = multisig.unlock( [PubKey(key_pub_0), PubKey(key_pub_2), PubKey(key_pub_2)], [Sig(sig_0), Sig(sig_1), Sig(sig_2)]).verify(context)
def _realize_script_sig(self, x_pubkeys: List[XPublicKey], signatures: List[bytes]) -> Script: if self.script_type == ScriptType.P2PK: return Script(push_item(signatures[0])) elif self.script_type == ScriptType.P2PKH: return Script( push_item(signatures[0]) + push_item(x_pubkeys[0].to_bytes())) elif self.script_type == ScriptType.MULTISIG_P2SH: parts = [pack_byte(Ops.OP_0)] parts.extend(push_item(signature) for signature in signatures) nested_script = multisig_script(x_pubkeys, self.threshold) parts.append(push_item(nested_script)) return Script(b''.join(parts)) elif self.script_type == ScriptType.MULTISIG_BARE: parts = [pack_byte(Ops.OP_0)] parts.extend(push_item(signature) for signature in signatures) return Script(b''.join(parts)) raise ValueError(f"unable to realize script {self.script_type}")
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 test_verify_correct(): MultiSig = scryptlib.contract.build_contract_class(desc) multisig = MultiSig([Ripemd160(pkh_0), Ripemd160(pkh_1), Ripemd160(pkh_2)]) context = scryptlib.utils.create_dummy_input_context() sighash = context.tx.signature_hash(input_idx, context.utxo.value, multisig.locking_script, sighash_flag) sig_0 = key_priv_0.sign(sighash, hasher=None) + pack_byte(sighash_flag) sig_1 = key_priv_1.sign(sighash, hasher=None) + pack_byte(sighash_flag) sig_2 = key_priv_2.sign(sighash, hasher=None) + pack_byte(sighash_flag) verify_result = multisig.unlock( [PubKey(key_pub_0), PubKey(key_pub_1), PubKey(key_pub_2)], [Sig(sig_0), Sig(sig_1), Sig(sig_2)]).verify(context) assert verify_result == True
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 encode_state_len(len_int, size_bytes): len_bytes = pack_byte(len_int) size_diff = size_bytes - len(len_bytes) if size_diff < 0: raise Exception( 'Size of encoding too small for passed state length. Needs at least {} additional bytes.' .fromat(size_diff * -1)) return len_bytes[::-1] + b'\x00' * size_diff
def multisig_script(x_pubkeys: List[XPublicKey], threshold: int) -> bytes: '''Returns bytes. x_pubkeys is an array of XPulicKey objects or an array of PublicKey objects. ''' assert 1 <= threshold <= len(x_pubkeys) parts = [push_int(threshold)] parts.extend(push_item(x_pubkey.to_bytes()) for x_pubkey in x_pubkeys) parts.append(push_int(len(x_pubkeys))) parts.append(pack_byte(Ops.OP_CHECKMULTISIG)) return b''.join(parts)
def _realize_script_sig(self, x_pubkeys, signatures): type_ = self.type() if type_ == 'p2pk': return Script(push_item(signatures[0])) if type_ == 'p2pkh': return Script(push_item(signatures[0]) + push_item(x_pubkeys[0].to_bytes())) if type_ == 'p2sh': parts = [pack_byte(Ops.OP_0)] parts.extend(push_item(signature) for signature in signatures) nested_script = multisig_script(x_pubkeys, self.threshold) parts.append(push_item(nested_script)) return Script(b''.join(parts)) return self.script_sig
def multisig_script(public_keys, threshold): '''public_keys should be sorted hex strings. P2MultiSig_Ouput is not used as they may be derivation rules and not valid public keys. ''' if sorted(public_keys) != public_keys: logger.warning('public keys are not sorted') assert 1 <= threshold <= len(public_keys) parts = [push_int(threshold)] parts.extend( push_item(bytes.fromhex(public_key)) for public_key in public_keys) parts.append(push_int(len(public_keys))) parts.append(pack_byte(Ops.OP_CHECKMULTISIG)) return b''.join(parts).hex()
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_to_script_bytes(self, threshold, count): output = P2MultiSig_Output(MS_PUBKEYS[:count], threshold) assert output.public_key_count() == count raw = output.to_script_bytes() assert isinstance(raw, bytes) assert raw == b''.join(( push_int(threshold), b''.join(push_item(public_key.to_bytes()) for public_key in MS_PUBKEYS[:count]), push_int(count), pack_byte(OP_CHECKMULTISIG), )) S = output.to_script() assert isinstance(S, Script) assert S == raw
def test_P2PK(self): script_hex = '210363f75554e05e05a04551e59d78d78965ec6789f42199f7cbaa9fa4bd2df0a4b4ac' s = Script.from_hex(script_hex) sc = classify_output_script(s, Bitcoin) assert isinstance(sc, P2PK_Output) assert (sc.public_key.to_hex() == '0363f75554e05e05a04551e59d78d78965ec6789f42199f7cbaa9fa4bd2df0a4b4') suffix = push_item(b'foo') + push_item(b'bar') + pack_byte(OP_2DROP) s2 = Script.from_hex(script_hex + suffix.hex()) sc2 = classify_output_script(s2, Bitcoin) assert sc2.public_key == sc.public_key assert s2 != s assert isinstance(sc2, P2PK_Output)
def test_unknown(self): # Modified final pubkey byte; not a curve point script_hex = '210363f75554e05e05a04551e59d78d78965ec6789f42199f7cbaa9fa4bd2df0a4b3ac' s = Script.from_hex(script_hex) sc = classify_output_script(s, Bitcoin) assert isinstance(sc, Unknown_Output) # Truncated script script_hex = '210363f7' s = Script.from_hex(script_hex) sc = classify_output_script(s, Bitcoin) assert isinstance(sc, Unknown_Output) # Unknown script script_hex = pack_byte(OP_1).hex() s = Script.from_hex(script_hex) sc = classify_output_script(s, Bitcoin) assert isinstance(sc, Unknown_Output)
def to_public_key(self) -> Union[BIP32PublicKey, PublicKey]: '''Returns a PublicKey instance or an Address instance.''' kind = self.kind() if self._pubkey_bytes is not None: return PublicKey.from_bytes(self._pubkey_bytes) elif self._bip32_xpub is not None: assert self._derivation_path is not None result = bip32_key_from_string(self._bip32_xpub) for n in self._derivation_path: result = result.child(n) return result elif self._old_mpk is not None: assert self._derivation_path is not None path = self._derivation_path pubkey = PublicKey.from_bytes(pack_byte(4) + self._old_mpk) # pylint: disable=unsubscriptable-object delta = double_sha256(f'{path[1]}:{path[0]}:'.encode() + self._old_mpk) return pubkey.add(delta) raise ValueError("invalid key data")
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)