diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ce6c80..1cf8155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# 0.3.0 - 2025-11-04 + +* Add `array_fold` builtin function [#145](https://github.com/BlockstreamResearch/SimplicityHL/pull/145) +* Add getters for `Span` and improve error handling [#146](https://github.com/BlockstreamResearch/SimplicityHL/pull/146) +* Add VSCode extension with LSP support + [#148](https://github.com/BlockstreamResearch/SimplicityHL/pull/148) + [#149](https://github.com/BlockstreamResearch/SimplicityHL/pull/149) +* Switch NUMS key to BIP-0341 suggested key [#143](https://github.com/BlockstreamResearch/SimplicityHL/pull/143) +* Fix `array_fold` powers-of-two bug; fix simc CLI when serde is disabled; enable serde by default [#159](https://github.com/BlockstreamResearch/SimplicityHL/pull/159) +* Update rust-simplicity to 0.6 + [#143](https://github.com/BlockstreamResearch/SimplicityHL/pull/143) + [#160](https://github.com/BlockstreamResearch/SimplicityHL/pull/160) + # 0.2.0 - 2025-07-29 * Renamed from [Simfony](https://crates.io/crates/simfony) diff --git a/Cargo.lock b/Cargo.lock index 259976c..485cff6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -308,6 +308,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "ghost-cell" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8449d342b1c67f49169e92e71deb7b9b27f30062301a16dbc27a4cc8d2351b7" + [[package]] name = "hex-conservative" version = "0.2.1" @@ -616,15 +622,16 @@ dependencies = [ [[package]] name = "simplicity-lang" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525879699aba1f7f75c0d97355475072adeb0ed0530df4e18f23235252475e68" +checksum = "7938a6a4106edfe5b559a8718df506a0690ac12cfff8ce91c0f6b7f02a644f8c" dependencies = [ "bitcoin", "bitcoin_hashes", "byteorder", "elements", "getrandom", + "ghost-cell", "hex-conservative", "miniscript", "santiago", @@ -633,9 +640,9 @@ dependencies = [ [[package]] name = "simplicity-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3abf9c7d64c5bf45bb2fb966f3b0637d8c13c8d5cdfbd7587900421cb7584c49" +checksum = "875630d128f19818161cefe0a3d910b6aae921d8246711db574a689cb2c11747" dependencies = [ "bitcoin_hashes", "cc", @@ -643,7 +650,7 @@ dependencies = [ [[package]] name = "simplicityhl" -version = "0.2.0" +version = "0.3.0" dependencies = [ "arbitrary", "base64", diff --git a/Cargo.toml b/Cargo.toml index e77e317..ec7a60e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "simplicityhl" -version = "0.2.0" +version = "0.3.0" authors = ["sanket1729 "] license = "CC0-1.0" homepage = "https://github.com/BlockstreamResearch/SimplicityHL" @@ -27,7 +27,7 @@ pest = "2.1.3" pest_derive = "2.7.1" serde = { version = "1.0.188", features = ["derive"], optional = true } serde_json = { version = "1.0.105", optional = true } -simplicity-lang = { version = "0.5.0" } +simplicity-lang = { version = "0.6.0" } miniscript = "12.3.1" either = "1.12.0" itertools = "0.13.0" diff --git a/src/compile/builtins.rs b/src/compile/builtins.rs index 2cf2dd8..b850188 100644 --- a/src/compile/builtins.rs +++ b/src/compile/builtins.rs @@ -15,12 +15,15 @@ use crate::named::CoreExt; /// The fold `(fold f)_n : E^n × A → A` /// takes the array of type `E^n` and an initial accumulator of type `A`, /// and it produces the final accumulator of type `A`. -pub fn array_fold(size: NonZeroUsize, f: &ProgNode) -> Result { +pub fn array_fold<'brand>( + size: NonZeroUsize, + f: &ProgNode<'brand>, +) -> Result, simplicity::types::Error> { /// Recursively fold the array using the precomputed folding functions. - fn tree_fold( + fn tree_fold<'brand>( n: usize, - f_powers_of_two: &[ProgNode], - ) -> Result { + f_powers_of_two: &[ProgNode<'brand>], + ) -> Result, simplicity::types::Error> { // Array is a left-balanced (right-associative) binary tree. let max_pow2 = n.ilog2() as usize; debug_assert!(max_pow2 < f_powers_of_two.len()); @@ -38,10 +41,10 @@ pub fn array_fold(size: NonZeroUsize, f: &ProgNode) -> Result right. - fn f_array_fold( - f_left: &ProgNode, - f_right: &ProgNode, - ) -> Result { + fn f_array_fold<'brand>( + f_left: &ProgNode<'brand>, + f_right: &ProgNode<'brand>, + ) -> Result, simplicity::types::Error> { // The input is a tuple ((L, R), acc): ([E; n], A) where: // - L and R are arrays of varying size E^x and E^y respectively (x + y = n). // - acc is an accumulator of type A. diff --git a/src/compile/mod.rs b/src/compile/mod.rs index 31052e5..2af17e6 100644 --- a/src/compile/mod.rs +++ b/src/compile/mod.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use either::Either; use simplicity::jet::Elements; use simplicity::node::{CoreConstructible as _, JetConstructible as _}; -use simplicity::{Cmr, FailEntropy}; +use simplicity::{types, Cmr, FailEntropy}; use self::builtins::array_fold; use crate::array::{BTreeSlice, Partition}; @@ -26,7 +26,7 @@ use crate::value::StructuralValue; use crate::witness::Arguments; use crate::Value; -type ProgNode = Arc>; +type ProgNode<'brand> = Arc>; /// Each SimplicityHL expression expects an _input value_. /// A SimplicityHL expression is translated into a Simplicity expression @@ -39,7 +39,7 @@ type ProgNode = Arc>; /// Bindings from inner scopes overwrite bindings from outer scopes. /// Bindings live as long as their scope. #[derive(Debug, Clone)] -struct Scope { +struct Scope<'brand> { /// For each scope, the set of assigned variables. /// /// A stack of scopes. Each scope is a stack of patterns. @@ -66,7 +66,7 @@ struct Scope { /// Later assignments occur higher in the tree than earlier assignments. /// ``` variables: Vec>, - ctx: simplicity::types::Context, + ctx: simplicity::types::Context<'brand>, /// Tracker of function calls. call_tracker: Arc, /// Values for parameters inside the SimplicityHL program. @@ -74,7 +74,7 @@ struct Scope { include_debug_symbols: bool, } -impl Scope { +impl<'brand> Scope<'brand> { /// Create the main scope. /// /// _This function should be called at the start of the compilation and then never again._ @@ -84,13 +84,14 @@ impl Scope { /// The supplied `arguments` are consistent with the program's parameters. /// Call [`Arguments::is_consistent`] before calling this method! pub fn new( + ctx: simplicity::types::Context<'brand>, call_tracker: Arc, arguments: Arguments, include_debug_symbols: bool, ) -> Self { Self { variables: vec![vec![Pattern::Ignore]], - ctx: simplicity::types::Context::new(), + ctx, call_tracker, arguments, include_debug_symbols, @@ -183,12 +184,12 @@ impl Scope { /// ``` /// /// The expression `drop (IOH & OH)` returns the seeked value. - pub fn get(&self, target: &BasePattern) -> Option> { + pub fn get(&self, target: &BasePattern) -> Option>> { BasePattern::from(&self.get_input_pattern()).translate(&self.ctx, target) } /// Access the Simplicity type inference context. - pub fn ctx(&self) -> &simplicity::types::Context { + pub fn ctx(&self) -> &simplicity::types::Context<'brand> { &self.ctx } @@ -200,10 +201,10 @@ impl Scope { /// for debug symbols will simply ignore it. The semantics of the program remain unchanged. pub fn with_debug_symbol>( &mut self, - args: PairBuilder, - body: &ProgNode, + args: PairBuilder>, + body: &ProgNode<'brand>, span: &S, - ) -> Result, RichError> { + ) -> Result>, RichError> { match self.call_tracker.get_cmr(span.as_ref()) { Some(cmr) if self.include_debug_symbols => { let false_and_args = ProgNode::bit(self.ctx(), false).pair(args); @@ -221,12 +222,12 @@ impl Scope { } } -fn compile_blk( +fn compile_blk<'brand>( stmts: &[Statement], - scope: &mut Scope, + scope: &mut Scope<'brand>, index: usize, last_expr: Option<&Expression>, -) -> Result, RichError> { +) -> Result>, RichError> { if index >= stmts.len() { return match last_expr { Some(expr) => expr.compile(scope), @@ -263,22 +264,28 @@ impl Program { arguments: Arguments, include_debug_symbols: bool, ) -> Result>, RichError> { - let mut scope = Scope::new( - Arc::clone(self.call_tracker()), - arguments, - include_debug_symbols, - ); - - let main = self.main(); - let construct = main.compile(&mut scope).map(PairBuilder::build)?; - // SimplicityHL types should be correct by construction. If not, assign the - // whole main function as the span for them, which is as sensible as anything. - named::finalize_types(&construct).with_span(main) + types::Context::with_context(|ctx| { + let mut scope = Scope::new( + ctx, + Arc::clone(self.call_tracker()), + arguments, + include_debug_symbols, + ); + + let main = self.main(); + let construct = main.compile(&mut scope).map(PairBuilder::build)?; + // SimplicityHL types should be correct by construction. If not, assign the + // whole main function as the span for them, which is as sensible as anything. + named::finalize_types(&construct).with_span(main) + }) } } impl Expression { - fn compile(&self, scope: &mut Scope) -> Result, RichError> { + fn compile<'brand>( + &self, + scope: &mut Scope<'brand>, + ) -> Result>, RichError> { match self.inner() { ExpressionInner::Block(stmts, expr) => { scope.push_scope(); @@ -292,7 +299,10 @@ impl Expression { } impl SingleExpression { - fn compile(&self, scope: &mut Scope) -> Result, RichError> { + fn compile<'brand>( + &self, + scope: &mut Scope<'brand>, + ) -> Result>, RichError> { let expr = match self.inner() { SingleExpressionInner::Constant(value) => { let value = StructuralValue::from(value); @@ -360,7 +370,10 @@ impl SingleExpression { } impl Call { - fn compile(&self, scope: &mut Scope) -> Result, RichError> { + fn compile<'brand>( + &self, + scope: &mut Scope<'brand>, + ) -> Result>, RichError> { let args_ast = SingleExpression::tuple(self.args().clone(), *self.as_ref()); let args = args_ast.compile(scope)?; @@ -453,8 +466,13 @@ impl Call { /// The fold `(fold f)_n : E^(<2^n) × A → A` /// takes the list of type `E^(<2^n)` and an initial accumulator of type `A`, /// and it produces the final accumulator of type `A`. -fn list_fold(bound: NonZeroPow2Usize, f: &ProgNode) -> Result { - fn next_f_array(f_array: &ProgNode) -> Result { +fn list_fold<'brand>( + bound: NonZeroPow2Usize, + f: &ProgNode<'brand>, +) -> Result, simplicity::types::Error> { + fn next_f_array<'brand>( + f_array: &ProgNode<'brand>, + ) -> Result, simplicity::types::Error> { /* f_(n + 1) : E^(2^(n + 1)) × A → A * f_(n + 1) := OIH ▵ (OOH ▵ IH; f_n); f_n */ @@ -464,10 +482,10 @@ fn list_fold(bound: NonZeroPow2Usize, f: &ProgNode) -> Result Result { + fn next_f_fold<'brand>( + f_array: &ProgNode<'brand>, + f_fold: &ProgNode<'brand>, + ) -> Result, simplicity::types::Error> { /* (fold f)_(n + 1) : E<2^(n + 1) × A → A * (fold f)_(n + 1) := OOH ▵ (OIH ▵ IH); * case (drop (fold f)_n) @@ -525,16 +543,18 @@ fn list_fold(bound: NonZeroPow2Usize, f: &ProgNode) -> Result( bit_width: Pow2Usize, - f: PairBuilder, -) -> Result, simplicity::types::Error> { + f: PairBuilder>, +) -> Result>, simplicity::types::Error> { /* for_while_0 f : E × A → A * for_while_0 f := (OH ▵ (IH ▵ false); f) ▵ IH; * case (injl OH) * (OH ▵ (IH ▵ true); f) */ - fn for_while_0(f: &ProgNode) -> Result, simplicity::types::Error> { + fn for_while_0<'brand>( + f: &ProgNode<'brand>, + ) -> Result>, simplicity::types::Error> { let ctx = f.inference_context(); let f_output = ProgNode::o() .h(ctx) @@ -557,7 +577,9 @@ fn for_while( * where * f : A × (C × 2^(2^(n + 1))) → B + A */ - fn adapt_f(f: &ProgNode) -> Result, simplicity::types::Error> { + fn adapt_f<'brand>( + f: &ProgNode<'brand>, + ) -> Result>, simplicity::types::Error> { let ctx = f.inference_context(); let f_input = ProgNode::o().h(ctx).pair( ProgNode::i() @@ -626,7 +648,10 @@ fn for_while( } impl Match { - fn compile(&self, scope: &mut Scope) -> Result, RichError> { + fn compile<'brand>( + &self, + scope: &mut Scope<'brand>, + ) -> Result>, RichError> { scope.push_scope(); scope.insert( self.left() diff --git a/src/named.rs b/src/named.rs index 6310b91..8330655 100644 --- a/src/named.rs +++ b/src/named.rs @@ -56,7 +56,7 @@ impl Nullable for NoDisconnect { /// [`simplicity::ConstructNode`] with named witness nodes. /// /// Nodes other than witness don't have names. -pub type ConstructNode = Node>>; +pub type ConstructNode<'brand, J> = Node>>; /// [`simplicity::CommitNode`] with named witness nodes. /// @@ -253,15 +253,15 @@ pub fn populate_witnesses( // This awkward construction is required by rust-simplicity to implement WitnessConstructible // for Node>. See // https://docs.rs/simplicity-lang/latest/simplicity/node/trait.WitnessConstructible.html#foreign-impls -impl WitnessConstructible for node::ConstructData { - fn witness(inference_context: &types::Context, _: WitnessName) -> Self { +impl<'brand, J: Jet> WitnessConstructible<'brand, WitnessName> for node::ConstructData<'brand, J> { + fn witness(inference_context: &types::Context<'brand>, _: WitnessName) -> Self { WitnessConstructible::>::witness(inference_context, None) } } /// More constructors for types that implement [`CoreConstructible`]. -pub trait CoreExt: CoreConstructible + Sized { - fn h(inference_context: &types::Context) -> PairBuilder { +pub trait CoreExt<'brand>: CoreConstructible<'brand> + Sized { + fn h(inference_context: &types::Context<'brand>) -> PairBuilder { PairBuilder::iden(inference_context) } @@ -273,7 +273,7 @@ pub trait CoreExt: CoreConstructible + Sized { SelectorBuilder::default().i() } - fn bit(inference_context: &types::Context, bit: bool) -> PairBuilder { + fn bit(inference_context: &types::Context<'brand>, bit: bool) -> PairBuilder { match bit { false => PairBuilder::unit(inference_context).injl(), true => PairBuilder::unit(inference_context).injr(), @@ -292,7 +292,7 @@ pub trait CoreExt: CoreConstructible + Sized { /// --------------------------- /// comp unit scribe(v) : A → B /// ``` - fn unit_scribe(inference_context: &types::Context, value: &simplicity::Value) -> Self { + fn unit_scribe(inference_context: &types::Context<'brand>, value: &simplicity::Value) -> Self { Self::comp( &Self::unit(inference_context), &Self::scribe(inference_context, value), @@ -321,7 +321,7 @@ pub trait CoreExt: CoreConstructible + Sized { } /// `case false true` always type-checks. - fn case_false_true(inference_context: &types::Context) -> Self { + fn case_false_true(inference_context: &types::Context<'brand>) -> Self { Self::case( &Self::bit_false(inference_context), &Self::bit_true(inference_context), @@ -330,7 +330,7 @@ pub trait CoreExt: CoreConstructible + Sized { } /// `case true false` always type-checks. - fn case_true_false(inference_context: &types::Context) -> Self { + fn case_true_false(inference_context: &types::Context<'brand>) -> Self { Self::case( &Self::bit_true(inference_context), &Self::bit_false(inference_context), @@ -339,7 +339,7 @@ pub trait CoreExt: CoreConstructible + Sized { } } -impl CoreExt for N {} +impl<'brand, N: CoreConstructible<'brand>> CoreExt<'brand> for N {} /// Builder of expressions that contain /// `take`, `drop` and `iden` only. @@ -360,7 +360,7 @@ impl

