From 78568e63fff01af587da04916af3f79e485e7c98 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 3 Sep 2025 19:53:44 +0000 Subject: [PATCH 01/13] simplicity_sys: rename c_env.rs to c_env/mod.rs --- simplicity-sys/src/c_jets/{c_env.rs => c_env/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename simplicity-sys/src/c_jets/{c_env.rs => c_env/mod.rs} (100%) diff --git a/simplicity-sys/src/c_jets/c_env.rs b/simplicity-sys/src/c_jets/c_env/mod.rs similarity index 100% rename from simplicity-sys/src/c_jets/c_env.rs rename to simplicity-sys/src/c_jets/c_env/mod.rs From a925cd9663c8952c11c42deaf0dc3838a09eabf9 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 3 Sep 2025 19:07:18 +0000 Subject: [PATCH 02/13] simplicity-sys: move all the elements FFI stuff into its own module ...and remove Elements and _elements from the names (where they occured). --- simplicity-sys/src/c_jets/c_env/elements.rs | 281 +++++++++++++++++++ simplicity-sys/src/c_jets/c_env/mod.rs | 295 +------------------- simplicity-sys/src/c_jets/mod.rs | 35 +-- simplicity-sys/src/lib.rs | 6 +- simplicity-sys/src/tests/ffi.rs | 6 +- src/jet/elements/c_env.rs | 86 +++--- src/jet/elements/environment.rs | 6 +- 7 files changed, 355 insertions(+), 360 deletions(-) create mode 100644 simplicity-sys/src/c_jets/c_env/elements.rs diff --git a/simplicity-sys/src/c_jets/c_env/elements.rs b/simplicity-sys/src/c_jets/c_env/elements.rs new file mode 100644 index 00000000..b5581dc6 --- /dev/null +++ b/simplicity-sys/src/c_jets/c_env/elements.rs @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: CC0-1.0 + +use hashes::{sha256, Hash}; + +use crate::ffi::sha256::CSha256Midstate; +use crate::ffi::{c_size_t, c_uchar, c_uint, c_uint_fast32_t}; + +/// Documentation of RawInputData/RawOutputData/RawTapData/Raw. +/// +/// Data structure for holding data that CTransaction points to. +/// +/// Why do we this special data structure? +/// 1. We need to keep the data in memory until the CTransaction is dropped. +/// 2. The memory is Transaction is not saved in the same format as required by FFI. +/// We use more ergonomics in rust to allow better UX which interfere with the FFI. For example, +/// the Value is stored as Tagged Union, but we require it to be a slice of bytes in elements format. +/// 3. Allocating inside FFI functions does not work because the memory is freed after the function returns. +/// 4. We only create allocations for data fields that are stored differently from the +/// consensus serialization format. +#[derive(Debug)] +pub struct RawOutputData { + pub asset: Vec, + pub value: Vec, + pub nonce: Vec, + pub surjection_proof: Vec, + pub range_proof: Vec, +} + +#[derive(Debug)] +#[repr(C)] +pub struct CRawBuffer { + pub ptr: *const c_uchar, + pub len: u32, +} + +/// Similar to [`RawOutputData`], for inputs. +#[derive(Debug)] +pub struct RawInputData { + pub annex: Option>, + // issuance + pub issuance_amount: Vec, + pub issuance_inflation_keys: Vec, + pub amount_range_proof: Vec, + pub inflation_keys_range_proof: Vec, + // spent txo + pub asset: Vec, + pub value: Vec, +} + +/// Similar to [`RawOutputData`], but for transaction +#[derive(Debug)] +pub struct RawTransactionData { + pub inputs: Vec, + pub outputs: Vec, +} + +#[derive(Debug)] +#[repr(C)] +pub struct CRawOutput { + asset: *const c_uchar, + value: *const c_uchar, + nonce: *const c_uchar, + script_pubkey: CRawBuffer, + surjection_proof: CRawBuffer, + range_proof: CRawBuffer, +} + +#[derive(Debug)] +#[repr(C)] +pub struct CRawInput { + annex: *const CRawBuffer, + prev_txid: *const c_uchar, + pegin: *const c_uchar, + // issuance + blinding_nonce: *const c_uchar, + asset_entropy: *const c_uchar, + amount: *const c_uchar, + inflation_keys: *const c_uchar, + amount_range_proof: CRawBuffer, + inflation_keys_range_proof: CRawBuffer, + // spent txo + asset: *const c_uchar, + value: *const c_uchar, + script_pubkey: CRawBuffer, + // inputs + script_sig: CRawBuffer, + prev_txout_index: u32, + sequence: u32, +} + +#[derive(Debug)] +#[repr(C)] +pub struct CRawTransaction { + txid: *const c_uchar, + inputs: *const CRawInput, + outputs: *const CRawOutput, + version: u32, + locktime: u32, + n_inputs: u32, + n_outputs: u32, +} + +#[derive(Debug)] +#[repr(C)] +pub struct CRawTapEnv { + control_block: *const c_uchar, + script_cmr: *const c_uchar, + branch_len: u8, +} + +#[derive(Debug)] +pub enum CTransaction {} + +#[derive(Debug)] +#[repr(C)] +pub struct CTxEnv { + tx: *const CTransaction, + taproot: *const CTapEnv, + genesis_hash: CSha256Midstate, + sighash_all: CSha256Midstate, + ix: c_uint_fast32_t, +} + +#[derive(Debug)] +pub enum CTapEnv {} + +extern "C" { + #[link_name = "rustsimplicity_0_5_c_sizeof_rawElementsBuffer"] + pub static c_sizeof_rawBuffer: c_size_t; + #[link_name = "rustsimplicity_0_5_c_sizeof_rawElementsOutput"] + pub static c_sizeof_rawOutput: c_size_t; + #[link_name = "rustsimplicity_0_5_c_sizeof_rawElementsInput"] + pub static c_sizeof_rawInput: c_size_t; + #[link_name = "rustsimplicity_0_5_c_sizeof_rawElementsTransaction"] + pub static c_sizeof_rawTransaction: c_size_t; + #[link_name = "rustsimplicity_0_5_c_sizeof_rawElementsTapEnv"] + pub static c_sizeof_rawTapEnv: c_size_t; + #[link_name = "rustsimplicity_0_5_c_sizeof_txEnv"] + pub static c_sizeof_txEnv: c_size_t; + + #[link_name = "rustsimplicity_0_5_c_alignof_rawElementsBuffer"] + pub static c_alignof_rawBuffer: c_size_t; + #[link_name = "rustsimplicity_0_5_c_alignof_rawElementsOutput"] + pub static c_alignof_rawOutput: c_size_t; + #[link_name = "rustsimplicity_0_5_c_alignof_rawElementsInput"] + pub static c_alignof_rawInput: c_size_t; + #[link_name = "rustsimplicity_0_5_c_alignof_rawElementsTransaction"] + pub static c_alignof_rawTransaction: c_size_t; + #[link_name = "rustsimplicity_0_5_c_alignof_rawElementsTapEnv"] + pub static c_alignof_rawTapEnv: c_size_t; + #[link_name = "rustsimplicity_0_5_c_alignof_txEnv"] + pub static c_alignof_txEnv: c_size_t; + + #[link_name = "rustsimplicity_0_5_c_set_rawElementsBuffer"] + pub fn c_set_rawBuffer(res: *mut CRawBuffer, buf: *const c_uchar, len: c_uint); + #[link_name = "rustsimplicity_0_5_c_set_rawElementsOutput"] + pub fn c_set_rawOutput( + res: *mut CRawOutput, + asset: *const c_uchar, + value: *const c_uchar, + nonce: *const c_uchar, + scriptPubKey: *const CRawBuffer, + surjectionProof: *const CRawBuffer, + rangeProof: *const CRawBuffer, + ); + #[link_name = "rustsimplicity_0_5_c_set_rawElementsInput"] + pub fn c_set_rawInput( + result: *mut CRawInput, + annex: *const CRawBuffer, + pegin: *const c_uchar, + scriptSig: *const CRawBuffer, + prevTxid: *const c_uchar, + prevIx: c_uint, + asset: *const c_uchar, + value: *const c_uchar, + scriptPubKey: *const CRawBuffer, + sequence: c_uint, + blindingNonce: *const c_uchar, + assetEntropy: *const c_uchar, + amount: *const c_uchar, + inflationKeys: *const c_uchar, + amountRangePrf: *const CRawBuffer, + inflationKeysRangePrf: *const CRawBuffer, + ); + + #[link_name = "rustsimplicity_0_5_c_set_rawElementsTransaction"] + pub fn c_set_rawTransaction( + result: *mut CRawTransaction, + version: c_uint, + txid: *const c_uchar, + input: *const CRawInput, + numInputs: c_uint, + output: *const CRawOutput, + numOutputs: c_uint, + lockTime: c_uint, + ); + #[link_name = "rustsimplicity_0_5_c_set_rawElementsTapEnv"] + pub fn c_set_rawTapEnv( + result: *mut CRawTapEnv, + controlBlock: *const c_uchar, + pathLen: c_uchar, + scriptCMR: *const c_uchar, + ); + #[link_name = "rustsimplicity_0_5_c_set_txEnv"] + pub fn c_set_txEnv( + result: *mut CTxEnv, + tx: *const CTransaction, + taproot: *const CTapEnv, + genesisHash: *const c_uchar, + ix: c_uint, + ); + #[link_name = "rustsimplicity_0_5_elements_mallocTapEnv"] + pub fn simplicity_mallocTapEnv(rawEnv: *const CRawTapEnv) -> *mut CTapEnv; + #[link_name = "rustsimplicity_0_5_elements_mallocTransaction"] + pub fn simplicity_mallocTransaction(rawTx: *const CRawTransaction) -> *mut CTransaction; + #[link_name = "rustsimplicity_0_5_c_free_transaction"] + pub fn c_free_transaction(tx: *mut CTransaction); + #[link_name = "rustsimplicity_0_5_c_free_tapEnv"] + pub fn c_free_tapEnv(env: *mut CTapEnv); +} +impl CTxEnv { + pub fn sighash_all(&self) -> sha256::Hash { + let midstate: sha256::Midstate = self.sighash_all.into(); + sha256::Hash::from_byte_array(midstate.to_byte_array()) + } +} + +// Pointer must be manually free after dropping +impl Drop for CTxEnv { + fn drop(&mut self) { + unsafe { + crate::alloc::rust_0_5_free(self.tx as *mut u8); + crate::alloc::rust_0_5_free(self.taproot as *mut u8); + } + } +} + +impl CRawBuffer { + pub fn new(buf: &[c_uchar]) -> Self { + unsafe { + let mut raw_buffer = std::mem::MaybeUninit::::uninit(); + c_set_rawBuffer(raw_buffer.as_mut_ptr(), buf.as_ptr(), buf.len() as c_uint); + raw_buffer.assume_init() + } + } +} + +#[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 42eec296..fcca587c 100644 --- a/simplicity-sys/src/c_jets/c_env/mod.rs +++ b/simplicity-sys/src/c_jets/c_env/mod.rs @@ -1,296 +1,3 @@ // SPDX-License-Identifier: CC0-1.0 -use hashes::{sha256, Hash}; - -use crate::ffi::sha256::CSha256Midstate; -use crate::ffi::{c_size_t, c_uchar, c_uint, c_uint_fast32_t}; - -/// Documentation of RawInputData/RawOutputData/RawTapData/Raw. -/// -/// Data structure for holding data that CTransaction points to. -/// -/// Why do we this special data structure? -/// 1. We need to keep the data in memory until the CTransaction is dropped. -/// 2. The memory is Transaction is not saved in the same format as required by FFI. -/// We use more ergonomics in rust to allow better UX which interfere with the FFI. For example, -/// the Value is stored as Tagged Union, but we require it to be a slice of bytes in elements format. -/// 3. Allocating inside FFI functions does not work because the memory is freed after the function returns. -/// 4. We only create allocations for data fields that are stored differently from the -/// consensus serialization format. -#[derive(Debug)] -pub struct RawOutputData { - pub asset: Vec, - pub value: Vec, - pub nonce: Vec, - pub surjection_proof: Vec, - pub range_proof: Vec, -} - -#[derive(Debug)] -#[repr(C)] -pub struct CElementsRawBuffer { - pub ptr: *const c_uchar, - pub len: u32, -} - -/// Similar to [`RawOutputData`], for inputs. -#[derive(Debug)] -pub struct RawInputData { - pub annex: Option>, - // issuance - pub issuance_amount: Vec, - pub issuance_inflation_keys: Vec, - pub amount_range_proof: Vec, - pub inflation_keys_range_proof: Vec, - // spent txo - pub asset: Vec, - pub value: Vec, -} - -/// Similar to [`RawOutputData`], but for transaction -#[derive(Debug)] -pub struct RawTransactionData { - pub inputs: Vec, - pub outputs: Vec, -} - -#[derive(Debug)] -#[repr(C)] -pub struct CElementsRawOutput { - asset: *const c_uchar, - value: *const c_uchar, - nonce: *const c_uchar, - script_pubkey: CElementsRawBuffer, - surjection_proof: CElementsRawBuffer, - range_proof: CElementsRawBuffer, -} - -#[derive(Debug)] -#[repr(C)] -pub struct CElementsRawInput { - annex: *const CElementsRawBuffer, - prev_txid: *const c_uchar, - pegin: *const c_uchar, - // issuance - blinding_nonce: *const c_uchar, - asset_entropy: *const c_uchar, - amount: *const c_uchar, - inflation_keys: *const c_uchar, - amount_range_proof: CElementsRawBuffer, - inflation_keys_range_proof: CElementsRawBuffer, - // spent txo - asset: *const c_uchar, - value: *const c_uchar, - script_pubkey: CElementsRawBuffer, - // inputs - script_sig: CElementsRawBuffer, - prev_txout_index: u32, - sequence: u32, -} - -#[derive(Debug)] -#[repr(C)] -pub struct CElementsRawTransaction { - txid: *const c_uchar, - inputs: *const CElementsRawInput, - outputs: *const CElementsRawOutput, - version: u32, - locktime: u32, - n_inputs: u32, - n_outputs: u32, -} - -#[derive(Debug)] -#[repr(C)] -pub struct CElementsRawTapEnv { - control_block: *const c_uchar, - script_cmr: *const c_uchar, - branch_len: u8, -} - -#[derive(Debug)] -pub enum CTransaction {} - -#[derive(Debug)] -#[repr(C)] -pub struct CElementsTxEnv { - tx: *const CTransaction, - taproot: *const CTapEnv, - genesis_hash: CSha256Midstate, - sighash_all: CSha256Midstate, - ix: c_uint_fast32_t, -} - -#[derive(Debug)] -pub enum CTapEnv {} - -extern "C" { - #[link_name = "rustsimplicity_0_5_c_sizeof_rawElementsBuffer"] - pub static c_sizeof_rawElementsBuffer: c_size_t; - #[link_name = "rustsimplicity_0_5_c_sizeof_rawElementsOutput"] - pub static c_sizeof_rawElementsOutput: c_size_t; - #[link_name = "rustsimplicity_0_5_c_sizeof_rawElementsInput"] - pub static c_sizeof_rawElementsInput: c_size_t; - #[link_name = "rustsimplicity_0_5_c_sizeof_rawElementsTransaction"] - pub static c_sizeof_rawElementsTransaction: c_size_t; - #[link_name = "rustsimplicity_0_5_c_sizeof_rawElementsTapEnv"] - pub static c_sizeof_rawElementsTapEnv: c_size_t; - #[link_name = "rustsimplicity_0_5_c_sizeof_txEnv"] - pub static c_sizeof_txEnv: c_size_t; - - #[link_name = "rustsimplicity_0_5_c_alignof_rawElementsBuffer"] - pub static c_alignof_rawElementsBuffer: c_size_t; - #[link_name = "rustsimplicity_0_5_c_alignof_rawElementsOutput"] - pub static c_alignof_rawElementsOutput: c_size_t; - #[link_name = "rustsimplicity_0_5_c_alignof_rawElementsInput"] - pub static c_alignof_rawElementsInput: c_size_t; - #[link_name = "rustsimplicity_0_5_c_alignof_rawElementsTransaction"] - pub static c_alignof_rawElementsTransaction: c_size_t; - #[link_name = "rustsimplicity_0_5_c_alignof_rawElementsTapEnv"] - pub static c_alignof_rawElementsTapEnv: c_size_t; - #[link_name = "rustsimplicity_0_5_c_alignof_txEnv"] - pub static c_alignof_txEnv: c_size_t; - - #[link_name = "rustsimplicity_0_5_c_set_rawElementsBuffer"] - pub fn c_set_rawElementsBuffer(res: *mut CElementsRawBuffer, buf: *const c_uchar, len: c_uint); - #[link_name = "rustsimplicity_0_5_c_set_rawElementsOutput"] - pub fn c_set_rawElementsOutput( - res: *mut CElementsRawOutput, - asset: *const c_uchar, - value: *const c_uchar, - nonce: *const c_uchar, - scriptPubKey: *const CElementsRawBuffer, - surjectionProof: *const CElementsRawBuffer, - rangeProof: *const CElementsRawBuffer, - ); - #[link_name = "rustsimplicity_0_5_c_set_rawElementsInput"] - pub fn c_set_rawElementsInput( - result: *mut CElementsRawInput, - annex: *const CElementsRawBuffer, - pegin: *const c_uchar, - scriptSig: *const CElementsRawBuffer, - prevTxid: *const c_uchar, - prevIx: c_uint, - asset: *const c_uchar, - value: *const c_uchar, - scriptPubKey: *const CElementsRawBuffer, - sequence: c_uint, - blindingNonce: *const c_uchar, - assetEntropy: *const c_uchar, - amount: *const c_uchar, - inflationKeys: *const c_uchar, - amountRangePrf: *const CElementsRawBuffer, - inflationKeysRangePrf: *const CElementsRawBuffer, - ); - - #[link_name = "rustsimplicity_0_5_c_set_rawElementsTransaction"] - pub fn c_set_rawElementsTransaction( - result: *mut CElementsRawTransaction, - version: c_uint, - txid: *const c_uchar, - input: *const CElementsRawInput, - numInputs: c_uint, - output: *const CElementsRawOutput, - numOutputs: c_uint, - lockTime: c_uint, - ); - #[link_name = "rustsimplicity_0_5_c_set_rawElementsTapEnv"] - pub fn c_set_rawElementsTapEnv( - result: *mut CElementsRawTapEnv, - controlBlock: *const c_uchar, - pathLen: c_uchar, - scriptCMR: *const c_uchar, - ); - #[link_name = "rustsimplicity_0_5_c_set_txEnv"] - pub fn c_set_txEnv( - result: *mut CElementsTxEnv, - tx: *const CTransaction, - taproot: *const CTapEnv, - genesisHash: *const c_uchar, - ix: c_uint, - ); - #[link_name = "rustsimplicity_0_5_elements_mallocTapEnv"] - pub fn simplicity_elements_mallocTapEnv(rawEnv: *const CElementsRawTapEnv) -> *mut CTapEnv; - #[link_name = "rustsimplicity_0_5_elements_mallocTransaction"] - pub fn simplicity_elements_mallocTransaction( - rawTx: *const CElementsRawTransaction, - ) -> *mut CTransaction; - #[link_name = "rustsimplicity_0_5_c_free_transaction"] - pub fn c_free_transaction(tx: *mut CTransaction); - #[link_name = "rustsimplicity_0_5_c_free_tapEnv"] - pub fn c_free_tapEnv(env: *mut CTapEnv); -} -impl CElementsTxEnv { - pub fn sighash_all(&self) -> sha256::Hash { - let midstate: sha256::Midstate = self.sighash_all.into(); - sha256::Hash::from_byte_array(midstate.to_byte_array()) - } -} - -// Pointer must be manually free after dropping -impl Drop for CElementsTxEnv { - fn drop(&mut self) { - unsafe { - crate::alloc::rust_0_5_free(self.tx as *mut u8); - crate::alloc::rust_0_5_free(self.taproot as *mut u8); - } - } -} - -impl CElementsRawBuffer { - pub fn new(buf: &[c_uchar]) -> Self { - unsafe { - let mut raw_buffer = std::mem::MaybeUninit::::uninit(); - c_set_rawElementsBuffer(raw_buffer.as_mut_ptr(), buf.as_ptr(), buf.len() as c_uint); - raw_buffer.assume_init() - } - } -} - -#[cfg(test)] -mod tests { - use std::mem::{align_of, size_of}; - - use crate::c_jets::{c_env::*, frame_ffi::*}; - - #[test] - fn test_sizes() { - unsafe { - assert_eq!(size_of::(), c_sizeof_frameItem); - assert_eq!(size_of::(), c_sizeof_rawElementsBuffer); - assert_eq!(size_of::(), c_sizeof_rawElementsInput); - assert_eq!(size_of::(), c_sizeof_rawElementsOutput); - assert_eq!( - size_of::(), - c_sizeof_rawElementsTransaction - ); - assert_eq!(size_of::(), c_sizeof_rawElementsTapEnv); - 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_rawElementsBuffer - ); - assert_eq!(align_of::(), c_alignof_rawElementsInput); - assert_eq!( - align_of::(), - c_alignof_rawElementsOutput - ); - assert_eq!( - align_of::(), - c_alignof_rawElementsTransaction - ); - assert_eq!( - align_of::(), - c_alignof_rawElementsTapEnv - ); - assert_eq!(align_of::(), c_alignof_txEnv); - } - } -} +pub mod elements; diff --git a/simplicity-sys/src/c_jets/mod.rs b/simplicity-sys/src/c_jets/mod.rs index 6175bf4f..2cc7d864 100644 --- a/simplicity-sys/src/c_jets/mod.rs +++ b/simplicity-sys/src/c_jets/mod.rs @@ -12,18 +12,13 @@ pub mod frame_ffi; #[rustfmt::skip] pub mod jets_ffi; #[rustfmt::skip] pub mod jets_wrapper; -pub use c_env::{CElementsTxEnv, CTapEnv, CTransaction}; +pub use c_env::elements; pub use c_frame::{byte_width, uword_width}; pub use frame_ffi::CFrameItem; // The bindings use elements_ffi instead of jets_ffi. pub use jets_ffi as elements_ffi; -use crate::c_jets::c_env::{ - CElementsRawBuffer, CElementsRawInput, CElementsRawOutput, CElementsRawTapEnv, - CElementsRawTransaction, -}; - #[cfg(feature = "test-utils")] pub mod exec_ffi; @@ -41,25 +36,25 @@ pub fn sanity_checks() -> bool { } if std::mem::size_of::() != frame_ffi::c_sizeof_frameItem - || std::mem::size_of::() != c_env::c_sizeof_rawElementsBuffer - || std::mem::size_of::() != c_env::c_sizeof_rawElementsInput - || std::mem::size_of::() != c_env::c_sizeof_rawElementsOutput - || std::mem::size_of::() - != c_env::c_sizeof_rawElementsTransaction - || std::mem::size_of::() != c_env::c_sizeof_txEnv - || std::mem::size_of::() != c_env::c_sizeof_rawElementsTapEnv + || std::mem::size_of::() != c_env::elements::c_sizeof_rawBuffer + || std::mem::size_of::() != c_env::elements::c_sizeof_rawInput + || std::mem::size_of::() != c_env::elements::c_sizeof_rawOutput + || std::mem::size_of::() + != c_env::elements::c_sizeof_rawTransaction + || std::mem::size_of::() != c_env::elements::c_sizeof_txEnv + || std::mem::size_of::() != c_env::elements::c_sizeof_rawTapEnv { return false; } if std::mem::align_of::() != frame_ffi::c_alignof_frameItem - || std::mem::align_of::() != c_env::c_alignof_rawElementsBuffer - || std::mem::align_of::() != c_env::c_alignof_rawElementsInput - || std::mem::align_of::() != c_env::c_alignof_rawElementsOutput - || std::mem::align_of::() - != c_env::c_alignof_rawElementsTransaction - || std::mem::align_of::() != c_env::c_alignof_txEnv - || std::mem::align_of::() != c_env::c_alignof_rawElementsTapEnv + || std::mem::align_of::() != c_env::elements::c_alignof_rawBuffer + || std::mem::align_of::() != c_env::elements::c_alignof_rawInput + || std::mem::align_of::() != c_env::elements::c_alignof_rawOutput + || std::mem::align_of::() + != c_env::elements::c_alignof_rawTransaction + || std::mem::align_of::() != c_env::elements::c_alignof_txEnv + || std::mem::align_of::() != c_env::elements::c_alignof_rawTapEnv { return false; } diff --git a/simplicity-sys/src/lib.rs b/simplicity-sys/src/lib.rs index 2b84fa2d..8202a892 100644 --- a/simplicity-sys/src/lib.rs +++ b/simplicity-sys/src/lib.rs @@ -1,7 +1,11 @@ // SPDX-License-Identifier: CC0-1.0 pub mod c_jets; -pub use c_jets::{CElementsTxEnv, CFrameItem, CTapEnv, CTransaction}; +pub use c_jets::elements; +pub use c_jets::CFrameItem; + +// Temporary to keep the Haskell-generated code compiling +pub use c_jets::elements::CTxEnv as CElementsTxEnv; pub mod alloc; pub mod ffi; diff --git a/simplicity-sys/src/tests/ffi.rs b/simplicity-sys/src/tests/ffi.rs index cbfe1035..1acad2c7 100644 --- a/simplicity-sys/src/tests/ffi.rs +++ b/simplicity-sys/src/tests/ffi.rs @@ -376,7 +376,7 @@ pub mod elements { pub mod eval { use super::*; - use crate::c_jets::c_env::CElementsTxEnv; + use crate::c_jets::c_env::elements; use crate::ffi::UWORD; use crate::tests::ffi::dag::CDagNode; use crate::tests::ffi::ty::CType; @@ -399,7 +399,7 @@ pub mod eval { type_dag: *mut CType, len: c_size_t, budget: *const ubounded, - env: *const CElementsTxEnv, + env: *const elements::CTxEnv, ) -> SimplicityErr; /// Given a well-typed dag representing a Simplicity expression, @@ -435,7 +435,7 @@ pub mod eval { type_dag: *mut CType, len: c_size_t, budget: *const ubounded, - env: *const CElementsTxEnv, + env: *const elements::CTxEnv, ) -> SimplicityErr { simplicity_evalTCOExpression( CHECK_ALL, diff --git a/src/jet/elements/c_env.rs b/src/jet/elements/c_env.rs index 78d4a00b..93465dc6 100644 --- a/src/jet/elements/c_env.rs +++ b/src/jet/elements/c_env.rs @@ -12,22 +12,19 @@ use elements::{ secp256k1_zkp::{RangeProof, SurjectionProof}, taproot::ControlBlock, }; -use simplicity_sys::c_jets::c_env::{ - c_set_rawElementsInput, c_set_rawElementsOutput, c_set_rawElementsTapEnv, - c_set_rawElementsTransaction, c_set_txEnv, simplicity_elements_mallocTapEnv, - simplicity_elements_mallocTransaction, CElementsRawBuffer, CElementsRawInput, - CElementsRawOutput, CElementsRawTapEnv, CElementsRawTransaction, CElementsTxEnv, CTapEnv, - CTransaction, RawInputData, RawOutputData, RawTransactionData, -}; +use simplicity_sys::c_jets::c_env::elements as c_elements; use crate::merkle::cmr::Cmr; use super::ElementsUtxo; -fn new_raw_output(out: &elements::TxOut, out_data: &RawOutputData) -> CElementsRawOutput { +fn new_raw_output( + out: &elements::TxOut, + out_data: &c_elements::RawOutputData, +) -> c_elements::CRawOutput { unsafe { - let mut raw_output = std::mem::MaybeUninit::::uninit(); - c_set_rawElementsOutput( + let mut raw_output = std::mem::MaybeUninit::::uninit(); + c_elements::c_set_rawOutput( raw_output.as_mut_ptr(), asset_ptr(out.asset, &out_data.asset), value_ptr(out.value, &out_data.value), @@ -43,10 +40,10 @@ fn new_raw_output(out: &elements::TxOut, out_data: &RawOutputData) -> CElementsR fn new_raw_input( inp: &elements::TxIn, in_utxo: &ElementsUtxo, - inp_data: &RawInputData, -) -> CElementsRawInput { + inp_data: &c_elements::RawInputData, +) -> c_elements::CRawInput { unsafe { - let mut raw_input = std::mem::MaybeUninit::::uninit(); + let mut raw_input = std::mem::MaybeUninit::::uninit(); let (issue_nonce_ptr, issue_entropy_ptr, issue_amt_ptr, issue_infl_key_ptr) = if inp.has_issuance() { @@ -67,7 +64,7 @@ fn new_raw_input( std::ptr::null(), ) }; - c_set_rawElementsInput( + c_elements::c_set_rawInput( raw_input.as_mut_ptr(), opt_ptr(annex_ptr(&inp_data.annex).as_ref()), inp.pegin_data() @@ -91,13 +88,16 @@ fn new_raw_input( } } -fn new_tx_data(tx: &elements::Transaction, in_utxos: &[ElementsUtxo]) -> RawTransactionData { - let mut tx_data = RawTransactionData { +fn new_tx_data( + tx: &elements::Transaction, + in_utxos: &[ElementsUtxo], +) -> c_elements::RawTransactionData { + let mut tx_data = c_elements::RawTransactionData { inputs: Vec::with_capacity(tx.input.len()), outputs: Vec::with_capacity(tx.output.len()), }; for (inp, in_utxo) in tx.input.iter().zip(in_utxos.iter()) { - let inp_data = RawInputData { + let inp_data = c_elements::RawInputData { annex: None, // Actually store annex issuance_amount: serialize(&inp.asset_issuance.amount), issuance_inflation_keys: serialize(&inp.asset_issuance.inflation_keys), @@ -111,7 +111,7 @@ fn new_tx_data(tx: &elements::Transaction, in_utxos: &[ElementsUtxo]) -> RawTran tx_data.inputs.push(inp_data); } for out in &tx.output { - let out_data = RawOutputData { + let out_data = c_elements::RawOutputData { asset: serialize(&out.asset), value: serialize(&out.value), nonce: serialize(&out.nonce), @@ -123,7 +123,10 @@ fn new_tx_data(tx: &elements::Transaction, in_utxos: &[ElementsUtxo]) -> RawTran tx_data } -pub(super) fn new_tx(tx: &elements::Transaction, in_utxos: &[ElementsUtxo]) -> *mut CTransaction { +pub(super) fn new_tx( + tx: &elements::Transaction, + in_utxos: &[ElementsUtxo], +) -> *mut c_elements::CTransaction { let mut raw_inputs = Vec::new(); let mut raw_outputs = Vec::new(); let txid = tx.txid(); @@ -141,8 +144,8 @@ pub(super) fn new_tx(tx: &elements::Transaction, in_utxos: &[ElementsUtxo]) -> * raw_outputs.push(new_raw_output(out, out_data)); } unsafe { - let mut raw_tx = std::mem::MaybeUninit::::uninit(); - c_set_rawElementsTransaction( + let mut raw_tx = std::mem::MaybeUninit::::uninit(); + c_elements::c_set_rawTransaction( raw_tx.as_mut_ptr(), tx.version as c_uint, AsRef::<[u8]>::as_ref(&txid).as_ptr(), @@ -153,34 +156,37 @@ pub(super) fn new_tx(tx: &elements::Transaction, in_utxos: &[ElementsUtxo]) -> * tx.lock_time.to_consensus_u32() as c_uint, ); let raw_tx = raw_tx.assume_init(); - simplicity_elements_mallocTransaction(&raw_tx) + c_elements::simplicity_mallocTransaction(&raw_tx) } } -pub(super) fn new_tap_env(control_block: &ControlBlock, script_cmr: Cmr) -> *mut CTapEnv { +pub(super) fn new_tap_env( + control_block: &ControlBlock, + script_cmr: Cmr, +) -> *mut c_elements::CTapEnv { unsafe { - let mut raw_tap_env = std::mem::MaybeUninit::::uninit(); + let mut raw_tap_env = std::mem::MaybeUninit::::uninit(); let cb_ser = control_block.serialize(); - c_set_rawElementsTapEnv( + c_elements::c_set_rawTapEnv( raw_tap_env.as_mut_ptr(), cb_ser.as_ptr(), control_block.merkle_branch.as_inner().len() as c_uchar, script_cmr.as_ref().as_ptr(), ); let raw_tap_env = raw_tap_env.assume_init(); - simplicity_elements_mallocTapEnv(&raw_tap_env) + c_elements::simplicity_mallocTapEnv(&raw_tap_env) } } pub(super) fn new_tx_env( - tx: *const CTransaction, - taproot: *const CTapEnv, + tx: *const c_elements::CTransaction, + taproot: *const c_elements::CTapEnv, genesis_hash: elements::BlockHash, ix: u32, -) -> CElementsTxEnv { +) -> c_elements::CTxEnv { unsafe { - let mut tx_env = std::mem::MaybeUninit::::uninit(); - c_set_txEnv( + let mut tx_env = std::mem::MaybeUninit::::uninit(); + c_elements::c_set_txEnv( tx_env.as_mut_ptr(), tx, taproot, @@ -223,20 +229,22 @@ fn opt_ptr(t: Option<&T>) -> *const T { } } -fn script_ptr(script: &elements::Script) -> CElementsRawBuffer { - CElementsRawBuffer::new(script.as_bytes()) +fn script_ptr(script: &elements::Script) -> c_elements::CRawBuffer { + c_elements::CRawBuffer::new(script.as_bytes()) } -fn annex_ptr(annex: &Option>) -> Option { - annex.as_ref().map(|annex| CElementsRawBuffer::new(annex)) +fn annex_ptr(annex: &Option>) -> Option { + annex + .as_ref() + .map(|annex| c_elements::CRawBuffer::new(annex)) } -fn surjection_proof_ptr(surjection_proof: &[c_uchar]) -> CElementsRawBuffer { - CElementsRawBuffer::new(surjection_proof) +fn surjection_proof_ptr(surjection_proof: &[c_uchar]) -> c_elements::CRawBuffer { + c_elements::CRawBuffer::new(surjection_proof) } -fn range_proof_ptr(rangeproof: &[c_uchar]) -> CElementsRawBuffer { - CElementsRawBuffer::new(rangeproof) +fn range_proof_ptr(rangeproof: &[c_uchar]) -> c_elements::CRawBuffer { + c_elements::CRawBuffer::new(rangeproof) } fn serialize_rangeproof(rangeproof: &Option>) -> Vec { diff --git a/src/jet/elements/environment.rs b/src/jet/elements/environment.rs index 94ef1b3e..fc3a9433 100644 --- a/src/jet/elements/environment.rs +++ b/src/jet/elements/environment.rs @@ -3,7 +3,7 @@ use crate::merkle::cmr::Cmr; use elements::confidential; use elements::taproot::ControlBlock; -use simplicity_sys::c_jets::c_env::CElementsTxEnv; +use simplicity_sys::c_jets::c_env::elements as c_elements; use std::ops::Deref; use super::c_env; @@ -39,7 +39,7 @@ impl From for ElementsUtxo { #[derive(Debug)] pub struct ElementsEnv> { /// The CTxEnv struct - c_tx_env: CElementsTxEnv, + c_tx_env: c_elements::CTxEnv, /// The elements transaction tx: T, /// the current index of the input @@ -79,7 +79,7 @@ where } /// Obtains the FFI compatible CTxEnv from self - pub fn c_tx_env(&self) -> &CElementsTxEnv { + pub fn c_tx_env(&self) -> &c_elements::CTxEnv { &self.c_tx_env } From 66a468cf07efad229b0556e469ea3f06ee81fb36 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 3 Sep 2025 20:18:54 +0000 Subject: [PATCH 03/13] simplicity-sys: delete dead links to C code These don't refer to anything --- simplicity-sys/src/c_jets/c_env/elements.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/simplicity-sys/src/c_jets/c_env/elements.rs b/simplicity-sys/src/c_jets/c_env/elements.rs index b5581dc6..4add5e6f 100644 --- a/simplicity-sys/src/c_jets/c_env/elements.rs +++ b/simplicity-sys/src/c_jets/c_env/elements.rs @@ -213,10 +213,6 @@ extern "C" { pub fn simplicity_mallocTapEnv(rawEnv: *const CRawTapEnv) -> *mut CTapEnv; #[link_name = "rustsimplicity_0_5_elements_mallocTransaction"] pub fn simplicity_mallocTransaction(rawTx: *const CRawTransaction) -> *mut CTransaction; - #[link_name = "rustsimplicity_0_5_c_free_transaction"] - pub fn c_free_transaction(tx: *mut CTransaction); - #[link_name = "rustsimplicity_0_5_c_free_tapEnv"] - pub fn c_free_tapEnv(env: *mut CTapEnv); } impl CTxEnv { pub fn sighash_all(&self) -> sha256::Hash { From e23a1b3ad5aa5a296911feaa6c44b9ba4ed19856 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 4 Sep 2025 20:29:16 +0000 Subject: [PATCH 04/13] ffi: don't flatten CRawInput structure I can't find any citation that this is safe to do. We don't actually use this structure in our Rust code; we populate it by calling a c_set function which is defined on the C side, and then pass it to C functions. So no code needs to be changed. However, I want to remove the c_set_* functions because they hide life information (and in fact, are unsound, as we currently use them) and serve to obfuscate code. They seem to have come from #48 #49 #50 #51 in which Sanket seems to have copied the corresponding Haskell code into Rust, where it is unnecessary and dangerous. --- simplicity-sys/src/c_jets/c_env/elements.rs | 22 +++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/simplicity-sys/src/c_jets/c_env/elements.rs b/simplicity-sys/src/c_jets/c_env/elements.rs index 4add5e6f..1380b894 100644 --- a/simplicity-sys/src/c_jets/c_env/elements.rs +++ b/simplicity-sys/src/c_jets/c_env/elements.rs @@ -65,24 +65,30 @@ pub struct CRawOutput { range_proof: CRawBuffer, } -#[derive(Debug)] #[repr(C)] -pub struct CRawInput { - annex: *const CRawBuffer, - prev_txid: *const c_uchar, - pegin: *const c_uchar, - // issuance +pub struct CRawInputIssuance { blinding_nonce: *const c_uchar, asset_entropy: *const c_uchar, amount: *const c_uchar, inflation_keys: *const c_uchar, amount_range_proof: CRawBuffer, inflation_keys_range_proof: CRawBuffer, - // spent txo +} + +#[repr(C)] +pub struct CRawInputTxo { asset: *const c_uchar, value: *const c_uchar, script_pubkey: CRawBuffer, - // inputs +} + +#[repr(C)] +pub struct CRawInput { + annex: *const CRawBuffer, + prev_txid: *const c_uchar, + pegin: *const c_uchar, + issuance: CRawInputIssuance, + txo: CRawInputTxo, script_sig: CRawBuffer, prev_txout_index: u32, sequence: u32, From 818b380ed26107a9ccd2b51b2358ac1fe6a57427 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 4 Sep 2025 20:41:20 +0000 Subject: [PATCH 05/13] ffi: replace some "pointer to assumed length" types with optional references This adds some lifetimes to our raw C structures, which in the next commits will be used to enforce that we are not giving the C code pointers to local variables or other unsound things. (In fact, our existing code does do this.) As a starting point, replace a few "easy" FFI fields with stronger types. We do not change the pointers to value types, which are listed in Rust as "NULL, or a pointer to 9 bytes, or to 33" because it was not clear that there was any better representation for this than *const u8. Through the C FFI we have structs which have listed invariants of the form "either this is NULL or it points to an array of 33 bytes". We further have a guarantee (because we are maintainers of both the C and Rust code, and we are responsible citizens) that these const pointers will never be mutated through by the C code. The rest of this comment is to justify this change: According to the nomicon, we may use references in FFI types as long as we promise they won't be NULL and that we won't violate the aliasing rules (which for const pointers, simply means we won't cast them to non-const pointers): https://doc.rust-lang.org/nomicon/ffi.html#interoperability-with-foreign-code The repr(C) docs further say that if our pointers might be NULL, we can use Option<&T>: https://doc.rust-lang.org/nomicon/other-reprs.html#reprc Finally, where the C code takes a pointer to a u8 with an implicit claim that this is actually a pointer to 33 contiguous u8s, we may use pointers or references to Rust arrays, which is permissible because: * all thin pointers have the same size and representation, justified by the Rust reference https://doc.rust-lang.org/reference/expressions/operator-expr.html#r-expr.as.pointer.sized * arrays are guaranteed to be contiguous in memory in both C and Rust --- simplicity-sys/src/c_jets/c_env/elements.rs | 44 ++++++++--------- src/jet/elements/c_env.rs | 54 ++++++++++----------- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/simplicity-sys/src/c_jets/c_env/elements.rs b/simplicity-sys/src/c_jets/c_env/elements.rs index 1380b894..c2aa0718 100644 --- a/simplicity-sys/src/c_jets/c_env/elements.rs +++ b/simplicity-sys/src/c_jets/c_env/elements.rs @@ -19,9 +19,9 @@ use crate::ffi::{c_size_t, c_uchar, c_uint, c_uint_fast32_t}; /// consensus serialization format. #[derive(Debug)] pub struct RawOutputData { - pub asset: Vec, + pub asset: Option<[c_uchar; 33]>, pub value: Vec, - pub nonce: Vec, + pub nonce: Option<[c_uchar; 33]>, pub surjection_proof: Vec, pub range_proof: Vec, } @@ -43,7 +43,7 @@ pub struct RawInputData { pub amount_range_proof: Vec, pub inflation_keys_range_proof: Vec, // spent txo - pub asset: Vec, + pub asset: Option<[c_uchar; 33]>, pub value: Vec, } @@ -56,19 +56,19 @@ pub struct RawTransactionData { #[derive(Debug)] #[repr(C)] -pub struct CRawOutput { - asset: *const c_uchar, +pub struct CRawOutput<'raw> { + asset: Option<&'raw [c_uchar; 33]>, value: *const c_uchar, - nonce: *const c_uchar, + nonce: Option<&'raw [c_uchar; 33]>, script_pubkey: CRawBuffer, surjection_proof: CRawBuffer, range_proof: CRawBuffer, } #[repr(C)] -pub struct CRawInputIssuance { - blinding_nonce: *const c_uchar, - asset_entropy: *const c_uchar, +pub struct CRawInputIssuance<'raw> { + blinding_nonce: Option<&'raw [c_uchar; 32]>, + asset_entropy: Option<&'raw [c_uchar; 32]>, amount: *const c_uchar, inflation_keys: *const c_uchar, amount_range_proof: CRawBuffer, @@ -76,19 +76,19 @@ pub struct CRawInputIssuance { } #[repr(C)] -pub struct CRawInputTxo { - asset: *const c_uchar, +pub struct CRawInputTxo<'raw> { + asset: Option<&'raw [c_uchar; 33]>, value: *const c_uchar, script_pubkey: CRawBuffer, } #[repr(C)] -pub struct CRawInput { +pub struct CRawInput<'tx, 'raw> { annex: *const CRawBuffer, - prev_txid: *const c_uchar, - pegin: *const c_uchar, - issuance: CRawInputIssuance, - txo: CRawInputTxo, + prev_txid: &'tx [c_uchar; 32], + pegin: Option<&'raw [c_uchar; 32]>, + issuance: CRawInputIssuance<'raw>, + txo: CRawInputTxo<'raw>, script_sig: CRawBuffer, prev_txout_index: u32, sequence: u32, @@ -96,10 +96,10 @@ pub struct CRawInput { #[derive(Debug)] #[repr(C)] -pub struct CRawTransaction { +pub struct CRawTransaction<'tx, 'raw> { txid: *const c_uchar, - inputs: *const CRawInput, - outputs: *const CRawOutput, + inputs: *const CRawInput<'tx, 'raw>, + outputs: *const CRawOutput<'raw>, version: u32, locktime: u32, n_inputs: u32, @@ -162,9 +162,9 @@ extern "C" { #[link_name = "rustsimplicity_0_5_c_set_rawElementsOutput"] pub fn c_set_rawOutput( res: *mut CRawOutput, - asset: *const c_uchar, + asset: Option<&[u8; 33]>, value: *const c_uchar, - nonce: *const c_uchar, + nonce: Option<&[u8; 33]>, scriptPubKey: *const CRawBuffer, surjectionProof: *const CRawBuffer, rangeProof: *const CRawBuffer, @@ -177,7 +177,7 @@ extern "C" { scriptSig: *const CRawBuffer, prevTxid: *const c_uchar, prevIx: c_uint, - asset: *const c_uchar, + asset: Option<&[u8; 33]>, value: *const c_uchar, scriptPubKey: *const CRawBuffer, sequence: c_uint, diff --git a/src/jet/elements/c_env.rs b/src/jet/elements/c_env.rs index 93465dc6..fea3e752 100644 --- a/src/jet/elements/c_env.rs +++ b/src/jet/elements/c_env.rs @@ -18,17 +18,17 @@ use crate::merkle::cmr::Cmr; use super::ElementsUtxo; -fn new_raw_output( +fn new_raw_output<'raw>( out: &elements::TxOut, - out_data: &c_elements::RawOutputData, -) -> c_elements::CRawOutput { + out_data: &'raw c_elements::RawOutputData, +) -> c_elements::CRawOutput<'raw> { unsafe { let mut raw_output = std::mem::MaybeUninit::::uninit(); c_elements::c_set_rawOutput( raw_output.as_mut_ptr(), - asset_ptr(out.asset, &out_data.asset), + out_data.asset.as_ref(), value_ptr(out.value, &out_data.value), - nonce_ptr(out.nonce, &out_data.nonce), + out_data.nonce.as_ref(), &script_ptr(&out.script_pubkey), &surjection_proof_ptr(&out_data.surjection_proof), &range_proof_ptr(&out_data.range_proof), @@ -37,11 +37,11 @@ fn new_raw_output( } } -fn new_raw_input( - inp: &elements::TxIn, - in_utxo: &ElementsUtxo, +fn new_raw_input<'raw, 'tx>( + inp: &'tx elements::TxIn, + in_utxo: &'raw ElementsUtxo, inp_data: &c_elements::RawInputData, -) -> c_elements::CRawInput { +) -> c_elements::CRawInput<'raw, 'tx> { unsafe { let mut raw_input = std::mem::MaybeUninit::::uninit(); @@ -73,7 +73,7 @@ fn new_raw_input( &script_ptr(&inp.script_sig), AsRef::<[u8]>::as_ref(&inp.previous_output.txid).as_ptr(), inp.previous_output.vout as c_uint, - asset_ptr(in_utxo.asset, &inp_data.asset), + inp_data.asset.as_ref(), value_ptr(in_utxo.value, &inp_data.value), &script_ptr(&in_utxo.script_pubkey), inp.sequence.0 as c_uint, @@ -105,16 +105,16 @@ fn new_tx_data( inflation_keys_range_proof: serialize_rangeproof( &inp.witness.inflation_keys_rangeproof, ), - asset: serialize(&in_utxo.asset), + asset: asset_array(&in_utxo.asset), value: serialize(&in_utxo.value), }; tx_data.inputs.push(inp_data); } for out in &tx.output { let out_data = c_elements::RawOutputData { - asset: serialize(&out.asset), + asset: asset_array(&out.asset), value: serialize(&out.value), - nonce: serialize(&out.nonce), + nonce: nonce_array(&out.nonce), surjection_proof: serialize_surjection_proof(&out.witness.surjection_proof), range_proof: serialize_rangeproof(&out.witness.rangeproof), }; @@ -197,24 +197,24 @@ pub(super) fn new_tx_env( } } -fn asset_ptr(asset: confidential::Asset, data: &[u8]) -> *const c_uchar { - if asset.is_null() { - std::ptr::null() - } else { - data.as_ptr() - } +fn asset_array(asset: &confidential::Asset) -> Option<[u8; 33]> { + (!asset.is_null()).then(|| { + serialize(asset) + .try_into() + .expect("non-null asset is 33 bytes") + }) } -fn value_ptr(value: confidential::Value, data: &[u8]) -> *const c_uchar { - if value.is_null() { - std::ptr::null() - } else { - data.as_ptr() - } +fn nonce_array(nonce: &confidential::Nonce) -> Option<[u8; 33]> { + (!nonce.is_null()).then(|| { + serialize(nonce) + .try_into() + .expect("non-null asset is 33 bytes") + }) } -fn nonce_ptr(nonce: confidential::Nonce, data: &[u8]) -> *const c_uchar { - if nonce.is_null() { +fn value_ptr(value: confidential::Value, data: &[u8]) -> *const c_uchar { + if value.is_null() { std::ptr::null() } else { data.as_ptr() From 224645a67cd36edaa9d9f89d9403bc67c0fb5229 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 4 Sep 2025 21:11:04 +0000 Subject: [PATCH 06/13] ffi: delete the c_set_rawElementsOutput method (The following commits will delete the rest of these.) The role of these functions is to marshall Rust data into C structures. This is implemented in env.c, on the C side of the FFI boundary, and use raw pointers and out-pointers. None of this is necessary because Rust has a well-defined FFI interface with C. Furthermore, it serves to obfuscate the lifetimes and provenance of the various pointers at play. We can remove this function, replace its call sites with direct construction of the Rust version of the C structure, and replace raw pointers (where possible) with references that have named lifetimes. Then our Rust code is formally safe and the compiler will give us much more help. --- simplicity-sys/depend/env.c | 6 ----- simplicity-sys/src/c_jets/c_env/elements.rs | 30 +++++++-------------- src/jet/elements/c_env.rs | 29 +++++++------------- 3 files changed, 20 insertions(+), 45 deletions(-) diff --git a/simplicity-sys/depend/env.c b/simplicity-sys/depend/env.c index 62570ed6..61a6457e 100644 --- a/simplicity-sys/depend/env.c +++ b/simplicity-sys/depend/env.c @@ -30,12 +30,6 @@ void rustsimplicity_0_5_c_set_rawElementsBuffer(rawElementsBuffer *result, const *result = (rawElementsBuffer){.buf = buf, .len = len}; } -void rustsimplicity_0_5_c_set_rawElementsOutput(rawElementsOutput *result, const unsigned char *asset, const unsigned char *value, const unsigned char *nonce, const rawElementsBuffer *scriptPubKey, - const rawElementsBuffer *surjectionProof, const rawElementsBuffer *rangeProof) -{ - *result = (rawElementsOutput){.asset = asset, .value = value, .nonce = nonce, .scriptPubKey = *scriptPubKey, .surjectionProof = *surjectionProof, .rangeProof = *rangeProof}; -} - void rustsimplicity_0_5_c_set_rawElementsInput(rawElementsInput *result, const rawElementsBuffer *annex, const unsigned char *pegin, const rawElementsBuffer *scriptSig, const unsigned char *prevTxid, unsigned int prevIx, const unsigned char *asset, const unsigned char *value, const rawElementsBuffer *scriptPubKey, diff --git a/simplicity-sys/src/c_jets/c_env/elements.rs b/simplicity-sys/src/c_jets/c_env/elements.rs index c2aa0718..4adadf0c 100644 --- a/simplicity-sys/src/c_jets/c_env/elements.rs +++ b/simplicity-sys/src/c_jets/c_env/elements.rs @@ -57,12 +57,12 @@ pub struct RawTransactionData { #[derive(Debug)] #[repr(C)] pub struct CRawOutput<'raw> { - asset: Option<&'raw [c_uchar; 33]>, - value: *const c_uchar, - nonce: Option<&'raw [c_uchar; 33]>, - script_pubkey: CRawBuffer, - surjection_proof: CRawBuffer, - range_proof: CRawBuffer, + pub asset: Option<&'raw [c_uchar; 33]>, + pub value: *const c_uchar, + pub nonce: Option<&'raw [c_uchar; 33]>, + pub script_pubkey: CRawBuffer, + pub surjection_proof: CRawBuffer, + pub range_proof: CRawBuffer, } #[repr(C)] @@ -83,9 +83,9 @@ pub struct CRawInputTxo<'raw> { } #[repr(C)] -pub struct CRawInput<'tx, 'raw> { +pub struct CRawInput<'raw> { annex: *const CRawBuffer, - prev_txid: &'tx [c_uchar; 32], + prev_txid: &'raw [c_uchar; 32], pegin: Option<&'raw [c_uchar; 32]>, issuance: CRawInputIssuance<'raw>, txo: CRawInputTxo<'raw>, @@ -96,9 +96,9 @@ pub struct CRawInput<'tx, 'raw> { #[derive(Debug)] #[repr(C)] -pub struct CRawTransaction<'tx, 'raw> { +pub struct CRawTransaction<'raw> { txid: *const c_uchar, - inputs: *const CRawInput<'tx, 'raw>, + inputs: *const CRawInput<'raw>, outputs: *const CRawOutput<'raw>, version: u32, locktime: u32, @@ -159,16 +159,6 @@ extern "C" { #[link_name = "rustsimplicity_0_5_c_set_rawElementsBuffer"] pub fn c_set_rawBuffer(res: *mut CRawBuffer, buf: *const c_uchar, len: c_uint); - #[link_name = "rustsimplicity_0_5_c_set_rawElementsOutput"] - pub fn c_set_rawOutput( - res: *mut CRawOutput, - asset: Option<&[u8; 33]>, - value: *const c_uchar, - nonce: Option<&[u8; 33]>, - scriptPubKey: *const CRawBuffer, - surjectionProof: *const CRawBuffer, - rangeProof: *const CRawBuffer, - ); #[link_name = "rustsimplicity_0_5_c_set_rawElementsInput"] pub fn c_set_rawInput( result: *mut CRawInput, diff --git a/src/jet/elements/c_env.rs b/src/jet/elements/c_env.rs index fea3e752..87d66bba 100644 --- a/src/jet/elements/c_env.rs +++ b/src/jet/elements/c_env.rs @@ -22,26 +22,21 @@ fn new_raw_output<'raw>( out: &elements::TxOut, out_data: &'raw c_elements::RawOutputData, ) -> c_elements::CRawOutput<'raw> { - unsafe { - let mut raw_output = std::mem::MaybeUninit::::uninit(); - c_elements::c_set_rawOutput( - raw_output.as_mut_ptr(), - out_data.asset.as_ref(), - value_ptr(out.value, &out_data.value), - out_data.nonce.as_ref(), - &script_ptr(&out.script_pubkey), - &surjection_proof_ptr(&out_data.surjection_proof), - &range_proof_ptr(&out_data.range_proof), - ); - raw_output.assume_init() + c_elements::CRawOutput { + asset: out_data.asset.as_ref(), + value: value_ptr(out.value, &out_data.value), + nonce: out_data.nonce.as_ref(), + script_pubkey: c_elements::CRawBuffer::new(out.script_pubkey.as_bytes()), + surjection_proof: c_elements::CRawBuffer::new(&out_data.surjection_proof), + range_proof: c_elements::CRawBuffer::new(&out_data.range_proof), } } -fn new_raw_input<'raw, 'tx>( - inp: &'tx elements::TxIn, +fn new_raw_input<'raw>( + inp: &'raw elements::TxIn, in_utxo: &'raw ElementsUtxo, inp_data: &c_elements::RawInputData, -) -> c_elements::CRawInput<'raw, 'tx> { +) -> c_elements::CRawInput<'raw> { unsafe { let mut raw_input = std::mem::MaybeUninit::::uninit(); @@ -239,10 +234,6 @@ fn annex_ptr(annex: &Option>) -> Option { .map(|annex| c_elements::CRawBuffer::new(annex)) } -fn surjection_proof_ptr(surjection_proof: &[c_uchar]) -> c_elements::CRawBuffer { - c_elements::CRawBuffer::new(surjection_proof) -} - fn range_proof_ptr(rangeproof: &[c_uchar]) -> c_elements::CRawBuffer { c_elements::CRawBuffer::new(rangeproof) } From 32c66e0705770a967b4b62f08029791b7ca4d76e Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Fri, 5 Sep 2025 14:25:24 +0000 Subject: [PATCH 07/13] ffi: store pegin genesis hash in RawInputData The existing code creates a temporary `PeginData` structure, then stores a pointer to its `genesis_hash` field in a struct that we pass to the C code. This is unsound. In general, we lack transaction environment tests where we construct different environments then run Simplicity code that exercises them. --- simplicity-sys/src/c_jets/c_env/elements.rs | 4 +++- src/jet/elements/c_env.rs | 8 +++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/simplicity-sys/src/c_jets/c_env/elements.rs b/simplicity-sys/src/c_jets/c_env/elements.rs index 4adadf0c..bbd81e1a 100644 --- a/simplicity-sys/src/c_jets/c_env/elements.rs +++ b/simplicity-sys/src/c_jets/c_env/elements.rs @@ -37,6 +37,8 @@ pub struct CRawBuffer { #[derive(Debug)] pub struct RawInputData { pub annex: Option>, + // pegin + pub genesis_hash: Option<[c_uchar; 32]>, // issuance pub issuance_amount: Vec, pub issuance_inflation_keys: Vec, @@ -163,7 +165,7 @@ extern "C" { pub fn c_set_rawInput( result: *mut CRawInput, annex: *const CRawBuffer, - pegin: *const c_uchar, + pegin: Option<&[c_uchar; 32]>, scriptSig: *const CRawBuffer, prevTxid: *const c_uchar, prevIx: c_uint, diff --git a/src/jet/elements/c_env.rs b/src/jet/elements/c_env.rs index 87d66bba..b132b2ad 100644 --- a/src/jet/elements/c_env.rs +++ b/src/jet/elements/c_env.rs @@ -4,6 +4,7 @@ //! use elements::secp256k1_zkp::ffi::CPtr; +use hashes::Hash; use std::os::raw::{c_uchar, c_uint}; use elements::{ @@ -62,9 +63,7 @@ fn new_raw_input<'raw>( c_elements::c_set_rawInput( raw_input.as_mut_ptr(), opt_ptr(annex_ptr(&inp_data.annex).as_ref()), - inp.pegin_data() - .map(|x| AsRef::<[u8]>::as_ref(&x.genesis_hash).as_ptr()) - .unwrap_or(std::ptr::null()), + inp_data.genesis_hash.as_ref(), &script_ptr(&inp.script_sig), AsRef::<[u8]>::as_ref(&inp.previous_output.txid).as_ptr(), inp.previous_output.vout as c_uint, @@ -94,6 +93,9 @@ fn new_tx_data( for (inp, in_utxo) in tx.input.iter().zip(in_utxos.iter()) { let inp_data = c_elements::RawInputData { annex: None, // Actually store annex + genesis_hash: inp + .pegin_data() + .map(|x| x.genesis_hash.to_raw_hash().to_byte_array()), issuance_amount: serialize(&inp.asset_issuance.amount), issuance_inflation_keys: serialize(&inp.asset_issuance.inflation_keys), amount_range_proof: serialize_rangeproof(&inp.witness.amount_rangeproof), From 5dab248bcff914306f1db7f0e367eb7aa5baad3a Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 4 Sep 2025 22:20:32 +0000 Subject: [PATCH 08/13] ffi: delete the c_set_rawElementsInput method --- simplicity-sys/depend/env.c | 10 --- simplicity-sys/src/c_jets/c_env/elements.rs | 67 +++++++------- src/jet/elements/c_env.rs | 96 +++++++-------------- 3 files changed, 62 insertions(+), 111 deletions(-) diff --git a/simplicity-sys/depend/env.c b/simplicity-sys/depend/env.c index 61a6457e..a09021a0 100644 --- a/simplicity-sys/depend/env.c +++ b/simplicity-sys/depend/env.c @@ -30,16 +30,6 @@ void rustsimplicity_0_5_c_set_rawElementsBuffer(rawElementsBuffer *result, const *result = (rawElementsBuffer){.buf = buf, .len = len}; } -void rustsimplicity_0_5_c_set_rawElementsInput(rawElementsInput *result, const rawElementsBuffer *annex, const unsigned char *pegin, const rawElementsBuffer *scriptSig, - const unsigned char *prevTxid, unsigned int prevIx, - const unsigned char *asset, const unsigned char *value, const rawElementsBuffer *scriptPubKey, - unsigned int sequence, - const unsigned char *blindingNonce, const unsigned char *assetEntropy, const unsigned char *amount, const unsigned char *inflationKeys, - const rawElementsBuffer *amountRangePrf, const rawElementsBuffer *inflationKeysRangePrf) -{ - *result = (rawElementsInput){.annex = annex, .scriptSig = *scriptSig, .prevTxid = prevTxid, .pegin = pegin, .issuance = {.blindingNonce = blindingNonce, .assetEntropy = assetEntropy, .amount = amount, .inflationKeys = inflationKeys, .amountRangePrf = *amountRangePrf, .inflationKeysRangePrf = *inflationKeysRangePrf}, .txo = {.asset = asset, .value = value, .scriptPubKey = *scriptPubKey}, .prevIx = prevIx, .sequence = sequence}; -} - void rustsimplicity_0_5_c_set_rawElementsTransaction(rawElementsTransaction *result, unsigned int version, const unsigned char *txid, const rawElementsInput *input, unsigned int numInputs, diff --git a/simplicity-sys/src/c_jets/c_env/elements.rs b/simplicity-sys/src/c_jets/c_env/elements.rs index bbd81e1a..4d36de4e 100644 --- a/simplicity-sys/src/c_jets/c_env/elements.rs +++ b/simplicity-sys/src/c_jets/c_env/elements.rs @@ -69,31 +69,45 @@ pub struct CRawOutput<'raw> { #[repr(C)] pub struct CRawInputIssuance<'raw> { - blinding_nonce: Option<&'raw [c_uchar; 32]>, - asset_entropy: Option<&'raw [c_uchar; 32]>, - amount: *const c_uchar, - inflation_keys: *const c_uchar, - amount_range_proof: CRawBuffer, - inflation_keys_range_proof: CRawBuffer, + pub blinding_nonce: Option<&'raw [c_uchar; 32]>, + pub asset_entropy: Option<&'raw [c_uchar; 32]>, + pub amount: *const c_uchar, + pub inflation_keys: *const c_uchar, + pub amount_range_proof: CRawBuffer, + pub inflation_keys_range_proof: CRawBuffer, +} + +impl<'raw> CRawInputIssuance<'raw> { + /// Constructs a raw input issuance structure corresponding to "no issuance". + pub fn no_issuance() -> Self { + Self { + blinding_nonce: None, + asset_entropy: None, + amount: core::ptr::null(), + inflation_keys: core::ptr::null(), + amount_range_proof: CRawBuffer::new(&[]), + inflation_keys_range_proof: CRawBuffer::new(&[]), + } + } } #[repr(C)] pub struct CRawInputTxo<'raw> { - asset: Option<&'raw [c_uchar; 33]>, - value: *const c_uchar, - script_pubkey: CRawBuffer, + pub asset: Option<&'raw [c_uchar; 33]>, + pub value: *const c_uchar, + pub script_pubkey: CRawBuffer, } #[repr(C)] pub struct CRawInput<'raw> { - annex: *const CRawBuffer, - prev_txid: &'raw [c_uchar; 32], - pegin: Option<&'raw [c_uchar; 32]>, - issuance: CRawInputIssuance<'raw>, - txo: CRawInputTxo<'raw>, - script_sig: CRawBuffer, - prev_txout_index: u32, - sequence: u32, + pub annex: *const CRawBuffer, + pub prev_txid: &'raw [c_uchar; 32], + pub pegin: Option<&'raw [c_uchar; 32]>, + pub issuance: CRawInputIssuance<'raw>, + pub txo: CRawInputTxo<'raw>, + pub script_sig: CRawBuffer, + pub prev_txout_index: u32, + pub sequence: u32, } #[derive(Debug)] @@ -161,25 +175,6 @@ extern "C" { #[link_name = "rustsimplicity_0_5_c_set_rawElementsBuffer"] pub fn c_set_rawBuffer(res: *mut CRawBuffer, buf: *const c_uchar, len: c_uint); - #[link_name = "rustsimplicity_0_5_c_set_rawElementsInput"] - pub fn c_set_rawInput( - result: *mut CRawInput, - annex: *const CRawBuffer, - pegin: Option<&[c_uchar; 32]>, - scriptSig: *const CRawBuffer, - prevTxid: *const c_uchar, - prevIx: c_uint, - asset: Option<&[u8; 33]>, - value: *const c_uchar, - scriptPubKey: *const CRawBuffer, - sequence: c_uint, - blindingNonce: *const c_uchar, - assetEntropy: *const c_uchar, - amount: *const c_uchar, - inflationKeys: *const c_uchar, - amountRangePrf: *const CRawBuffer, - inflationKeysRangePrf: *const CRawBuffer, - ); #[link_name = "rustsimplicity_0_5_c_set_rawElementsTransaction"] pub fn c_set_rawTransaction( diff --git a/src/jet/elements/c_env.rs b/src/jet/elements/c_env.rs index b132b2ad..c3d20199 100644 --- a/src/jet/elements/c_env.rs +++ b/src/jet/elements/c_env.rs @@ -3,7 +3,6 @@ //! High level APIs for creating C FFI compatible environment. //! -use elements::secp256k1_zkp::ffi::CPtr; use hashes::Hash; use std::os::raw::{c_uchar, c_uint}; @@ -36,49 +35,38 @@ fn new_raw_output<'raw>( fn new_raw_input<'raw>( inp: &'raw elements::TxIn, in_utxo: &'raw ElementsUtxo, - inp_data: &c_elements::RawInputData, + inp_data: &'raw c_elements::RawInputData, ) -> c_elements::CRawInput<'raw> { - unsafe { - let mut raw_input = std::mem::MaybeUninit::::uninit(); - - let (issue_nonce_ptr, issue_entropy_ptr, issue_amt_ptr, issue_infl_key_ptr) = - if inp.has_issuance() { - ( - inp.asset_issuance.asset_blinding_nonce.as_c_ptr(), - inp.asset_issuance.asset_entropy.as_ptr(), - value_ptr(inp.asset_issuance.amount, &inp_data.issuance_amount), - value_ptr( - inp.asset_issuance.inflation_keys, - &inp_data.issuance_inflation_keys, - ), - ) - } else { - ( - std::ptr::null(), - std::ptr::null(), - std::ptr::null(), - std::ptr::null(), - ) - }; - c_elements::c_set_rawInput( - raw_input.as_mut_ptr(), - opt_ptr(annex_ptr(&inp_data.annex).as_ref()), - inp_data.genesis_hash.as_ref(), - &script_ptr(&inp.script_sig), - AsRef::<[u8]>::as_ref(&inp.previous_output.txid).as_ptr(), - inp.previous_output.vout as c_uint, - inp_data.asset.as_ref(), - value_ptr(in_utxo.value, &inp_data.value), - &script_ptr(&in_utxo.script_pubkey), - inp.sequence.0 as c_uint, - issue_nonce_ptr, // FIXME: CHECK ASSET ISSUANCE IS NOT NULL. EASIER WITH NEW ELEMENTS VERSION. - issue_entropy_ptr, - issue_amt_ptr, - issue_infl_key_ptr, - &range_proof_ptr(&inp_data.amount_range_proof), - &range_proof_ptr(&inp_data.inflation_keys_range_proof), - ); - raw_input.assume_init() + c_elements::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_ref(), + pegin: inp_data.genesis_hash.as_ref(), + issuance: if inp.has_issuance() { + c_elements::CRawInputIssuance { + blinding_nonce: Some(inp.asset_issuance.asset_blinding_nonce.as_ref()), + asset_entropy: Some(&inp.asset_issuance.asset_entropy), + amount: value_ptr(inp.asset_issuance.amount, &inp_data.issuance_amount), + inflation_keys: value_ptr( + inp.asset_issuance.inflation_keys, + &inp_data.issuance_inflation_keys, + ), + amount_range_proof: c_elements::CRawBuffer::new(&inp_data.amount_range_proof), + inflation_keys_range_proof: c_elements::CRawBuffer::new( + &inp_data.inflation_keys_range_proof, + ), + } + } else { + c_elements::CRawInputIssuance::no_issuance() + }, + txo: c_elements::CRawInputTxo { + asset: inp_data.asset.as_ref(), + value: value_ptr(in_utxo.value, &inp_data.value), + script_pubkey: c_elements::CRawBuffer::new(in_utxo.script_pubkey.as_bytes()), + }, + script_sig: c_elements::CRawBuffer::new(inp.script_sig.as_bytes()), + prev_txout_index: inp.previous_output.vout, + sequence: inp.sequence.to_consensus_u32(), } } @@ -218,28 +206,6 @@ fn value_ptr(value: confidential::Value, data: &[u8]) -> *const c_uchar { } } -fn opt_ptr(t: Option<&T>) -> *const T { - if let Some(t) = t { - t - } else { - std::ptr::null() - } -} - -fn script_ptr(script: &elements::Script) -> c_elements::CRawBuffer { - c_elements::CRawBuffer::new(script.as_bytes()) -} - -fn annex_ptr(annex: &Option>) -> Option { - annex - .as_ref() - .map(|annex| c_elements::CRawBuffer::new(annex)) -} - -fn range_proof_ptr(rangeproof: &[c_uchar]) -> c_elements::CRawBuffer { - c_elements::CRawBuffer::new(rangeproof) -} - fn serialize_rangeproof(rangeproof: &Option>) -> Vec { rangeproof .as_ref() From b0e848404f3ccf28a4490be32296679829ba51d1 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Fri, 5 Sep 2025 15:01:31 +0000 Subject: [PATCH 09/13] ffi: remove c_set_rawBuffer method This one was easy. --- simplicity-sys/depend/env.c | 5 ----- simplicity-sys/src/c_jets/c_env/elements.rs | 10 +++------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/simplicity-sys/depend/env.c b/simplicity-sys/depend/env.c index a09021a0..0484af8e 100644 --- a/simplicity-sys/depend/env.c +++ b/simplicity-sys/depend/env.c @@ -25,11 +25,6 @@ const size_t rustsimplicity_0_5_c_alignof_rawElementsTransaction = alignof(rawEl const size_t rustsimplicity_0_5_c_alignof_rawElementsTapEnv = alignof(rawElementsTapEnv); const size_t rustsimplicity_0_5_c_alignof_txEnv = alignof(txEnv); -void rustsimplicity_0_5_c_set_rawElementsBuffer(rawElementsBuffer *result, const unsigned char *buf, unsigned int len) -{ - *result = (rawElementsBuffer){.buf = buf, .len = len}; -} - void rustsimplicity_0_5_c_set_rawElementsTransaction(rawElementsTransaction *result, unsigned int version, const unsigned char *txid, const rawElementsInput *input, unsigned int numInputs, diff --git a/simplicity-sys/src/c_jets/c_env/elements.rs b/simplicity-sys/src/c_jets/c_env/elements.rs index 4d36de4e..7be51f0e 100644 --- a/simplicity-sys/src/c_jets/c_env/elements.rs +++ b/simplicity-sys/src/c_jets/c_env/elements.rs @@ -173,9 +173,6 @@ extern "C" { #[link_name = "rustsimplicity_0_5_c_alignof_txEnv"] pub static c_alignof_txEnv: c_size_t; - #[link_name = "rustsimplicity_0_5_c_set_rawElementsBuffer"] - pub fn c_set_rawBuffer(res: *mut CRawBuffer, buf: *const c_uchar, len: c_uint); - #[link_name = "rustsimplicity_0_5_c_set_rawElementsTransaction"] pub fn c_set_rawTransaction( result: *mut CRawTransaction, @@ -226,10 +223,9 @@ impl Drop for CTxEnv { impl CRawBuffer { pub fn new(buf: &[c_uchar]) -> Self { - unsafe { - let mut raw_buffer = std::mem::MaybeUninit::::uninit(); - c_set_rawBuffer(raw_buffer.as_mut_ptr(), buf.as_ptr(), buf.len() as c_uint); - raw_buffer.assume_init() + Self { + ptr: buf.as_ptr(), + len: buf.len().try_into().expect("sane buffer lengths"), } } } From 4bc03bf6c753eddc1105112eee6a7f0b6d97be73 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Fri, 5 Sep 2025 15:27:32 +0000 Subject: [PATCH 10/13] ffi: remove c_set_rawElementsTransaction --- simplicity-sys/depend/env.c | 17 ------------- simplicity-sys/src/c_jets/c_env/elements.rs | 25 ++++++------------- src/jet/elements/c_env.rs | 27 ++++++++++----------- 3 files changed, 20 insertions(+), 49 deletions(-) diff --git a/simplicity-sys/depend/env.c b/simplicity-sys/depend/env.c index 0484af8e..6e37b61a 100644 --- a/simplicity-sys/depend/env.c +++ b/simplicity-sys/depend/env.c @@ -25,23 +25,6 @@ const size_t rustsimplicity_0_5_c_alignof_rawElementsTransaction = alignof(rawEl const size_t rustsimplicity_0_5_c_alignof_rawElementsTapEnv = alignof(rawElementsTapEnv); const size_t rustsimplicity_0_5_c_alignof_txEnv = alignof(txEnv); -void rustsimplicity_0_5_c_set_rawElementsTransaction(rawElementsTransaction *result, unsigned int version, - const unsigned char *txid, - const rawElementsInput *input, unsigned int numInputs, - const rawElementsOutput *output, unsigned int numOutputs, - unsigned int lockTime) -{ - *result = (rawElementsTransaction){ - .version = version, - .txid = txid, - .input = input, - .numInputs = numInputs, - .output = output, - .numOutputs = numOutputs, - .lockTime = lockTime, - }; -} - void rustsimplicity_0_5_c_set_rawElementsTapEnv(rawElementsTapEnv *result, const unsigned char *controlBlock, unsigned char pathLen, const unsigned char *scriptCMR) { *result = (rawElementsTapEnv){.controlBlock = controlBlock, .pathLen = pathLen, .scriptCMR = scriptCMR}; diff --git a/simplicity-sys/src/c_jets/c_env/elements.rs b/simplicity-sys/src/c_jets/c_env/elements.rs index 7be51f0e..1900d9fd 100644 --- a/simplicity-sys/src/c_jets/c_env/elements.rs +++ b/simplicity-sys/src/c_jets/c_env/elements.rs @@ -113,13 +113,13 @@ pub struct CRawInput<'raw> { #[derive(Debug)] #[repr(C)] pub struct CRawTransaction<'raw> { - txid: *const c_uchar, - inputs: *const CRawInput<'raw>, - outputs: *const CRawOutput<'raw>, - version: u32, - locktime: u32, - n_inputs: u32, - n_outputs: u32, + pub txid: &'raw [c_uchar; 32], + pub inputs: *const CRawInput<'raw>, + pub outputs: *const CRawOutput<'raw>, + pub n_inputs: u32, + pub n_outputs: u32, + pub version: u32, + pub locktime: u32, } #[derive(Debug)] @@ -173,17 +173,6 @@ extern "C" { #[link_name = "rustsimplicity_0_5_c_alignof_txEnv"] pub static c_alignof_txEnv: c_size_t; - #[link_name = "rustsimplicity_0_5_c_set_rawElementsTransaction"] - pub fn c_set_rawTransaction( - result: *mut CRawTransaction, - version: c_uint, - txid: *const c_uchar, - input: *const CRawInput, - numInputs: c_uint, - output: *const CRawOutput, - numOutputs: c_uint, - lockTime: c_uint, - ); #[link_name = "rustsimplicity_0_5_c_set_rawElementsTapEnv"] pub fn c_set_rawTapEnv( result: *mut CRawTapEnv, diff --git a/src/jet/elements/c_env.rs b/src/jet/elements/c_env.rs index c3d20199..5a1ad468 100644 --- a/src/jet/elements/c_env.rs +++ b/src/jet/elements/c_env.rs @@ -4,7 +4,7 @@ //! use hashes::Hash; -use std::os::raw::{c_uchar, c_uint}; +use std::os::raw::c_uchar; use elements::{ confidential, @@ -128,20 +128,19 @@ pub(super) fn new_tx( for (out, out_data) in tx.output.iter().zip(tx_data.outputs.iter()) { raw_outputs.push(new_raw_output(out, out_data)); } + + let c_raw_tx = c_elements::CRawTransaction { + txid: txid.as_raw_hash().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, + locktime: tx.lock_time.to_consensus_u32(), + }; unsafe { - let mut raw_tx = std::mem::MaybeUninit::::uninit(); - c_elements::c_set_rawTransaction( - raw_tx.as_mut_ptr(), - tx.version as c_uint, - AsRef::<[u8]>::as_ref(&txid).as_ptr(), - raw_inputs.as_ptr(), - raw_inputs.len() as c_uint, - raw_outputs.as_ptr(), - raw_outputs.len() as c_uint, - tx.lock_time.to_consensus_u32() as c_uint, - ); - let raw_tx = raw_tx.assume_init(); - c_elements::simplicity_mallocTransaction(&raw_tx) + // SAFETY: this is a FFI call and we constructed its argument correctly. + c_elements::simplicity_mallocTransaction(&c_raw_tx) } } From 2d8c56e71173f65e449d82870f186356f0e392e9 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Fri, 5 Sep 2025 15:29:56 +0000 Subject: [PATCH 11/13] ffi: remove c_set_rawElementsTapEnv --- simplicity-sys/depend/env.c | 5 ----- simplicity-sys/src/c_jets/c_env/elements.rs | 13 +++--------- src/jet/elements/c_env.rs | 22 ++++++++++++--------- 3 files changed, 16 insertions(+), 24 deletions(-) diff --git a/simplicity-sys/depend/env.c b/simplicity-sys/depend/env.c index 6e37b61a..55c10a44 100644 --- a/simplicity-sys/depend/env.c +++ b/simplicity-sys/depend/env.c @@ -25,11 +25,6 @@ const size_t rustsimplicity_0_5_c_alignof_rawElementsTransaction = alignof(rawEl const size_t rustsimplicity_0_5_c_alignof_rawElementsTapEnv = alignof(rawElementsTapEnv); const size_t rustsimplicity_0_5_c_alignof_txEnv = alignof(txEnv); -void rustsimplicity_0_5_c_set_rawElementsTapEnv(rawElementsTapEnv *result, const unsigned char *controlBlock, unsigned char pathLen, const unsigned char *scriptCMR) -{ - *result = (rawElementsTapEnv){.controlBlock = controlBlock, .pathLen = pathLen, .scriptCMR = scriptCMR}; -} - void rustsimplicity_0_5_c_set_txEnv(txEnv *result, const elementsTransaction *tx, const elementsTapEnv *taproot, const unsigned char *genesisHash, unsigned int ix) { sha256_midstate genesis; diff --git a/simplicity-sys/src/c_jets/c_env/elements.rs b/simplicity-sys/src/c_jets/c_env/elements.rs index 1900d9fd..b1685918 100644 --- a/simplicity-sys/src/c_jets/c_env/elements.rs +++ b/simplicity-sys/src/c_jets/c_env/elements.rs @@ -125,9 +125,9 @@ pub struct CRawTransaction<'raw> { #[derive(Debug)] #[repr(C)] pub struct CRawTapEnv { - control_block: *const c_uchar, - script_cmr: *const c_uchar, - branch_len: u8, + pub control_block: *const c_uchar, + pub script_cmr: *const c_uchar, + pub branch_len: u8, } #[derive(Debug)] @@ -173,13 +173,6 @@ extern "C" { #[link_name = "rustsimplicity_0_5_c_alignof_txEnv"] pub static c_alignof_txEnv: c_size_t; - #[link_name = "rustsimplicity_0_5_c_set_rawElementsTapEnv"] - pub fn c_set_rawTapEnv( - result: *mut CRawTapEnv, - controlBlock: *const c_uchar, - pathLen: c_uchar, - scriptCMR: *const c_uchar, - ); #[link_name = "rustsimplicity_0_5_c_set_txEnv"] pub fn c_set_txEnv( result: *mut CTxEnv, diff --git a/src/jet/elements/c_env.rs b/src/jet/elements/c_env.rs index 5a1ad468..40fbe69f 100644 --- a/src/jet/elements/c_env.rs +++ b/src/jet/elements/c_env.rs @@ -148,16 +148,20 @@ pub(super) fn new_tap_env( control_block: &ControlBlock, script_cmr: Cmr, ) -> *mut c_elements::CTapEnv { + let cb_ser = control_block.serialize(); + let raw_tap_env = c_elements::CRawTapEnv { + control_block: cb_ser.as_ptr(), + script_cmr: script_cmr.as_ref().as_ptr(), + branch_len: control_block + .merkle_branch + .as_inner() + .len() + .try_into() + .expect("sane length"), + }; + unsafe { - let mut raw_tap_env = std::mem::MaybeUninit::::uninit(); - let cb_ser = control_block.serialize(); - c_elements::c_set_rawTapEnv( - raw_tap_env.as_mut_ptr(), - cb_ser.as_ptr(), - control_block.merkle_branch.as_inner().len() as c_uchar, - script_cmr.as_ref().as_ptr(), - ); - let raw_tap_env = raw_tap_env.assume_init(); + // SAFETY: this is a FFI call and we constructed its argument correctly. c_elements::simplicity_mallocTapEnv(&raw_tap_env) } } From c42a3750e8927f66b313b90ba1f2b1ae124ffba4 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Fri, 5 Sep 2025 15:46:37 +0000 Subject: [PATCH 12/13] ffi: don't use empty enums for opaque types The nomicon doesn't like this. From the bottom of https://doc.rust-lang.org/nomicon/ffi.html "Notice that it is a really bad idea to use an empty enum as FFI type. The compiler relies on empty enums being uninhabited, so handling values of type &Empty is a huge footgun and can lead to buggy program behavior (by triggering undefined behavior)." They give an example that uses a phantom to also remove Send, Sync, and Unpin. I did not do this. I think all these traits are fine, because the type has no self-references and is never mutated by the C code except when it is freed, and then only through a `*mut` pointer. --- simplicity-sys/src/c_jets/c_env/elements.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/simplicity-sys/src/c_jets/c_env/elements.rs b/simplicity-sys/src/c_jets/c_env/elements.rs index b1685918..f9f5f10d 100644 --- a/simplicity-sys/src/c_jets/c_env/elements.rs +++ b/simplicity-sys/src/c_jets/c_env/elements.rs @@ -130,8 +130,10 @@ pub struct CRawTapEnv { pub branch_len: u8, } -#[derive(Debug)] -pub enum CTransaction {} +#[repr(C)] +pub struct CTransaction { + _data: (), +} #[derive(Debug)] #[repr(C)] @@ -143,8 +145,10 @@ pub struct CTxEnv { ix: c_uint_fast32_t, } -#[derive(Debug)] -pub enum CTapEnv {} +#[repr(C)] +pub struct CTapEnv { + _data: (), +} extern "C" { #[link_name = "rustsimplicity_0_5_c_sizeof_rawElementsBuffer"] From 405487e41425969a99540952a0e23b504ba23c10 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Fri, 5 Sep 2025 16:05:18 +0000 Subject: [PATCH 13/13] ffi: move Raw* structs out of FFI crate These structures are private data types used ephemerally by src/jet/elements/c_env.rs. They should not be public and certainly should not be exposed in simplicity-sys. --- simplicity-sys/src/c_jets/c_env/elements.rs | 44 ----------------- src/jet/elements/c_env.rs | 52 +++++++++++++++++---- 2 files changed, 43 insertions(+), 53 deletions(-) diff --git a/simplicity-sys/src/c_jets/c_env/elements.rs b/simplicity-sys/src/c_jets/c_env/elements.rs index f9f5f10d..1adbccd8 100644 --- a/simplicity-sys/src/c_jets/c_env/elements.rs +++ b/simplicity-sys/src/c_jets/c_env/elements.rs @@ -5,27 +5,6 @@ use hashes::{sha256, Hash}; use crate::ffi::sha256::CSha256Midstate; use crate::ffi::{c_size_t, c_uchar, c_uint, c_uint_fast32_t}; -/// Documentation of RawInputData/RawOutputData/RawTapData/Raw. -/// -/// Data structure for holding data that CTransaction points to. -/// -/// Why do we this special data structure? -/// 1. We need to keep the data in memory until the CTransaction is dropped. -/// 2. The memory is Transaction is not saved in the same format as required by FFI. -/// We use more ergonomics in rust to allow better UX which interfere with the FFI. For example, -/// the Value is stored as Tagged Union, but we require it to be a slice of bytes in elements format. -/// 3. Allocating inside FFI functions does not work because the memory is freed after the function returns. -/// 4. We only create allocations for data fields that are stored differently from the -/// consensus serialization format. -#[derive(Debug)] -pub struct RawOutputData { - pub asset: Option<[c_uchar; 33]>, - pub value: Vec, - pub nonce: Option<[c_uchar; 33]>, - pub surjection_proof: Vec, - pub range_proof: Vec, -} - #[derive(Debug)] #[repr(C)] pub struct CRawBuffer { @@ -33,29 +12,6 @@ pub struct CRawBuffer { pub len: u32, } -/// Similar to [`RawOutputData`], for inputs. -#[derive(Debug)] -pub struct RawInputData { - pub annex: Option>, - // pegin - pub genesis_hash: Option<[c_uchar; 32]>, - // issuance - pub issuance_amount: Vec, - pub issuance_inflation_keys: Vec, - pub amount_range_proof: Vec, - pub inflation_keys_range_proof: Vec, - // spent txo - pub asset: Option<[c_uchar; 33]>, - pub value: Vec, -} - -/// Similar to [`RawOutputData`], but for transaction -#[derive(Debug)] -pub struct RawTransactionData { - pub inputs: Vec, - pub outputs: Vec, -} - #[derive(Debug)] #[repr(C)] pub struct CRawOutput<'raw> { diff --git a/src/jet/elements/c_env.rs b/src/jet/elements/c_env.rs index 40fbe69f..c50ab0fb 100644 --- a/src/jet/elements/c_env.rs +++ b/src/jet/elements/c_env.rs @@ -18,9 +18,46 @@ use crate::merkle::cmr::Cmr; use super::ElementsUtxo; +/// Holds transaction output data which needs to be re-serialized before being +/// passed to the C FFI. +#[derive(Debug)] +struct RawOutputData { + pub asset: Option<[c_uchar; 33]>, + pub value: Vec, + pub nonce: Option<[c_uchar; 33]>, + pub surjection_proof: Vec, + pub range_proof: Vec, +} + +/// Holds transaction input data which needs to be re-serialized before being +/// passed to the C FFI. +#[derive(Debug)] +struct RawInputData { + #[allow(dead_code)] // see FIXME below + pub annex: Option>, + // pegin + pub genesis_hash: Option<[c_uchar; 32]>, + // issuance + pub issuance_amount: Vec, + pub issuance_inflation_keys: Vec, + pub amount_range_proof: Vec, + pub inflation_keys_range_proof: Vec, + // spent txo + pub asset: Option<[c_uchar; 33]>, + pub value: Vec, +} + +/// Holds transaction data which needs to be re-serialized before being +/// passed to the C FFI. +#[derive(Debug)] +struct RawTransactionData { + pub inputs: Vec, + pub outputs: Vec, +} + fn new_raw_output<'raw>( out: &elements::TxOut, - out_data: &'raw c_elements::RawOutputData, + out_data: &'raw RawOutputData, ) -> c_elements::CRawOutput<'raw> { c_elements::CRawOutput { asset: out_data.asset.as_ref(), @@ -35,7 +72,7 @@ fn new_raw_output<'raw>( fn new_raw_input<'raw>( inp: &'raw elements::TxIn, in_utxo: &'raw ElementsUtxo, - inp_data: &'raw c_elements::RawInputData, + inp_data: &'raw RawInputData, ) -> c_elements::CRawInput<'raw> { c_elements::CRawInput { // FIXME actually pass the annex in; see https://github.com/BlockstreamResearch/simplicity/issues/311 for some difficulty here. @@ -70,16 +107,13 @@ fn new_raw_input<'raw>( } } -fn new_tx_data( - tx: &elements::Transaction, - in_utxos: &[ElementsUtxo], -) -> c_elements::RawTransactionData { - let mut tx_data = c_elements::RawTransactionData { +fn new_tx_data(tx: &elements::Transaction, in_utxos: &[ElementsUtxo]) -> RawTransactionData { + let mut tx_data = RawTransactionData { inputs: Vec::with_capacity(tx.input.len()), outputs: Vec::with_capacity(tx.output.len()), }; for (inp, in_utxo) in tx.input.iter().zip(in_utxos.iter()) { - let inp_data = c_elements::RawInputData { + let inp_data = RawInputData { annex: None, // Actually store annex genesis_hash: inp .pegin_data() @@ -96,7 +130,7 @@ fn new_tx_data( tx_data.inputs.push(inp_data); } for out in &tx.output { - let out_data = c_elements::RawOutputData { + let out_data = RawOutputData { asset: asset_array(&out.asset), value: serialize(&out.value), nonce: nonce_array(&out.nonce),