From a0b54351d7303f0ee9339c96411ed33d4c2989a0 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 3 Sep 2025 18:59:43 +0000 Subject: [PATCH 01/10] simplicity-sys: split up bitcoin and elements build Don't actually build bitcoin yet; just refactor build.rs --- simplicity-sys/build.rs | 138 ++++++++++++++++++++++++++++------------ 1 file changed, 98 insertions(+), 40 deletions(-) diff --git a/simplicity-sys/build.rs b/simplicity-sys/build.rs index 14470d74..065166ce 100644 --- a/simplicity-sys/build.rs +++ b/simplicity-sys/build.rs @@ -7,56 +7,105 @@ fn main() { // rerun if changes to the C code println!("cargo:rerun-if-changed=depend"); let simplicity_path = Path::new("depend/simplicity"); - let jet_files: Vec<_> = vec![ - "frame.c", - "jets.c", - "jets-secp256k1.c", - "rsort.c", - "sha256.c", - "elements/env.c", - "elements/ops.c", - "elements/elementsJets.c", - "elements/txEnv.c", - ] - .into_iter() - .map(|x| simplicity_path.join(x)) - .collect(); + let mut files = vec![]; + + // 1. Base files. + files.extend( + [ + "frame.c", + "jets.c", + "jets-secp256k1.c", + "rsort.c", + "sha256.c", + ] + .into_iter() + .map(|x| simplicity_path.join(x)), + ); + // 2. Test files. + if cfg!(feature = "test-utils") { + files.extend( + [ + "bitstream.c", + "dag.c", + "deserialize.c", + "eval.c", + "type.c", + "typeInference.c", + "ctx8Pruned.c", + "ctx8Unpruned.c", + "hashBlock.c", + "schnorr0.c", + "schnorr6.c", + ] + .into_iter() + .map(|x| simplicity_path.join(x)), + ); + } + + // Split into Bitcoin and Elements. + let mut elements_files = files.clone(); + let mut bitcoin_files = files; + + // 3B. Bitcoin base files. + bitcoin_files.extend( + [ + "bitcoin/env.c", + "bitcoin/ops.c", + "bitcoin/bitcoinJets.c", + "bitcoin/txEnv.c", + ] + .into_iter() + .map(|x| simplicity_path.join(x)), + ); + + // 3E. Elements base files. + elements_files.extend( + [ + "elements/env.c", + "elements/ops.c", + "elements/elementsJets.c", + "elements/txEnv.c", + ] + .into_iter() + .map(|x| simplicity_path.join(x)), + ); + + if cfg!(feature = "test-utils") { + // 4B. Bitcoin base files. + bitcoin_files.extend( + [ + "bitcoin/exec.c", + "bitcoin/primitive.c", + // "bitcoin/checkSigHashAllTx1.c", // no sighashall test + ] + .into_iter() + .map(|x| simplicity_path.join(x)), + ); + + // 4E. Elements base files. + elements_files.extend( + [ + "elements/exec.c", + "elements/primitive.c", + "elements/checkSigHashAllTx1.c", + ] + .into_iter() + .map(|x| simplicity_path.join(x)), + ); + } + + // General build let mut build = cc::Build::new(); build .std("c11") .flag_if_supported("-fno-inline-functions") .opt_level(2) - .files(jet_files) .file(Path::new("depend/wrapper.c")) .file(Path::new("depend/env.c")) .file(Path::new("depend/jets_wrapper.c")) .include(simplicity_path.join("include")); - if cfg!(feature = "test-utils") { - let test_files: Vec<_> = vec![ - "bitstream.c", - "dag.c", - "deserialize.c", - "eval.c", - "type.c", - "typeInference.c", - "elements/exec.c", - "elements/primitive.c", - "ctx8Pruned.c", - "ctx8Unpruned.c", - "hashBlock.c", - "schnorr0.c", - "schnorr6.c", - "elements/checkSigHashAllTx1.c", - ] - .into_iter() - .map(|x| simplicity_path.join(x)) - .collect(); - - build.files(test_files); - } - if cfg!(not(fuzzing)) { build.define("PRODUCTION", None); } @@ -66,5 +115,14 @@ fn main() { build.include("wasm-sysroot"); } - build.compile("ElementsSimplicity"); + let mut _bitcoin_build = build.clone(); + let mut elements_build = build; + + // Bitcoin build + // TODO + + // Elements build + elements_build + .files(elements_files) + .compile("ElementsSimplicity"); } From 8607ed8a35ebfee79658b2a54ca2618c84a81b0d Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 3 Sep 2025 21:02:43 +0000 Subject: [PATCH 02/10] simplicity-sys: split env.c into bitcoin_env.c For dumb "C polymorphism" reasons we have two structures in the C code named txEnv. We can call the corresponding Rust structures different things, but we need to access them from the C file env.c, which provides some C wrappers for FFI stuff. Since you can't have two different structs with the same name in one compilation unit, split it into two: add depend/bitcoin_env.c holding the Bitcoin txEnv wrappers, alongside the existing depend/env.c for Elements. --- simplicity-sys/build.rs | 3 ++- simplicity-sys/depend/bitcoin_env.c | 31 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 simplicity-sys/depend/bitcoin_env.c diff --git a/simplicity-sys/build.rs b/simplicity-sys/build.rs index 065166ce..4643be4a 100644 --- a/simplicity-sys/build.rs +++ b/simplicity-sys/build.rs @@ -58,6 +58,7 @@ fn main() { .into_iter() .map(|x| simplicity_path.join(x)), ); + bitcoin_files.push("depend/bitcoin_env.c".into()); // 3E. Elements base files. elements_files.extend( @@ -70,6 +71,7 @@ fn main() { .into_iter() .map(|x| simplicity_path.join(x)), ); + elements_files.push("depend/env.c".into()); if cfg!(feature = "test-utils") { // 4B. Bitcoin base files. @@ -102,7 +104,6 @@ fn main() { .flag_if_supported("-fno-inline-functions") .opt_level(2) .file(Path::new("depend/wrapper.c")) - .file(Path::new("depend/env.c")) .file(Path::new("depend/jets_wrapper.c")) .include(simplicity_path.join("include")); diff --git a/simplicity-sys/depend/bitcoin_env.c b/simplicity-sys/depend/bitcoin_env.c new file mode 100644 index 00000000..99928e88 --- /dev/null +++ b/simplicity-sys/depend/bitcoin_env.c @@ -0,0 +1,31 @@ +#include +#include +#include "simplicity/bitcoin/env.h" +#include "simplicity/bitcoin/txEnv.h" +#include "simplicity/bitcoin/primitive.h" + +typedef rawBitcoinBuffer rawBuffer; +typedef rawBitcoinBuffer rawBuffer; +typedef rawBitcoinOutput rawOutput; +typedef rawBitcoinInput rawInput; +typedef rawBitcoinTransaction rawTransaction; +typedef rawBitcoinTapEnv rawTapEnv; + +const size_t rustsimplicity_0_8_c_sizeof_rawBitcoinBuffer = sizeof(rawBitcoinBuffer); +const size_t rustsimplicity_0_8_c_sizeof_rawBitcoinOutput = sizeof(rawBitcoinOutput); +const size_t rustsimplicity_0_8_c_sizeof_rawBitcoinInput = sizeof(rawBitcoinInput); +const size_t rustsimplicity_0_8_c_sizeof_rawBitcoinTransaction = sizeof(rawBitcoinTransaction); +const size_t rustsimplicity_0_8_c_sizeof_rawBitcoinTapEnv = sizeof(rawBitcoinTapEnv); +const size_t rustsimplicity_0_8_c_sizeof_bitcoinTxEnv = sizeof(txEnv); + +const size_t rustsimplicity_0_8_c_alignof_rawBitcoinBuffer = alignof(rawBitcoinBuffer); +const size_t rustsimplicity_0_8_c_alignof_rawBitcoinOutput = alignof(rawBitcoinOutput); +const size_t rustsimplicity_0_8_c_alignof_rawBitcoinInput = alignof(rawBitcoinInput); +const size_t rustsimplicity_0_8_c_alignof_rawBitcoinTransaction = alignof(rawBitcoinTransaction); +const size_t rustsimplicity_0_8_c_alignof_rawBitcoinTapEnv = alignof(rawBitcoinTapEnv); +const size_t rustsimplicity_0_8_c_alignof_bitcoinTxEnv = alignof(txEnv); + +void rustsimplicity_0_8_c_bitcoin_set_txEnv(txEnv *result, const bitcoinTransaction *tx, const bitcoinTapEnv *taproot, unsigned int ix) +{ + *result = rustsimplicity_0_8_bitcoin_build_txEnv(tx, taproot, ix); +} From 0182d01e859af2a8fec3b5e859338cc342657b0e Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 3 Sep 2025 19:56:00 +0000 Subject: [PATCH 03/10] simplicity-sys: define bitcoin transaction environment --- simplicity-sys/src/c_jets/c_env/bitcoin.rs | 176 +++++++++++++++++++++ simplicity-sys/src/c_jets/c_env/mod.rs | 1 + simplicity-sys/src/c_jets/mod.rs | 2 +- simplicity-sys/src/lib.rs | 1 + 4 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 simplicity-sys/src/c_jets/c_env/bitcoin.rs diff --git a/simplicity-sys/src/c_jets/c_env/bitcoin.rs b/simplicity-sys/src/c_jets/c_env/bitcoin.rs new file mode 100644 index 00000000..dfb3e8a8 --- /dev/null +++ b/simplicity-sys/src/c_jets/c_env/bitcoin.rs @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: CC0-1.0 + +use hashes::sha256; + +use crate::ffi::sha256::CSha256Midstate; +use crate::ffi::{c_size_t, c_uchar, c_uint, c_uint_fast32_t}; + +#[derive(Debug)] +#[repr(C)] +pub struct CRawBuffer { + pub ptr: *const c_uchar, + pub len: u32, +} + +#[derive(Debug)] +#[repr(C)] +pub struct CRawOutput { + pub value: u64, + pub script_pubkey: CRawBuffer, +} + +#[repr(C)] +pub struct CRawInput<'raw> { + pub annex: *const CRawBuffer, + pub prev_txid: &'raw [c_uchar; 32], + pub txo: CRawOutput, + pub script_sig: CRawBuffer, + pub prev_txout_index: u32, + pub sequence: u32, +} + +#[derive(Debug)] +#[repr(C)] +pub struct CRawTransaction<'raw> { + pub txid: &'raw [c_uchar; 32], + pub inputs: *const CRawInput<'raw>, + pub outputs: *const CRawOutput, + pub n_inputs: u32, + pub n_outputs: u32, + pub version: u32, + pub locktime: u32, +} + +#[derive(Debug)] +#[repr(C)] +pub struct CRawTapEnv { + pub control_block: *const c_uchar, + pub script_cmr: *const c_uchar, + pub branch_len: u8, +} + +#[repr(C)] +pub struct CTransaction { + _data: (), +} + +#[derive(Debug)] +#[repr(C)] +pub struct CTxEnv { + tx: *const CTransaction, + taproot: *const CTapEnv, + sighash_all: CSha256Midstate, + ix: c_uint_fast32_t, +} + +#[repr(C)] +pub struct CTapEnv { + _data: (), +} + +/* +// Will uncomment in a later commit; need to update libsimplicity first so these +// symbols have something to link against. +extern "C" { + #[link_name = "rustsimplicity_0_8_c_sizeof_rawBitcoinBuffer"] + pub static c_sizeof_rawBuffer: c_size_t; + #[link_name = "rustsimplicity_0_8_c_sizeof_rawBitcoinOutput"] + pub static c_sizeof_rawOutput: c_size_t; + #[link_name = "rustsimplicity_0_8_c_sizeof_rawBitcoinInput"] + pub static c_sizeof_rawInput: c_size_t; + #[link_name = "rustsimplicity_0_8_c_sizeof_rawBitcoinTransaction"] + pub static c_sizeof_rawTransaction: c_size_t; + #[link_name = "rustsimplicity_0_8_c_sizeof_rawBitcoinTapEnv"] + pub static c_sizeof_rawTapEnv: c_size_t; + #[link_name = "rustsimplicity_0_8_c_sizeof_bitcoinTxEnv"] + pub static c_sizeof_txEnv: c_size_t; + + #[link_name = "rustsimplicity_0_8_c_alignof_rawBitcoinBuffer"] + pub static c_alignof_rawBuffer: c_size_t; + #[link_name = "rustsimplicity_0_8_c_alignof_rawBitcoinOutput"] + pub static c_alignof_rawOutput: c_size_t; + #[link_name = "rustsimplicity_0_8_c_alignof_rawBitcoinInput"] + pub static c_alignof_rawInput: c_size_t; + #[link_name = "rustsimplicity_0_8_c_alignof_rawBitcoinTransaction"] + pub static c_alignof_rawTransaction: c_size_t; + #[link_name = "rustsimplicity_0_8_c_alignof_rawBitcoinTapEnv"] + pub static c_alignof_rawTapEnv: c_size_t; + #[link_name = "rustsimplicity_0_8_c_alignof_bitcoinTxEnv"] + pub static c_alignof_txEnv: c_size_t; + + #[link_name = "rustsimplicity_0_8_c_bitcoin_set_txEnv"] + pub fn c_set_txEnv( + result: *mut CTxEnv, + tx: *const CTransaction, + taproot: *const CTapEnv, + ix: c_uint, + ); + #[link_name = "rustsimplicity_0_8_bitcoin_mallocTapEnv"] + pub fn simplicity_mallocTapEnv(rawEnv: *const CRawTapEnv) -> *mut CTapEnv; + #[link_name = "rustsimplicity_0_8_bitcoin_mallocTransaction"] + pub fn simplicity_mallocTransaction(rawTx: *const CRawTransaction) -> *mut CTransaction; +} +*/ +impl CTxEnv { + pub fn sighash_all(&self) -> sha256::Hash { + let midstate: sha256::Midstate = self.sighash_all.into(); + sha256::Hash::from_byte_array(midstate.to_parts().0) + } +} + +// Pointer must be manually free after dropping +impl Drop for CTxEnv { + fn drop(&mut self) { + unsafe { + crate::alloc::rust_0_8_free(self.tx as *mut u8); + crate::alloc::rust_0_8_free(self.taproot as *mut u8); + } + } +} + +impl CRawBuffer { + pub fn new(buf: &[c_uchar]) -> Self { + Self { + ptr: buf.as_ptr(), + len: buf.len().try_into().expect("sane buffer lengths"), + } + } +} + +/* +// Will uncomment in a later commit; need to update libsimplicity first. +#[cfg(test)] +mod tests { + use core::mem::{align_of, size_of}; + + use crate::c_jets::frame_ffi::{c_alignof_frameItem, c_sizeof_frameItem, CFrameItem}; + + use super::*; + + #[test] + fn test_sizes() { + unsafe { + assert_eq!(size_of::(), c_sizeof_frameItem); + assert_eq!(size_of::(), c_sizeof_rawBuffer); + assert_eq!(size_of::(), c_sizeof_rawInput); + assert_eq!(size_of::(), c_sizeof_rawOutput); + assert_eq!(size_of::(), c_sizeof_rawTransaction); + assert_eq!(size_of::(), c_sizeof_rawTapEnv); + assert_eq!(size_of::(), c_sizeof_txEnv); + } + } + + #[test] + fn test_aligns() { + unsafe { + assert_eq!(align_of::(), c_alignof_frameItem); + assert_eq!(align_of::(), c_alignof_rawBuffer); + assert_eq!(align_of::(), c_alignof_rawInput); + assert_eq!(align_of::(), c_alignof_rawOutput); + assert_eq!(align_of::(), c_alignof_rawTransaction); + assert_eq!(align_of::(), c_alignof_rawTapEnv); + assert_eq!(align_of::(), c_alignof_txEnv); + } + } +} +*/ diff --git a/simplicity-sys/src/c_jets/c_env/mod.rs b/simplicity-sys/src/c_jets/c_env/mod.rs index fcca587c..6136d485 100644 --- a/simplicity-sys/src/c_jets/c_env/mod.rs +++ b/simplicity-sys/src/c_jets/c_env/mod.rs @@ -1,3 +1,4 @@ // SPDX-License-Identifier: CC0-1.0 +pub mod bitcoin; pub mod elements; diff --git a/simplicity-sys/src/c_jets/mod.rs b/simplicity-sys/src/c_jets/mod.rs index ae2fc852..a163cf57 100644 --- a/simplicity-sys/src/c_jets/mod.rs +++ b/simplicity-sys/src/c_jets/mod.rs @@ -12,7 +12,7 @@ pub mod frame_ffi; #[rustfmt::skip] pub mod jets_ffi; #[rustfmt::skip] pub mod jets_wrapper; -pub use c_env::elements; +pub use c_env::{bitcoin, elements}; pub use c_frame::{byte_width, uword_width}; pub use frame_ffi::CFrameItem; diff --git a/simplicity-sys/src/lib.rs b/simplicity-sys/src/lib.rs index f032d902..b35f3541 100644 --- a/simplicity-sys/src/lib.rs +++ b/simplicity-sys/src/lib.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: CC0-1.0 pub mod c_jets; +pub use c_jets::bitcoin; pub use c_jets::elements; pub use c_jets::CFrameItem; From 87549f61737ba96f070fd9641ac2f77b353cdd62 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 18 Dec 2025 16:49:05 +0000 Subject: [PATCH 04/10] jet: switch from Deref to Borrow for transaction environments This is the more correct trait. When we update the Jet trait we will be forced to pick one, and we will pick Borrow. --- src/jet/bitcoin/environment.rs | 26 +++++++++++--------------- src/jet/elements/environment.rs | 10 +++++----- src/policy/sighash.rs | 2 +- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/jet/bitcoin/environment.rs b/src/jet/bitcoin/environment.rs index c530cec6..22c9f85a 100644 --- a/src/jet/bitcoin/environment.rs +++ b/src/jet/bitcoin/environment.rs @@ -1,26 +1,22 @@ // SPDX-License-Identifier: CC0-1.0 -use bitcoin::absolute; +use simplicity_sys::c_jets::c_env::bitcoin as c_bitcoin; /// Environment for Bitcoin Simplicity -pub struct BitcoinEnv { - pub tx: bitcoin::Transaction, +// In later commit, when we update Jet trait, will remove default type. +pub struct BitcoinEnv { + pub tx: T, } -impl BitcoinEnv { - pub fn new(tx: bitcoin::Transaction) -> Self { +impl BitcoinEnv +where + T: core::borrow::Borrow, +{ + pub fn new(tx: T) -> Self { BitcoinEnv { tx } } -} -impl Default for BitcoinEnv { - fn default() -> Self { - // FIXME: Review and check if the defaults make sense - BitcoinEnv::new(bitcoin::Transaction { - version: bitcoin::transaction::Version::TWO, - lock_time: absolute::LockTime::ZERO, - input: vec![], - output: vec![], - }) + pub fn c_tx_env(&self) -> &c_bitcoin::CTxEnv { + unimplemented!() } } diff --git a/src/jet/elements/environment.rs b/src/jet/elements/environment.rs index 202582c4..812bf388 100644 --- a/src/jet/elements/environment.rs +++ b/src/jet/elements/environment.rs @@ -4,7 +4,7 @@ use crate::merkle::cmr::Cmr; use elements::confidential; use elements::taproot::ControlBlock; use simplicity_sys::c_jets::c_env::elements as c_elements; -use std::ops::Deref; +use std::borrow::Borrow; use super::c_env; @@ -37,7 +37,7 @@ impl From for ElementsUtxo { /// The order of `utxos` must be same as of the order of inputs in the /// transaction. #[derive(Debug)] -pub struct ElementsEnv> { +pub struct ElementsEnv> { /// The CTxEnv struct c_tx_env: c_elements::CTxEnv, /// The elements transaction @@ -54,7 +54,7 @@ pub struct ElementsEnv> { impl ElementsEnv where - T: Deref, + T: Borrow, { pub fn new( tx: T, @@ -65,7 +65,7 @@ where annex: Option>, genesis_hash: elements::BlockHash, ) -> Self { - let c_tx = c_env::new_tx(&tx, &utxos); + let c_tx = c_env::new_tx(tx.borrow(), &utxos); let c_tap_env = c_env::new_tap_env(&control_block, script_cmr); let c_tx_env = c_env::new_tx_env(c_tx, c_tap_env, genesis_hash, ix); ElementsEnv { @@ -85,7 +85,7 @@ where /// Returns the transaction of this environment pub fn tx(&self) -> &elements::Transaction { - &self.tx + self.tx.borrow() } /// Returns the input index of this environment diff --git a/src/policy/sighash.rs b/src/policy/sighash.rs index 2766c454..d65ca7aa 100644 --- a/src/policy/sighash.rs +++ b/src/policy/sighash.rs @@ -81,7 +81,7 @@ impl + Clone> SighashCache { .collect(); let simplicity_env = crate::jet::elements::ElementsEnv::new( - self.tx.clone(), + (*self.tx).borrow(), utxos, input_index as u32, script_cmr, From a5ff0797080f36c97f1d3fa44a59120595399af0 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 3 Sep 2025 18:47:15 +0000 Subject: [PATCH 05/10] simplicity-sys: update libsimplicity to bitcoin branch Updates to https://github.com/BlockstreamResearch/simplicity/pull/324 This PR does a couple things simultaneously: * Runs vendor-simplicity.sh and update-jets.sh * Updates the `Jet` trait to have associated transaction and environment types, and for the environment to be paramterized by the transaction type. * Changes the Core environment from () to CoreEnv:: * Updates some fixed Core CMR/IHR vectors (this update to libsimplicity changes the benchmarks for the Core jets and thus changes these vectors) * Uncomments the commented-out symbols in simplicity-sys/src/c_jets/c_env/bitcoin.rs * Adds the "build bitcoin" lines to simplicity-sys/build.rs The update to the `Jet` trait is a bit noisy but ultimately mechanical: everywhere that we're generic over all J: Jet, we now also have to be generic over all T: Borrow, which leads to some extra line noise especially in unit tests where we have assert_* helper functions. The last four points are tiny diffs, thanks to the previous preparatory commits. The use of CoreEnv:: as the core environment type is kinda fun. It means that it is impossible to execute any code which attempts to access the transaction in the environment. (No such code exists, since it would be nonsensical, but now we have some assurance that it won't exist by accident in the future.) Unfortunately this mixes mechanical and non-mechanical things in one commit. But the mechanical changes are exclusively in simplicity-sys/depend/ and src/jet/init/ and the non-mechanical changes are exclusively outside of those files, so it should be possible to review this. --- simplicity-sys/build.rs | 32 ++++- .../depend/simplicity/CMakeLists.txt | 36 +++++ .../depend/simplicity/bitcoin/cmr.c | 22 +++ .../depend/simplicity/bitcoin/exec.c | 136 ++++++++++++++++++ simplicity-sys/depend/simplicity/cmr.c | 41 ++++++ simplicity-sys/depend/simplicity/cmr.h | 24 ++++ .../include/simplicity/bitcoin/cmr.h | 23 +++ .../include/simplicity/bitcoin/exec.h | 41 ++++++ simplicity-sys/src/c_jets/c_env/bitcoin.rs | 5 +- src/jet/bitcoin/environment.rs | 3 +- src/jet/bitcoin/mod.rs | 2 +- src/jet/core/environment.rs | 1 + 12 files changed, 352 insertions(+), 14 deletions(-) create mode 100644 simplicity-sys/depend/simplicity/CMakeLists.txt create mode 100644 simplicity-sys/depend/simplicity/bitcoin/cmr.c create mode 100644 simplicity-sys/depend/simplicity/bitcoin/exec.c create mode 100644 simplicity-sys/depend/simplicity/cmr.c create mode 100644 simplicity-sys/depend/simplicity/cmr.h create mode 100644 simplicity-sys/depend/simplicity/include/simplicity/bitcoin/cmr.h create mode 100644 simplicity-sys/depend/simplicity/include/simplicity/bitcoin/exec.h create mode 100644 src/jet/core/environment.rs diff --git a/simplicity-sys/build.rs b/simplicity-sys/build.rs index 4643be4a..7873473d 100644 --- a/simplicity-sys/build.rs +++ b/simplicity-sys/build.rs @@ -43,11 +43,17 @@ fn main() { ); } - // Split into Bitcoin and Elements. - let mut elements_files = files.clone(); - let mut bitcoin_files = files; + // Common files are compiled once into their own static library; Bitcoin + // and Elements each get their own library with only the chain-specific + // files. Duplicating the common files into both the Bitcoin and Elements + // libraries (as before) builds fine on stable (the linker only pulls the + // archive members it needs), but MSRV (1.74.0) forces `--whole-archive` + // on every native static lib, which pulls every object from both copies + // and fails with "multiple definition of" for every common symbol. + let common_files = files; // 3B. Bitcoin base files. + let mut bitcoin_files = vec![]; bitcoin_files.extend( [ "bitcoin/env.c", @@ -61,6 +67,7 @@ fn main() { bitcoin_files.push("depend/bitcoin_env.c".into()); // 3E. Elements base files. + let mut elements_files = vec![]; elements_files.extend( [ "elements/env.c", @@ -103,8 +110,6 @@ fn main() { .std("c11") .flag_if_supported("-fno-inline-functions") .opt_level(2) - .file(Path::new("depend/wrapper.c")) - .file(Path::new("depend/jets_wrapper.c")) .include(simplicity_path.join("include")); if cfg!(not(fuzzing)) { @@ -116,11 +121,24 @@ fn main() { build.include("wasm-sysroot"); } - let mut _bitcoin_build = build.clone(); + let mut common_build = build.clone(); + let mut bitcoin_build = build.clone(); let mut elements_build = build; + // Common build: files shared between Bitcoin and Elements, including the + // wrapper files. Compiled once into its own static library so neither + // the Bitcoin nor the Elements library contains these object files -- + // see the comment above `common_files` for why that matters. + common_build + .file(Path::new("depend/wrapper.c")) + .file(Path::new("depend/jets_wrapper.c")) + .files(common_files) + .compile("RustSimplicityCommon"); + // Bitcoin build - // TODO + bitcoin_build + .files(bitcoin_files) + .compile("BitcoinSimplicity"); // Elements build elements_build diff --git a/simplicity-sys/depend/simplicity/CMakeLists.txt b/simplicity-sys/depend/simplicity/CMakeLists.txt new file mode 100644 index 00000000..f1355bd0 --- /dev/null +++ b/simplicity-sys/depend/simplicity/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.16) + +project(BitcoinSimplicity) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_EXTENSIONS OFF) + +add_library(BitcoinSimplicity STATIC + bitstream.c + dag.c + deserialize.c + eval.c + frame.c + jets-secp256k1.c + jets.c + rsort.c + sha256.c + type.c + typeInference.c + bitcoin/env.c + bitcoin/exec.c + bitcoin/bitcoinJets.c + bitcoin/ops.c + bitcoin/primitive.c + bitcoin/txEnv.c +) + +option(PRODUCTION "Enable production build" ON) +if (PRODUCTION) + target_compile_definitions(BitcoinSimplicity PRIVATE "PRODUCTION") +endif() + +target_include_directories(BitcoinSimplicity PUBLIC + $ + $ + ) diff --git a/simplicity-sys/depend/simplicity/bitcoin/cmr.c b/simplicity-sys/depend/simplicity/bitcoin/cmr.c new file mode 100644 index 00000000..e230cd93 --- /dev/null +++ b/simplicity-sys/depend/simplicity/bitcoin/cmr.c @@ -0,0 +1,22 @@ +#include + +#include "../cmr.h" +#include "primitive.h" + +/* Deserialize a Simplicity 'program' and compute its CMR. + * + * Caution: no typechecking is performed, only a well-formedness check. + * + * If at any time malloc fails then '*error' is set to 'SIMPLICITY_ERR_MALLOC' and 'false' is returned, + * Otherwise, 'true' is returned indicating that the result was successfully computed and returned in the '*error' value. + * + * If the operation completes successfully then '*error' is set to 'SIMPLICITY_NO_ERROR', and the 'cmr' array is filled in with the program's computed CMR. + * + * Precondition: NULL != error; + * unsigned char cmr[32] + * unsigned char program[program_len] + */ +bool rustsimplicity_0_8_bitcoin_computeCmr( simplicity_err* error, unsigned char* cmr + , const unsigned char* program, size_t program_len) { + return rustsimplicity_0_8_computeCmr(error, cmr, rustsimplicity_0_8_bitcoin_decodeJet, program, program_len); +} diff --git a/simplicity-sys/depend/simplicity/bitcoin/exec.c b/simplicity-sys/depend/simplicity/bitcoin/exec.c new file mode 100644 index 00000000..bbe3f5a3 --- /dev/null +++ b/simplicity-sys/depend/simplicity/bitcoin/exec.c @@ -0,0 +1,136 @@ +#include + +#include +#include +#include "primitive.h" +#include "txEnv.h" +#include "../deserialize.h" +#include "../eval.h" +#include "../limitations.h" +#include "../simplicity_alloc.h" +#include "../simplicity_assert.h" +#include "../typeInference.h" + +/* Deserialize a Simplicity 'program' with its 'witness' data and execute it in the environment of the 'ix'th input of 'tx' with `taproot`. + * + * If at any time malloc fails then '*error' is set to 'SIMPLICITY_ERR_MALLOC' and 'false' is returned, + * meaning we were unable to determine the result of the simplicity program. + * Otherwise, 'true' is returned indicating that the result was successfully computed and returned in the '*error' value. + * + * If deserialization, analysis, or execution fails, then '*error' is set to some simplicity_err. + * In particular, if the cost analysis exceeds the budget, or exceeds BUDGET_MAX, then '*error' is set to 'SIMPLICITY_ERR_EXEC_BUDGET'. + * On the other hand, if the cost analysis is less than or equal to minCost, then '*error' is set to 'SIMPLICITY_ERR_OVERWEIGHT'. + * + * Note that minCost and budget parameters are in WU, while the cost analysis will be performed in milliWU. + * Thus the minCost and budget specify a half open interval (minCost, budget] of acceptable cost values in milliWU. + * Setting minCost to 0 effectively disables the minCost check as every Simplicity program has a non-zero cost analysis. + * + * If 'amr != NULL' and the annotated Merkle root of the decoded expression doesn't match 'amr' then '*error' is set to 'SIMPLICITY_ERR_AMR'. + * + * Otherwise '*error' is set to 'SIMPLICITY_NO_ERROR'. + * + * If 'ihr != NULL' and '*error' is set to 'SIMPLICITY_NO_ERROR', then the identity hash of the root of the decoded expression is written to 'ihr'. + * Otherwise if 'ihr != NULL' and '*error' is not set to 'SIMPLCITY_NO_ERROR', then 'ihr' may or may not be written to. + * + * Precondition: NULL != error; + * NULL != ihr implies unsigned char ihr[32] + * NULL != tx; + * NULL != taproot; + * 0 <= minCost <= budget; + * NULL != amr implies unsigned char amr[32] + * unsigned char program[program_len] + * unsigned char witness[witness_len] + */ +extern bool rustsimplicity_0_8_bitcoin_execSimplicity( simplicity_err* error, unsigned char* ihr + , const bitcoinTransaction* tx, uint_fast32_t ix, const bitcoinTapEnv* taproot + , int64_t minCost, int64_t budget + , const unsigned char* amr + , const unsigned char* program, size_t program_len + , const unsigned char* witness, size_t witness_len) { + rustsimplicity_0_8_assert(NULL != error); + rustsimplicity_0_8_assert(NULL != tx); + rustsimplicity_0_8_assert(NULL != taproot); + rustsimplicity_0_8_assert(0 <= minCost); + rustsimplicity_0_8_assert(minCost <= budget); + rustsimplicity_0_8_assert(NULL != program || 0 == program_len); + rustsimplicity_0_8_assert(NULL != witness || 0 == witness_len); + + combinator_counters census; + dag_node* dag = NULL; + int_fast32_t dag_len; + sha256_midstate amr_hash; + + if (amr) sha256_toMidstate(amr_hash.s, amr); + + { + bitstream stream = initializeBitstream(program, program_len); + dag_len = rustsimplicity_0_8_decodeMallocDag(&dag, rustsimplicity_0_8_bitcoin_decodeJet, &census, &stream); + if (dag_len <= 0) { + rustsimplicity_0_8_assert(dag_len < 0); + *error = (simplicity_err)dag_len; + return IS_PERMANENT(*error); + } + rustsimplicity_0_8_assert(NULL != dag); + rustsimplicity_0_8_assert((uint_fast32_t)dag_len <= DAG_LEN_MAX); + *error = rustsimplicity_0_8_closeBitstream(&stream); + } + + if (IS_OK(*error)) { + if (0 != memcmp(taproot->scriptCMR.s, dag[dag_len-1].cmr.s, sizeof(uint32_t[8]))) { + *error = SIMPLICITY_ERR_CMR; + } + } + + if (IS_OK(*error)) { + type* type_dag = NULL; + *error = rustsimplicity_0_8_mallocTypeInference(&type_dag, rustsimplicity_0_8_bitcoin_mallocBoundVars, dag, (uint_fast32_t)dag_len, &census); + if (IS_OK(*error)) { + rustsimplicity_0_8_assert(NULL != type_dag); + if (0 != dag[dag_len-1].sourceType || 0 != dag[dag_len-1].targetType) { + *error = SIMPLICITY_ERR_TYPE_INFERENCE_NOT_PROGRAM; + } + } + if (IS_OK(*error)) { + bitstream witness_stream = initializeBitstream(witness, witness_len); + *error = rustsimplicity_0_8_fillWitnessData(dag, type_dag, (uint_fast32_t)dag_len, &witness_stream); + if (IS_OK(*error)) { + *error = rustsimplicity_0_8_closeBitstream(&witness_stream); + if (SIMPLICITY_ERR_BITSTREAM_TRAILING_BYTES == *error) *error = SIMPLICITY_ERR_WITNESS_TRAILING_BYTES; + if (SIMPLICITY_ERR_BITSTREAM_ILLEGAL_PADDING == *error) *error = SIMPLICITY_ERR_WITNESS_ILLEGAL_PADDING; + } + } + if (IS_OK(*error)) { + sha256_midstate ihr_buf; + *error = rustsimplicity_0_8_verifyNoDuplicateIdentityHashes(&ihr_buf, dag, type_dag, (uint_fast32_t)dag_len); + if (IS_OK(*error) && ihr) sha256_fromMidstate(ihr, ihr_buf.s); + } + if (IS_OK(*error) && amr) { + static_assert(DAG_LEN_MAX <= SIZE_MAX / sizeof(analyses), "analysis array too large."); + static_assert(1 <= DAG_LEN_MAX, "DAG_LEN_MAX is zero."); + static_assert(DAG_LEN_MAX - 1 <= UINT32_MAX, "analysis array index does nto fit in uint32_t."); + analyses *analysis = rustsimplicity_0_8_malloc((size_t)dag_len * sizeof(analyses)); + if (analysis) { + rustsimplicity_0_8_computeAnnotatedMerkleRoot(analysis, dag, type_dag, (uint_fast32_t)dag_len); + if (0 != memcmp(amr_hash.s, analysis[dag_len-1].annotatedMerkleRoot.s, sizeof(uint32_t[8]))) { + *error = SIMPLICITY_ERR_AMR; + } + } else { + /* malloc failed which counts as a transient error. */ + *error = SIMPLICITY_ERR_MALLOC; + } + rustsimplicity_0_8_free(analysis); + } + if (IS_OK(*error)) { + txEnv env = rustsimplicity_0_8_bitcoin_build_txEnv(tx, taproot, ix); + static_assert(BUDGET_MAX <= UBOUNDED_MAX, "BUDGET_MAX doesn't fit in ubounded."); + *error = evalTCOProgram( dag, type_dag, (size_t)dag_len + , minCost <= BUDGET_MAX ? (ubounded)minCost : BUDGET_MAX + , &(ubounded){budget <= BUDGET_MAX ? (ubounded)budget : BUDGET_MAX} + , &env); + } + rustsimplicity_0_8_free(type_dag); + } + + rustsimplicity_0_8_free(dag); + return IS_PERMANENT(*error); +} diff --git a/simplicity-sys/depend/simplicity/cmr.c b/simplicity-sys/depend/simplicity/cmr.c new file mode 100644 index 00000000..eb7c2a39 --- /dev/null +++ b/simplicity-sys/depend/simplicity/cmr.c @@ -0,0 +1,41 @@ +#include "cmr.h" + +#include "limitations.h" +#include "simplicity_alloc.h" +#include "simplicity_assert.h" + +/* Deserialize a Simplicity 'program' and compute its CMR. + * + * Caution: no typechecking is performed, only a well-formedness check. + * + * If at any time malloc fails then '*error' is set to 'SIMPLICITY_ERR_MALLOC' and 'false' is returned, + * Otherwise, 'true' is returned indicating that the result was successfully computed and returned in the '*error' value. + * + * If the operation completes successfully then '*error' is set to 'SIMPLICITY_NO_ERROR', and the 'cmr' array is filled in with the program's computed CMR. + * + * Precondition: NULL != error; + * unsigned char cmr[32] + * unsigned char program[program_len] + */ +bool rustsimplicity_0_6_computeCmr( simplicity_err* error, unsigned char* cmr, rustsimplicity_0_6_callback_decodeJet decodeJet + , const unsigned char* program, size_t program_len) { + rustsimplicity_0_6_assert(NULL != error); + rustsimplicity_0_6_assert(NULL != cmr); + rustsimplicity_0_6_assert(NULL != program || 0 == program_len); + + bitstream stream = initializeBitstream(program, program_len); + dag_node* dag = NULL; + int_fast32_t dag_len = rustsimplicity_0_6_decodeMallocDag(&dag, decodeJet, NULL, &stream); + if (dag_len <= 0) { + rustsimplicity_0_6_assert(dag_len < 0); + *error = (simplicity_err)dag_len; + } else { + rustsimplicity_0_6_assert(NULL != dag); + rustsimplicity_0_6_assert((uint_fast32_t)dag_len <= DAG_LEN_MAX); + *error = rustsimplicity_0_6_closeBitstream(&stream); + sha256_fromMidstate(cmr, dag[dag_len-1].cmr.s); + } + + rustsimplicity_0_6_free(dag); + return IS_PERMANENT(*error); +} diff --git a/simplicity-sys/depend/simplicity/cmr.h b/simplicity-sys/depend/simplicity/cmr.h new file mode 100644 index 00000000..8e7762f6 --- /dev/null +++ b/simplicity-sys/depend/simplicity/cmr.h @@ -0,0 +1,24 @@ +#ifndef SIMPLICITY_CMR_H +#define SIMPLICITY_CMR_H + +#include +#include +#include +#include "deserialize.h" + +/* Deserialize a Simplicity 'program' and compute its CMR. + * + * Caution: no typechecking is performed, only a well-formedness check. + * + * If at any time malloc fails then '*error' is set to 'SIMPLICITY_ERR_MALLOC' and 'false' is returned, + * Otherwise, 'true' is returned indicating that the result was successfully computed and returned in the '*error' value. + * + * If the operation completes successfully then '*error' is set to 'SIMPLICITY_NO_ERROR', and the 'cmr' array is filled in with the program's computed CMR. + * + * Precondition: NULL != error; + * unsigned char cmr[32] + * unsigned char program[program_len] + */ +extern bool rustsimplicity_0_6_computeCmr( simplicity_err* error, unsigned char* cmr, rustsimplicity_0_6_callback_decodeJet decodeJet + , const unsigned char* program, size_t program_len); +#endif diff --git a/simplicity-sys/depend/simplicity/include/simplicity/bitcoin/cmr.h b/simplicity-sys/depend/simplicity/include/simplicity/bitcoin/cmr.h new file mode 100644 index 00000000..4611f7b4 --- /dev/null +++ b/simplicity-sys/depend/simplicity/include/simplicity/bitcoin/cmr.h @@ -0,0 +1,23 @@ +#ifndef SIMPLICITY_BITCOIN_CMR_H +#define SIMPLICITY_BITCOIN_CMR_H + +#include +#include +#include + +/* Deserialize a Simplicity 'program' and compute its CMR. + * + * Caution: no typechecking is performed, only a well-formedness check. + * + * If at any time malloc fails then '*error' is set to 'SIMPLICITY_ERR_MALLOC' and 'false' is returned, + * Otherwise, 'true' is returned indicating that the result was successfully computed and returned in the '*error' value. + * + * If the operation completes successfully then '*error' is set to 'SIMPLICITY_NO_ERROR', and the 'cmr' array is filled in with the program's computed CMR. + * + * Precondition: NULL != error; + * unsigned char cmr[32] + * unsigned char program[program_len] + */ +extern bool rustsimplicity_0_6_bitcoin_computeCmr( simplicity_err* error, unsigned char* cmr + , const unsigned char* program, size_t program_len); +#endif diff --git a/simplicity-sys/depend/simplicity/include/simplicity/bitcoin/exec.h b/simplicity-sys/depend/simplicity/include/simplicity/bitcoin/exec.h new file mode 100644 index 00000000..74fd3b1c --- /dev/null +++ b/simplicity-sys/depend/simplicity/include/simplicity/bitcoin/exec.h @@ -0,0 +1,41 @@ +#ifndef SIMPLICITY_BITCOIN_EXEC_H +#define SIMPLICITY_BITCOIN_EXEC_H + +#include +#include +#include +#include +#include + +/* Deserialize a Simplicity 'program' with its 'witness' data and execute it in the environment of the 'ix'th input of 'tx' with `taproot`. + * + * If at any time malloc fails then '*error' is set to 'SIMPLICITY_ERR_MALLOC' and 'false' is returned, + * meaning we were unable to determine the result of the simplicity program. + * Otherwise, 'true' is returned indicating that the result was successfully computed and returned in the '*error' value. + * + * If deserialization, analysis, or execution fails, then '*error' is set to some simplicity_err. + * + * If 'amr != NULL' and the annotated Merkle root of the decoded expression doesn't match 'amr' then '*error' is set to 'SIMPLICITY_ERR_AMR'. + * + * Otherwise '*error' is set to 'SIMPLICITY_NO_ERROR'. + * + * If 'ihr != NULL' and '*error' is set to 'SIMPLICITY_NO_ERROR', then the identity hash of the root of the decoded expression is written to 'ihr'. + * Otherwise if 'ihr != NULL' and '*error' is not set to 'SIMPLCITY_NO_ERROR', then 'ihr' may or may not be written to. + * + * Precondition: NULL != error; + * NULL != ihr implies unsigned char ihr[32] + * NULL != tx; + * NULL != taproot; + * 0 <= budget; + * 0 <= minCost <= budget; + * NULL != amr implies unsigned char amr[32] + * unsigned char program[program_len] + * unsigned char witness[witness_len] + */ +extern bool rustsimplicity_0_6_bitcoin_execSimplicity( simplicity_err* error, unsigned char* ihr + , const bitcoinTransaction* tx, uint_fast32_t ix, const bitcoinTapEnv* taproot + , int64_t minCost, int64_t budget + , const unsigned char* amr + , const unsigned char* program, size_t program_len + , const unsigned char* witness, size_t witness_len); +#endif diff --git a/simplicity-sys/src/c_jets/c_env/bitcoin.rs b/simplicity-sys/src/c_jets/c_env/bitcoin.rs index dfb3e8a8..b1517268 100644 --- a/simplicity-sys/src/c_jets/c_env/bitcoin.rs +++ b/simplicity-sys/src/c_jets/c_env/bitcoin.rs @@ -68,7 +68,6 @@ pub struct CTapEnv { _data: (), } -/* // Will uncomment in a later commit; need to update libsimplicity first so these // symbols have something to link against. extern "C" { @@ -110,7 +109,7 @@ extern "C" { #[link_name = "rustsimplicity_0_8_bitcoin_mallocTransaction"] pub fn simplicity_mallocTransaction(rawTx: *const CRawTransaction) -> *mut CTransaction; } -*/ + impl CTxEnv { pub fn sighash_all(&self) -> sha256::Hash { let midstate: sha256::Midstate = self.sighash_all.into(); @@ -137,7 +136,6 @@ impl CRawBuffer { } } -/* // Will uncomment in a later commit; need to update libsimplicity first. #[cfg(test)] mod tests { @@ -173,4 +171,3 @@ mod tests { } } } -*/ diff --git a/src/jet/bitcoin/environment.rs b/src/jet/bitcoin/environment.rs index 22c9f85a..ba0eae40 100644 --- a/src/jet/bitcoin/environment.rs +++ b/src/jet/bitcoin/environment.rs @@ -3,8 +3,7 @@ use simplicity_sys::c_jets::c_env::bitcoin as c_bitcoin; /// Environment for Bitcoin Simplicity -// In later commit, when we update Jet trait, will remove default type. -pub struct BitcoinEnv { +pub struct BitcoinEnv { pub tx: T, } diff --git a/src/jet/bitcoin/mod.rs b/src/jet/bitcoin/mod.rs index d4faa7ae..357908ff 100644 --- a/src/jet/bitcoin/mod.rs +++ b/src/jet/bitcoin/mod.rs @@ -8,7 +8,7 @@ use super::init::bitcoin::Bitcoin; use super::JetEnvironment; use simplicity_sys::c_jets::frame_ffi::CFrameItem; -impl JetEnvironment for BitcoinEnv { +impl> JetEnvironment for BitcoinEnv { type Jet = Bitcoin; type CJetEnvironment = (); diff --git a/src/jet/core/environment.rs b/src/jet/core/environment.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/jet/core/environment.rs @@ -0,0 +1 @@ + From 179a9c516b2a673499f69a86637b109d70b8ce37 Mon Sep 17 00:00:00 2001 From: Byron Hambly Date: Mon, 7 Sep 2026 14:40:12 +0200 Subject: [PATCH 06/10] jet: generalize JetEnvironment impls to use Borrow Change the JetEnvironment impl for BitcoinEnv and ElementsEnv to be generic over T: Borrow instead of fixed to Arc. Update Policy::satisfy, get_satisfier, execute_successful, execute_unsuccessful, and serialize test helpers to accept ElementsEnv>. This allows callers to construct environments without Arc overhead (for example, using a plain reference or owned value), and is required so that BitcoinEnv::c_jet_env can return a real CTxEnv from a borrowed transaction. --- src/jet/elements/mod.rs | 2 +- src/policy/satisfy.rs | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/jet/elements/mod.rs b/src/jet/elements/mod.rs index c36ed73a..35a4ae33 100644 --- a/src/jet/elements/mod.rs +++ b/src/jet/elements/mod.rs @@ -15,7 +15,7 @@ use simplicity_sys::CElementsTxEnv; /// Type alias for the Elements transaction environment. pub type ElementsTxEnv = ElementsEnv>; -impl JetEnvironment for ElementsTxEnv { +impl> JetEnvironment for ElementsEnv { type Jet = Elements; type CJetEnvironment = CElementsTxEnv; diff --git a/src/policy/satisfy.rs b/src/policy/satisfy.rs index 534c5b5a..52c0713e 100644 --- a/src/policy/satisfy.rs +++ b/src/policy/satisfy.rs @@ -268,7 +268,7 @@ impl Policy { pub fn satisfy<'brand, S: Satisfier<'brand, Pk>>( &self, satisfier: &S, - env: &ElementsEnv>, + env: &ElementsEnv>, ) -> Result, SatisfierError> { let result = self.satisfy_internal(satisfier)?; match result.into_node() { @@ -332,10 +332,9 @@ mod tests { } } - fn get_satisfier<'tx, 'brand>( + fn get_satisfier<'tx, 'brand, T: core::borrow::Borrow>( context: types::Context<'brand>, - - env: &'tx ElementsEnv>, + env: &'tx ElementsEnv, ) -> PolicySatisfier<'tx, 'brand, XOnlyPublicKey> { let mut preimages = HashMap::new(); @@ -372,14 +371,17 @@ mod tests { } } - fn execute_successful(program: Arc, env: &ElementsEnv>) { + fn execute_successful>( + program: Arc, + env: &ElementsEnv, + ) { let mut mac = BitMachine::for_program(&program).unwrap(); assert!(mac.exec(&program, env).is_ok()); } - fn execute_unsuccessful( + fn execute_unsuccessful>( program: Arc, - env: &ElementsEnv>, + env: &ElementsEnv, ) { let mut mac = BitMachine::for_program(&program).unwrap(); assert!(mac.exec(&program, env).is_err()); From 1dfdf0f4c151fdd0ec77f7aed6dfb9ec50bfd2b0 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 11 Sep 2025 16:38:08 +0000 Subject: [PATCH 07/10] jet: drop unnecessary Arcs in environments Now that we've updated the Jet trait to allow the transactions in environments to be arbitrary T: Borrow, we don't need to use Arc everywhere. In many cases we can use normal references. --- src/human_encoding/parse/mod.rs | 5 ++--- src/jet/elements/environment.rs | 6 +++--- src/node/redeem.rs | 10 +++++----- src/policy/serialize.rs | 4 ++-- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/human_encoding/parse/mod.rs b/src/human_encoding/parse/mod.rs index 64045a77..d42d7647 100644 --- a/src/human_encoding/parse/mod.rs +++ b/src/human_encoding/parse/mod.rs @@ -713,11 +713,10 @@ mod tests { #[cfg(feature = "elements")] fn bip340_program() { use crate::jet::elements::ElementsEnv; - use crate::jet::ElementsTxEnv; let empty = HashMap::new(); let dummy = ElementsEnv::dummy(); - assert_cmr_witness::( + assert_cmr_witness::>( "main := unit", "c40a10263f7436b4160acbef1c36fba4be4d95df181a968afeab5eac247adff7", &empty, @@ -734,7 +733,7 @@ mod tests { ]; let signature = HashMap::from([(Arc::from("wit1"), Value::u512(sig))]); - assert_cmr_witness::( + assert_cmr_witness::>( " -- Witnesses wit1 := witness : 1 -> 2^512 diff --git a/src/jet/elements/environment.rs b/src/jet/elements/environment.rs index 812bf388..e022753c 100644 --- a/src/jet/elements/environment.rs +++ b/src/jet/elements/environment.rs @@ -110,7 +110,7 @@ where } #[cfg(test)] -impl ElementsEnv> { +impl ElementsEnv { /// Return a dummy Elements environment pub fn dummy() -> Self { Self::dummy_with(elements::LockTime::ZERO, elements::Sequence::MAX) @@ -127,7 +127,7 @@ impl ElementsEnv> { ]; ElementsEnv::new( - std::sync::Arc::new(elements::Transaction { + elements::Transaction { version: 2, lock_time, // Enable locktime in dummy txin @@ -140,7 +140,7 @@ impl ElementsEnv> { witness: elements::TxInWitness::default(), }], output: Vec::default(), - }), + }, vec![ElementsUtxo { script_pubkey: elements::Script::new(), asset: confidential::Asset::Null, diff --git a/src/node/redeem.rs b/src/node/redeem.rs index c8748217..89202b5d 100644 --- a/src/node/redeem.rs +++ b/src/node/redeem.rs @@ -1050,7 +1050,7 @@ mod tests { #[test] #[cfg(all(feature = "elements", feature = "human_encoding"))] fn prune() { - use crate::jet::ElementsTxEnv; + use crate::jet::elements::ElementsEnv; let env = crate::jet::elements::ElementsEnv::dummy(); @@ -1083,7 +1083,7 @@ main := comp input comp process jet_verify : 1 -> 1"#; Value::product(Value::u64(0), Value::unit()), ), ]); - assert_correct_pruning::( + assert_correct_pruning::>( unpruned_prog, &unpruned_wit, pruned_prog, @@ -1111,7 +1111,7 @@ main := comp input comp process jet_verify : 1 -> 1"#; Value::product(Value::unit(), Value::u64(0)), ), ]); - assert_correct_pruning::( + assert_correct_pruning::>( unpruned_prog, &unpruned_wit, pruned_prog, @@ -1136,7 +1136,7 @@ process := assertl (take jet_is_zero_64) #{take jet_is_zero_64} : (2^64 + 1) * 1 main := comp input comp process jet_verify : 1 -> 1"#; let pruned_wit = HashMap::from([(Arc::from("wit1"), Value::left(Value::u64(0), Final::unit()))]); - assert_correct_pruning::( + assert_correct_pruning::>( prune_sum, &unpruned_wit, pruned_prog, @@ -1157,7 +1157,7 @@ main := comp input comp process jet_verify : 1 -> 1"#; Arc::from("wit1"), Value::right(Final::unit(), Value::u64(0)), )]); - assert_correct_pruning::( + assert_correct_pruning::>( prune_sum, &unpruned_wit, pruned_prog, diff --git a/src/policy/serialize.rs b/src/policy/serialize.rs index b56ca453..ae5c376a 100644 --- a/src/policy/serialize.rs +++ b/src/policy/serialize.rs @@ -257,7 +257,7 @@ mod tests { fn compile( policy: Policy, - ) -> (Arc, ElementsEnv>) { + ) -> (Arc, ElementsEnv) { let commit = policy.commit(); let env = ElementsEnv::dummy(); @@ -267,7 +267,7 @@ mod tests { fn execute_successful( commit: &CommitNode, witness: Vec, - env: &ElementsEnv>, + env: &ElementsEnv, ) -> bool { let finalized = commit .finalize(&mut SimpleFinalizer::new(witness.into_iter())) From e85d8f576c82c8058ff9e346d9cc4973f3f02456 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 1 Oct 2025 23:38:11 +0000 Subject: [PATCH 08/10] jet: implement Rust bitcoin environment This is a Rust type which can be used, among other things, to construct the transaction environment needed by C jets. Also provides accessors for the underlying transaction and input index, both of which are used (in the Elements version of this struct) in the policy satisfier. --- src/jet/bitcoin/c_env.rs | 84 ++++++++++++++++++++++++++++++++++ src/jet/bitcoin/environment.rs | 37 +++++++++++++-- src/jet/bitcoin/mod.rs | 1 + 3 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 src/jet/bitcoin/c_env.rs diff --git a/src/jet/bitcoin/c_env.rs b/src/jet/bitcoin/c_env.rs new file mode 100644 index 00000000..5dd27a7c --- /dev/null +++ b/src/jet/bitcoin/c_env.rs @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: CC0-1.0 + +use crate::Cmr; + +use bitcoin::hashes::Hash as _; +use bitcoin::taproot::ControlBlock; +use simplicity_sys::c_jets::c_env::bitcoin as c_bitcoin; + +pub(super) fn new_tx( + tx: &bitcoin::Transaction, + in_utxos: &[bitcoin::TxOut], +) -> *mut c_bitcoin::CTransaction { + let mut raw_inputs = Vec::with_capacity(tx.input.len()); + let mut raw_outputs = Vec::with_capacity(tx.output.len()); + + for (inp, utxo) in tx.input.iter().zip(in_utxos.iter()) { + raw_inputs.push(c_bitcoin::CRawInput { + // FIXME actually pass the annex in; see https://github.com/BlockstreamResearch/simplicity/issues/311 for some difficulty here. + annex: core::ptr::null(), + prev_txid: inp.previous_output.txid.as_byte_array(), + txo: c_bitcoin::CRawOutput { + value: utxo.value.to_sat(), + script_pubkey: c_bitcoin::CRawBuffer::new(utxo.script_pubkey.as_bytes()), + }, + script_sig: c_bitcoin::CRawBuffer::new(inp.script_sig.as_bytes()), + prev_txout_index: inp.previous_output.vout, + sequence: inp.sequence.to_consensus_u32(), + }); + } + for out in tx.output.iter() { + raw_outputs.push(c_bitcoin::CRawOutput { + value: out.value.to_sat(), + script_pubkey: c_bitcoin::CRawBuffer::new(out.script_pubkey.as_bytes()), + }); + } + let txid = tx.compute_txid(); + + let c_raw_tx = c_bitcoin::CRawTransaction { + txid: txid.as_byte_array(), + inputs: raw_inputs.as_ptr(), + outputs: raw_outputs.as_ptr(), + n_inputs: raw_inputs.len().try_into().expect("sane length"), + n_outputs: raw_outputs.len().try_into().expect("sane length"), + version: tx.version.0 as u32, // in 1.87.0 can use .cast_unsigned + locktime: tx.lock_time.to_consensus_u32(), + }; + unsafe { + // SAFETY: this is a FFI call and we constructed its argument correctly. + c_bitcoin::simplicity_mallocTransaction(&c_raw_tx) + } +} + +pub(super) fn new_tap_env( + control_block: &ControlBlock, + script_cmr: Cmr, +) -> *mut c_bitcoin::CTapEnv { + let cb_ser = control_block.serialize(); + let raw_tap_env = c_bitcoin::CRawTapEnv { + control_block: cb_ser.as_ptr(), + script_cmr: script_cmr.as_ref().as_ptr(), + branch_len: control_block + .merkle_branch + .len() + .try_into() + .expect("sane length"), + }; + + unsafe { + // SAFETY: this is a FFI call and we constructed its argument correctly. + c_bitcoin::simplicity_mallocTapEnv(&raw_tap_env) + } +} + +pub(super) fn new_tx_env( + tx: *const c_bitcoin::CTransaction, + taproot: *const c_bitcoin::CTapEnv, + ix: u32, +) -> c_bitcoin::CTxEnv { + unsafe { + let mut tx_env = std::mem::MaybeUninit::::uninit(); + c_bitcoin::c_set_txEnv(tx_env.as_mut_ptr(), tx, taproot, ix); + tx_env.assume_init() + } +} diff --git a/src/jet/bitcoin/environment.rs b/src/jet/bitcoin/environment.rs index ba0eae40..2980ca18 100644 --- a/src/jet/bitcoin/environment.rs +++ b/src/jet/bitcoin/environment.rs @@ -1,21 +1,50 @@ // SPDX-License-Identifier: CC0-1.0 +use crate::Cmr; + +use bitcoin::taproot::ControlBlock; use simplicity_sys::c_jets::c_env::bitcoin as c_bitcoin; +use super::c_env; + /// Environment for Bitcoin Simplicity pub struct BitcoinEnv { - pub tx: T, + /// The CTxEnv struct + c_tx_env: c_bitcoin::CTxEnv, + tx: T, + ix: u32, } impl BitcoinEnv where T: core::borrow::Borrow, { - pub fn new(tx: T) -> Self { - BitcoinEnv { tx } + pub fn new( + tx: T, + utxos: &[bitcoin::TxOut], + ix: u32, + script_cmr: Cmr, + control_block: ControlBlock, + ) -> Self { + let c_tx = c_env::new_tx(tx.borrow(), utxos); + let c_tap_env = c_env::new_tap_env(&control_block, script_cmr); + let c_tx_env = c_env::new_tx_env(c_tx, c_tap_env, ix); + + BitcoinEnv { c_tx_env, tx, ix } + } + + /// The transaction of this environment + pub fn tx(&self) -> &bitcoin::Transaction { + self.tx.borrow() + } + + /// The input index of this environment + pub fn ix(&self) -> u32 { + self.ix } + /// A version of this environment used by the C implementation of the Bit Machine. pub fn c_tx_env(&self) -> &c_bitcoin::CTxEnv { - unimplemented!() + &self.c_tx_env } } diff --git a/src/jet/bitcoin/mod.rs b/src/jet/bitcoin/mod.rs index 357908ff..61b56565 100644 --- a/src/jet/bitcoin/mod.rs +++ b/src/jet/bitcoin/mod.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: CC0-1.0 +mod c_env; mod environment; pub use environment::BitcoinEnv; From cae2c23c63756f9d4db0de99b80f80157545f417 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 18 Dec 2025 18:43:48 +0000 Subject: [PATCH 09/10] jet: populate annexes in Bitcoin environment --- src/jet/bitcoin/c_env.rs | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/jet/bitcoin/c_env.rs b/src/jet/bitcoin/c_env.rs index 5dd27a7c..1b01d9da 100644 --- a/src/jet/bitcoin/c_env.rs +++ b/src/jet/bitcoin/c_env.rs @@ -12,11 +12,24 @@ pub(super) fn new_tx( ) -> *mut c_bitcoin::CTransaction { let mut raw_inputs = Vec::with_capacity(tx.input.len()); let mut raw_outputs = Vec::with_capacity(tx.output.len()); + // Allocate space for the raw annexes. This dumb `Vec::from_iter` construction is + // equivalent to `vec![None; tx.input.len()]`, but that won't compile because it + // requires Option::::None to be cloneable, which it's not because + // CRawBuffer isn't. - for (inp, utxo) in tx.input.iter().zip(in_utxos.iter()) { + // SAFETY: this allocation *must* live until after the `simplicity_mallocTransaction` + // at the bottom of this function. We convert the vector to a boxed slice to ensure + // it cannot be resized, which would potentially trigger a reallocation. + let mut raw_annexes = Vec::from_iter((0..tx.input.len()).map(|_| None)).into_boxed_slice(); + + for (n, (inp, utxo)) in tx.input.iter().zip(in_utxos.iter()).enumerate() { + raw_annexes[n] = inp.witness.taproot_annex().map(c_bitcoin::CRawBuffer::new); raw_inputs.push(c_bitcoin::CRawInput { - // FIXME actually pass the annex in; see https://github.com/BlockstreamResearch/simplicity/issues/311 for some difficulty here. - annex: core::ptr::null(), + // This `as_ref().map_or()` construction converts an Option<&T> to a nullable *const T. + // In theory it's a no-op. + annex: raw_annexes[n] + .as_ref() + .map_or(core::ptr::null(), |ptr| ptr as *const _), // cast should be changed to ptr::from_ref in rust 1.76 prev_txid: inp.previous_output.txid.as_byte_array(), txo: c_bitcoin::CRawOutput { value: utxo.value.to_sat(), @@ -44,10 +57,16 @@ pub(super) fn new_tx( version: tx.version.0 as u32, // in 1.87.0 can use .cast_unsigned locktime: tx.lock_time.to_consensus_u32(), }; - unsafe { + let ret = unsafe { // SAFETY: this is a FFI call and we constructed its argument correctly. c_bitcoin::simplicity_mallocTransaction(&c_raw_tx) - } + }; + // Explicitly drop raw_annexes so Rust doesn't try any funny business dropping it early. + // Drop raw_inputs first since it contains pointers into raw_annexes and we don't want + // them to dangle. (It'd be safe since they're raw pointers, but still bad mojo.) + drop(raw_inputs); + drop(raw_annexes); + ret } pub(super) fn new_tap_env( From 1f91d94d93c6db5ea845d003c902920b55105d99 Mon Sep 17 00:00:00 2001 From: Byron Hambly Date: Mon, 7 Sep 2026 14:40:13 +0200 Subject: [PATCH 10/10] bitcoin: extract annex per BIP341 instead of Witness::taproot_annex rust-bitcoin 0.32 removed Witness::taproot_annex. Replace it with an inline BIP341 annex extraction: the last witness element that begins with 0x50 is the annex. This matches the Elements c_env helper and the BIP341 specification. --- src/jet/bitcoin/c_env.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/jet/bitcoin/c_env.rs b/src/jet/bitcoin/c_env.rs index 1b01d9da..53494d91 100644 --- a/src/jet/bitcoin/c_env.rs +++ b/src/jet/bitcoin/c_env.rs @@ -3,7 +3,7 @@ use crate::Cmr; use bitcoin::hashes::Hash as _; -use bitcoin::taproot::ControlBlock; +use bitcoin::taproot::{ControlBlock, TAPROOT_ANNEX_PREFIX}; use simplicity_sys::c_jets::c_env::bitcoin as c_bitcoin; pub(super) fn new_tx( @@ -23,7 +23,7 @@ pub(super) fn new_tx( let mut raw_annexes = Vec::from_iter((0..tx.input.len()).map(|_| None)).into_boxed_slice(); for (n, (inp, utxo)) in tx.input.iter().zip(in_utxos.iter()).enumerate() { - raw_annexes[n] = inp.witness.taproot_annex().map(c_bitcoin::CRawBuffer::new); + raw_annexes[n] = get_annex(&inp.witness).map(c_bitcoin::CRawBuffer::new); raw_inputs.push(c_bitcoin::CRawInput { // This `as_ref().map_or()` construction converts an Option<&T> to a nullable *const T. // In theory it's a no-op. @@ -101,3 +101,15 @@ pub(super) fn new_tx_env( tx_env.assume_init() } } + +/// Extracts the annex from a taproot witness stack per BIP341: if there are at +/// least two witness elements and the last one starts with 0x50, it is the annex. +/// (rust-bitcoin 0.32 removed `Witness::taproot_annex`.) +fn get_annex(in_witness: &bitcoin::Witness) -> Option<&[u8]> { + let last_item = in_witness.last()?; + if *last_item.first()? == TAPROOT_ANNEX_PREFIX { + Some(last_item) + } else { + None + } +}