From acbc6fca0cb6a4eb2798175b35d9391761b59b27 Mon Sep 17 00:00:00 2001 From: Russell O'Connor Date: Mon, 21 Apr 2025 16:20:40 -0400 Subject: [PATCH 1/4] Implementation of exec.c for bitcoin --- C/Makefile | 2 +- C/bitcoin/exec.c | 136 ++++++++++++++++++++++++++++ C/include/simplicity/bitcoin/exec.h | 41 +++++++++ 3 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 C/bitcoin/exec.c create mode 100644 C/include/simplicity/bitcoin/exec.h diff --git a/C/Makefile b/C/Makefile index dcc9a4f7..bbeaaf09 100644 --- a/C/Makefile +++ b/C/Makefile @@ -1,5 +1,5 @@ CORE_OBJS := bitstream.o dag.o deserialize.o eval.o frame.o jets.o jets-secp256k1.o rsort.o sha256.o type.o typeInference.o -BITCOIN_OBJS := bitcoin/env.o bitcoin/ops.o bitcoin/bitcoinJets.o bitcoin/primitive.o bitcoin/txEnv.o +BITCOIN_OBJS := bitcoin/env.o bitcoin/exec.o bitcoin/ops.o bitcoin/bitcoinJets.o bitcoin/primitive.o bitcoin/txEnv.o ELEMENTS_OBJS := elements/env.o elements/exec.o elements/ops.o elements/elementsJets.o elements/primitive.o elements/cmr.o elements/txEnv.o TEST_OBJS := test.o ctx8Pruned.o ctx8Unpruned.o hashBlock.o regression4.o schnorr0.o schnorr6.o typeSkipTest.o elements/checkSigHashAllTx1.o diff --git a/C/bitcoin/exec.c b/C/bitcoin/exec.c new file mode 100644 index 00000000..36a4a879 --- /dev/null +++ b/C/bitcoin/exec.c @@ -0,0 +1,136 @@ +#include + +#include +#include +#include "primitive.h" +#include "txEnv.h" +#include "../deserialize.h" +#include "../eval.h" +#include "../limitations.h" +#include "../simplicity_alloc.h" +#include "../simplicity_assert.h" +#include "../typeInference.h" + +/* Deserialize a Simplicity 'program' with its 'witness' data and execute it in the environment of the 'ix'th input of 'tx' with `taproot`. + * + * If at any time malloc fails then '*error' is set to 'SIMPLICITY_ERR_MALLOC' and 'false' is returned, + * meaning we were unable to determine the result of the simplicity program. + * Otherwise, 'true' is returned indicating that the result was successfully computed and returned in the '*error' value. + * + * If deserialization, analysis, or execution fails, then '*error' is set to some simplicity_err. + * In particular, if the cost analysis exceeds the budget, or exceeds BUDGET_MAX, then '*error' is set to 'SIMPLICITY_ERR_EXEC_BUDGET'. + * On the other hand, if the cost analysis is less than or equal to minCost, then '*error' is set to 'SIMPLICITY_ERR_OVERWEIGHT'. + * + * Note that minCost and budget parameters are in WU, while the cost analysis will be performed in milliWU. + * Thus the minCost and budget specify a half open interval (minCost, budget] of acceptable cost values in milliWU. + * Setting minCost to 0 effectively disables the minCost check as every Simplicity program has a non-zero cost analysis. + * + * If 'amr != NULL' and the annotated Merkle root of the decoded expression doesn't match 'amr' then '*error' is set to 'SIMPLICITY_ERR_AMR'. + * + * Otherwise '*error' is set to 'SIMPLICITY_NO_ERROR'. + * + * If 'ihr != NULL' and '*error' is set to 'SIMPLICITY_NO_ERROR', then the identity hash of the root of the decoded expression is written to 'ihr'. + * Otherwise if 'ihr != NULL' and '*error' is not set to 'SIMPLCITY_NO_ERROR', then 'ihr' may or may not be written to. + * + * Precondition: NULL != error; + * NULL != ihr implies unsigned char ihr[32] + * NULL != tx; + * NULL != taproot; + * 0 <= minCost <= budget; + * NULL != amr implies unsigned char amr[32] + * unsigned char program[program_len] + * unsigned char witness[witness_len] + */ +extern bool simplicity_bitcoin_execSimplicity( simplicity_err* error, unsigned char* ihr + , const bitcoinTransaction* tx, uint_fast32_t ix, const bitcoinTapEnv* taproot + , int64_t minCost, int64_t budget + , const unsigned char* amr + , const unsigned char* program, size_t program_len + , const unsigned char* witness, size_t witness_len) { + simplicity_assert(NULL != error); + simplicity_assert(NULL != tx); + simplicity_assert(NULL != taproot); + simplicity_assert(0 <= minCost); + simplicity_assert(minCost <= budget); + simplicity_assert(NULL != program || 0 == program_len); + simplicity_assert(NULL != witness || 0 == witness_len); + + combinator_counters census; + dag_node* dag = NULL; + int_fast32_t dag_len; + sha256_midstate amr_hash; + + if (amr) sha256_toMidstate(amr_hash.s, amr); + + { + bitstream stream = initializeBitstream(program, program_len); + dag_len = simplicity_decodeMallocDag(&dag, simplicity_bitcoin_decodeJet, &census, &stream); + if (dag_len <= 0) { + simplicity_assert(dag_len < 0); + *error = (simplicity_err)dag_len; + return IS_PERMANENT(*error); + } + simplicity_assert(NULL != dag); + simplicity_assert((uint_fast32_t)dag_len <= DAG_LEN_MAX); + *error = simplicity_closeBitstream(&stream); + } + + if (IS_OK(*error)) { + if (0 != memcmp(taproot->scriptCMR.s, dag[dag_len-1].cmr.s, sizeof(uint32_t[8]))) { + *error = SIMPLICITY_ERR_CMR; + } + } + + if (IS_OK(*error)) { + type* type_dag = NULL; + *error = simplicity_mallocTypeInference(&type_dag, simplicity_bitcoin_mallocBoundVars, dag, (uint_fast32_t)dag_len, &census); + if (IS_OK(*error)) { + simplicity_assert(NULL != type_dag); + if (0 != dag[dag_len-1].sourceType || 0 != dag[dag_len-1].targetType) { + *error = SIMPLICITY_ERR_TYPE_INFERENCE_NOT_PROGRAM; + } + } + if (IS_OK(*error)) { + bitstream witness_stream = initializeBitstream(witness, witness_len); + *error = simplicity_fillWitnessData(dag, type_dag, (uint_fast32_t)dag_len, &witness_stream); + if (IS_OK(*error)) { + *error = simplicity_closeBitstream(&witness_stream); + if (SIMPLICITY_ERR_BITSTREAM_TRAILING_BYTES == *error) *error = SIMPLICITY_ERR_WITNESS_TRAILING_BYTES; + if (SIMPLICITY_ERR_BITSTREAM_ILLEGAL_PADDING == *error) *error = SIMPLICITY_ERR_WITNESS_ILLEGAL_PADDING; + } + } + if (IS_OK(*error)) { + sha256_midstate ihr_buf; + *error = simplicity_verifyNoDuplicateIdentityHashes(&ihr_buf, dag, type_dag, (uint_fast32_t)dag_len); + if (IS_OK(*error) && ihr) sha256_fromMidstate(ihr, ihr_buf.s); + } + if (IS_OK(*error) && amr) { + static_assert(DAG_LEN_MAX <= SIZE_MAX / sizeof(analyses), "analysis array too large."); + static_assert(1 <= DAG_LEN_MAX, "DAG_LEN_MAX is zero."); + static_assert(DAG_LEN_MAX - 1 <= UINT32_MAX, "analysis array index does nto fit in uint32_t."); + analyses *analysis = simplicity_malloc((size_t)dag_len * sizeof(analyses)); + if (analysis) { + simplicity_computeAnnotatedMerkleRoot(analysis, dag, type_dag, (uint_fast32_t)dag_len); + if (0 != memcmp(amr_hash.s, analysis[dag_len-1].annotatedMerkleRoot.s, sizeof(uint32_t[8]))) { + *error = SIMPLICITY_ERR_AMR; + } + } else { + /* malloc failed which counts as a transient error. */ + *error = SIMPLICITY_ERR_MALLOC; + } + simplicity_free(analysis); + } + if (IS_OK(*error)) { + txEnv env = simplicity_bitcoin_build_txEnv(tx, taproot, ix); + static_assert(BUDGET_MAX <= UBOUNDED_MAX, "BUDGET_MAX doesn't fit in ubounded."); + *error = evalTCOProgram( dag, type_dag, (size_t)dag_len + , minCost <= BUDGET_MAX ? (ubounded)minCost : BUDGET_MAX + , &(ubounded){budget <= BUDGET_MAX ? (ubounded)budget : BUDGET_MAX} + , &env); + } + simplicity_free(type_dag); + } + + simplicity_free(dag); + return IS_PERMANENT(*error); +} diff --git a/C/include/simplicity/bitcoin/exec.h b/C/include/simplicity/bitcoin/exec.h new file mode 100644 index 00000000..cd1f3b65 --- /dev/null +++ b/C/include/simplicity/bitcoin/exec.h @@ -0,0 +1,41 @@ +#ifndef SIMPLICITY_BITCOIN_EXEC_H +#define SIMPLICITY_BITCOIN_EXEC_H + +#include +#include +#include +#include +#include + +/* Deserialize a Simplicity 'program' with its 'witness' data and execute it in the environment of the 'ix'th input of 'tx' with `taproot`. + * + * If at any time malloc fails then '*error' is set to 'SIMPLICITY_ERR_MALLOC' and 'false' is returned, + * meaning we were unable to determine the result of the simplicity program. + * Otherwise, 'true' is returned indicating that the result was successfully computed and returned in the '*error' value. + * + * If deserialization, analysis, or execution fails, then '*error' is set to some simplicity_err. + * + * If 'amr != NULL' and the annotated Merkle root of the decoded expression doesn't match 'amr' then '*error' is set to 'SIMPLICITY_ERR_AMR'. + * + * Otherwise '*error' is set to 'SIMPLICITY_NO_ERROR'. + * + * If 'ihr != NULL' and '*error' is set to 'SIMPLICITY_NO_ERROR', then the identity hash of the root of the decoded expression is written to 'ihr'. + * Otherwise if 'ihr != NULL' and '*error' is not set to 'SIMPLCITY_NO_ERROR', then 'ihr' may or may not be written to. + * + * Precondition: NULL != error; + * NULL != ihr implies unsigned char ihr[32] + * NULL != tx; + * NULL != taproot; + * 0 <= budget; + * 0 <= minCost <= budget; + * NULL != amr implies unsigned char amr[32] + * unsigned char program[program_len] + * unsigned char witness[witness_len] + */ +extern bool simplicity_bitcoin_execSimplicity( simplicity_err* error, unsigned char* ihr + , const bitcoinTransaction* tx, uint_fast32_t ix, const bitcoinTapEnv* taproot + , int64_t minCost, int64_t budget + , const unsigned char* amr + , const unsigned char* program, size_t program_len + , const unsigned char* witness, size_t witness_len); +#endif From 73be5568ad6c75f028db75b84e9ccb4036bbc099 Mon Sep 17 00:00:00 2001 From: Russell O'Connor Date: Mon, 12 May 2025 16:37:06 -0400 Subject: [PATCH 2/4] Implementation of cmr.c for bitcoin --- C/Makefile | 4 +-- C/bitcoin/cmr.c | 22 ++++++++++++++++ C/cmr.c | 41 ++++++++++++++++++++++++++++++ C/cmr.h | 24 +++++++++++++++++ C/elements/cmr.c | 25 ++---------------- C/include/simplicity/bitcoin/cmr.h | 23 +++++++++++++++++ 6 files changed, 114 insertions(+), 25 deletions(-) create mode 100644 C/bitcoin/cmr.c create mode 100644 C/cmr.c create mode 100644 C/cmr.h create mode 100644 C/include/simplicity/bitcoin/cmr.h diff --git a/C/Makefile b/C/Makefile index bbeaaf09..e3d90ac5 100644 --- a/C/Makefile +++ b/C/Makefile @@ -1,5 +1,5 @@ -CORE_OBJS := bitstream.o dag.o deserialize.o eval.o frame.o jets.o jets-secp256k1.o rsort.o sha256.o type.o typeInference.o -BITCOIN_OBJS := bitcoin/env.o bitcoin/exec.o bitcoin/ops.o bitcoin/bitcoinJets.o bitcoin/primitive.o bitcoin/txEnv.o +CORE_OBJS := bitstream.o cmr.o dag.o deserialize.o eval.o frame.o jets.o jets-secp256k1.o rsort.o sha256.o type.o typeInference.o +BITCOIN_OBJS := bitcoin/env.o bitcoin/exec.o bitcoin/ops.o bitcoin/bitcoinJets.o bitcoin/primitive.o bitcoin/cmr.o bitcoin/txEnv.o ELEMENTS_OBJS := elements/env.o elements/exec.o elements/ops.o elements/elementsJets.o elements/primitive.o elements/cmr.o elements/txEnv.o TEST_OBJS := test.o ctx8Pruned.o ctx8Unpruned.o hashBlock.o regression4.o schnorr0.o schnorr6.o typeSkipTest.o elements/checkSigHashAllTx1.o diff --git a/C/bitcoin/cmr.c b/C/bitcoin/cmr.c new file mode 100644 index 00000000..82135b27 --- /dev/null +++ b/C/bitcoin/cmr.c @@ -0,0 +1,22 @@ +#include + +#include "../cmr.h" +#include "primitive.h" + +/* Deserialize a Simplicity 'program' and compute its CMR. + * + * Caution: no typechecking is performed, only a well-formedness check. + * + * If at any time malloc fails then '*error' is set to 'SIMPLICITY_ERR_MALLOC' and 'false' is returned, + * Otherwise, 'true' is returned indicating that the result was successfully computed and returned in the '*error' value. + * + * If the operation completes successfully then '*error' is set to 'SIMPLICITY_NO_ERROR', and the 'cmr' array is filled in with the program's computed CMR. + * + * Precondition: NULL != error; + * unsigned char cmr[32] + * unsigned char program[program_len] + */ +bool simplicity_bitcoin_computeCmr( simplicity_err* error, unsigned char* cmr + , const unsigned char* program, size_t program_len) { + return simplicity_computeCmr(error, cmr, simplicity_bitcoin_decodeJet, program, program_len); +} diff --git a/C/cmr.c b/C/cmr.c new file mode 100644 index 00000000..e02ae4b6 --- /dev/null +++ b/C/cmr.c @@ -0,0 +1,41 @@ +#include "cmr.h" + +#include "limitations.h" +#include "simplicity_alloc.h" +#include "simplicity_assert.h" + +/* Deserialize a Simplicity 'program' and compute its CMR. + * + * Caution: no typechecking is performed, only a well-formedness check. + * + * If at any time malloc fails then '*error' is set to 'SIMPLICITY_ERR_MALLOC' and 'false' is returned, + * Otherwise, 'true' is returned indicating that the result was successfully computed and returned in the '*error' value. + * + * If the operation completes successfully then '*error' is set to 'SIMPLICITY_NO_ERROR', and the 'cmr' array is filled in with the program's computed CMR. + * + * Precondition: NULL != error; + * unsigned char cmr[32] + * unsigned char program[program_len] + */ +bool simplicity_computeCmr( simplicity_err* error, unsigned char* cmr, simplicity_callback_decodeJet decodeJet + , const unsigned char* program, size_t program_len) { + simplicity_assert(NULL != error); + simplicity_assert(NULL != cmr); + simplicity_assert(NULL != program || 0 == program_len); + + bitstream stream = initializeBitstream(program, program_len); + dag_node* dag = NULL; + int_fast32_t dag_len = simplicity_decodeMallocDag(&dag, decodeJet, NULL, &stream); + if (dag_len <= 0) { + simplicity_assert(dag_len < 0); + *error = (simplicity_err)dag_len; + } else { + simplicity_assert(NULL != dag); + simplicity_assert((uint_fast32_t)dag_len <= DAG_LEN_MAX); + *error = simplicity_closeBitstream(&stream); + sha256_fromMidstate(cmr, dag[dag_len-1].cmr.s); + } + + simplicity_free(dag); + return IS_PERMANENT(*error); +} diff --git a/C/cmr.h b/C/cmr.h new file mode 100644 index 00000000..9e7b0f86 --- /dev/null +++ b/C/cmr.h @@ -0,0 +1,24 @@ +#ifndef SIMPLICITY_CMR_H +#define SIMPLICITY_CMR_H + +#include +#include +#include +#include "deserialize.h" + +/* Deserialize a Simplicity 'program' and compute its CMR. + * + * Caution: no typechecking is performed, only a well-formedness check. + * + * If at any time malloc fails then '*error' is set to 'SIMPLICITY_ERR_MALLOC' and 'false' is returned, + * Otherwise, 'true' is returned indicating that the result was successfully computed and returned in the '*error' value. + * + * If the operation completes successfully then '*error' is set to 'SIMPLICITY_NO_ERROR', and the 'cmr' array is filled in with the program's computed CMR. + * + * Precondition: NULL != error; + * unsigned char cmr[32] + * unsigned char program[program_len] + */ +extern bool simplicity_computeCmr( simplicity_err* error, unsigned char* cmr, simplicity_callback_decodeJet decodeJet + , const unsigned char* program, size_t program_len); +#endif diff --git a/C/elements/cmr.c b/C/elements/cmr.c index dd73be4e..3f7dedeb 100644 --- a/C/elements/cmr.c +++ b/C/elements/cmr.c @@ -1,9 +1,6 @@ #include -#include "../deserialize.h" -#include "../limitations.h" -#include "../simplicity_alloc.h" -#include "../simplicity_assert.h" +#include "../cmr.h" #include "primitive.h" /* Deserialize a Simplicity 'program' and compute its CMR. @@ -21,23 +18,5 @@ */ bool simplicity_elements_computeCmr( simplicity_err* error, unsigned char* cmr , const unsigned char* program, size_t program_len) { - simplicity_assert(NULL != error); - simplicity_assert(NULL != cmr); - simplicity_assert(NULL != program || 0 == program_len); - - bitstream stream = initializeBitstream(program, program_len); - dag_node* dag = NULL; - int_fast32_t dag_len = simplicity_decodeMallocDag(&dag, simplicity_elements_decodeJet, NULL, &stream); - if (dag_len <= 0) { - simplicity_assert(dag_len < 0); - *error = (simplicity_err)dag_len; - } else { - simplicity_assert(NULL != dag); - simplicity_assert((uint_fast32_t)dag_len <= DAG_LEN_MAX); - *error = simplicity_closeBitstream(&stream); - sha256_fromMidstate(cmr, dag[dag_len-1].cmr.s); - } - - simplicity_free(dag); - return IS_PERMANENT(*error); + return simplicity_computeCmr(error, cmr, simplicity_elements_decodeJet, program, program_len); } diff --git a/C/include/simplicity/bitcoin/cmr.h b/C/include/simplicity/bitcoin/cmr.h new file mode 100644 index 00000000..2a1f82aa --- /dev/null +++ b/C/include/simplicity/bitcoin/cmr.h @@ -0,0 +1,23 @@ +#ifndef SIMPLICITY_BITCOIN_CMR_H +#define SIMPLICITY_BITCOIN_CMR_H + +#include +#include +#include + +/* Deserialize a Simplicity 'program' and compute its CMR. + * + * Caution: no typechecking is performed, only a well-formedness check. + * + * If at any time malloc fails then '*error' is set to 'SIMPLICITY_ERR_MALLOC' and 'false' is returned, + * Otherwise, 'true' is returned indicating that the result was successfully computed and returned in the '*error' value. + * + * If the operation completes successfully then '*error' is set to 'SIMPLICITY_NO_ERROR', and the 'cmr' array is filled in with the program's computed CMR. + * + * Precondition: NULL != error; + * unsigned char cmr[32] + * unsigned char program[program_len] + */ +extern bool simplicity_bitcoin_computeCmr( simplicity_err* error, unsigned char* cmr + , const unsigned char* program, size_t program_len); +#endif From 3b03bdbfd9f3bb31912a9c1c87345e9224bd3435 Mon Sep 17 00:00:00 2001 From: Russell O'Connor Date: Wed, 3 Sep 2025 16:36:35 -0400 Subject: [PATCH 3/4] Update GenRustJets to generate Bitcoin Jets --- Haskell-Generate/GenRustJets.hs | 128 ++++++++++++++++++-------------- 1 file changed, 74 insertions(+), 54 deletions(-) diff --git a/Haskell-Generate/GenRustJets.hs b/Haskell-Generate/GenRustJets.hs index 91ad306a..641aeb04 100644 --- a/Haskell-Generate/GenRustJets.hs +++ b/Haskell-Generate/GenRustJets.hs @@ -5,7 +5,7 @@ import Data.Char (toLower) import Data.Foldable (toList) import Data.Function (on) import Data.Functor.Fixedpoint (Fix(..)) -import Data.List (sortBy) +import Data.List (nubBy, sortBy) import Data.List.Split (chunksOf) import Data.Maybe (fromMaybe) import qualified Data.Map as Map @@ -47,7 +47,14 @@ sortJetName = sortBy (compare `on` name) where name (SomeArrow j) = jetName j -cJetName = lowerSnakeCase . jetName +rustJetName :: JetData x y -> String +rustJetName jd = lowerSnakeCase (jetName jd) + +cJetName :: JetData x y -> String +cJetName jd = prefix (jetModule jd) ++ lowerSnakeCase (jetName jd) + where + prefix BitcoinModule = "bitcoin_" + prefix _ = "" coreJetData :: (TyC x, TyC y) => CoreJet x y -> JetData x y coreJetData jet = JetData { jetName = mkName jet @@ -87,6 +94,7 @@ moduleJets = sortJetName . toList . moduleCodes rustModuleName = fromMaybe "Core" . moduleName lowerRustModuleName = map toLower . rustModuleName +moduleEnvType mod = lowerRustModuleName mod ++ "::CTxEnv, " coreModule :: Module coreModule = Module Nothing (someArrowMap coreJetData <$> (treeEvalBitStream Core.getJetBit)) @@ -100,6 +108,11 @@ elementsModule = Module (Just "Elements") (someArrowMap elementsJetData <$> take bitcoinModule :: Module bitcoinModule = Module (Just "Bitcoin") (someArrowMap bitcoinJetData <$> takeRight (treeEvalBitStream Bitcoin.getJetBit)) +allJets :: [SomeArrow JetData] +allJets = nubBy eqJet . sortJetName $ moduleJets =<< [bitcoinModule, elementsModule] + where + eqJet (SomeArrow jt1) (SomeArrow jt2) = jetName jt1 == jetName jt2 && jetModule jt1 == jetModule jt2 + data CompactTy = CTyOne | CTyWord Int | CTyMaybe CompactTy @@ -190,16 +203,14 @@ rustJetTargetTy = rustJetTy "target_ty" (\(SomeArrow jet) -> unreflect (snd (rei rustJetPtr :: Module -> Doc a rustJetPtr mod = vsep $ [ nest 4 (vsep ("fn c_jet_ptr(&self) -> &dyn Fn(&mut CFrameItem, CFrameItem, &Self::CJetEnvironment) -> bool {" : - if modname == "Bitcoin" - then ["unimplemented!(\"Bitcoin jets have not yet been implemented.\")"] - else [ nest 4 (vsep ("match self {" : - map (<>comma) - [ pretty modname <> "::" <> pretty (jetName jet) <+> "=>" <+> - pretty ("&simplicity_sys::c_jets::jets_wrapper::"++cJetName jet) - | SomeArrow jet <- moduleJets mod - ])) - , "}" - ])) + [ nest 4 (vsep ("match self {" : + map (<>comma) + [ pretty modname <> "::" <> pretty (jetName jet) <+> "=>" <+> + pretty ("&simplicity_sys::c_jets::jets_wrapper::"++cJetName jet) + | SomeArrow jet <- moduleJets mod + ])) + , "}" + ])) , "}" ] where @@ -264,23 +275,26 @@ rustJetImpl mod = vsep $ where modname = rustModuleName mod env = vsep - [ pretty $ "type Environment = "++env++";" + [ pretty $ "type Transaction = "++transaction++";" + , pretty $ "type Environment = "++env++" where T: Borrow;" , pretty $ "type CJetEnvironment = "++cEnv++";" , "" - , pretty $ "fn c_jet_env("++envArg++": &Self::Environment) -> &Self::CJetEnvironment {" + , pretty $ "fn c_jet_env("++envArg++": &Self::Environment) -> &Self::CJetEnvironment" + , " where T: Borrow" + , "{" , pretty $ " "++envBody , "}" ] where - env | Nothing <- moduleName mod = "()" - | Just "Elements" == moduleName mod = "ElementsEnv>" + transaction | Nothing <- moduleName mod = "core::convert::Infallible" + | Just name <- moduleName mod = map toLower name ++"::Transaction" + env | Nothing <- moduleName mod = "CoreEnv" | Just name <- moduleName mod = name ++ "Env" - cEnv | Just "Elements" == moduleName mod = "CElementsTxEnv" - | otherwise = "()" - envArg | Just "Bitcoin" == moduleName mod = "_env" + cEnv | Nothing <- moduleName mod = "CoreEnv" + | otherwise = "CTxEnv" + envArg | Nothing <- moduleName mod = "_" | otherwise = "env" - envBody | Nothing == moduleName mod = "env" - | Just "Bitcoin" == moduleName mod = "unimplemented!(\"Unspecified CJetEnvironment for Bitcoin jets\")" + envBody | Nothing <- moduleName mod = "&CoreEnv::EMPTY" | otherwise = "env.c_tx_env()" rustJetEnum :: Module -> Doc a @@ -310,7 +324,7 @@ rustJetDisplay mod = nestBraces ("match self" <+> nestBraces (vsep ( map (<>comma) - [ pretty modname <> "::" <> pretty (jetName jet) <+> "=> f.write_str" <> (parens . dquotes . pretty $ cJetName jet) + [ pretty modname <> "::" <> pretty (jetName jet) <+> "=> f.write_str" <> (parens . dquotes . pretty $ rustJetName jet) | SomeArrow jet <- moduleJets mod ])) ) @@ -328,7 +342,7 @@ rustJetFromStr mod = nestBraces ("match s" <+> nestBraces (vsep ( map (<> comma) - ([ dquotes (pretty (cJetName jet)) <+> "=> Ok" <> parens (pretty modname <> "::" <> pretty (jetName jet)) + ([ dquotes (pretty (rustJetName jet)) <+> "=> Ok" <> parens (pretty modname <> "::" <> pretty (jetName jet)) | SomeArrow jet <- moduleJets mod ] ++ [ "x => Err(crate::Error::InvalidJetName(x.to_owned()))" ] ))) @@ -351,14 +365,13 @@ rustImports mod = vsep (map (<> semi) , "use hashes::sha256::Midstate" , "use simplicity_sys::CFrameItem" , "use std::io::Write" - , "use std::{fmt, str}" + , "use std::{borrow::Borrow, fmt, str}" ] ++ envImports)) where - envImports | Nothing == moduleName mod = [] - | Just "Bitcoin" == moduleName mod = ["use crate::jet::bitcoin::BitcoinEnv"] + envImports | Nothing == moduleName mod = ["use crate::jet::core::CoreEnv"] | Just name <- moduleName mod = [ pretty $ "use crate::jet::"++map toLower name++"::"++name++"Env" - , pretty $ "use simplicity_sys::C"++name++"TxEnv" + , pretty $ "use simplicity_sys::"++map toLower name++"::CTxEnv" ] rustJetDoc :: Module -> SimpleDocStream a @@ -374,14 +387,16 @@ rustJetDoc mod = layoutPretty layoutOptions $ vsep (map (<> line) rustFFIImports :: Doc a rustFFIImports = vsep (map (<> semi) [ "use crate::ffi::c_void" - , "use crate::{CElementsTxEnv, CFrameItem}" + , "use crate::bitcoin" + , "use crate::elements" + , "use crate::CFrameItem" ]) -rustFFISigs :: Module -> Doc a -rustFFISigs mod = vsep +rustFFISigs :: Doc a +rustFFISigs = vsep [ nest 4 $ vsep $ "extern \"C\" {" : - (declaration <$> moduleJets mod) + (declaration <$> allJets) , "}" ] where @@ -393,28 +408,31 @@ rustFFISigs mod = vsep linkName = "#[link_name = \"c_"++cJetName jet++"\"]" signature = "pub fn "++cJetName jet++"(dst: *mut CFrameItem, src: *const CFrameItem, env: *const "++envType++") -> bool" envType | CoreModule <- jetModule jet = "c_void" - | ElementsModule <- jetModule jet = "CElementsTxEnv" + | ElementsModule <- jetModule jet = "elements::CTxEnv" + | BitcoinModule <- jetModule jet = "bitcoin::CTxEnv" -rustFFIDoc :: Module -> SimpleDocStream a -rustFFIDoc mod = layoutPretty layoutOptions $ vsep (map (<> line) +rustFFIDoc :: SimpleDocStream a +rustFFIDoc = layoutPretty layoutOptions $ vsep (map (<> line) [ rustHeader , rustFFIImports - , rustFFISigs mod + , rustFFISigs ]) rustWrapperImports :: Doc a rustWrapperImports = vsep (map (<> semi) - [ "use crate::{CElementsTxEnv, CFrameItem}" - , "use super::elements_ffi" + [ "use crate::bitcoin" + , "use crate::elements" + , "use crate::CFrameItem" + , "use super::jets_ffi" ]) -rustWrappers :: Module -> Doc a -rustWrappers mod = vsep ((<> line) . wrapper <$> moduleJets mod) +rustWrappers :: Doc a +rustWrappers = vsep ((<> line) . wrapper <$> allJets) where wrapper (SomeArrow jet) = vsep [ nest 4 $ vsep [ pretty $ "pub fn "++cJetName jet++templateParam++"(dst: &mut CFrameItem, src: CFrameItem, "++envParam++") -> bool {" - , pretty $ "unsafe { "++lowerRustModuleName mod++"_ffi::"++cJetName jet++"(dst, &src, "++envArg++") }" + , pretty $ "unsafe { jets_ffi::"++cJetName jet++"(dst, &src, "++envArg++") }" ] , "}" ] @@ -422,34 +440,36 @@ rustWrappers mod = vsep ((<> line) . wrapper <$> moduleJets mod) templateParam | CoreModule <- jetModule jet = "" | otherwise = "" envParam | CoreModule <- jetModule jet = "_env: &T" - | ElementsModule <- jetModule jet = "env: &CElementsTxEnv" + | ElementsModule <- jetModule jet = "env: &elements::CTxEnv" + | BitcoinModule <- jetModule jet = "env: &bitcoin::CTxEnv" envArg | CoreModule <- jetModule jet = "std::ptr::null()" - | ElementsModule <- jetModule jet = "env" + | otherwise = "env" -rustWrapperDoc :: Module -> SimpleDocStream a -rustWrapperDoc mod = layoutPretty layoutOptions $ vsep (map (<> line) +rustWrapperDoc :: SimpleDocStream a +rustWrapperDoc = layoutPretty layoutOptions $ vsep (map (<> line) [ rustHeader , rustWrapperImports - , rustWrappers mod + , rustWrappers ]) cWrapperImports :: Doc a cWrapperImports = vsep - [ "#include \"simplicity/elements/elementsJets.h\"" + [ "#include \"simplicity/bitcoin/bitcoinJets.h\"" + , "#include \"simplicity/elements/elementsJets.h\"" , "#include \"simplicity/simplicity_assert.h\"" , "#include \"wrapper.h\"" ] -cWrappers :: Module -> Doc a -cWrappers mod = vsep (map wrapper $ moduleJets mod) +cWrappers :: Doc a +cWrappers = vsep (map wrapper $ allJets) where wrapper (SomeArrow jet) = pretty $ "WRAP_("++cJetName jet++")" -cWrapperDoc :: Module -> SimpleDocStream a -cWrapperDoc mod = layoutPretty layoutOptions $ vsep (map (<> line) +cWrapperDoc :: SimpleDocStream a +cWrapperDoc = layoutPretty layoutOptions $ vsep (map (<> line) [ rustHeader -- also works for C , cWrapperImports - , cWrappers mod + , cWrappers ]) renderFile name doc = withFile name WriteMode (\h -> renderIO h doc) @@ -458,8 +478,8 @@ main = do renderFile "core.rs" (rustJetDoc coreModule) renderFile "elements.rs" (rustJetDoc elementsModule) renderFile "bitcoin.rs" (rustJetDoc bitcoinModule) - renderFile "jets_ffi.rs" (rustFFIDoc elementsModule) - renderFile "jets_wrapper.rs" (rustWrapperDoc elementsModule) - renderFile "jets_wrapper.c" (cWrapperDoc elementsModule) + renderFile "jets_ffi.rs" rustFFIDoc + renderFile "jets_wrapper.rs" rustWrapperDoc + renderFile "jets_wrapper.c" cWrapperDoc layoutOptions = LayoutOptions { layoutPageWidth = AvailablePerLine 100 1 } From fba36f60e2b71fda41175688dc5332e5fb60054a Mon Sep 17 00:00:00 2001 From: Russell O'Connor Date: Wed, 17 Dec 2025 15:52:04 -0500 Subject: [PATCH 4/4] Add basic CMake build support for libBitcoinSimplicity --- .gitignore | 5 +++++ C/CMakeLists.txt | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 C/CMakeLists.txt diff --git a/.gitignore b/.gitignore index e448abb6..fc4d1734 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,11 @@ alectryon-doc *.o *.out *.a +# cmake +*.cmake +CMakeCache.txt +CMakeFiles +/build/ # Coverage *.gcda *.gcno diff --git a/C/CMakeLists.txt b/C/CMakeLists.txt new file mode 100644 index 00000000..f1355bd0 --- /dev/null +++ b/C/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.16) + +project(BitcoinSimplicity) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_EXTENSIONS OFF) + +add_library(BitcoinSimplicity STATIC + bitstream.c + dag.c + deserialize.c + eval.c + frame.c + jets-secp256k1.c + jets.c + rsort.c + sha256.c + type.c + typeInference.c + bitcoin/env.c + bitcoin/exec.c + bitcoin/bitcoinJets.c + bitcoin/ops.c + bitcoin/primitive.c + bitcoin/txEnv.c +) + +option(PRODUCTION "Enable production build" ON) +if (PRODUCTION) + target_compile_definitions(BitcoinSimplicity PRIVATE "PRODUCTION") +endif() + +target_include_directories(BitcoinSimplicity PUBLIC + $ + $ + )