Default for SelectorBuilder

{ } } -impl SelectorBuilder

{ +impl<'brand, P: CoreExt<'brand>> SelectorBuilder

{ /// Select the first component '0' of the input pair. pub fn o(mut self) -> Self { self.selection.push(false); @@ -384,7 +384,7 @@ impl SelectorBuilder

{ } /// Select the current input value. - pub fn h(self, inference_context: &types::Context) -> PairBuilder

{ + pub fn h(self, inference_context: &types::Context<'brand>) -> PairBuilder

{ let mut expr = PairBuilder::iden(inference_context); for bit in self.selection.into_iter().rev() { match bit { @@ -406,7 +406,7 @@ impl SelectorBuilder

{ #[derive(Debug, Clone, Hash)] pub struct PairBuilder

(P); -impl PairBuilder

{ +impl<'brand, P: CoreExt<'brand>> PairBuilder

{ /// Create the unit expression. /// /// ## Invariant @@ -417,7 +417,7 @@ impl PairBuilder

{ /// ------------ /// unit : A → 1 /// ``` - pub fn unit(inference_context: &types::Context) -> Self { + pub fn unit(inference_context: &types::Context<'brand>) -> Self { Self(P::unit(inference_context)) } @@ -431,7 +431,7 @@ impl PairBuilder

{ /// ------------ /// iden : A → A /// ``` - pub fn iden(inference_context: &types::Context) -> Self { + pub fn iden(inference_context: &types::Context<'brand>) -> Self { Self(P::iden(inference_context)) } @@ -445,7 +445,7 @@ impl PairBuilder

{ /// ------------ /// fail : A → B /// ``` - pub fn fail(inference_context: &types::Context, entropy: FailEntropy) -> Self { + pub fn fail(inference_context: &types::Context<'brand>, entropy: FailEntropy) -> Self { Self(P::fail(inference_context, entropy)) } @@ -596,12 +596,15 @@ impl PairBuilder

{ /// --------------------------- /// comp unit scribe(v) : A → B /// ``` - pub fn unit_scribe(inference_context: &types::Context, value: &simplicity::Value) -> Self { + pub fn unit_scribe( + inference_context: &types::Context<'brand>, + value: &simplicity::Value, + ) -> Self { Self(P::unit_scribe(inference_context, value)) } } -impl> PairBuilder

{ +impl<'brand, P: WitnessConstructible<'brand, WitnessName>> PairBuilder

{ /// Create the witness expression. /// /// ## Invariant @@ -612,7 +615,7 @@ impl> PairBuilder

{ /// --------------- /// witness : A → B /// ``` - pub fn witness(inference_context: &types::Context, witness: WitnessName) -> Self { + pub fn witness(inference_context: &types::Context<'brand>, witness: WitnessName) -> Self { Self(P::witness(inference_context, witness)) } } diff --git a/src/pattern.rs b/src/pattern.rs index 1e2dae3..b1a1f4f 100644 --- a/src/pattern.rs +++ b/src/pattern.rs @@ -202,7 +202,10 @@ impl BasePattern { /// The expression takes as input a value that matches the `self` pattern. /// /// The expression is a sequence of `take` and `drop` followed by `iden`. - fn get(&self, identifier: &Identifier) -> Option> { + fn get<'brand, P: CoreExt<'brand>>( + &self, + identifier: &Identifier, + ) -> Option> { let mut selector = SelectorBuilder::default(); for data in self.verbose_pre_order_iter() { @@ -302,9 +305,9 @@ impl BasePattern { /// This means there are infinitely many translating expressions from `self` to `to`. /// For instance, `iden`, `iden & iden`, `(iden & iden) & iden`, and so on. /// We enforce a unique translation by banning ignore from the `to` pattern. - pub fn translate( + pub fn translate<'brand, P: CoreExt<'brand>>( &self, - ctx: &simplicity::types::Context, + ctx: &simplicity::types::Context<'brand>, to: &Self, ) -> Option> { #[derive(Debug, Clone)] @@ -463,11 +466,12 @@ mod tests { ]; for (target, expected_expr) in target_expr { - let ctx = simplicity::types::Context::new(); - let expr = env - .translate::>>(&ctx, &target) - .unwrap(); - assert_eq!(expected_expr, expr.as_ref().display_expr().to_string()); + simplicity::types::Context::with_context(|ctx| { + let expr = env + .translate::>>(&ctx, &target) + .unwrap(); + assert_eq!(expected_expr, expr.as_ref().display_expr().to_string()); + }); } } } diff --git a/src/types.rs b/src/types.rs index c5dc5a7..e68c9f0 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1065,10 +1065,10 @@ impl TypeConstructible for StructuralType { impl StructuralType { /// Convert into an unfinalized type that can be used in Simplicity's unification algorithm. - pub fn to_unfinalized( + pub fn to_unfinalized<'brand>( &self, - inference_context: &simplicity::types::Context, - ) -> simplicity::types::Type { + inference_context: &simplicity::types::Context<'brand>, + ) -> simplicity::types::Type<'brand> { simplicity::types::Type::complete(inference_context, self.0.clone()) } }