diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 00000000..a652995f --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +msrv = "1.41.0" \ No newline at end of file diff --git a/src/bit_machine/exec.rs b/src/bit_machine/exec.rs index 0949fb9c..a2902374 100644 --- a/src/bit_machine/exec.rs +++ b/src/bit_machine/exec.rs @@ -55,7 +55,7 @@ impl BitMachine { } /// Push a new frame of given size onto the write frame stack - fn new_frame(&mut self, len: usize) { + pub(crate) fn new_frame(&mut self, len: usize) { debug_assert!( self.next_frame_start + len <= self.data.len() * 8, "Data out of bounds: number of cells" @@ -70,14 +70,14 @@ impl BitMachine { } /// Move the active write frame to the read frame stack - fn move_frame(&mut self) { + pub(crate) fn move_frame(&mut self) { let mut _active_write_frame = self.write.pop().unwrap(); _active_write_frame.reset_cursor(); self.read.push(_active_write_frame); } /// Drop the active read frame - fn drop_frame(&mut self) { + pub(crate) fn drop_frame(&mut self) { let active_read_frame = self.read.pop().unwrap(); self.next_frame_start -= active_read_frame.len; assert_eq!(self.next_frame_start, active_read_frame.start); @@ -243,7 +243,7 @@ impl BitMachine { } /// Write a value to the current write frame - fn write_value(&mut self, val: &Value) { + pub(crate) fn write_value(&mut self, val: &Value) { // FIXME don't recurse match *val { Value::Unit => {} @@ -274,9 +274,9 @@ impl BitMachine { /// Execute the given program on the Bit Machine, using the given environment. /// /// Make sure the Bit Machine has enough space by constructing it via [`Self::for_program()`]. - pub fn exec<'a, J: Jet + std::fmt::Debug>( + pub fn exec( &mut self, - program: &'a RedeemNode, + program: &RedeemNode, env: &J::Environment, ) -> Result { // Rust cannot use `J` from parent function @@ -413,7 +413,7 @@ impl BitMachine { RedeemNodeInner::Hidden(hash) => { return Err(ExecutionError::ReachedPrunedBranch(*hash)) } - RedeemNodeInner::Jet(jet) => jet.exec()(self, env)?, + RedeemNodeInner::Jet(jet) => jet.exec(self, env)?, RedeemNodeInner::Fail(left, right) => { return Err(ExecutionError::ReachedFailNode(*left, *right)) } @@ -480,3 +480,15 @@ impl From for ExecutionError { ExecutionError::JetFailed(jet_failed) } } + +#[cfg(test)] +impl BitMachine { + /// Push a new frame of given size onto the write frame stack + /// without checking any assertions on IO. This is really handy when + /// we want to construct a BitMachine for testing purposes. + /// DO NOT USE THIS WITH REAL PROGRAMS. IT WILL FAIL. + pub(crate) fn new_frame_unchecked(&mut self, len: usize) { + self.write.push(Frame::new(self.next_frame_start, len)); + self.next_frame_start += len; + } +} diff --git a/src/bit_machine/mod.rs b/src/bit_machine/mod.rs index 50cebbe1..ded4e232 100644 --- a/src/bit_machine/mod.rs +++ b/src/bit_machine/mod.rs @@ -1,3 +1,3 @@ #[allow(dead_code)] pub mod exec; -mod frame; +pub(crate) mod frame; diff --git a/src/inference.rs b/src/inference.rs index 78745bf4..c3b808e0 100644 --- a/src/inference.rs +++ b/src/inference.rs @@ -342,12 +342,12 @@ pub(crate) fn get_arrow( let pow2s = Variable::powers_of_two(); bind( &arrow.source, - jet.source_ty().to_type(&pow2s), + jet.source_ty().to_variable_type(&pow2s), "Cannot fail", )?; bind( &arrow.target, - jet.target_ty().to_type(&pow2s), + jet.target_ty().to_variable_type(&pow2s), "Cannot fail", )?; } diff --git a/src/jet/bitcoin/mod.rs b/src/jet/bitcoin/mod.rs index 9303930a..bbefad81 100644 --- a/src/jet/bitcoin/mod.rs +++ b/src/jet/bitcoin/mod.rs @@ -13,11 +13,11 @@ // mod environment; +#[allow(dead_code)] mod exec; use crate::jet::Bitcoin; pub use environment::BitcoinEnv; -pub(crate) use exec::*; impl std::fmt::Display for Bitcoin { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { diff --git a/src/jet/elements/c_env.rs b/src/jet/elements/c_env.rs new file mode 100644 index 00000000..e0c19da7 --- /dev/null +++ b/src/jet/elements/c_env.rs @@ -0,0 +1,222 @@ +//! High level APIs for creating C FFI compatible environment. +//! + +use std::os::raw::{c_uchar, c_uint}; + +use elements::{ + confidential, + encode::serialize, + secp256k1_zkp::{RangeProof, SurjectionProof}, + taproot::ControlBlock, +}; +use simplicity_sys::c_jets::c_env::{ + c_set_rawInput, c_set_rawOutput, c_set_rawTapEnv, c_set_rawTransaction, c_set_txEnv, + elements_simplicity_mallocTapEnv, elements_simplicity_mallocTransaction, CElementsTxEnv, + CRawBuffer, CRawInput, CRawOutput, CRawTapEnv, CRawTransaction, CTapEnv, CTransaction, + RawInputData, RawOutputData, RawTransactionData, +}; + +use crate::merkle::cmr::Cmr; + +use super::ElementsUtxo; + +fn new_raw_output(out: &elements::TxOut, out_data: &RawOutputData) -> CRawOutput { + unsafe { + let mut raw_output = std::mem::MaybeUninit::::uninit(); + c_set_rawOutput( + raw_output.as_mut_ptr(), + asset_ptr(out.asset, &out_data.asset), + value_ptr(out.value, &out_data.value), + nonce_ptr(out.nonce, &out_data.nonce), + &script_ptr(&out.script_pubkey), + &surjection_proof_ptr(&out_data.surjection_proof), + &range_proof_ptr(&out_data.range_proof), + ); + raw_output.assume_init() + } +} + +fn new_raw_input( + inp: &elements::TxIn, + in_utxo: &ElementsUtxo, + inp_data: &RawInputData, +) -> CRawInput { + unsafe { + let mut raw_input = std::mem::MaybeUninit::::uninit(); + c_set_rawInput( + raw_input.as_mut_ptr(), + opt_ptr(annex_ptr(&inp_data.annex).as_ref()), // FIXME: ACTUALLY STORE ANNEX + std::ptr::null(), // FIXME: ACTUALLY STORE PEGIN + &script_ptr(&inp.script_sig), + inp.previous_output.txid.as_ptr(), + inp.previous_output.vout as c_uint, + asset_ptr(in_utxo.asset, &inp_data.asset), + value_ptr(in_utxo.value, &inp_data.value), + &script_ptr(&in_utxo.script_pubkey), + inp.sequence as c_uint, + inp.asset_issuance.asset_blinding_nonce.as_ptr(), // FIXME: CHECK ASSET ISSUANCE IS NOT NULL. EASIER WITH NEW ELEMENTS VERSION. + 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, + ), + &range_proof_ptr(&inp_data.amount_range_proof), + &range_proof_ptr(&inp_data.inflation_keys_range_proof), + ); + raw_input.assume_init() + } +} + +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 = RawInputData { + annex: None, // Actually store annex + 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), + inflation_keys_range_proof: serialize_rangeproof( + &inp.witness.inflation_keys_rangeproof, + ), + asset: serialize(&in_utxo.asset), + value: serialize(&in_utxo.value), + }; + tx_data.inputs.push(inp_data); + } + for out in tx.output.iter() { + let out_data = RawOutputData { + asset: serialize(&out.asset), + value: serialize(&out.value), + nonce: serialize(&out.nonce), + surjection_proof: serialize_surjection_proof(&out.witness.surjection_proof), + range_proof: serialize_rangeproof(&out.witness.rangeproof), + }; + tx_data.outputs.push(out_data); + } + tx_data +} + +pub(super) fn new_tx(tx: &elements::Transaction, in_utxos: &[ElementsUtxo]) -> *mut CTransaction { + let mut raw_inputs = Vec::new(); + let mut raw_outputs = Vec::new(); + let tx_data = new_tx_data(tx, in_utxos); + for ((inp, in_utxo), inp_data) in tx + .input + .iter() + .zip(in_utxos.iter()) + .zip(tx_data.inputs.iter()) + { + let res = new_raw_input(inp, in_utxo, inp_data); + raw_inputs.push(res); + } + for (out, out_data) in tx.output.iter().zip(tx_data.outputs.iter()) { + raw_outputs.push(new_raw_output(out, out_data)); + } + unsafe { + let mut raw_tx = std::mem::MaybeUninit::::uninit(); + c_set_rawTransaction( + raw_tx.as_mut_ptr(), + tx.version as c_uint, + raw_inputs.as_ptr(), + raw_inputs.len() as c_uint, + raw_outputs.as_ptr(), + raw_outputs.len() as c_uint, + tx.lock_time as c_uint, + ); + let raw_tx = raw_tx.assume_init(); + elements_simplicity_mallocTransaction(&raw_tx) + } +} + +pub(super) fn new_tap_env(control_block: &ControlBlock, script_cmr: Cmr) -> *mut CTapEnv { + unsafe { + let mut raw_tap_env = std::mem::MaybeUninit::::uninit(); + c_set_rawTapEnv( + raw_tap_env.as_mut_ptr(), + control_block.serialize().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(); + elements_simplicity_mallocTapEnv(&raw_tap_env) + } +} + +pub(super) fn new_tx_env( + tx: *const CTransaction, + taproot: *const CTapEnv, + genesis_hash: elements::BlockHash, + ix: u32, +) -> CElementsTxEnv { + unsafe { + let mut tx_env = std::mem::MaybeUninit::::uninit(); + c_set_txEnv(tx_env.as_mut_ptr(), tx, taproot, genesis_hash.as_ptr(), ix); + tx_env.assume_init() + } +} + +fn asset_ptr(asset: confidential::Asset, data: &[u8]) -> *const c_uchar { + if asset.is_null() { + std::ptr::null() + } else { + data.as_ptr() + } +} + +fn value_ptr(value: confidential::Value, data: &[u8]) -> *const c_uchar { + if value.is_null() { + std::ptr::null() + } else { + data.as_ptr() + } +} + +fn nonce_ptr(nonce: confidential::Nonce, data: &[u8]) -> *const c_uchar { + if nonce.is_null() { + std::ptr::null() + } else { + data.as_ptr() + } +} + +fn opt_ptr(t: Option<&T>) -> *const T { + if let Some(t) = t { + t + } else { + std::ptr::null() + } +} + +fn script_ptr(script: &elements::Script) -> CRawBuffer { + CRawBuffer::new(script.as_bytes()) +} + +fn annex_ptr(annex: &Option>) -> Option { + annex.as_ref().map(|annex| CRawBuffer::new(annex)) +} + +fn surjection_proof_ptr(surjection_proof: &[c_uchar]) -> CRawBuffer { + CRawBuffer::new(surjection_proof) +} + +fn range_proof_ptr(rangeproof: &[c_uchar]) -> CRawBuffer { + CRawBuffer::new(rangeproof) +} + +fn serialize_rangeproof(rangeproof: &Option>) -> Vec { + rangeproof + .as_ref() + .map(|x| x.serialize()) + .unwrap_or_default() +} + +fn serialize_surjection_proof(surjection_proof: &Option>) -> Vec { + surjection_proof + .as_ref() + .map(|x| x.serialize()) + .unwrap_or_default() +} diff --git a/src/jet/elements/environment.rs b/src/jet/elements/environment.rs index 157f2ebd..bb47fc54 100644 --- a/src/jet/elements/environment.rs +++ b/src/jet/elements/environment.rs @@ -18,15 +18,19 @@ use crate::merkle::cmr::Cmr; use bitcoin_hashes::{sha256, Hash, HashEngine}; use byteorder::{BigEndian, LittleEndian, WriteBytesExt}; use elements::confidential::{Asset, Nonce, Value}; -use elements::{confidential, AssetIssuance}; +use elements::taproot::ControlBlock; +use elements::{confidential, AssetIssuance, BlockHash}; +use simplicity_sys::c_jets::c_env::CElementsTxEnv; use std::sync::Arc; +use super::c_env; + /// An Elements UTXO // This is not a complete TxOut as it does not contain the nonce that // is sent to the recipient. pub struct ElementsUtxo { /// The 'scriptpubkey' (hash of Simplicity program) - pub script_pubkey: sha256::Hash, + pub script_pubkey: elements::Script, /// The explicit or confidential asset pub asset: confidential::Asset, /// The explict or confidential value @@ -48,6 +52,8 @@ pub struct ElementsUtxo { // Similar story if we tried to use a &'a elements::Transaction rather than // an Arc: we'd have a lifetime parameter <'a> that would cause us trouble. pub struct ElementsEnv { + /// The CTxEnv struct + pub(super) c_tx_env: CElementsTxEnv, /// The elements transaction pub(super) tx: Arc, /// The input utxo information corresponding to outpoint being spent. @@ -56,10 +62,12 @@ pub struct ElementsEnv { pub(super) ix: u32, /// Commitment merkle root of the script being executed pub(super) script_cmr: Cmr, - /// cached InputHash - pub(super) inputs_hash: sha256::Hash, - /// cached OutputHash - pub(super) outputs_hash: sha256::Hash, + /// Control block used to spend this leaf script + pub(super) control_block: ControlBlock, + /// Optional Annex. + pub(super) annex: Option>, + /// Genesis block hash + pub(super) genesis_hash: BlockHash, } impl ElementsEnv { @@ -68,24 +76,44 @@ impl ElementsEnv { utxos: Vec, ix: u32, script_cmr: Cmr, + control_block: ControlBlock, + annex: Option>, + genesis_hash: BlockHash, ) -> Self { - let mut inp_eng = sha256::Hash::engine(); - let mut output_eng = sha256::Hash::engine(); - - tx.input.simplicity_hash(&mut inp_eng); - tx.output.simplicity_hash(&mut output_eng); - let inputs_hash = sha256::Hash::from_engine(inp_eng); - let outputs_hash = sha256::Hash::from_engine(output_eng); - + let c_tx = c_env::new_tx(&tx, &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 { + c_tx_env, tx, utxos, ix, script_cmr, - inputs_hash, - outputs_hash, + control_block, + annex, + genesis_hash, } } + + /// Obtains the FFI compatible CTxEnv from self + pub fn c_tx_env(&self) -> &CElementsTxEnv { + &self.c_tx_env + } + + /// Returns a reference to the control block of this [`ElementsEnv`]. + pub fn control_block(&self) -> &ControlBlock { + &self.control_block + } + + /// Returns the annex of this [`ElementsEnv`]. + pub fn annex(&self) -> Option<&Vec> { + self.annex.as_ref() + } + + /// Returns the genesis hash of this [`ElementsEnv`]. + pub fn genesis_hash(&self) -> BlockHash { + self.genesis_hash + } } /// Helper trait for writing various components of diff --git a/src/jet/elements/exec.rs b/src/jet/elements/exec.rs index 3632e846..75b17039 100644 --- a/src/jet/elements/exec.rs +++ b/src/jet/elements/exec.rs @@ -101,8 +101,8 @@ pub(crate) fn input_script_hash(mac: &mut BitMachine, env: &ElementsEnv) -> Resu mac.write_bit(is_valid_idx); if is_valid_idx { - let script_pubkey = env.utxos[idx].script_pubkey; - mac.write_bytes(&script_pubkey); + let script_pubkey = &env.utxos[idx].script_pubkey; + mac.write_bytes(script_pubkey.as_bytes()); } else { // 256 bits for hash. mac.skip(256); @@ -340,7 +340,7 @@ pub(crate) fn current_script_hash( let curr_idx = env.ix as usize; let curr_utxo = &env.utxos[curr_idx]; // TODO: cache these while creating utxo - mac.write_bytes(&curr_utxo.script_pubkey); + mac.write_bytes(curr_utxo.script_pubkey.as_bytes()); Ok(()) } @@ -419,20 +419,6 @@ pub(crate) fn current_issuance_token_amount( } } -pub(crate) fn inputs_hash(mac: &mut BitMachine, env: &ElementsEnv) -> Result<(), JetFailed> { - /* - inputHash(l) := - BE256(LE[prevOutpoint.txid]),LE32(prevOutpoint.vout),LE32(sequence),encIssuance(l[issuance]) - */ - mac.write_bytes(&env.inputs_hash); - Ok(()) -} - -pub(crate) fn outputs_hash(mac: &mut BitMachine, env: &ElementsEnv) -> Result<(), JetFailed> { - mac.write_bytes(&env.outputs_hash); - Ok(()) -} - pub(crate) fn num_inputs(mac: &mut BitMachine, env: &ElementsEnv) -> Result<(), JetFailed> { mac.write_u32(env.tx.input.len() as u32); Ok(()) diff --git a/src/jet/elements/mod.rs b/src/jet/elements/mod.rs index 09aea595..aea26872 100644 --- a/src/jet/elements/mod.rs +++ b/src/jet/elements/mod.rs @@ -12,14 +12,15 @@ // If not, see . // +mod c_env; mod environment; +#[allow(dead_code)] mod exec; #[cfg(test)] mod tests; use crate::jet::Elements; pub use environment::{ElementsEnv, ElementsUtxo}; -pub(crate) use exec::*; impl std::fmt::Display for Elements { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { diff --git a/src/jet/elements/tests.rs b/src/jet/elements/tests.rs index 2ac6e3ee..bbbfafa9 100644 --- a/src/jet/elements/tests.rs +++ b/src/jet/elements/tests.rs @@ -4,16 +4,17 @@ use crate::bititer::BitIter; use crate::core::RedeemNode; use crate::exec::BitMachine; use crate::jet::elements::{ElementsEnv, ElementsUtxo}; -use crate::jet::Elements; +use crate::jet::{Elements, Jet}; use crate::merkle::cmr::Cmr; use crate::merkle::common::MerkleRoot; use crate::test_progs::sighash_all; use bitcoin_hashes::sha256::Midstate; -use bitcoin_hashes::{sha256, Hash}; +use bitcoin_hashes::Hash; use elements::secp256k1_zkp::Tweak; +use elements::taproot::ControlBlock; use elements::{ - confidential, AssetId, AssetIssuance, OutPoint, Transaction, TxIn, TxInWitness, TxOut, - TxOutWitness, + confidential, AssetId, AssetIssuance, BlockHash, OutPoint, Transaction, TxIn, TxInWitness, + TxOut, TxOutWitness, }; #[test] @@ -72,6 +73,11 @@ fn exec_sighash_all() { 0x2f, 0xb7, 0x1d, 0xda, 0x90, 0xff, 0x4b, 0xef, 0x53, 0x70, 0xf2, 0x52, 0x26, 0xd3, 0xbc, 0x09, 0xfc, ]; + let ctrl_blk: [u8; 33] = [ + 0xc0, 0xeb, 0x04, 0xb6, 0x8e, 0x9a, 0x26, 0xd1, 0x16, 0x04, 0x6c, 0x76, 0xe8, 0xff, 0x47, + 0x33, 0x2f, 0xb7, 0x1d, 0xda, 0x90, 0xff, 0x4b, 0xef, 0x53, 0x70, 0xf2, 0x52, 0x26, 0xd3, + 0xbc, 0x09, 0xfc, + ]; let asset = confidential::Asset::Explicit(AssetId::from_inner(Midstate::from_inner(asset))); let tx = Transaction { version: 2, @@ -123,12 +129,21 @@ fn exec_sighash_all() { ], }; let utxo = ElementsUtxo { - script_pubkey: sha256::Hash::from_inner([0; 32]), + script_pubkey: elements::Script::new(), asset, value: confidential::Value::Explicit(0x00000002540be400), }; + let ctrl_block = ControlBlock::from_slice(&ctrl_blk).expect("ctrl block from slice"); let script_cmr = Cmr::from(sighash_all::SIGHASH_ALL_CMR); - let env = ElementsEnv::new(Arc::new(tx), vec![utxo], 0, script_cmr); + let env = ElementsEnv::new( + Arc::new(tx), + vec![utxo], + 0, + script_cmr, + ctrl_block, + None, + BlockHash::default(), + ); let mut bits: BitIter<_> = sighash_all::ELEMENTS_CHECK_SIGHASH_ALL .iter() @@ -140,6 +155,107 @@ fn exec_sighash_all() { mac.exec(&program, &env).unwrap(); } +#[test] +fn test_ffi_env() { + let asset: [u8; 32] = [ + 0x23, 0x0f, 0x4f, 0x5d, 0x4b, 0x7c, 0x6f, 0xa8, 0x45, 0x80, 0x6e, 0xe4, 0xf6, 0x77, 0x13, + 0x45, 0x9e, 0x1b, 0x69, 0xe8, 0xe6, 0x0f, 0xce, 0xe2, 0xe4, 0x94, 0x0c, 0x7a, 0x0d, 0x5d, + 0xe1, 0xb2, + ]; + let tx_id: [u8; 32] = [ + 0xeb, 0x04, 0xb6, 0x8e, 0x9a, 0x26, 0xd1, 0x16, 0x04, 0x6c, 0x76, 0xe8, 0xff, 0x47, 0x33, + 0x2f, 0xb7, 0x1d, 0xda, 0x90, 0xff, 0x4b, 0xef, 0x53, 0x70, 0xf2, 0x52, 0x26, 0xd3, 0xbc, + 0x09, 0xfc, + ]; + let ctrl_blk: [u8; 33] = [ + 0xc0, 0xeb, 0x04, 0xb6, 0x8e, 0x9a, 0x26, 0xd1, 0x16, 0x04, 0x6c, 0x76, 0xe8, 0xff, 0x47, + 0x33, 0x2f, 0xb7, 0x1d, 0xda, 0x90, 0xff, 0x4b, 0xef, 0x53, 0x70, 0xf2, 0x52, 0x26, 0xd3, + 0xbc, 0x09, 0xfc, + ]; + let asset = confidential::Asset::Explicit(AssetId::from_inner(Midstate::from_inner(asset))); + let tx = Transaction { + version: 2, + lock_time: 100, + input: vec![TxIn { + previous_output: OutPoint { + txid: elements::Txid::from_inner(tx_id), + vout: 0, + }, + sequence: 0xfffffffe, + is_pegin: false, + has_issuance: false, + // perhaps make this an option in elements upstream? + asset_issuance: AssetIssuance { + asset_blinding_nonce: Tweak::from_inner([0; 32]).expect("tweak from inner"), + asset_entropy: [0; 32], + amount: confidential::Value::Null, + inflation_keys: confidential::Value::Null, + }, + script_sig: elements::Script::new(), + witness: TxInWitness { + amount_rangeproof: None, + inflation_keys_rangeproof: None, + script_witness: vec![ctrl_blk.to_vec()], + pegin_witness: vec![], + }, + }], + output: vec![ + TxOut { + asset: asset.clone(), + value: confidential::Value::Explicit(0x00000002540bd71c), + nonce: confidential::Nonce::Null, + script_pubkey: hex_script(&"1976a91448633e2c0ee9495dd3f9c43732c47f4702a362c888ac"), + witness: TxOutWitness { + surjection_proof: None, + rangeproof: None, + }, + }, + TxOut { + asset: asset.clone(), + value: confidential::Value::Explicit(0x0000000000000ce4), + nonce: confidential::Nonce::Null, + script_pubkey: elements::Script::new(), + witness: TxOutWitness { + surjection_proof: None, + rangeproof: None, + }, + }, + ], + }; + let utxo = ElementsUtxo { + script_pubkey: elements::Script::new(), + asset, + value: confidential::Value::Explicit(0x00000002540be400), + }; + let ctrl_block = ControlBlock::from_slice(&ctrl_blk).expect("ctrl block from slice"); + let script_cmr = Cmr::from(sighash_all::SIGHASH_ALL_CMR); + let env = ElementsEnv::new( + Arc::new(tx), + vec![utxo], + 0, + script_cmr, + ctrl_block, + None, + BlockHash::default(), + ); + + let mut mac = BitMachine { + data: vec![0; 1000], + next_frame_start: 0, + read: Vec::with_capacity(5), + write: Vec::with_capacity(5), + }; + let jet = Elements::LockTime; + mac.new_frame(100); + mac.write_u32(2); + mac.move_frame(); + mac.new_frame(35); + jet.exec(&mut mac, &env).unwrap(); + mac.move_frame(); + let res = mac.read_u32(); + assert_eq!(res, 100); +} + fn hex_script(s: &str) -> elements::Script { let v: Vec = bitcoin_hashes::hex::FromHex::from_hex(s).unwrap(); elements::Script::from(v) diff --git a/src/jet/init/bitcoin.rs b/src/jet/init/bitcoin.rs index 12f846dd..8122dd65 100644 --- a/src/jet/init/bitcoin.rs +++ b/src/jet/init/bitcoin.rs @@ -2,13 +2,13 @@ use crate::bititer::BitIter; use crate::bitwriter::BitWriter; -use crate::exec::BitMachine; use crate::jet::bitcoin::BitcoinEnv; use crate::jet::type_name::TypeName; -use crate::jet::{Jet, JetFailed}; +use crate::jet::Jet; use crate::merkle::cmr::Cmr; use crate::{decode_bits, Error}; use bitcoin_hashes::sha256::Midstate; +use simplicity_sys::c_jets::frame_ffi::CFrameItem; use std::io::Write; /// Bitcoin jet family @@ -49,6 +49,11 @@ pub enum Bitcoin { impl Jet for Bitcoin { type Environment = BitcoinEnv; + type CJetEnvironment = (); + + fn c_jet_env<'env>(&self, _env: &'env Self::Environment) -> &'env Self::CJetEnvironment { + unimplemented!("Unspecified CJetEnvironment for Bitcoin jets") + } fn cmr(&self) -> Cmr { let bytes = match self { @@ -378,39 +383,10 @@ impl Jet for Bitcoin { }) } - fn exec(&self) -> fn(&mut BitMachine, &Self::Environment) -> Result<(), JetFailed> { - match self { - Bitcoin::Version => crate::jet::bitcoin::version, - Bitcoin::LockTime => crate::jet::bitcoin::lock_time, - Bitcoin::InputsHash => crate::jet::bitcoin::inputs_hash, - Bitcoin::OutputsHash => crate::jet::bitcoin::outputs_hash, - Bitcoin::CurrentValue => crate::jet::bitcoin::current_value, - Bitcoin::CurrentIndex => crate::jet::bitcoin::current_index, - Bitcoin::Add32 => crate::jet::bitcoin::add_32, - Bitcoin::FullAdd32 => crate::jet::bitcoin::full_add_32, - Bitcoin::Sub32 => crate::jet::bitcoin::sub_32, - Bitcoin::FullSub32 => crate::jet::bitcoin::full_sub_32, - Bitcoin::Mul32 => crate::jet::bitcoin::mul_32, - Bitcoin::FullMul32 => crate::jet::bitcoin::full_mul_32, - Bitcoin::Eq32Verify => crate::jet::bitcoin::eq_32_verify, - Bitcoin::Eq256Verify => crate::jet::bitcoin::eq_256_verify, - Bitcoin::Lt32Verify => crate::jet::bitcoin::lt_32_verify, - Bitcoin::Sha256 => crate::jet::bitcoin::sha256, - Bitcoin::Sha256Block => crate::jet::bitcoin::sha256_block, - Bitcoin::NumInputs - | Bitcoin::TotalInputValue - | Bitcoin::CurrentPrevOutpoint - | Bitcoin::CurrentSequence - | Bitcoin::InputPrevOutpoint - | Bitcoin::InputValue - | Bitcoin::InputSequence - | Bitcoin::NumOutputs - | Bitcoin::TotalOutputValue - | Bitcoin::OutputValue - | Bitcoin::OutputScriptHash - | Bitcoin::ScriptCMR - | Bitcoin::SighashAll - | Bitcoin::Bip0340Verify => unimplemented!("Undefined jet execution"), - } + fn c_jet_ptr( + &self, + ) -> &'static dyn Fn(&mut CFrameItem, CFrameItem, &Self::CJetEnvironment) -> bool { + // TODO: Figure out How to do bitcoin jets here? + unimplemented!("Undefined bitcoin jets") } } diff --git a/src/jet/init/core.rs b/src/jet/init/core.rs index 8fac3d84..8836dfff 100644 --- a/src/jet/init/core.rs +++ b/src/jet/init/core.rs @@ -2,12 +2,12 @@ use crate::bititer::BitIter; use crate::bitwriter::BitWriter; -use crate::exec::BitMachine; use crate::jet::type_name::TypeName; -use crate::jet::{Jet, JetFailed}; +use crate::jet::Jet; use crate::merkle::cmr::Cmr; use crate::{decode_bits, Error}; use bitcoin_hashes::sha256::Midstate; +use simplicity_sys::CFrameItem; use std::io::Write; /// Core jet family @@ -29,6 +29,11 @@ pub enum Core { impl Jet for Core { type Environment = (); + type CJetEnvironment = (); + + fn c_jet_env<'env>(&self, env: &'env Self::Environment) -> &'env Self::CJetEnvironment { + env + } fn cmr(&self) -> Cmr { let bytes = match self { @@ -203,20 +208,22 @@ impl Jet for Core { }) } - fn exec(&self) -> fn(&mut BitMachine, &Self::Environment) -> Result<(), JetFailed> { + fn c_jet_ptr( + &self, + ) -> &'static dyn Fn(&mut CFrameItem, CFrameItem, &Self::Environment) -> bool { match self { - Core::Add32 => crate::jet::core::add_32, - Core::FullAdd32 => crate::jet::core::full_add_32, - Core::Sub32 => crate::jet::core::sub_32, - Core::FullSub32 => crate::jet::core::full_sub_32, - Core::Mul32 => crate::jet::core::mul_32, - Core::FullMul32 => crate::jet::core::full_mul_32, - Core::Eq32Verify => crate::jet::core::eq_32_verify, - Core::Eq256Verify => crate::jet::core::eq_256_verify, - Core::Lt32Verify => crate::jet::core::lt_32_verify, - Core::Sha256 => crate::jet::core::sha256, - Core::Sha256Block => crate::jet::core::sha256_block, - Core::Bip0340Verify => unimplemented!("Unknown jet execution"), + Core::Add32 => &simplicity_sys::c_jets::jets_wrapper::add_32, + Core::FullAdd32 => todo!(), + Core::Sub32 => todo!(), + Core::FullSub32 => todo!(), + Core::Mul32 => todo!(), + Core::FullMul32 => todo!(), + Core::Eq32Verify => todo!(), + Core::Eq256Verify => todo!(), + Core::Lt32Verify => todo!(), + Core::Sha256 => todo!(), + Core::Sha256Block => todo!(), + Core::Bip0340Verify => todo!(), } } } diff --git a/src/jet/init/elements.rs b/src/jet/init/elements.rs index 74572834..b16fb2b3 100644 --- a/src/jet/init/elements.rs +++ b/src/jet/init/elements.rs @@ -2,13 +2,13 @@ use crate::bititer::BitIter; use crate::bitwriter::BitWriter; -use crate::exec::BitMachine; use crate::jet::elements::ElementsEnv; use crate::jet::type_name::TypeName; -use crate::jet::{Jet, JetFailed}; +use crate::jet::Jet; use crate::merkle::cmr::Cmr; use crate::{decode_bits, Error}; use bitcoin_hashes::sha256::Midstate; +use simplicity_sys::{CElementsTxEnv, CFrameItem}; use std::io::Write; /// Elements jet family @@ -66,6 +66,11 @@ pub enum Elements { impl Jet for Elements { type Environment = ElementsEnv; + type CJetEnvironment = CElementsTxEnv; + + fn c_jet_env<'env>(&self, env: &'env Self::Environment) -> &'env Self::CJetEnvironment { + env.c_tx_env() + } fn cmr(&self) -> Cmr { let bytes = match self { @@ -633,60 +638,58 @@ impl Jet for Elements { }) } - fn exec(&self) -> fn(&mut BitMachine, &Self::Environment) -> Result<(), JetFailed> { + fn c_jet_ptr( + &self, + ) -> &'static dyn Fn(&mut CFrameItem, CFrameItem, &Self::CJetEnvironment) -> bool { match self { - Elements::Version => crate::jet::elements::version, - Elements::LockTime => crate::jet::elements::lock_time, - Elements::InputIsPegin => crate::jet::elements::input_is_pegin, - Elements::InputPrevOutpoint => crate::jet::elements::input_prev_outpoint, - Elements::InputAsset => crate::jet::elements::input_asset, - Elements::InputAmount => crate::jet::elements::input_amount, - Elements::InputScriptHash => crate::jet::elements::input_script_hash, - Elements::InputSequence => crate::jet::elements::input_sequence, - Elements::InputIssuanceBlinding => crate::jet::elements::input_issuance_blinding, - Elements::InputIssuanceContract => crate::jet::elements::input_issuance_contract, - Elements::InputIssuanceEntropy => crate::jet::elements::input_issuance_entropy, - Elements::InputIssuanceAssetAmount => crate::jet::elements::input_issuance_asset_amount, - Elements::InputIssuanceTokenAmount => crate::jet::elements::input_issuance_token_amount, - Elements::OutputAsset => crate::jet::elements::output_asset, - Elements::OutputAmount => crate::jet::elements::output_amount, - Elements::OutputNonce => crate::jet::elements::output_nonce, - Elements::OutputScriptHash => crate::jet::elements::output_script_hash, - Elements::ScriptCmr => crate::jet::elements::script_cmr, - Elements::CurrentIndex => crate::jet::elements::current_index, - Elements::CurrentIsPegin => crate::jet::elements::current_is_pegin, - Elements::CurrentPrevOutpoint => crate::jet::elements::current_prev_outpoint, - Elements::CurrentAsset => crate::jet::elements::current_asset, - Elements::CurrentAmount => crate::jet::elements::current_amount, - Elements::CurrentScriptHash => crate::jet::elements::current_script_hash, - Elements::CurrentSequence => crate::jet::elements::current_sequence, - Elements::CurrentIssuanceBlinding => crate::jet::elements::current_issuance_blinding, - Elements::CurrentIssuanceContract => crate::jet::elements::current_issuance_contract, - Elements::CurrentIssuanceEntropy => crate::jet::elements::current_issuance_entropy, - Elements::CurrentIssuanceAssetAmount => { - crate::jet::elements::current_issuance_asset_amount - } - Elements::CurrentIssuanceTokenAmount => { - crate::jet::elements::current_issuance_token_amount - } - Elements::InputsHash => crate::jet::elements::inputs_hash, - Elements::OutputsHash => crate::jet::elements::outputs_hash, - Elements::NumInputs => crate::jet::elements::num_inputs, - Elements::NumOutputs => crate::jet::elements::num_outputs, - Elements::Add32 => crate::jet::elements::add_32, - Elements::FullAdd32 => crate::jet::elements::full_add_32, - Elements::Sub32 => crate::jet::elements::sub_32, - Elements::FullSub32 => crate::jet::elements::full_sub_32, - Elements::Mul32 => crate::jet::elements::mul_32, - Elements::FullMul32 => crate::jet::elements::full_mul_32, - Elements::Eq32Verify => crate::jet::elements::eq_32_verify, - Elements::Eq256Verify => crate::jet::elements::eq_256_verify, - Elements::Lt32Verify => crate::jet::elements::lt_32_verify, - Elements::Sha256 => crate::jet::elements::sha256, - Elements::Sha256Block => crate::jet::elements::sha256_block, - Elements::OutputNullDatum | Elements::Fee | Elements::Bip0340Verify => { - unimplemented!("Undefined jet execution") - } + Elements::Version => unimplemented!(), + Elements::LockTime => &simplicity_sys::c_jets::jets_wrapper::lock_time, + Elements::InputIsPegin => unimplemented!(), + Elements::InputPrevOutpoint => unimplemented!(), + Elements::InputAsset => unimplemented!(), + Elements::InputAmount => unimplemented!(), + Elements::InputScriptHash => unimplemented!(), + Elements::InputSequence => unimplemented!(), + Elements::InputIssuanceBlinding => unimplemented!(), + Elements::InputIssuanceContract => unimplemented!(), + Elements::InputIssuanceEntropy => unimplemented!(), + Elements::InputIssuanceAssetAmount => unimplemented!(), + Elements::InputIssuanceTokenAmount => unimplemented!(), + Elements::OutputAsset => unimplemented!(), + Elements::OutputAmount => unimplemented!(), + Elements::OutputNonce => unimplemented!(), + Elements::OutputScriptHash => unimplemented!(), + Elements::OutputNullDatum => unimplemented!(), + Elements::ScriptCmr => unimplemented!(), + Elements::CurrentIndex => unimplemented!(), + Elements::CurrentIsPegin => unimplemented!(), + Elements::CurrentPrevOutpoint => unimplemented!(), + Elements::CurrentAsset => unimplemented!(), + Elements::CurrentAmount => unimplemented!(), + Elements::CurrentScriptHash => unimplemented!(), + Elements::CurrentSequence => unimplemented!(), + Elements::CurrentIssuanceBlinding => unimplemented!(), + Elements::CurrentIssuanceContract => unimplemented!(), + Elements::CurrentIssuanceEntropy => unimplemented!(), + Elements::CurrentIssuanceAssetAmount => unimplemented!(), + Elements::CurrentIssuanceTokenAmount => unimplemented!(), + Elements::InputsHash => unimplemented!(), + Elements::OutputsHash => unimplemented!(), + Elements::NumInputs => unimplemented!(), + Elements::NumOutputs => unimplemented!(), + Elements::Fee => unimplemented!(), + Elements::Add32 => unimplemented!(), + Elements::FullAdd32 => unimplemented!(), + Elements::Sub32 => unimplemented!(), + Elements::FullSub32 => unimplemented!(), + Elements::Mul32 => unimplemented!(), + Elements::FullMul32 => unimplemented!(), + Elements::Eq32Verify => unimplemented!(), + Elements::Eq256Verify => unimplemented!(), + Elements::Lt32Verify => unimplemented!(), + Elements::Sha256 => unimplemented!(), + Elements::Sha256Block => unimplemented!(), + Elements::Bip0340Verify => unimplemented!(), } } } diff --git a/src/jet/mod.rs b/src/jet/mod.rs index 7c08ce1c..1089d920 100644 --- a/src/jet/mod.rs +++ b/src/jet/mod.rs @@ -37,9 +37,12 @@ pub use init::bitcoin::Bitcoin; pub use init::core::Core; #[cfg(feature = "elements")] pub use init::elements::Elements; +use simplicity_sys::c_jets::frame_ffi::{c_readBit, c_writeBit, CFrameItem}; +use simplicity_sys::c_jets::round_u_word; use crate::bititer::BitIter; use crate::bitwriter::BitWriter; +use crate::core::types::Type; use crate::exec::BitMachine; use crate::jet::type_name::TypeName; use crate::merkle::cmr::Cmr; @@ -74,6 +77,8 @@ impl std::error::Error for JetFailed {} pub trait Jet: Copy + Eq + Ord + Hash + std::fmt::Debug + std::fmt::Display { /// Environment for jet to read from type Environment; + /// CJetEnvironment to interact with C FFI. + type CJetEnvironment; /// Return the CMR of the jet. fn cmr(&self) -> Cmr; @@ -95,6 +100,126 @@ pub trait Jet: Copy + Eq + Ord + Hash + std::fmt::Debug + std::fmt::Display { /// Decode a jet from bits. fn decode>(bits: &mut BitIter) -> Result; + /// Obtains a C FFI compatible environment for the jet. + fn c_jet_env<'env>(&self, env: &'env Self::Environment) -> &'env Self::CJetEnvironment; + + /// Obtain the FFI C pointer for the jet. + fn c_jet_ptr(&self) -> &dyn Fn(&mut CFrameItem, CFrameItem, &Self::CJetEnvironment) -> bool; + /// Execute the jet on the Bit Machine, using the given environment. - fn exec(&self) -> fn(&mut BitMachine, &Self::Environment) -> Result<(), JetFailed>; + fn exec<'env>( + &self, + mac: &mut BitMachine, + env: &'env Self::Environment, + ) -> Result<(), JetFailed> + where + Self::CJetEnvironment: 'env, + { + // Sanity Check: This should never really fail, but still good to do + if !simplicity_sys::c_jets::sanity_checks() { + return Err(JetFailed); + } + let pows_of_two = Type::powers_of_two(); // TODO: make this compile time static + let src_ty = self.source_ty().to_type(&pows_of_two); + let target_ty = self.target_ty().to_type(&pows_of_two); + + let a_frame_size = round_u_word(src_ty.bit_width); + let b_frame_size = round_u_word(target_ty.bit_width); + // a_frame_size + b_frame_size must be non-zero unless it is a unit to unit jet + if a_frame_size == 0 && b_frame_size == 0 { + return Ok(()); + } + let mut src_buf = vec![0usize; a_frame_size + b_frame_size]; + let src_ptr_end = unsafe { src_buf.as_mut_ptr().add(a_frame_size) }; // A frame write + let src_ptr = src_buf.as_mut_ptr(); // A read frame at ptr start + let dst_ptr_begin = unsafe { src_buf.as_mut_ptr().add(a_frame_size) }; // B read frame at ptr begin + let dst_ptr_end = unsafe { src_buf.as_mut_ptr().add(a_frame_size + b_frame_size) }; // B write frame at ptr end + + // For jet from type A -> B + // Jets execution: There is single buffer with a_frame_size + b_frame_size UWORDs + // ------[ A read frame ][ B write frame ]--- + // ^ src_ptr ^src_ptr_end(dst_ptr_begin) ^ dst_ptr_end + // 1. Write into C bitmachine using A write frame(= src_ptr_end) + // Precondition satisfied: src_ptr_end is one past the end of slice of UWORDs for A. + let mut a_frame = unsafe { CFrameItem::new_write(src_ty.bit_width, src_ptr_end) }; + for _ in 0..src_ty.bit_width { + let bit = mac.read_bit(); + unsafe { + c_writeBit(&mut a_frame, bit); + } + } + + // 2. Execute the jet. src = A read frame, dst = B write frame + // Precondition satisfied: src_ptr is the start of slice of UWORDs of A. + let src_frame = unsafe { CFrameItem::new_read(src_ty.bit_width, src_ptr) }; + // Precondition satisfied: dst_ptr_end is one past the end of slice of UWORDs of B. + let mut dst_frame = unsafe { CFrameItem::new_write(target_ty.bit_width, dst_ptr_end) }; + let jet_fn = self.c_jet_ptr(); + let c_env = self.c_jet_env(env); + let res = jet_fn(&mut dst_frame, src_frame, c_env); + + if !res { + return Err(JetFailed); + } + + // 3. Read the result from B read frame + // Precondition satisfied: dst_ptr_begin is the start of slice of UWORDs of B. + let mut b_frame = unsafe { CFrameItem::new_read(target_ty.bit_width, dst_ptr_begin) }; + // Read the value from b_frame + for _ in 0..target_ty.bit_width { + let bit = unsafe { c_readBit(&mut b_frame) }; + mac.write_bit(bit); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::{exec::BitMachine, jet::Jet}; + + #[test] + fn test_ffi_jet() { + // Indirectly create bit machine + let mut mac = BitMachine { + data: vec![0u8; 1024], + next_frame_start: 0, + read: vec![], + write: vec![], + }; + mac.new_frame_unchecked(64); + mac.write_u32(2); + mac.write_u32(16); + mac.move_frame(); + + mac.new_frame_unchecked(33); + let env = (); + let jet = super::init::core::Core::Add32; + jet.exec(&mut mac, &env).unwrap(); + + mac.move_frame(); + let carry = mac.read_bit(); + let res = mac.read_u32(); + assert_eq!(carry, false); + assert_eq!(res, 2 + 16); + } + + #[test] + fn test_simple() { + let mut mac = BitMachine { + data: vec![0u8; 1024], + next_frame_start: 0, + read: vec![], + write: vec![], + }; + + mac.new_frame_unchecked(64); + mac.write_u32(2); + mac.write_u32(16); + mac.move_frame(); + let x = mac.read_u32(); + let y = mac.read_u32(); + assert_eq!(x, 2); + assert_eq!(y, 16); + } } diff --git a/src/jet/type_name.rs b/src/jet/type_name.rs index 0e14f157..e9be8bc8 100644 --- a/src/jet/type_name.rs +++ b/src/jet/type_name.rs @@ -16,8 +16,10 @@ //! //! Source and target types of jet nodes need to be specified manually. -use crate::core::types::VariableType; +use std::sync::Arc; + use crate::core::types::{RcVar, Variable}; +use crate::core::types::{Type, VariableType}; /// Byte-based specification of a Simplicity type. /// @@ -52,7 +54,7 @@ impl TypeName { // b'+' = 43 // b'*' = 42 /// Convert the type name into a type. - pub(crate) fn to_type(&self, pow2s: &[RcVar]) -> VariableType { + pub(crate) fn to_variable_type(&self, pow2s: &[RcVar]) -> VariableType { let it = self.0.iter().rev(); let mut stack = Vec::new(); @@ -88,4 +90,41 @@ impl TypeName { panic!("Illegal type name syntax!") } } + + // TODO: In future commit change to return Type instead of Arc. + // Would require some refactors internally. + pub(crate) fn to_type(&self, pow2s: &[Arc]) -> Arc { + let it = self.0.iter().rev(); + let mut stack = Vec::new(); + + let unit = Type::unit(); + for c in it { + match c { + b'1' => stack.push(Type::unit()), + b'2' => { + stack.push(Type::sum(Arc::clone(&unit), Arc::clone(&unit))); + } + b'i' => stack.push(Type::product(pow2s[4].clone(), pow2s[4].clone())), + b'l' => stack.push(Type::product(pow2s[5].clone(), pow2s[5].clone())), + b'h' => stack.push(Type::product(pow2s[7].clone(), pow2s[7].clone())), + b'+' | b'*' => { + let left = stack.pop().expect("Illegal type name syntax!"); + let right = stack.pop().expect("Illegal type name syntax!"); + + match c { + b'+' => stack.push(Type::sum(left, right)), + b'*' => stack.push(Type::product(left, right)), + _ => unreachable!(), + } + } + _ => panic!("Illegal type name syntax!"), + } + } + + if stack.len() == 1 { + stack.pop().unwrap() + } else { + panic!("Illegal type name syntax!") + } + } } diff --git a/src/lib.rs b/src/lib.rs index acf5d430..f492217b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,8 +37,8 @@ mod encode; mod inference; pub mod jet; pub mod merkle; -#[cfg(feature = "bitcoin")] -pub mod policy; +// #[cfg(feature = "bitcoin")] +// pub mod policy; mod sharing; #[cfg(test)] mod test_progs; @@ -78,7 +78,7 @@ pub enum Error { RightChildNotHidden, /// Left child of right assertion must be hidden LeftChildNotHidden, - /// Bitstream ended early + /// Bitstream ended early EndOfStream, /// Program must not be empty EmptyProgram,