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_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 set_data_part(self, state): if isinstance(state, bytes): self._manual_data_part = Script(state) elif isinstance(state, str): self._manual_data_part = Script(bytes.fromhex(state)) elif isinstance(state, dict): self._manual_data_part = Script(serializer.serialize_state(state)) else: raise NotImplementedError('Invalid object type for contract data part "{}".'.format(state.__class__))
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_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 read(cls, read): prev_hash = read(32) prev_idx = read_le_uint32(read) script_sig = Script(read_varbytes(read)) sequence = read_le_uint32(read) kwargs = {'x_pubkeys': [], 'address': None, 'threshold': 0, 'signatures': []} if prev_hash != bytes(32): _parse_script_sig(script_sig.to_bytes(), kwargs) result = cls(prev_hash, prev_idx, script_sig, sequence, value=0, **kwargs) if not result.is_complete(): result.value = read_le_int64(read) return result
def _get_address_from_script_hex(self, script_hex): # NOTE: In theory we could verify the bytes and extract the variable P2PKH hash. script_bytes = bytes.fromhex(script_hex) script_asm = Script(script_bytes).to_asm() tokens = script_asm.split(" ") assert len(tokens) == 5 assert tokens[0] == "OP_DUP" assert tokens[1] == "OP_HASH160" assert tokens[3] == "OP_EQUALVERIFY" assert tokens[4] == "OP_CHECKSIG" pkh_hex = tokens[2] return address.Address.from_P2PKH_hash(bytes.fromhex(pkh_hex))
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 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 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_withdraw(): withdraw_sats = 2000000 fee = 3000 input_sats = 10000000 output_sats = input_sats - withdraw_sats - fee mature_time = 1627122529 faucet.set_data_part(scryptlib.utils.get_push_int(mature_time)) context = scryptlib.utils.create_dummy_input_context() context.utxo.script_pubkey = faucet.locking_script context.utxo.value = input_sats new_locking_script = faucet.code_part << Script( scryptlib.utils.get_push_int(mature_time + 300)) tx_out_0 = TxOutput(value=output_sats, script_pubkey=new_locking_script) context.tx.outputs.append(tx_out_0) tx_out_1 = TxOutput(value=withdraw_sats, script_pubkey=P2PKH_Address(pkh, Bitcoin).to_script()) context.tx.outputs.append(tx_out_1) context.tx.inputs[0].sequence = 0xfffffffe context.tx.locktime = mature_time + 300 preimage = scryptlib.utils.get_preimage_from_input_context(context) verify_result = faucet.withdraw(SigHashPreimage(preimage), Ripemd160(pkh)).verify(context) assert verify_result == True # Wrong mature time context.tx.outputs = [] new_locking_script = faucet.code_part << Script( scryptlib.utils.get_push_int(mature_time + 299)) tx_out_0 = TxOutput(value=output_sats, script_pubkey=new_locking_script) context.tx.outputs.append(tx_out_0) tx_out_1 = TxOutput(value=withdraw_sats, script_pubkey=P2PKH_Address(pkh, Bitcoin).to_script()) context.tx.outputs.append(tx_out_1) context.tx.locktime = mature_time + 299 preimage = scryptlib.utils.get_preimage_from_input_context(context) with pytest.raises(bitcoinx.VerifyFailed): faucet.withdraw(SigHashPreimage(preimage), Ripemd160(pkh)).verify(context)
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_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_P2MultiSig(self): script_hex = ('5221022812701688bc76ef3610b46c8e97f4b385241d5ed6eab6269b8af5f9bfd5a89c210' '3fa0879c543ac97f34daffdaeed808f3500811aa5070e4a1f7e2daed3dd22ef2052ae') s = Script.from_hex(script_hex) sc = classify_output_script(s, Bitcoin) assert isinstance(sc, P2MultiSig_Output) assert len(sc.public_keys) == 2 assert sc.threshold == 2 # Confirm suffix fails to match s = Script.from_hex(script_hex + 'a0') assert isinstance(classify_output_script(s, Bitcoin), Unknown_Output) # Confirm prefix fails to match s = Script.from_hex('a0' + script_hex) assert isinstance(classify_output_script(s, Bitcoin), Unknown_Output)
def test_eq(self): assert Signature(MISSING_SIG_BYTES) == MISSING_SIG_BYTES assert Signature(MISSING_SIG_BYTES) == Script(MISSING_SIG_BYTES) assert Signature(MISSING_SIG_BYTES) == Signature(MISSING_SIG_BYTES) assert Signature(MISSING_SIG_BYTES) == bytearray(MISSING_SIG_BYTES) assert Signature(MISSING_SIG_BYTES) == memoryview(MISSING_SIG_BYTES) assert Signature(MISSING_SIG_BYTES) != 2.5
def string_to_bip276_script(text: str) -> Script: if text.startswith(PREFIX_BIP276_SCRIPT): prefix, version, network, data = bip276_decode(text, Net.BIP276_VERSION) assert network == Net.BIP276_VERSION, "incompatible network" return Script(data) raise ValueError("string is not bip276")
def sale(n_bought, pkh, pub_key): context = scryptlib.utils.create_dummy_input_context() context.utxo.script_pubkey = ats.locking_script context.utxo.value = input_sats new_data_part = ats.data_part << pub_key.to_bytes( ) + scryptlib.utils.get_push_int(n_bought)[1:] new_locking_script = Script(ats.code_part.to_bytes() + new_data_part.to_bytes()) change_sats = input_sats - n_bought * SATS_PER_TOKEN out_sats = input_sats + n_bought * SATS_PER_TOKEN # Counter output tx_out = TxOutput(value=out_sats, script_pubkey=new_locking_script) context.tx.outputs.append(tx_out) # Change output change_out = TxOutput( change_sats, P2PKH_Address(pub_key.hash160(), Bitcoin).to_script()) context.tx.outputs.append(change_out) preimage = scryptlib.utils.get_preimage_from_input_context( context, sighash_flag) verify_result = ats.buy(SigHashPreimage(preimage), Ripemd160(pkh), change_sats, Bytes(pub_key.to_bytes()), n_bought).verify(context) assert verify_result == True return new_data_part
def test_verify_correct(): # Intial state state = {'counter': 11, 'buf': b'\x12\x34', 'flag': True} counter.set_data_part(state) # Alter state state['counter'] += 1 state['buf'] += b'\xff\xff' state['flag'] = False serialized_state = scryptlib.serializer.serialize_state(state) new_locking_script = Script(counter.code_part.to_bytes() + serialized_state) # Deserialize state from new locking script new_state = scryptlib.serializer.deserialize_state(new_locking_script, state) assert new_state['counter'] == 12 assert new_state['buf'] == b'\x12\x34\xff\xff' assert new_state['flag'] == False context = scryptlib.utils.create_dummy_input_context() context.utxo.script_pubkey = counter.locking_script tx_out = TxOutput(value=out_sats, script_pubkey=new_locking_script) context.tx.outputs.append(tx_out) preimage = scryptlib.utils.get_preimage_from_input_context(context) verfiy_result = counter.mutate(SigHashPreimage(preimage), out_sats).verify(context) assert verfiy_result == True
def _parse_output(self, x): try: address = self._parse_address(x) self._show_cashaddr_warning(x) return address except: return Script.from_asm(x)
def make_signed_opreturn_transaction( self, wallet_name: Optional[str] = None, password: Optional[str] = None, pushdatas_b64: Optional[List[str]] = None) -> dict: wallet = self._get_wallet(wallet_name) pushdatas = [] for pushdata_b64 in pushdatas_b64: pushdata_bytes = base64.b64decode(pushdata_b64) pushdatas.append(pushdata_bytes) domain = None confirmed_coins = wallet.get_spendable_coins(None, {'confirmed_only': True}) script = (Script() << OP_RETURN).push_many(pushdatas) outputs = [TxOutput(0, script)] tx = wallet.make_unsigned_transaction(confirmed_coins, outputs, app_state.config) wallet.sign_transaction(tx, password) return { "tx_id": tx.txid(), "tx_hex": str(tx), "fee": tx.get_fee(), }
def string_to_script_template(text: str) -> ScriptTemplate: # raises bip276.ChecksumMismatchError if text.startswith(PREFIX_SCRIPT): prefix, version, network, data = bip276_decode(text, Net.BIP276_VERSION) assert network == Net.BIP276_VERSION, "incompatible network" return classify_output_script(Script(data), Net.COIN) return Address.from_string(text, Net.COIN)
def script_to_display_text(script: Script, kind: ScriptTemplate) -> str: if isinstance(kind, Address): text = kind.to_string() elif isinstance(kind, P2PK_Output): text = kind.public_key.to_hex() else: text = script.to_asm() return text
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 _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 get_preimage_after_purchase(key_pub): new_locking_script = Script(token_sale.locking_script.to_bytes() + key_pub.to_bytes() + scryptlib.utils.get_push_int(n_tokens)[1:]) tx_out = TxOutput(value=in_sats + n_tokens * token_price_sats, script_pubkey=new_locking_script) context.tx.outputs.append(tx_out) return scryptlib.utils.get_preimage_from_input_context(context)
def hex(self): self.bind() res_buff = [] flat_struct = utils.flatten_struct(self, '') for elem in flat_struct: res_buff.append(elem['value'].hex) return Script.from_hex(''.join(res_buff)).to_hex()
def script_bytes_to_asm(script: Script) -> str: # Adapted version of `script.to_asm` which just shows "[error]" in event of truncation. # Ideally we need an updated version in bitcoinx that identifies the truncation point. op_to_asm_word = script.op_to_asm_word parts = [] try: for op in script.ops(): parts.append(op_to_asm_word(op)) except TruncatedScriptError: parts.insert(0, "[decoding error]") parts.append("[script truncated]") return ' '.join(parts)
def create_dummy_input_context(): ''' Creates dummy instance of bitoinx.TxInputContext with empty locking and unlocking scripts. ''' tx_version = 2 tx_locktime = 0x00000000 utxo_satoshis = 0 script_pubkey = Script() utxo = TxOutput(utxo_satoshis, script_pubkey) prev_tx = Tx(tx_version, [], [utxo], tx_locktime) prev_txid = prev_tx.hash() utxo_idx = 0 script_sig = Script() n_sequence = 0xffffffff curr_in = TxInput(prev_txid, utxo_idx, script_sig, n_sequence) curr_tx = Tx(tx_version, [curr_in], [], tx_locktime) input_idx = 0 return TxInputContext(curr_tx, input_idx, utxo, is_utxo_after_genesis=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)