Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lwk_bindings/src/simplicity/cmr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ mod tests {

use crate::simplicity::{SimplicityArguments, SimplicityProgram, SimplicityTypedValue};

const TEST_CMR: &str = "b685a4424842507d7d747e6611a740d8c421038e9744e75d423d0e2e9f164d02";
const TEST_CMR: &str = "1b8b0834914675a1566132c81c2708fc614fab5cc38f17bf51643cc6c05d2d6f";
const TEST_PUBLIC_KEY: &str =
"8a65c55726dc32b59b649ad0187eb44490de681bb02601b8d3f58c8b9fff9083";
const P2PK_SOURCE: &str = include_str!("../../../lwk_simplicity/data/p2pk.simf");
Expand Down
20 changes: 2 additions & 18 deletions lwk_bindings/src/simplicity/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use lwk_simplicity::signer;
use lwk_simplicity::simplicityhl;

use crate::blockdata::tx_out::TxOut;
use crate::types::XOnlyPublicKey;
use crate::{Address, ControlBlock, LwkError, Network, Transaction};

use super::arguments::{SimplicityArguments, SimplicityWitnessValues};
Expand Down Expand Up @@ -38,28 +37,24 @@ impl SimplicityProgram {
/// Create a P2TR (Pay-to-Taproot) address for this Simplicity program.
pub fn create_p2tr_address(
&self,
internal_key: &XOnlyPublicKey,
network: &Network,
) -> Result<Arc<Address>, LwkError> {
let x_only_key = internal_key.to_simplicityhl()?;

let inner_network: lwk_common::Network = network.into();
let cmr = self.inner.commit().cmr();
let address =
scripts::create_p2tr_address(cmr, &x_only_key, inner_network.address_params());
scripts::create_p2tr_address(cmr, inner_network.address_params());

Ok(Arc::new(address.into()))
}

/// Get the taproot control block for script-path spending.
pub fn control_block(
&self,
internal_key: &XOnlyPublicKey,
) -> Result<Arc<ControlBlock>, LwkError> {
let x_only_key = internal_key.to_simplicityhl()?;

let cmr = self.inner.commit().cmr();
let control_block = scripts::control_block(cmr, x_only_key);
let control_block = scripts::control_block(cmr);

ControlBlock::from_bytes(&control_block.serialize())
}
Expand All @@ -68,18 +63,15 @@ impl SimplicityProgram {
pub fn get_sighash_all(
&self,
tx: &Transaction,
program_public_key: &XOnlyPublicKey,
utxos: &[Arc<TxOut>],
input_index: u32,
network: &Network,
) -> Result<Vec<u8>, LwkError> {
let x_only_key = program_public_key.to_simplicityhl()?;
let utxos_inner = convert_utxos(utxos);

let message = signer::get_sighash_all(
tx.as_ref(),
&self.inner,
&x_only_key,
&utxos_inner,
input_index as usize,
network.into(),
Expand All @@ -93,20 +85,17 @@ impl SimplicityProgram {
pub fn finalize_transaction(
&self,
tx: &Transaction,
program_public_key: &XOnlyPublicKey,
utxos: &[Arc<TxOut>],
input_index: u32,
witness_values: &SimplicityWitnessValues,
network: &Network,
log_level: SimplicityLogLevel,
) -> Result<Arc<Transaction>, LwkError> {
let x_only_key = program_public_key.to_simplicityhl()?;
let utxos_inner = convert_utxos(utxos);

let finalized = signer::finalize_transaction(
tx.as_ref().clone(),
&self.inner,
&x_only_key,
&utxos_inner,
input_index as usize,
witness_values.to_inner()?,
Expand All @@ -129,13 +118,11 @@ impl SimplicityProgram {
network: &Network,
) -> Result<Vec<u8>, LwkError> {
let keypair = derive_keypair(signer, derivation_path)?;
let x_only_pubkey = keypair.x_only_public_key().0;
let utxos_inner = convert_utxos(utxos);

let sighash = signer::get_sighash_all(
tx.as_ref(),
&self.inner,
&x_only_pubkey,
&utxos_inner,
input_index as usize,
network.into(),
Expand All @@ -151,20 +138,17 @@ impl SimplicityProgram {
pub fn run(
&self,
tx: &Transaction,
program_public_key: &XOnlyPublicKey,
utxos: &[Arc<TxOut>],
input_index: u32,
witness_values: &SimplicityWitnessValues,
network: &Network,
log_level: SimplicityLogLevel,
) -> Result<Arc<SimplicityRunResult>, LwkError> {
let x_only_key = program_public_key.to_simplicityhl()?;
let utxos_inner = convert_utxos(utxos);

let env = signer::get_and_verify_env(
tx.as_ref(),
&self.inner,
&x_only_key,
&utxos_inner,
network.into(),
input_index as usize,
Expand Down
4 changes: 1 addition & 3 deletions lwk_bindings/src/simplicity/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,8 @@ pub fn simplicity_derive_xonly_pubkey(
#[uniffi::export]
pub fn simplicity_control_block(
cmr: &Cmr,
internal_key: &XOnlyPublicKey,
) -> Result<Arc<ControlBlock>, LwkError> {
let internal_key = internal_key.to_simplicityhl()?;
let control_block = scripts::control_block(cmr.inner(), internal_key);
let control_block = scripts::control_block(cmr.inner());
let serialized = control_block.serialize();
ControlBlock::from_bytes(&serialized)
}
Expand Down
4 changes: 2 additions & 2 deletions lwk_bindings/tests/bindings/simplicity_options_regtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def find_outputs_by_script(tx, script_hex):
p2pk_args = SimplicityArguments()
p2pk_args = p2pk_args.add_value("PUBLIC_KEY", SimplicityTypedValue.u256(xonly_pubkey.to_bytes()))
p2pk_program = SimplicityProgram.load(P2PK_SOURCE, p2pk_args)
p2pk_address = p2pk_program.create_p2tr_address(xonly_pubkey, network)
p2pk_address = p2pk_program.create_p2tr_address(network)
p2pk_script = p2pk_address.script_pubkey()
p2pk_script_hex = str(p2pk_script)

Expand Down Expand Up @@ -148,7 +148,7 @@ def find_outputs_by_script(tx, script_hex):
options_args = build_options_arguments(options_params)
options_program = SimplicityProgram.load(OPTIONS_SOURCE, options_args)

contract_address = options_program.create_p2tr_address(xonly_pubkey, network)
contract_address = options_program.create_p2tr_address(network)
contract_script = contract_address.script_pubkey()
contract_script_hex = str(contract_script)

Expand Down
65 changes: 40 additions & 25 deletions lwk_bindings/tests/bindings/simplicity_p2pk.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,39 @@
import os
from lwk import *

_SIMF_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "..", "lwk_simplicity", "data")
_SIMF_DIR = os.path.join(os.path.dirname(
__file__), "..", "..", "..", "lwk_simplicity", "data")
P2PK_SOURCE = open(os.path.join(_SIMF_DIR, "p2pk.simf")).read()

TEST_X_ONLY_PUBLIC_KEY = "8a65c55726dc32b59b649ad0187eb44490de681bb02601b8d3f58c8b9fff9083"
TEST_PRIVATE_KEY = "0000000000000000000000000000000000000000000000000000000000000001"
TEST_X_ONLY_PUBLIC_KEY = "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
TEST_UNSIGNED_TX = "02000000000113226c2af4a18516258790b9c6f118afdf0bfe9cb0cf79574807ddf6bf680be80000000000ffffffff0301499a818545f6bae39fc03b637f2a4e1e64e590cac1bc3a6f6d71aa4443654c140100000000000003e800225120f8b916e58321fccfb61529245bcae76bdf0cedbcb0df2b23c2ac8f1adfed577501499a818545f6bae39fc03b637f2a4e1e64e590cac1bc3a6f6d71aa4443654c140100000000000181be00225120f8b916e58321fccfb61529245bcae76bdf0cedbcb0df2b23c2ac8f1adfed577501499a818545f6bae39fc03b637f2a4e1e64e590cac1bc3a6f6d71aa4443654c140100000000000000fa000000000000"
TEST_UTXO_SCRIPT_PUBKEY = "5120f8b916e58321fccfb61529245bcae76bdf0cedbcb0df2b23c2ac8f1adfed5775"
TEST_UTXO_VALUE = 100000
TEST_SIGNATURE = "ab5173c154e62da5e4f5d983177d7398918d61d6149f3b1bb7271d00d165391c19f86c53c5d6dcfcd50f4fc4dbf8748fbb6a1614ddacd663818bfc90ef8f818d"
TEST_FINALIZED_TX = "02000000010113226c2af4a18516258790b9c6f118afdf0bfe9cb0cf79574807ddf6bf680be80000000000ffffffff0301499a818545f6bae39fc03b637f2a4e1e64e590cac1bc3a6f6d71aa4443654c140100000000000003e800225120f8b916e58321fccfb61529245bcae76bdf0cedbcb0df2b23c2ac8f1adfed577501499a818545f6bae39fc03b637f2a4e1e64e590cac1bc3a6f6d71aa4443654c140100000000000181be00225120f8b916e58321fccfb61529245bcae76bdf0cedbcb0df2b23c2ac8f1adfed577501499a818545f6bae39fc03b637f2a4e1e64e590cac1bc3a6f6d71aa4443654c140100000000000000fa00000000000000000440ab5173c154e62da5e4f5d983177d7398918d61d6149f3b1bb7271d00d165391c19f86c53c5d6dcfcd50f4fc4dbf8748fbb6a1614ddacd663818bfc90ef8f818d76e06922d314cb8aae4db8656b36c935a030fd688921bcd037604c0371a7eb19173fff21060a15c38735d772f07a13a9e8c81c672ff1b725794653e7b2a65b5569ecb5da314478a8c43860188599c502d8d8c399c5510915d0998008ab858317852c27b030a85d612fd7152321658700adbbf004300c4020b685a4424842507d7d747e6611a740d8c421038e9744e75d423d0e2e9f164d0221bf8a65c55726dc32b59b649ad0187eb44490de681bb02601b8d3f58c8b9fff908300000000000000"

# Expected CMR for P2PK program with TEST_PUBLIC_KEY
TEST_CMR = "b685a4424842507d7d747e6611a740d8c421038e9744e75d423d0e2e9f164d02"
TEST_ADDRESS = "tex1plzu3devry87vlds49yj9hjh8d00semdukr0jkg7z4j834hld2a6s6y4amk"
# These are computed from the private key above; values are printed on first run
TEST_CMR = "1abc423d746ee0fff5bb8ad259bec3b89d319f08a425d6b8092e9c1c5ec7d465"
TEST_ADDRESS = "tex1peavhc0s5wcm0ans49jxg445enyh6uuwl8radea7expf2syt5rkzqjre6vm"
TEST_UTXO_SCRIPT_PUBKEY = "5120cf597c3e147636fece152c8c8ad699992fae71df38fadcf7d93052a811741d84"

network = Network.testnet()
genesis_hash = network.genesis_block_hash()

assert genesis_hash == "a771da8e52ee6ad581ed1e9a99825e5b3b7992225534eaa2ae23244fe26ab1c1"
assert len(genesis_hash) == 64

# Build keypair and verify public key matches expected
keypair = Keypair.from_secret_bytes(bytes.fromhex(TEST_PRIVATE_KEY))
xonly = keypair.x_only_public_key()
assert str(xonly) == TEST_X_ONLY_PUBLIC_KEY

# Test loading p2pk program with public key argument
args = SimplicityArguments()
args = args.add_value("PUBLIC_KEY", SimplicityTypedValue.u256(bytes.fromhex(TEST_X_ONLY_PUBLIC_KEY)))
args = args.add_value(
"PUBLIC_KEY", SimplicityTypedValue.u256(xonly.to_bytes()))

program = SimplicityProgram.load(P2PK_SOURCE, args)
cmr_from_program = program.cmr()
print("TEST_CMR =", repr(str(cmr_from_program)))
assert str(cmr_from_program) == TEST_CMR

cmr_from_str = Cmr.from_string(TEST_CMR)
Expand All @@ -38,33 +45,39 @@
assert str(cmr_from_bytes) == TEST_CMR
assert cmr_from_program.to_bytes() == cmr_bytes

# Test creating P2TR address for p2pk program
address = program.create_p2tr_address(XOnlyPublicKey.from_string(TEST_X_ONLY_PUBLIC_KEY), network)
# Test creating P2TR address for p2pk program (uses NUMS internal key)
address = program.create_p2tr_address(network)
assert address is not None
print("TEST_ADDRESS =", repr(str(address)))
print("TEST_UTXO_SCRIPT_PUBKEY =", repr(str(address.script_pubkey())))
assert str(address) == TEST_ADDRESS

# Test building witness values with signature (64 bytes)
witness = SimplicityWitnessValues()
witness = witness.add_value("SIGNATURE", SimplicityTypedValue.byte_array(bytes.fromhex(TEST_SIGNATURE)))
assert witness is not None
assert str(address.script_pubkey()) == TEST_UTXO_SCRIPT_PUBKEY

# Test creating TxOut from explicit values
utxo_script = Script(TEST_UTXO_SCRIPT_PUBKEY)
utxo = TxOut.from_explicit(utxo_script, network.policy_asset(), TEST_UTXO_VALUE)
utxo = TxOut.from_explicit(
address.script_pubkey(), network.policy_asset(), TEST_UTXO_VALUE)
assert utxo is not None
assert utxo.value() == TEST_UTXO_VALUE

# Test full transaction finalization with real test vectors
# Test full transaction finalization
tx = Transaction.from_string(TEST_UNSIGNED_TX)

sighash = program.get_sighash_all(tx, [utxo], 0, network)
sig_hex = keypair.sign_schnorr(sighash.hex())
print("TEST_SIGNATURE =", repr(sig_hex))

witness = SimplicityWitnessValues()
witness = witness.add_value(
"SIGNATURE", SimplicityTypedValue.byte_array(bytes.fromhex(sig_hex)))
assert witness is not None

finalized_tx = program.finalize_transaction(
tx, XOnlyPublicKey.from_string(TEST_X_ONLY_PUBLIC_KEY), [utxo], 0,
tx, [utxo], 0,
witness, network, SimplicityLogLevel.NONE
)

assert finalized_tx is not None
finalized_hex = str(finalized_tx)
assert finalized_hex == TEST_FINALIZED_TX
print("TEST_FINALIZED_TX =", repr(str(finalized_tx)))

# Test SimplicityType constructors
t_u32 = SimplicityType.u32()
Expand Down Expand Up @@ -97,7 +110,8 @@

# Verify add_value works for loading a program (regression)
args3 = SimplicityArguments()
args3 = args3.add_value("PUBLIC_KEY", SimplicityTypedValue.u256(bytes.fromhex(TEST_X_ONLY_PUBLIC_KEY)))
args3 = args3.add_value("PUBLIC_KEY", SimplicityTypedValue.u256(
bytes.fromhex(TEST_X_ONLY_PUBLIC_KEY)))
program2 = SimplicityProgram.load(P2PK_SOURCE, args3)
assert str(program2.cmr()) == TEST_CMR

Expand Down Expand Up @@ -128,7 +142,8 @@
x_only_public_key_str = XOnlyPublicKey.from_string(TEST_X_ONLY_PUBLIC_KEY)

x_only_public_key_bytes = x_only_public_key_str.to_bytes()
x_only_public_key_from_bytes = XOnlyPublicKey.from_bytes(x_only_public_key_bytes)
x_only_public_key_from_bytes = XOnlyPublicKey.from_bytes(
x_only_public_key_bytes)

assert str(x_only_public_key_str) == TEST_X_ONLY_PUBLIC_KEY

Expand All @@ -145,4 +160,4 @@
assert str(tweak_str) == TEST_TWEAK_KEY

assert tweak_from_bytes.to_bytes() == tweak_bytes
assert str(tweak_from_bytes) == TEST_TWEAK_KEY
assert str(tweak_from_bytes) == TEST_TWEAK_KEY
32 changes: 20 additions & 12 deletions lwk_bindings/tests/bindings/simplicity_p2pk_regtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import time
from lwk import *

_SIMF_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "..", "lwk_simplicity", "data")
_SIMF_DIR = os.path.join(os.path.dirname(
__file__), "..", "..", "..", "lwk_simplicity", "data")
P2PK_SOURCE = open(os.path.join(_SIMF_DIR, "p2pk.simf")).read()

# 1. Set up regtest environment
Expand All @@ -12,18 +13,20 @@
client = ElectrumClient.from_url(node.electrum_url())

# 2. Create signer and derive x-only public key
mnemonic = Mnemonic("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about")
mnemonic = Mnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about")
signer = Signer(mnemonic, network)
derivation_path = "m/86'/1'/0'/0/0"
xonly_pubkey = simplicity_derive_xonly_pubkey(signer, derivation_path)

# 3. Compile P2PK program with the public key
args = SimplicityArguments()
args = args.add_value("PUBLIC_KEY", SimplicityTypedValue.u256(xonly_pubkey.to_bytes()))
args = args.add_value(
"PUBLIC_KEY", SimplicityTypedValue.u256(xonly_pubkey.to_bytes()))
program = SimplicityProgram.load(P2PK_SOURCE, args)

# 4. Create P2TR address from the program
simplicity_address = program.create_p2tr_address(xonly_pubkey, network)
simplicity_address = program.create_p2tr_address(network)
simplicity_script = simplicity_address.script_pubkey()

# Create Wollet
Expand All @@ -33,7 +36,8 @@

# 5. Fund the Simplicity address
funded_satoshi = 100000
funding_txid = node.send_to_address(simplicity_address, funded_satoshi, asset=None)
funding_txid = node.send_to_address(
simplicity_address, funded_satoshi, asset=None)
node.generate(1)
funding_tx = wollet.wait_for_tx(funding_txid, client).tx()

Expand All @@ -44,7 +48,8 @@
)

# 7. Create ExternalUtxo for TxBuilder
SIMPLICITY_WITNESS_WEIGHT = 700 # FIXME(KyrylR): Conservative estimate for Simplicity witness
# FIXME(KyrylR): Conservative estimate for Simplicity witness
SIMPLICITY_WITNESS_WEIGHT = 700
unblinded = TxOutSecrets.from_explicit(policy_asset, funded_satoshi)
external_utxo = ExternalUtxo.from_unchecked_data(
OutPoint.from_parts(funding_txid, vout),
Expand Down Expand Up @@ -74,22 +79,24 @@

# 10. Finalize transaction with Simplicity witness
witness = SimplicityWitnessValues()
witness = witness.add_value("SIGNATURE", SimplicityTypedValue.byte_array(signature))
witness = witness.add_value(
"SIGNATURE", SimplicityTypedValue.byte_array(signature))

finalized_tx = program.finalize_transaction(
unsigned_tx, xonly_pubkey, all_utxos, 0,
unsigned_tx, all_utxos, 0,
witness, network, SimplicityLogLevel.NONE
)

# 11. Verify TxInWitness can be built manually and matches finalize_transaction output
finalized_witness = finalized_tx.inputs()[0].witness()
assert not finalized_witness.is_empty(), "Finalized witness should not be empty"
finalized_script_witness = finalized_witness.script_witness()
assert len(finalized_script_witness) == 4, "Simplicity witness should have 4 elements"
assert len(
finalized_script_witness) == 4, "Simplicity witness should have 4 elements"

# Run the program to get the pruned program and witness bytes
run_result = program.run(
unsigned_tx, xonly_pubkey, all_utxos, 0,
unsigned_tx, all_utxos, 0,
witness, network, SimplicityLogLevel.NONE
)

Expand All @@ -99,11 +106,12 @@
simplicity_program_bytes = run_result.program_bytes()
cmr = run_result.cmr()

control_block = simplicity_control_block(cmr, xonly_pubkey)
control_block = simplicity_control_block(cmr)
control_block_hex = control_block.to_bytes().hex()

# Verify it matches what program.control_block() returns
program_control_block_hex = program.control_block(xonly_pubkey).to_bytes().hex()
program_control_block_hex = program.control_block(
xonly_pubkey).to_bytes().hex()
assert control_block_hex == program_control_block_hex, \
"simplicity_control_block should match program.control_block()"

Expand Down
Loading