├── .github └── workflows │ ├── book.yml │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── README.md ├── bitcoind-tests ├── Cargo.lock ├── Cargo.toml ├── elementsd-simplicity.nix └── tests │ ├── common │ ├── daemon.rs │ ├── mod.rs │ ├── test.rs │ └── util.rs │ └── spend_utxo.rs ├── book ├── book.toml └── src │ ├── SUMMARY.md │ ├── function.md │ ├── introduction.md │ ├── let_statement.md │ ├── match_expression.md │ ├── program.md │ ├── type.md │ ├── type_alias.md │ └── type_casting.md ├── clippy.toml ├── codegen ├── Cargo.toml └── src │ ├── jet.rs │ └── main.rs ├── doc ├── context.md ├── environment.md └── translation.md ├── examples ├── cat.simf ├── ctv.simf ├── escrow_with_delay.simf ├── escrow_with_delay.timeout.wit ├── hash_loop.simf ├── hodl_vault.simf ├── hodl_vault.wit ├── htlc.complete.wit ├── htlc.simf ├── last_will.inherit.wit ├── last_will.simf ├── non_interactive_fee_bump.simf ├── non_interactive_fee_bump.wit ├── p2ms.simf ├── p2ms.wit ├── p2pk.args ├── p2pk.simf ├── p2pk.wit ├── p2pkh.simf ├── p2pkh.wit ├── presigned_vault.complete.wit ├── presigned_vault.simf ├── reveal_collision.simf ├── reveal_fix_point.simf ├── sighash_all_anyonecanpay.simf ├── sighash_all_anyonecanpay.wit ├── sighash_all_anyprevout.simf ├── sighash_all_anyprevout.wit ├── sighash_all_anyprevoutanyscript.simf ├── sighash_all_anyprevoutanyscript.wit ├── sighash_none.simf ├── sighash_none.wit ├── sighash_single.simf ├── sighash_single.wit ├── transfer_with_timeout.simf └── transfer_with_timeout.transfer.wit ├── flake.lock ├── flake.nix ├── fuzz ├── .gitignore ├── Cargo.toml ├── README.md └── fuzz_targets │ ├── compile_parse_tree.rs │ ├── compile_text.rs │ ├── display_parse_tree.rs │ ├── parse_value_rtt.rs │ ├── parse_witness_json_rtt.rs │ ├── parse_witness_module_rtt.rs │ └── reconstruct_value.rs ├── justfile ├── src ├── array.rs ├── ast.rs ├── compile.rs ├── debug.rs ├── dummy_env.rs ├── error.rs ├── jet.rs ├── lib.rs ├── main.rs ├── minimal.pest ├── named.rs ├── num.rs ├── parse.rs ├── pattern.rs ├── serde.rs ├── str.rs ├── types.rs ├── value.rs └── witness.rs └── vscode ├── LICENSE ├── README.md ├── language-configuration.json ├── package-lock.json ├── package.json └── syntaxes └── simfony.tmLanguage.json /.github/workflows/book.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Book on GitHub Pages 2 | on: 3 | push: 4 | branches: 5 | - master 6 | permissions: 7 | contents: write 8 | jobs: 9 | deploy: 10 | name: Deploy Book on GitHub Pages 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: Install nix 18 | uses: cachix/install-nix-action@v24 19 | with: 20 | github_access_token: ${{ secrets.GITHUB_TOKEN }} 21 | 22 | - name: Build website 23 | run: | 24 | nix develop .#book --command bash -c "mdbook build book" 25 | 26 | - name: Deploy to GitHub pages 27 | uses: JamesIves/github-pages-deploy-action@v4 28 | with: 29 | branch: pages # The branch the action should deploy to. 30 | folder: book/book # The folder the action should deploy. 31 | clean: true # Automatically remove deleted files from the deployment branch. 32 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | push: 4 | pull_request: 5 | jobs: 6 | check-flake: 7 | name: Check flake 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v3 13 | 14 | - name: Install nix 15 | uses: cachix/install-nix-action@v24 16 | with: 17 | github_access_token: ${{ secrets.GITHUB_TOKEN }} 18 | 19 | - name: Check flake 20 | run: nix flake check --all-systems 21 | 22 | test-stable: 23 | name: Test - stable toolchain 24 | runs-on: ${{ matrix.os }} 25 | strategy: 26 | matrix: 27 | os: [ubuntu-latest, macos-latest] 28 | 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v3 32 | 33 | - name: Install nix 34 | uses: cachix/install-nix-action@v24 35 | with: 36 | github_access_token: ${{ secrets.GITHUB_TOKEN }} 37 | 38 | - name: Enable Rust cache 39 | uses: Swatinem/rust-cache@v2 40 | 41 | - name: Run all checks 42 | run: | 43 | nix develop .#ci --command bash -c "just check" 44 | 45 | test-msrv: 46 | name: Test - MSRV toolchain 47 | runs-on: ubuntu-latest 48 | 49 | steps: 50 | - name: Checkout 51 | uses: actions/checkout@v3 52 | 53 | - name: Install nix 54 | uses: cachix/install-nix-action@v24 55 | with: 56 | github_access_token: ${{ secrets.GITHUB_TOKEN }} 57 | 58 | - name: Enable Rust cache 59 | uses: Swatinem/rust-cache@v2 60 | 61 | - name: Run unit tests 62 | run: | 63 | nix develop .#msrv --command bash -c "just test" 64 | 65 | test-windows: 66 | name: Test - stable toolchain (windows-latest) 67 | runs-on: windows-latest 68 | 69 | steps: 70 | - name: Checkout 71 | uses: actions/checkout@v3 72 | 73 | - name: Checkout Toolchain 74 | uses: dtolnay/rust-toolchain@stable 75 | 76 | - name: Enable Rust cache 77 | uses: Swatinem/rust-cache@v2 78 | 79 | - name: Install just 80 | uses: extractions/setup-just@v2 81 | 82 | - name: Run unit tests 83 | run: just test 84 | 85 | test-fuzz: 86 | name: Test - fuzzer 87 | runs-on: ubuntu-latest 88 | needs: test-stable 89 | strategy: 90 | matrix: 91 | fuzz_target: 92 | - compile_parse_tree 93 | - compile_text 94 | - display_parse_tree 95 | - parse_value_rtt 96 | - parse_witness_json_rtt 97 | - parse_witness_module_rtt 98 | - reconstruct_value 99 | steps: 100 | - name: Checkout 101 | uses: actions/checkout@v3 102 | 103 | - name: Install nix 104 | uses: cachix/install-nix-action@v24 105 | with: 106 | github_access_token: ${{ secrets.GITHUB_TOKEN }} 107 | 108 | - name: Enable Rust cache 109 | uses: Swatinem/rust-cache@v2 110 | 111 | - name: Run fuzz target ${{ matrix.fuzz_target }} 112 | run: | 113 | nix develop .#fuzz --command bash -c "just fuzz ${{ matrix.fuzz_target }}" 114 | 115 | build-integration: 116 | name: Build - integration tests 117 | runs-on: ubuntu-latest 118 | 119 | steps: 120 | - name: Checkout 121 | uses: actions/checkout@v3 122 | 123 | - name: Install nix 124 | uses: cachix/install-nix-action@v24 125 | with: 126 | github_access_token: ${{ secrets.GITHUB_TOKEN }} 127 | 128 | - name: Enable Rust cache 129 | uses: Swatinem/rust-cache@v2 130 | 131 | - name: Build each integration test 132 | run: | 133 | nix develop .#ci --command bash -c "just build_integration" 134 | 135 | build-wasm: 136 | name: Build - WASM 137 | runs-on: ubuntu-latest 138 | needs: test-stable 139 | 140 | steps: 141 | - name: Checkout 142 | uses: actions/checkout@v3 143 | 144 | - name: Install nix 145 | uses: cachix/install-nix-action@v24 146 | with: 147 | github_access_token: ${{ secrets.GITHUB_TOKEN }} 148 | 149 | - name: Enable Rust cache 150 | uses: Swatinem/rust-cache@v2 151 | 152 | - name: Build WASM library 153 | run: | 154 | nix develop .#wasm --command bash -c "just build_wasm" 155 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # MSVC Windows builds of rustc generate these, which store debugging information 10 | *.pdb 11 | 12 | # IntelliJ project files 13 | .idea 14 | *.iml 15 | 16 | # Vscode project files 17 | .vscode 18 | 19 | # mdbook HTML output dir 20 | book/book/ 21 | 22 | # Node.js modules 23 | node_modules/ 24 | 25 | # VSCode extension package 26 | *.vsix 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.0 - 2025-04-10 2 | 3 | * Initial release. Not recommended for production use. 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "simfony" 3 | version = "0.1.0" 4 | authors = ["sanket1729 "] 5 | license = "CC0-1.0" 6 | homepage = "https://github.com/BlockstreamResearch/simfony/" 7 | repository = "https://github.com/BlockstreamResearch/simfony/" 8 | description = "Rust-like language that compiles to Simplicity bytecode." 9 | edition = "2021" 10 | rust-version = "1.78.0" 11 | 12 | [lib] 13 | name = "simfony" 14 | path = "src/lib.rs" 15 | 16 | [[bin]] 17 | name = "simc" 18 | path = "src/main.rs" 19 | 20 | [features] 21 | serde = ["dep:serde", "dep:serde_json"] 22 | 23 | [dependencies] 24 | base64 = "0.21.2" 25 | pest = "2.1.3" 26 | pest_derive = "2.7.1" 27 | serde = { version = "1.0.188", features = ["derive"], optional = true } 28 | serde_json = { version = "1.0.105", optional = true } 29 | simplicity-lang = { version = "0.4.0" } 30 | miniscript = "12.3.1" 31 | either = "1.12.0" 32 | itertools = "0.13.0" 33 | arbitrary = { version = "1", optional = true, features = ["derive"] } 34 | clap = "4.5.37" 35 | 36 | [target.wasm32-unknown-unknown.dependencies] 37 | getrandom = { version = "0.2", features = ["js"] } 38 | 39 | [workspace] 40 | members = ["codegen"] 41 | exclude = ["fuzz", "bitcoind-tests"] 42 | 43 | [lints.clippy] 44 | # Exclude lints we don't think are valuable. 45 | large_enum_variant = "allow" # docs say "measure before paying attention to this"; why is it on by default?? 46 | similar_names = "allow" # Too many (subjectively) false positives. 47 | uninlined_format_args = "allow" # This is a subjective style choice. 48 | indexing_slicing = "allow" # Too many false positives ... would be cool though 49 | match_bool = "allow" # Adds extra indentation and LOC. 50 | match_same_arms = "allow" # Collapses things that are conceptually unrelated to each other. 51 | must_use_candidate = "allow" # Useful for audit but many false positives. 52 | # Whitelist the cast lints because sometimes casts are unavoidable. But 53 | # every cast should contain a code comment! 54 | cast_possible_truncation = "allow" 55 | cast_possible_wrap = "allow" 56 | cast_sign_loss = "allow" 57 | # Exhaustive list of pedantic clippy lints 58 | assigning_clones = "warn" 59 | bool_to_int_with_if = "warn" 60 | borrow_as_ptr = "warn" 61 | case_sensitive_file_extension_comparisons = "warn" 62 | cast_lossless = "warn" 63 | cast_precision_loss = "warn" 64 | cast_ptr_alignment = "warn" 65 | checked_conversions = "warn" 66 | cloned_instead_of_copied = "warn" 67 | copy_iterator = "warn" 68 | default_trait_access = "warn" 69 | doc_link_with_quotes = "warn" 70 | doc_markdown = "warn" 71 | empty_enum = "warn" 72 | enum_glob_use = "allow" 73 | expl_impl_clone_on_copy = "warn" 74 | explicit_deref_methods = "warn" 75 | explicit_into_iter_loop = "warn" 76 | explicit_iter_loop = "warn" 77 | filter_map_next = "warn" 78 | flat_map_option = "warn" 79 | float_cmp = "warn" 80 | fn_params_excessive_bools = "warn" 81 | from_iter_instead_of_collect = "warn" 82 | if_not_else = "warn" 83 | ignored_unit_patterns = "warn" 84 | implicit_clone = "warn" 85 | implicit_hasher = "warn" 86 | inconsistent_struct_constructor = "warn" 87 | index_refutable_slice = "warn" 88 | inefficient_to_string = "allow" 89 | inline_always = "warn" 90 | into_iter_without_iter = "warn" 91 | invalid_upcast_comparisons = "warn" 92 | items_after_statements = "warn" 93 | iter_filter_is_ok = "warn" 94 | iter_filter_is_some = "warn" 95 | iter_not_returning_iterator = "warn" 96 | iter_without_into_iter = "warn" 97 | large_digit_groups = "warn" 98 | large_futures = "warn" 99 | large_stack_arrays = "warn" 100 | large_types_passed_by_value = "warn" 101 | linkedlist = "warn" 102 | macro_use_imports = "warn" 103 | manual_assert = "warn" 104 | manual_instant_elapsed = "warn" 105 | manual_is_power_of_two = "warn" 106 | manual_is_variant_and = "warn" 107 | manual_let_else = "warn" 108 | manual_ok_or = "warn" 109 | manual_string_new = "warn" 110 | many_single_char_names = "warn" 111 | map_unwrap_or = "warn" 112 | match_wild_err_arm = "warn" 113 | match_wildcard_for_single_variants = "allow" 114 | maybe_infinite_iter = "warn" 115 | mismatching_type_param_order = "warn" 116 | missing_errors_doc = "allow" 117 | missing_fields_in_debug = "warn" 118 | missing_panics_doc = "allow" 119 | mut_mut = "warn" 120 | naive_bytecount = "warn" 121 | needless_bitwise_bool = "warn" 122 | needless_continue = "allow" 123 | needless_for_each = "warn" 124 | needless_pass_by_value = "allow" 125 | needless_raw_string_hashes = "allow" 126 | no_effect_underscore_binding = "warn" 127 | no_mangle_with_rust_abi = "warn" 128 | option_as_ref_cloned = "warn" 129 | option_option = "allow" 130 | ptr_as_ptr = "warn" 131 | ptr_cast_constness = "warn" 132 | pub_underscore_fields = "warn" 133 | range_minus_one = "warn" 134 | range_plus_one = "warn" 135 | redundant_closure_for_method_calls = "allow" 136 | redundant_else = "warn" 137 | ref_as_ptr = "warn" 138 | ref_binding_to_reference = "warn" 139 | ref_option = "warn" 140 | ref_option_ref = "warn" 141 | return_self_not_must_use = "allow" 142 | same_functions_in_if_condition = "warn" 143 | semicolon_if_nothing_returned = "allow" 144 | should_panic_without_expect = "warn" 145 | single_char_pattern = "warn" 146 | single_match_else = "allow" 147 | stable_sort_primitive = "warn" 148 | str_split_at_newline = "warn" 149 | string_add_assign = "warn" 150 | struct_excessive_bools = "warn" 151 | struct_field_names = "warn" 152 | too_many_lines = "allow" 153 | transmute_ptr_to_ptr = "warn" 154 | trivially_copy_pass_by_ref = "warn" 155 | unchecked_duration_subtraction = "warn" 156 | unicode_not_nfc = "warn" 157 | unnecessary_box_returns = "warn" 158 | unnecessary_join = "warn" 159 | unnecessary_literal_bound = "warn" 160 | unnecessary_wraps = "warn" 161 | unnested_or_patterns = "allow" 162 | unreadable_literal = "allow" 163 | unsafe_derive_deserialize = "warn" 164 | unused_async = "warn" 165 | unused_self = "warn" 166 | used_underscore_binding = "warn" 167 | used_underscore_items = "warn" 168 | verbose_bit_mask = "warn" 169 | wildcard_imports = "allow" 170 | zero_sized_map_values = "warn" 171 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Simfony is a high-level language for writing Bitcoin smart contracts. 2 | 3 | Simfony looks and feels like [Rust](https://www.rust-lang.org). Just how Rust compiles down to assembly language, Simfony compiles down to [Simplicity](https://github.com/BlockstreamResearch/simplicity) bytecode. Developers write Simfony, full nodes execute Simplicity. 4 | 5 | **Simfony is a work in progress and is not yet ready for production use.** 6 | 7 | ```rust 8 | let a: u32 = 10; 9 | let b = { 10 | let c: u32 = 2; 11 | let d: u32 = 3; 12 | assert!(jet::eq_32(a, 10)); // Use variables from outer scopes 13 | let a: u32 = 7; // Shadow variables from outer scopes 14 | jet::max_32(jet::max_32(c, d), a) // Missing ; because the block returns a value 15 | }; 16 | assert!(jet::eq_32(b, 7)); 17 | ``` 18 | 19 | Take a look at the [example programs](https://github.com/BlockstreamResearch/simfony/tree/master/examples). 20 | 21 | ## MSRV 22 | 23 | This crate should compile with any feature combination on **Rust 1.78.0** or higher. 24 | 25 | ## Simplicity's need for a high-level language 26 | 27 | Simplicity introduces a groundbreaking low-level programming language and machine model meticulously crafted for blockchain-based smart contracts. The primary goal is to provide a streamlined and comprehensible foundation that facilitates static analysis and encourages reasoning through formal methods. While the elegance of the language itself is distilled into something as succinct as fitting onto a T-shirt, it's important to note that the simplicity of the language doesn't directly equate to simplicity in the development process. Simfony revolves around demystifying and simplifying the complexities involved in this ecosystem. 28 | 29 | The distinguishing aspects that set Simplicity apart from conventional programming languages are: 30 | 31 | - **Distinct Programming Paradigm**: Simplicity's programming model requires a paradigm shift from conventional programming. It hinges on reasoning about programs in a functional sense with a focus on combinators. This intricacy surpasses even popular functional languages like Haskell, with its own unique challenges. 32 | - **Exceptional Low-Level Nature**: Unlike high-level languages such as JavaScript or Python, Simplicity operates at an extremely low level, resembling assembly languages. This design choice enables easier reasoning about the formal semantics of programs, but is really work on directly. 33 | 34 | ## Simfony 35 | 36 | Simfony is a high-level language that compiles to Simplicity. It maps programming concepts from Simplicity onto programming concepts that developers are more familar with. In particular, Rust is a popular language whose functional aspects fit Simplicity well. Simfony aims to closely resemble Rust. 37 | 38 | Just how Rust is compiled to assembly language, Simfony is compiled to Simplicity. Just how writing Rust doesn't necessarily produce the most efficient assembly, writing Simfony doesn't necessarily produce the most efficient Simplicity code. The compilers try to optimize the target code they produce, but manually written target code can be more efficient. On the other hand, a compiled language is much easier to read, write and reason about. Assembly is meant to be consumed by machines while Rust is meant to be consumed by humans. Simplicity is meant to be consumed by Bitcoin full nodes while Simfony is meant to be consumed by Bitcoin developers. 39 | 40 | ## Installation 41 | 42 | Clone the repo and build the Simfony compiler using cargo. 43 | 44 | ```bash 45 | git clone https://github.com/BlockstreamResearch/simfony.git 46 | cd simfony 47 | cargo build 48 | ``` 49 | 50 | Optionally, install the Simfony compiler using cargo. 51 | 52 | ```bash 53 | cargo install --path . 54 | ``` 55 | 56 | ## Usage 57 | 58 | The Simfony compiler takes two arguments: 59 | 60 | 1. A path to a Simfony program file (`.simf`) 61 | 1. A path to a Simfony witness file (`.wit`, optional) 62 | 63 | The compiler produces a base64-encoded Simplicity program. Witness data will be included if a witness file is provided. 64 | 65 | ```bash 66 | ./target/debug/simc examples/test.simf examples/test.wit 67 | ``` 68 | 69 | ### VSCode extension 70 | 71 | See the installation [instructions](./vscode/README.md). 72 | -------------------------------------------------------------------------------- /bitcoind-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bitcoind-tests" 3 | version = "0.1.0" 4 | authors = ["sanket1729 "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | simfony = { path = ".." } 9 | elementsd = { version = "0.11.0" } 10 | actual-rand = { package = "rand", version = "0.8.4" } 11 | secp256k1 = { version = "0.29.0", features = ["rand-std"] } 12 | -------------------------------------------------------------------------------- /bitcoind-tests/elementsd-simplicity.nix: -------------------------------------------------------------------------------- 1 | { pkgs 2 | }: 3 | pkgs.elementsd.overrideAttrs (_: { 4 | version = "liquid-testnet-2024-10-08"; 5 | src = pkgs.fetchFromGitHub { 6 | owner = "ElementsProject"; 7 | repo = "elements"; 8 | rev = "f957d3cde17c85afb18c6747f9c0b4fcb599f19a"; # <-- update this to latest `simplicity` branch: https://github.com/ElementsProject/elements/commits/simplicity 9 | sha256 = "sha256-XzdfbrQ7s4PfM5N00oP1jo5BNmD4WUMUe79QsTxsL4s="; # <-- overwrite this, rerun and place the expected hash 10 | }; 11 | withWallet = true; 12 | withGui = false; 13 | doCheck = false; 14 | }) 15 | -------------------------------------------------------------------------------- /bitcoind-tests/tests/common/daemon.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use elements::encode::{deserialize, serialize_hex}; 4 | use elements::hex::FromHex; 5 | use elementsd::bitcoincore_rpc::jsonrpc::serde_json::{json, Value}; 6 | use elementsd::bitcoind::bitcoincore_rpc::RpcApi; 7 | use elementsd::ElementsD; 8 | use simfony::elements; 9 | 10 | // We are not using pegins right now, but it might be required in case in future 11 | // if we extend the tests to check pegins etc. 12 | pub fn setup() -> (ElementsD, elements::BlockHash) { 13 | let mut conf = elementsd::Conf::new(None); 14 | 15 | // HACK: Upstream has issued only 21 million sats intially, but our hard coded tests 16 | // consume more coins. In order to avoid further conflicts, mutate the default arg here. 17 | 18 | let arg_pos = conf 19 | .0 20 | .args 21 | .iter() 22 | .position(|x| x.starts_with("-initialfreecoins=")); 23 | 24 | match arg_pos { 25 | Some(i) => conf.0.args[i] = "-initialfreecoins=210000000000", 26 | None => conf.0.args.push("-initialfreecoins=210000000000"), 27 | }; 28 | conf.0.args.push("-evbparams=simplicity:-1:::"); 29 | 30 | let elementsd = ElementsD::with_conf(elementsd::exe_path().unwrap(), &conf).unwrap(); 31 | 32 | let create = elementsd.call("createwallet", &["wallet".into()]); 33 | assert_eq!(create.get("name").unwrap(), "wallet"); 34 | 35 | let rescan = elementsd.call("rescanblockchain", &[]); 36 | assert_eq!(rescan.get("stop_height").unwrap().as_u64().unwrap(), 0); 37 | 38 | let balances = elementsd.call("getbalances", &[]); 39 | let mine = balances.get("mine").unwrap(); 40 | let trusted = mine.get("trusted").unwrap(); 41 | assert_eq!(trusted.get("bitcoin").unwrap(), 2100.0); 42 | 43 | let genesis_str = elementsd.call("getblockhash", &[0u32.into()]); 44 | let genesis_str = genesis_str.as_str().unwrap(); 45 | let genesis_hash = elements::BlockHash::from_str(genesis_str).unwrap(); 46 | 47 | (elementsd, genesis_hash) 48 | } 49 | // Upstream all common methods later 50 | pub trait Call { 51 | fn call(&self, cmd: &str, args: &[Value]) -> Value; 52 | fn get_new_address(&self) -> elements::Address; 53 | fn send_to_address(&self, addr: &elements::Address, amt: &str) -> elements::Txid; 54 | fn get_transaction(&self, txid: &elements::Txid) -> elements::Transaction; 55 | fn test_mempool_accept(&self, hex: &elements::Transaction) -> bool; 56 | fn send_raw_transaction(&self, hex: &elements::Transaction) -> elements::Txid; 57 | fn generate(&self, blocks: u32); 58 | } 59 | 60 | impl Call for ElementsD { 61 | fn call(&self, cmd: &str, args: &[Value]) -> Value { 62 | self.client().call::(cmd, args).unwrap() 63 | } 64 | 65 | fn get_new_address(&self) -> elements::Address { 66 | let addr_str = self 67 | .call("getnewaddress", &[]) 68 | .as_str() 69 | .unwrap() 70 | .to_string(); 71 | 72 | elements::Address::from_str(&addr_str).unwrap() 73 | } 74 | 75 | fn get_transaction(&self, txid: &elements::Txid) -> elements::Transaction { 76 | let tx_hex = self.call("gettransaction", &[txid.to_string().into()])["hex"] 77 | .as_str() 78 | .unwrap() 79 | .to_string(); 80 | 81 | let tx_bytes = Vec::::from_hex(&tx_hex).unwrap(); 82 | deserialize(&tx_bytes).unwrap() 83 | } 84 | 85 | fn send_to_address(&self, addr: &elements::Address, amt: &str) -> elements::Txid { 86 | let tx_id = self 87 | .call("sendtoaddress", &[addr.to_string().into(), amt.into()]) 88 | .as_str() 89 | .unwrap() 90 | .to_string(); 91 | elements::Txid::from_str(&tx_id).unwrap() 92 | } 93 | 94 | fn send_raw_transaction(&self, tx: &elements::Transaction) -> elements::Txid { 95 | let tx_id = self 96 | .call("sendrawtransaction", &[serialize_hex(tx).into()]) 97 | .as_str() 98 | .unwrap() 99 | .to_string(); 100 | 101 | elements::Txid::from_str(&tx_id).unwrap() 102 | } 103 | 104 | fn generate(&self, blocks: u32) { 105 | let address = self.get_new_address(); 106 | let _value = self.call( 107 | "generatetoaddress", 108 | &[blocks.into(), address.to_string().into()], 109 | ); 110 | } 111 | 112 | fn test_mempool_accept(&self, tx: &elements::Transaction) -> bool { 113 | let result = self.call("testmempoolaccept", &[json!([serialize_hex(tx)])]); 114 | let allowed = result.get(0).unwrap().get("allowed"); 115 | allowed.unwrap().as_bool().unwrap() 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /bitcoind-tests/tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod daemon; 2 | pub mod test; 3 | pub mod util; 4 | -------------------------------------------------------------------------------- /bitcoind-tests/tests/common/test.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use elements::hashes::Hash; 4 | use elements::pset::PartiallySignedTransaction as Psbt; 5 | use elements::{confidential, secp256k1_zkp as secp256k1}; 6 | use elementsd::ElementsD; 7 | use secp256k1::XOnlyPublicKey; 8 | use simfony::{elements, simplicity}; 9 | use simplicity::jet::elements::{ElementsEnv, ElementsUtxo}; 10 | 11 | use crate::common::daemon::Call; 12 | 13 | type FnWitness = fn([u8; 32]) -> simfony::WitnessValues; 14 | 15 | #[derive(Clone)] 16 | pub struct TestCase<'a> { 17 | pub name: &'static str, 18 | template: Option, 19 | compiled: Option, 20 | witness: FnWitness, 21 | lock_time: elements::LockTime, 22 | sequence: elements::Sequence, 23 | daemon: &'a ElementsD, 24 | genesis_hash: elements::BlockHash, 25 | funding: Option, 26 | } 27 | 28 | #[derive(Clone)] 29 | struct Funding { 30 | address: elements::Address, 31 | txid: elements::Txid, 32 | } 33 | 34 | impl<'a> TestCase<'a> { 35 | pub fn new(daemon: &'a ElementsD, genesis_hash: elements::BlockHash) -> Self { 36 | Self { 37 | name: "test name is missing", 38 | template: None, 39 | compiled: None, 40 | witness: empty_witness, 41 | lock_time: elements::LockTime::ZERO, 42 | sequence: elements::Sequence::ZERO, 43 | daemon, 44 | genesis_hash, 45 | funding: None, 46 | } 47 | } 48 | 49 | pub fn name(mut self, name: &'static str) -> Self { 50 | self.name = name; 51 | self 52 | } 53 | 54 | pub fn program_path>(mut self, path: P) -> Self { 55 | let text = std::fs::read_to_string(path).expect("path should be readable"); 56 | let compiled = simfony::CompiledProgram::new(text.as_str(), simfony::Arguments::default(), false) 57 | .expect("program should compile"); 58 | self.compiled = Some(compiled); 59 | self 60 | } 61 | 62 | pub fn template_path>(mut self, path: P) -> Self { 63 | let text = std::fs::read_to_string(path).expect("path should be readable"); 64 | let template = 65 | simfony::TemplateProgram::new(text.as_str()).expect("program should compile"); 66 | self.template = Some(template); 67 | self 68 | } 69 | 70 | pub fn arguments(mut self, arguments: simfony::Arguments) -> Self { 71 | let compiled = self 72 | .template 73 | .as_ref() 74 | .expect("template should exist") 75 | .instantiate(arguments, false) 76 | .expect("arguments should be consistent with the program"); 77 | self.compiled = Some(compiled); 78 | self 79 | } 80 | 81 | pub fn witness(mut self, witness: FnWitness) -> Self { 82 | self.witness = witness; 83 | self 84 | } 85 | 86 | pub fn lock_time(mut self, lock_time: elements::LockTime) -> Self { 87 | self.lock_time = lock_time; 88 | self 89 | } 90 | 91 | pub fn sequence(mut self, sequence: elements::Sequence) -> Self { 92 | self.sequence = sequence; 93 | self 94 | } 95 | 96 | fn script_ver(&self) -> (elements::Script, elements::taproot::LeafVersion) { 97 | let script = elements::script::Script::from( 98 | self.compiled 99 | .as_ref() 100 | .expect("program should be defined") 101 | .commit() 102 | .cmr() 103 | .as_ref() 104 | .to_vec(), 105 | ); 106 | (script, simplicity::leaf_version()) 107 | } 108 | 109 | fn taproot_spend_info(&self) -> elements::taproot::TaprootSpendInfo { 110 | let internal_key = XOnlyPublicKey::from_str( 111 | "f5919fa64ce45f8306849072b26c1bfdd2937e6b81774796ff372bd1eb5362d2", 112 | ) 113 | .unwrap(); 114 | let builder = elements::taproot::TaprootBuilder::new(); 115 | let (script, version) = self.script_ver(); 116 | let builder = builder 117 | .add_leaf_with_ver(0, script, version) 118 | .expect("tap tree should be valid"); 119 | builder 120 | .finalize(secp256k1::SECP256K1, internal_key) 121 | .expect("tap tree should be valid") 122 | } 123 | 124 | // Find the Outpoint by value. 125 | // Ideally, we should find by scriptPubkey, but this 126 | // works for temp test case 127 | fn get_vout(&self) -> (elements::OutPoint, elements::TxOut) { 128 | let funding = self.funding.as_ref().expect("test funding is missing"); 129 | let tx = self.daemon.get_transaction(&funding.txid); 130 | let script = funding.address.script_pubkey(); 131 | for (vout, txout) in tx.output.into_iter().enumerate() { 132 | if txout.value == confidential::Value::Explicit(100_000_000) 133 | && txout.script_pubkey == script 134 | { 135 | return (elements::OutPoint::new(funding.txid, vout as u32), txout); 136 | } 137 | } 138 | unreachable!("Only call get vout on functions which have the expected outpoint"); 139 | } 140 | 141 | pub fn create_utxo(&mut self) { 142 | let info = self.taproot_spend_info(); 143 | let blinder = None; 144 | let address = elements::Address::p2tr( 145 | secp256k1::SECP256K1, 146 | info.internal_key(), 147 | info.merkle_root(), 148 | blinder, 149 | &elements::AddressParams::ELEMENTS, 150 | ); 151 | let amount = "1"; 152 | let txid = self.daemon.send_to_address(&address, amount); 153 | self.funding = Some(Funding { address, txid }); 154 | } 155 | 156 | pub fn spend_utxo(&self) { 157 | let compiled = self.compiled.as_ref().expect("test program is missing"); 158 | let (outpoint, utxo) = self.get_vout(); 159 | let mut psbt = Psbt::from_tx(elements::Transaction { 160 | version: 2, 161 | lock_time: self.lock_time, 162 | input: vec![elements::TxIn { 163 | previous_output: outpoint, 164 | is_pegin: false, 165 | script_sig: elements::Script::new(), 166 | sequence: self.sequence, 167 | asset_issuance: elements::AssetIssuance::null(), 168 | witness: elements::TxInWitness::empty(), 169 | }], 170 | output: vec![ 171 | elements::TxOut { 172 | value: confidential::Value::Explicit(99_997_000), 173 | script_pubkey: self.daemon.get_new_address().script_pubkey(), 174 | asset: utxo.asset, 175 | nonce: confidential::Nonce::Null, 176 | witness: elements::TxOutWitness::empty(), 177 | }, 178 | elements::TxOut::new_fee(3_000, utxo.asset.explicit().unwrap()), 179 | ], 180 | }); 181 | let info = self.taproot_spend_info(); 182 | let script_ver = self.script_ver(); 183 | let control_block = info 184 | .control_block(&script_ver) 185 | .expect("control block should exist"); 186 | let sighash_all = { 187 | let tx = psbt 188 | .extract_tx() 189 | .expect("transaction should be extractable"); 190 | let utxos = vec![ElementsUtxo::from(utxo)]; 191 | let index = 0; 192 | let annex = None; 193 | let env = ElementsEnv::new( 194 | &tx, 195 | utxos, 196 | index, 197 | compiled.commit().cmr(), 198 | control_block.clone(), 199 | annex, 200 | self.genesis_hash, 201 | ); 202 | env.c_tx_env().sighash_all() 203 | }; 204 | let witness_values = (self.witness)(sighash_all.to_byte_array()); 205 | let satisfied_program = compiled 206 | .satisfy(witness_values) 207 | .expect("program should be satisfiable"); 208 | let (program_bytes, witness_bytes) = satisfied_program.redeem().encode_to_vec(); 209 | psbt.inputs_mut()[0].final_script_witness = Some(vec![ 210 | witness_bytes, 211 | program_bytes, 212 | script_ver.0.into_bytes(), 213 | control_block.serialize(), 214 | ]); 215 | let tx = psbt 216 | .extract_tx() 217 | .expect("transaction should be extractable"); 218 | 219 | let _txid = self.daemon.send_raw_transaction(&tx); 220 | } 221 | } 222 | 223 | fn empty_witness(_sighash_all: [u8; 32]) -> simfony::WitnessValues { 224 | simfony::WitnessValues::default() 225 | } 226 | -------------------------------------------------------------------------------- /bitcoind-tests/tests/common/util.rs: -------------------------------------------------------------------------------- 1 | pub fn key_pair(secret_key: u32) -> secp256k1::Keypair { 2 | let mut secret_key_bytes = [0u8; 32]; 3 | secret_key_bytes[28..].copy_from_slice(&secret_key.to_be_bytes()); 4 | secp256k1::Keypair::from_seckey_slice(secp256k1::SECP256K1, &secret_key_bytes) 5 | .expect("secret key should be valid") 6 | } 7 | 8 | pub fn sign_schnorr(secret_key: u32, message: [u8; 32]) -> [u8; 64] { 9 | let key_pair = key_pair(secret_key); 10 | let message = secp256k1::Message::from_digest(message); 11 | key_pair.sign_schnorr(message).serialize() 12 | } 13 | 14 | pub fn xonly_public_key(secret_key: u32) -> simfony::num::U256 { 15 | let key_pair = key_pair(secret_key); 16 | let bytes = key_pair.x_only_public_key().0.serialize(); 17 | simfony::num::U256::from_byte_array(bytes) 18 | } 19 | -------------------------------------------------------------------------------- /bitcoind-tests/tests/spend_utxo.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use elements::hashes::Hash; 4 | use elements::secp256k1_zkp as secp256k1; 5 | use secp256k1::hashes::{sha256, HashEngine}; 6 | use simfony::str::WitnessName; 7 | use simfony::types::TypeConstructible; 8 | use simfony::value::ValueConstructible; 9 | use simfony::{elements, ResolvedType, Value}; 10 | 11 | mod common; 12 | use common::daemon::{self, Call}; 13 | use common::test::TestCase; 14 | use common::util; 15 | 16 | #[test] 17 | fn spend_utxo() { 18 | let (daemon, genesis_hash) = daemon::setup(); 19 | let mut tests = [ 20 | TestCase::new(&daemon, genesis_hash) 21 | .name("OP_CAT") 22 | .program_path("../examples/cat.simf"), 23 | TestCase::new(&daemon, genesis_hash) 24 | .name("HODL vault") 25 | .program_path("../examples/hodl_vault.simf") 26 | .witness(hodl_vault) 27 | .lock_time(elements::LockTime::from_consensus(1000)) 28 | .sequence(elements::Sequence::ENABLE_LOCKTIME_NO_RBF), 29 | TestCase::new(&daemon, genesis_hash) 30 | .name("Pay to public key") 31 | .template_path("../examples/p2pk.simf") 32 | .arguments(p2pk_args()) 33 | .witness(p2pk), 34 | TestCase::new(&daemon, genesis_hash) 35 | .name("Pay to public key hash") 36 | .program_path("../examples/p2pkh.simf") 37 | .witness(p2pkh), 38 | TestCase::new(&daemon, genesis_hash) 39 | .name("Pay to multisig") 40 | .program_path("../examples/p2ms.simf") 41 | .witness(p2ms), 42 | ]; 43 | tests.iter_mut().for_each(TestCase::create_utxo); 44 | daemon.generate(1000); // satisfies lock time of all test cases 45 | for test in tests { 46 | println!("⏳ {}", test.name); 47 | test.spend_utxo(); 48 | println!("✅ {}", test.name); 49 | } 50 | } 51 | 52 | fn hodl_vault(sighash_all: [u8; 32]) -> simfony::WitnessValues { 53 | let mut witness_values = HashMap::new(); 54 | let oracle_height = 1000; 55 | witness_values.insert( 56 | WitnessName::from_str_unchecked("ORACLE_HEIGHT"), 57 | Value::u32(oracle_height), 58 | ); 59 | let oracle_price = 100_000; 60 | witness_values.insert( 61 | WitnessName::from_str_unchecked("ORACLE_PRICE"), 62 | Value::u32(oracle_price), 63 | ); 64 | let mut hasher = sha256::HashEngine::default(); 65 | hasher.input(&oracle_height.to_be_bytes()); 66 | hasher.input(&oracle_price.to_be_bytes()); 67 | let oracle_hash = sha256::Hash::from_engine(hasher).to_byte_array(); 68 | witness_values.insert( 69 | WitnessName::from_str_unchecked("ORACLE_SIG"), 70 | Value::byte_array(util::sign_schnorr(1, oracle_hash)), 71 | ); 72 | witness_values.insert( 73 | WitnessName::from_str_unchecked("OWNER_SIG"), 74 | Value::byte_array(util::sign_schnorr(2, sighash_all)), 75 | ); 76 | simfony::WitnessValues::from(witness_values) 77 | } 78 | 79 | fn p2pk_args() -> simfony::Arguments { 80 | simfony::Arguments::from(HashMap::from([( 81 | WitnessName::from_str_unchecked("ALICE_PUBLIC_KEY"), 82 | Value::u256(util::xonly_public_key(1)), 83 | )])) 84 | } 85 | 86 | fn p2pk(sighash_all: [u8; 32]) -> simfony::WitnessValues { 87 | simfony::WitnessValues::from(HashMap::from([( 88 | WitnessName::from_str_unchecked("ALICE_SIGNATURE"), 89 | Value::byte_array(util::sign_schnorr(1, sighash_all)), 90 | )])) 91 | } 92 | 93 | fn p2pkh(sighash_all: [u8; 32]) -> simfony::WitnessValues { 94 | let mut witness_values = HashMap::new(); 95 | witness_values.insert( 96 | WitnessName::from_str_unchecked("PK"), 97 | Value::u256(util::xonly_public_key(1)), 98 | ); 99 | witness_values.insert( 100 | WitnessName::from_str_unchecked("SIG"), 101 | Value::byte_array(util::sign_schnorr(1, sighash_all)), 102 | ); 103 | simfony::WitnessValues::from(witness_values) 104 | } 105 | 106 | fn p2ms(sighash_all: [u8; 32]) -> simfony::WitnessValues { 107 | let mut witness_values = HashMap::new(); 108 | let sig1 = Value::some(Value::byte_array(util::sign_schnorr(1, sighash_all))); 109 | let sig2 = Value::none(ResolvedType::byte_array(64)); 110 | let sig3 = Value::some(Value::byte_array(util::sign_schnorr(3, sighash_all))); 111 | let ty = sig1.ty().clone(); 112 | let maybe_sigs = Value::array([sig1, sig2, sig3], ty); 113 | witness_values.insert(WitnessName::from_str_unchecked("MAYBE_SIGS"), maybe_sigs); 114 | simfony::WitnessValues::from(witness_values) 115 | } 116 | -------------------------------------------------------------------------------- /book/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Christian Lewe"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "The Simfony Programming Language" 7 | -------------------------------------------------------------------------------- /book/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Introduction](./introduction.md) 4 | 5 | # Types and Values 6 | - [Types and Values](./type.md) 7 | - [Type Aliases](./type_alias.md) 8 | - [Type Casting](./type_casting.md) 9 | 10 | # Writing a Program 11 | - [Let Statements](./let_statement.md) 12 | - [Match Expression](./match_expression.md) 13 | - [Functions](./function.md) 14 | - [Programs](./program.md) 15 | 16 | # Locking and Unlocking UTXOs 17 | -------------------------------------------------------------------------------- /book/src/function.md: -------------------------------------------------------------------------------- 1 | # Functions 2 | 3 | Functions are defined and called [just like in Rust](https://doc.rust-lang.org/std/keyword.fn.html). 4 | 5 | ```rust 6 | fn add(x: u32, y: u32) -> u32 { 7 | let (carry, sum): (bool, u32) = jet::add_32(x, y); 8 | match carry { 9 | true => panic!(), // overflow 10 | false => {}, // ok 11 | }; 12 | sum 13 | } 14 | ``` 15 | 16 | The above example defines a function called `add` that takes two parameters: variable `x` of type `u32` and variable `y` of type `u32`. The function returns a value of type `u32`. 17 | 18 | The body of the function is a block expression `{ ... }` that is executed from top to bottom. 19 | The function returns on the final line _(note the missing semicolon `;`)_. 20 | In the above example, `x` and `y` are added via the `add_32` jet. 21 | The function then checks if the carry is true, signaling an overflow, in which case it panics. 22 | On the last line, the value of `sum` is returned. 23 | 24 | The above function is called by writing its name `add` followed by a list of arguments `(40, 2)`. 25 | Each parameter needs an argument, so the list of arguments is as long as the list of arguments. 26 | Here, `x` is assigned the value `40` and `y` is assigned the value `2`. 27 | 28 | ```rust 29 | let z: u32 = add(40, 2); 30 | ``` 31 | 32 | ## No early returns 33 | 34 | Simfony has no support for an early return via a "return" keyword. 35 | The only branching that is available is via [match expressions](./match_expression.md). 36 | 37 | ## No recursion 38 | 39 | Simfony has no support for recursive function calls. 40 | A function can be called inside a function body if it has been defined before. 41 | This means that a function cannot call itself. 42 | Loops, where `f` calls `g` and `g` calls `f`, are also impossible. 43 | 44 | What _is_ possible are stratified function definitions, where level-0 functions depend on nothing, level-1 functions depend on level-0 functions, and so on. 45 | 46 | ```rust 47 | fn level_0() -> u32 { 48 | 0 49 | } 50 | 51 | fn level_1() -> u32 { 52 | let (_, next) = jet::increment_32(level_0()); 53 | next 54 | } 55 | 56 | fn level_2() -> u32 { 57 | let (_, next) = jet::increment_32(level_1)); 58 | next 59 | } 60 | ``` 61 | 62 | ## Order matters 63 | 64 | If function `g` calls function `f`, then `f` **must** be defined before `g`. 65 | 66 | ```rust 67 | fn f() -> u32 { 68 | 42 69 | } 70 | 71 | fn g() -> u32 { 72 | f() 73 | } 74 | ``` 75 | 76 | ## Main function 77 | 78 | The `main` function is the entry point of each Simfony program. 79 | Running a program means running its `main` function. 80 | Other functions are called from the `main` function. 81 | 82 | ```rust 83 | fn main() { 84 | // ... 85 | } 86 | ``` 87 | 88 | The `main` function is a reserved name and must exist in every program. 89 | Simfony programs are always "binaries". 90 | There is no support for "libraries". 91 | 92 | ## Jets 93 | 94 | Jets are predefined and optimized functions for common usecases. 95 | 96 | ```rust 97 | jet::add_32(40, 2) 98 | ``` 99 | 100 | Jets live inside the namespace `jet`, which is why they are prefixed with `jet::`. 101 | They can be called without defining them manually. 102 | 103 | It is usually more efficient to call a jet than to manually compute a value. 104 | 105 | [The jet documentation](https://docs.rs/simfony-as-rust/latest/simfony_as_rust/jet/index.html) lists each jet and explains what it does. 106 | -------------------------------------------------------------------------------- /book/src/introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Simfony is a high-level language for writing Bitcoin smart contracts. 4 | In other words, Simfony is a language for expressing spending conditions of UTXOs on the Bitcoin blockchain. 5 | 6 | Simfony looks and feels like [Rust](https://www.rust-lang.org/). 7 | Developers write Simfony, Bitcoin full nodes run Simplicity. 8 | -------------------------------------------------------------------------------- /book/src/let_statement.md: -------------------------------------------------------------------------------- 1 | # Let Statement 2 | 3 | Variables are defined in let statements, [just like in Rust](https://doc.rust-lang.org/std/keyword.let.html). 4 | 5 | ```rust 6 | let x: u32 = 1; 7 | ``` 8 | 9 | The above let statement defines a variable called `x`. 10 | The variable is of type `u32` and it is assigned the value `1`. 11 | 12 | ```rust 13 | let x: u32 = f(1337); 14 | ``` 15 | 16 | Variables can be assigned to the output value of any expression, such as function calls. 17 | 18 | ## Explicit typing 19 | 20 | In Simfony, the type of a defined variable **always** has to be written. 21 | This is different from Rust, which has better type inference. 22 | 23 | ## Immutability 24 | 25 | Simfony variables are **always** immutable. 26 | There are no mutable variables. 27 | 28 | ## Redefinition and scoping 29 | 30 | The same variable can be defined twice in the same scope. 31 | The later definition overrides the earlier definition. 32 | 33 | ```rust 34 | let x: u32 = 1; 35 | let x: u32 = 2; 36 | assert!(jet::eq_32(x, 2)); // x == 2 37 | ``` 38 | 39 | Normal scoping rules apply: 40 | Variables from outer scopes are available inside inner scopes. 41 | A variable defined in an inner scope shadows a variable of the same name from an outer scope. 42 | 43 | ```rust 44 | let x: u32 = 1; 45 | let y: u32 = 2; 46 | let z: u32 = { 47 | let x: u32 = 3; 48 | assert!(jet::eq_32(y, 2)); // y == 2 49 | x 50 | }; 51 | assert!(jet::eq_32(x, 3)); // z == 3 52 | ``` 53 | 54 | ## Pattern matching 55 | 56 | There is limited pattern matching support inside let statements. 57 | 58 | ```rust 59 | let (x, y, _): (u8, u16, u32) = (1, 2, 3); 60 | let [x, _, z]: [u32; 3] = [1, 2, 3]; 61 | ``` 62 | 63 | In the first line, the tuple `(1, 2, 3)` is deconstructed into the values `1`, `2` and `3`. 64 | These values are assigned to the variable names `x`, `y` and `_`. 65 | The variable name `_` is a special name that ignores its value. 66 | In the end, two variables are created: `x: u32 = 1` and `y: u16 = 2`. 67 | 68 | Similarly, arrays can be deconstructed element by element and assigned to a variable each. 69 | -------------------------------------------------------------------------------- /book/src/match_expression.md: -------------------------------------------------------------------------------- 1 | # Match Expression 2 | 3 | A match expression conditionally executes code branches. 4 | Which branch is executed depends on the input to the match expression. 5 | 6 | ```rust 7 | let result: u32 = match f(42) { 8 | Left(x: u32) => x, 9 | Right(x: u16) => jet::left_pad_low_16_32(x), 10 | }; 11 | ``` 12 | 13 | In the above example, the output of the function call `f(42)` is matched. 14 | `f` returns an output of type `Either`. 15 | If `f(42)` returns a value that matches the pattern `Left(x: u32)`, then the first match arm is executed. 16 | This arm simply returns the value `x`. 17 | Alternatively, if `f(42)` returns a value that matches the pattern `Right(x: u16)`, then the second match arm is executed. 18 | This arm extends the 16-bit number `x` to a 32-bit number by padding its left with zeroes. 19 | Because of type constraints, the output of `f` must match one of these two patterns. 20 | The whole match expression returns a value of type `u32`, from one of the two arms. 21 | 22 | ## Explicit typing 23 | 24 | In Simfony, the type of variables inside match arms must **always** be written. 25 | This is different from Rust, which has better type inference. 26 | 27 | ## Pattern matching 28 | 29 | There is limited support for pattern matching inside match expressions. 30 | 31 | Boolean values can be matched. 32 | The Boolean match expression is the replacement for an "if-then-else" in Simfony. 33 | 34 | ```rust 35 | let bit_flip: bool = match false { 36 | false => true, 37 | true => false, 38 | }; 39 | ``` 40 | 41 | Optional values can be matched. 42 | The `Some` arm introduces a variable which must be explicitly typed. 43 | 44 | ```rust 45 | let unwrap_or_default: u32 = match Some(42) { 46 | None => 0, 47 | Some(x: u32) => x, 48 | }; 49 | ``` 50 | 51 | Finally, `Either` values can be matched. 52 | Again, variables that are introduced in match arms must be explicitly typed. 53 | 54 | ```rust 55 | let map_either: u32 = match Left(1337) { 56 | Left(x: u32) => f(x), 57 | Right(y: u32) => f(y), 58 | }; 59 | ``` 60 | 61 | Match expressions don't support further pattern matching, in contrast to Rust. 62 | 63 | ```rust 64 | let unwrap_or_default: u32 = match Some((4, 2)) { 65 | None => 0, 66 | // this doesn't compile 67 | Some((y, z): (u16, u16)) => <(u16, u16)>::into((y, z)), 68 | }; 69 | ``` 70 | 71 | However, the match arm can contain code that performs the deconstruction. 72 | For example, the tuple `x` of type `(u16, u16)` can be deconstructed into two integers `y` and `z` of type `u16`. 73 | 74 | ```rust 75 | let unwrap_or_default: u32 = match Some((4, 2)) { 76 | None => 0, 77 | Some(x: (u16, u16)) => { 78 | let (y, z): (u16, u16) = x; 79 | <(u16, u16)>::into((y, z)) 80 | } 81 | }; 82 | ``` 83 | 84 | The match arm can also contain match expressions for further deconstruction. 85 | For example, the sum value `x` of type `Either` can be matched as either `Left(y: u32)` or `Right(z: u32)`. 86 | 87 | ```rust 88 | let unwrap_or_default: u32 = match Some(Left(42)) { 89 | None => 0, 90 | Some(x: Either) => match x { 91 | Left(y: u32) => y, 92 | Right(z: u32) => z, 93 | }, 94 | }; 95 | ``` 96 | -------------------------------------------------------------------------------- /book/src/program.md: -------------------------------------------------------------------------------- 1 | # Programs 2 | 3 | A Simfony program consists of a `main` [function](./function.md). 4 | 5 | A program may also have [type aliases](./type_alias.md) or custom [function definitions](./function.md). 6 | The `main` function comes last in the program, because everything it calls must be defined before it. 7 | 8 | ```rust 9 | type Furlong = u32; 10 | type Mile = u32; 11 | 12 | fn to_miles(distance: Either) -> Mile { 13 | match distance { 14 | Left(furlongs: Furlong) => jet::divide_32(furlongs, 8), 15 | Right(miles: Mile) => miles, 16 | } 17 | } 18 | 19 | fn main() { 20 | let eight_furlongs: Either = Left(8); 21 | let one_mile: Either = Right(1); 22 | assert!(jet::eq_32(1, to_miles(eight_furlongs))); 23 | assert!(jet::eq_32(1, to_miles(one_mile))); 24 | } 25 | ``` 26 | -------------------------------------------------------------------------------- /book/src/type.md: -------------------------------------------------------------------------------- 1 | # Types and Values 2 | 3 | Simfony mostly uses a subset of Rust's types. 4 | It extends Rust in some ways to make it work better with Simplicity and with the blockchain. 5 | 6 | ## Boolean Type 7 | 8 | | Type | Description | Values | 9 | |--------|-----------------|-----------------| 10 | | `bool` | Boolean | `false`, `true` | 11 | 12 | Values of type `bool` are truth values, which are either `true` or `false`. 13 | 14 | ## Integer Types 15 | 16 | | Type | Description | Values | 17 | |--------|-----------------|--------------------------------------------------------| 18 | | `u1` | 1-bit integer | `0`, `1` | 19 | | `u2` | 2-bit integer | `0`, `1`, `2`, `3` | 20 | | `u4` | 4-bit integer | `0`, `1`, …, `15` | 21 | | `u8` | 8-bit integer | `0`, `1`, …, `255` | 22 | | `u16` | 16-bit integer | `0`, `1`, …, `65535` | 23 | | `u32` | 32-bit integer | `0`, `1`, …, `4294967295` | 24 | | `u64` | 64-bit integer | `0`, `1`, …, `18446744073709551615` | 25 | | `u128` | 128-bit integer | `0`, `1`, …, `340282366920938463463374607431768211455` | 26 | | `u256` | 256-bit integer | `0`, `1`, …, 2256 - 1[^u256max] | 27 | 28 | Unsigned integers range from 1 bit to 256 bits. 29 | [`u8`](https://doc.rust-lang.org/std/primitive.u8.html) to [`u128`](https://doc.rust-lang.org/std/primitive.u128.html) are also supported in Rust. 30 | `u1`, `u2`, `u4` and `u256` are new to Simfony. 31 | Integer values can be written in decimal notation `123456`, binary notation[^bin] `0b10101010` or hexadecimal notation[^hex] `0xdeadbeef`. 32 | There are no signed integers. 33 | 34 | [^u256max]: The maximal value of type `u256` is `115792089237316195423570985008687907853269984665640564039457584007913129639935`. 35 | [^bin]: The number of bits must be equal to the bit width of the type. 36 | [^hex]: The number of hex digits must correspond to the bit width of the type. 37 | 38 | ## Tuple Types 39 | 40 | | Type | Description | Values | 41 | |--------------|-------------|---------------------------------------------------| 42 | | `()` | 0-tuple | `()` | 43 | | `(A)` | 1-tuple | `(a0,)`, `(a1,)`, … | 44 | | `(A, B)` | 2-tuple | `(a0, b0)`, `(a1, b1)`, `(a2, b2)`, `(a3, b3)`, … | 45 | | … | … | … | 46 | | `(A, B, …)` | n-tuple | `(a0, b0, …)`, … | 47 | 48 | [Tuples work just like in Rust](https://doc.rust-lang.org/std/primitive.tuple.html). 49 | 50 | The empty tuple `()` contains no information. 51 | It is also called the "unit". 52 | It is mostly used as the return type of functions that don't return anything. 53 | 54 | Singletons `(a0,)` must be written with an extra comma `,` to differentiate them from function calls. 55 | 56 | Bigger tuples `(a0, b0, …)` work like in pretty much any other programming language. 57 | Each tuple type `(A1, A2, …, AN)` defines a sequence `A1`, `A2`, …, `AN` of types. 58 | Values of that type must mirror the sequence of types: 59 | A tuple value `(a1, a2, …, aN)` consists of a sequence `a1`, `a2`, …, `aN` of values, where `a1` is of type `A1`, `a2` is of type `A2`, and so on. 60 | Tuples are always finite in length. 61 | 62 | > Tuples are different from arrays: 63 | > Each element of a tuple can have a different type. 64 | > Each element of an array must have the same type. 65 | 66 | ## Array Types 67 | 68 | | Type | Description | Values | 69 | |----------|-------------|---------------------------------------------------| 70 | | `[A; 0]` | 0-array | `[]` | 71 | | `[A; 1]` | 1-array | `[a0]`, `[a1]`, … | 72 | | `[A; 2]` | 2-array | `[a0, a1]`, `[a2, a3]`, `[a4, a5]`, `[a6, a7]`, … | 73 | | … | … | … | 74 | | `[A; N]` | n-array | `[a0, …, aN]`, … | 75 | 76 | [Arrays work just like in Rust](https://doc.rust-lang.org/std/primitive.array.html). 77 | 78 | The empty array `[]` is basically useless, but I included it for completeness. 79 | 80 | Arrays `[a0, …, aN]` work like in pretty much any other programming language. 81 | Each array type `[A; N]` defines an element type `A` and a length `N`. 82 | An array value `[a0, …, aN]` of that type consists of `N` many elements `a0`, …, `aN` that are each of type `A`. 83 | Arrays are always of finite length. 84 | 85 | > Arrays are different from tuples: 86 | > Each element of an array must have the same type. 87 | > Each element of a tuple can have a different type. 88 | 89 | ## List Types 90 | 91 | | Type | Description | Values | 92 | |---------------------------|---------------------|------------------------------------------------------| 93 | | `List` | <2-list | `list![]`, `list![a1]` | 94 | | `List` | <4-list | `list![]`, …, `list![a1, a2, a3]` | 95 | | `List` | <8-list | `list![]`, …, `list![a1, …, a7]` | 96 | | `List` | <16-list | `list![]`, …, `list![a1, …, a15]` | 97 | | `List` | <32-list | `list![]`, …, `list![a1, …, a31]` | 98 | | `List` | <64-list | `list![]`, …, `list![a1, …, a62]` | 99 | | `List` | <128-list | `list![]`, …, `list![a1, …, a127]` | 100 | | `List` | <256-list | `list![]`, …, `list![a1, …, a255]` | 101 | | `List` | <512-list | `list![]`, …, `list![a1, …, a511]` | 102 | | … | … | … | 103 | | `ListN`>` | <2N-list | `list![]`, …, `list![a1, …, a`(2N - 1)`]` | 104 | 105 | Lists hold a variable number of elements of the same type. 106 | This is similar to [Rust vectors](https://doc.rust-lang.org/std/vec/struct.Vec.html), but Simfony doesn't have a heap. 107 | In Simfony, lists exists on the stack, which is why the maximum list length is bounded. 108 | 109 | <2-lists hold fewer than 2 elements, so zero or one element. 110 | <4-lists hold fewer than 4 elements, so zero to three elements. 111 | <8-lists hold fewer than 8 elements, so zero to seven elements. 112 | And so on. 113 | For technical reasons, the list bound is always a power of two. 114 | The bound 1 is not supported, because it would only allow empty lists, which is useless. 115 | 116 | > Lists are different from arrays: 117 | > List values hold a variable number of elements. 118 | > Array values hold a fixed number of elements. 119 | 120 | On the blockchain, you pay for every byte that you use. 121 | If you use an array, then you pay for every single element. 122 | For example, values of type `[u8; 512]` cost roughly as much as 512 many `u8` values. 123 | However, if you use a list, then you only pay for the elements that you actually use. 124 | For example, the type `List` allows for up to 511 elements. 125 | If you only use three elements `list![1, 2, 3]`, then you pay for exactly three elements. 126 | You **don't** pay for the remaining 508 unused elements. 127 | 128 | ## Option Types 129 | 130 | | Type | Values | 131 | |-------------|-----------------------------------| 132 | | `Option` | `None`, `Some(a0)`, `Some(a1)`, … | 133 | 134 | Options represent values that might not be present. [They work just like in Rust](https://doc.rust-lang.org/std/option/index.html). 135 | 136 | An option type is generic over a type `A`. 137 | The value `None` is empty. 138 | The value `Some(a)` contains an inner value `a` of type `A`. 139 | 140 | In Rust, we implement options as follows. 141 | 142 | ```rust 143 | enum Option { 144 | None, 145 | Some(A), 146 | } 147 | ``` 148 | 149 | ## Either Types 150 | 151 | | Type | Values | 152 | |----------------|--------------------------------------------------------| 153 | | `Either` | `Left(a0)`, `Left(a1)`, …, `Right(b0)`, `Right(b1)`, … | 154 | 155 | Sum types represent values that are of some "left" type in some cases and that are of another "right" type in other cases. 156 | [They work just like in the either crate](https://docs.rs/either/latest/either/enum.Either.html). 157 | [The Result type from Rust is very similar, too](https://doc.rust-lang.org/std/result/index.html). 158 | 159 | A sum type type is generic over two types, `A` and `B`. 160 | The value `Left(a)` contains an inner value `a` of type `A`. 161 | The value `Right(b)` contains an inner value `b` of type `B`. 162 | 163 | In Rust, we implement sum types as follows. 164 | 165 | ```rust 166 | enum Either { 167 | Left(A), 168 | Right(B), 169 | } 170 | ``` 171 | -------------------------------------------------------------------------------- /book/src/type_alias.md: -------------------------------------------------------------------------------- 1 | # Type Aliases 2 | 3 | Simfony currently doesn't support Rust-like `struct`s for organizing data. 4 | 5 | ```rust 6 | struct User { 7 | active: bool, 8 | id: u256, 9 | sign_in_count: u64, 10 | } 11 | ``` 12 | 13 | Simfony programmers have to handle long tuples of unlabeled data, which can get messy. 14 | 15 | ```rust 16 | (bool, u256, u64) 17 | ``` 18 | 19 | To help with the situation, programmers can define custom type aliases. 20 | Aliases define a new name for an existing type. 21 | In contrast, `struct`s define an entirely new type, so aliases are different from `struct`s. 22 | However, aliases still help us to make the code more readable. 23 | 24 | ```rust 25 | type User = (bool, u256, u64); 26 | ``` 27 | 28 | There is also a list of builtin type aliases. 29 | These aliases can be used without defining them. 30 | 31 | | Builtin Alias | Definition | 32 | |------------------|-------------------------------| 33 | | `Amount1` | `Either<(u1, u256), u64>` | 34 | | `Asset1` | `Either<(u1, u256), u256>` | 35 | | `Confidential1` | `(u1, u256)` | 36 | | `Ctx8` | `(List, (u64, u256))` | 37 | | `Distance` | `u16` | 38 | | `Duration` | `u16` | 39 | | `ExplicitAmount` | `u256` | 40 | | `ExplicitAsset` | `u256` | 41 | | `ExplicitNonce` | `u256` | 42 | | `Fe` | `u256` | 43 | | `Ge` | `(u256, u256)` | 44 | | `Gej` | `((u256, u256), u256)` | 45 | | `Height` | `u32` | 46 | | `Lock` | `u32` | 47 | | `Message` | `u256` | 48 | | `Message64` | `[u8; 64]` | 49 | | `Nonce` | `Either<(u1, u256), u256>` | 50 | | `Outpoint` | `(u256, u32)` | 51 | | `Point` | `(u1, u256)` | 52 | | `Pubkey` | `u256` | 53 | | `Scalar` | `u256` | 54 | | `Signature` | `[u8; 64]` | 55 | | `Time` | `u32` | 56 | | `TokenAmount1` | `Either<(u1, u256), u64>` | 57 | -------------------------------------------------------------------------------- /book/src/type_casting.md: -------------------------------------------------------------------------------- 1 | # Casting 2 | 3 | A Simfony type can be cast into another Simfony type if both types share the same structure. 4 | The structure of a type has to do with how the type is implemented on the Simplicity "processor". 5 | I will spare you the boring details. 6 | 7 | Below is a table of types that can be cast into each other. 8 | 9 | | Type | Casts To (And Back) | 10 | |----------------|------------------------------------| 11 | | `bool` | `Either<(), ()>` | 12 | | `Option` | `Either<(), A>` | 13 | | `u1` | `bool` | 14 | | `u2` | `(u1, u1)` | 15 | | `u4` | `(u2, u2)` | 16 | | `u8` | `(u4, u4)` | 17 | | `u16` | `(u8, u8)` | 18 | | `u32` | `(u16, u16)` | 19 | | `u64` | `(u32, u32)` | 20 | | `u128` | `(u64, u64)` | 21 | | `u256` | `(u128, u128)` | 22 | | `(A)` | `A` | 23 | | `(A, B, C)` | `(A, (B, C))` | 24 | | `(A, B, C, D)` | `((A, B), (C, D))` | 25 | | … | … | 26 | | `[A; 0]` | `()` | 27 | | `[A; 1]` | `A` | 28 | | `[A; 2]` | `(A, A)` | 29 | | `[A; 3]` | `(A, (A, A))` | 30 | | `[A; 4]` | `((A, A), (A, A))` | 31 | | … | … | 32 | | `List` | `Option` | 33 | | `List` | `(Option<[A; 2]>, List)` | 34 | | `List` | `(Option<[A; 4]>, List)` | 35 | | `List` | `(Option<[A; 8]>, List)` | 36 | | `List` | `(Option<[A; 16]>, List)` | 37 | | `List` | `(Option<[A; 32]>, List)` | 38 | | `List` | `(Option<[A; 64]>, List)` | 39 | | `List` | `(Option<[A; 128]>, List)` | 40 | | `List` | `(Option<[A; 256]>, List)` | 41 | | … | … | 42 | 43 | ## Casting Rules 44 | 45 | Type `A` can be cast into itself (reflexivity). 46 | 47 | If type `A` can be cast into type `B`, then type `B` can be cast into type `A` (symmetry). 48 | 49 | If type `A` can be cast into type `B` and type `B` can be cast into type `C`, then type `A` can be cast into type `C` (transitivity). 50 | 51 | ## Casting Expression 52 | 53 | All casting in Simfony happens explicitly through a casting expression. 54 | 55 | ```rust 56 | ::into(input) 57 | ``` 58 | 59 | The above expression casts the value `input` of type `Input` into some output type. 60 | The input type of the cast is explict while the output type is implicit. 61 | 62 | In Simfony, the output type of every expression is known. 63 | 64 | ```rust 65 | let x: u32 = 1; 66 | ``` 67 | 68 | In the above example, the meaning of the expression `1` is clear because of the type `u32` of variable `x`. 69 | Here, `1` means a string of 31 zeroes and 1 one. 70 | _In other contexts, `1` could mean something different, like a string of 255 zeroes and 1 one._ 71 | 72 | The Simfony compiler knows the type of the outermost expression, and it tries to infer the types of inner expressions based on that. 73 | When it comes to casting expressions, the compiler has no idea about the input type of the cast. 74 | The programmer needs to supply this information by annotating the cast with its input type. 75 | 76 | ```rust 77 | let x: u32 = <(u16, u16)>::into((0, 1)); 78 | ``` 79 | 80 | In the above example, we cast the tuple `(0, 1)` of type `(u16, u16)` into type `u32`. 81 | Feel free to consult the table above to verify that this is valid cast. 82 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | msrv = "1.78.0" 2 | # We have an error type, `RichError`, of size 144. This is pushing it but probably fine. 3 | large-error-threshold = 145 4 | -------------------------------------------------------------------------------- /codegen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "codegen" 3 | version = "0.1.0" 4 | edition = "2021" 5 | description = "Generator of Rust code as interface between Simfony and Rust." 6 | publish = false 7 | 8 | [dependencies] 9 | simfony = { path = ".." } 10 | -------------------------------------------------------------------------------- /codegen/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io; 3 | 4 | use simfony::simplicity::jet::{Elements, Jet}; 5 | use simfony::types::TypeDeconstructible; 6 | 7 | mod jet; 8 | 9 | /// Write a Simfony jet as a Rust function to the sink. 10 | fn write_jet(jet: Elements, w: &mut W) -> io::Result<()> { 11 | for line in jet::documentation(jet).lines() { 12 | match line.is_empty() { 13 | true => writeln!(w, "///")?, 14 | false => writeln!(w, "/// {line}")?, 15 | } 16 | } 17 | writeln!(w, "///")?; 18 | writeln!(w, "/// ## Cost")?; 19 | writeln!(w, "///")?; 20 | writeln!(w, "/// {} mWU _(milli weight units)_", jet.cost())?; 21 | write!(w, "pub fn {jet}(")?; 22 | let parameters = simfony::jet::source_type(jet); 23 | for (i, ty) in parameters.iter().enumerate() { 24 | let identifier = (b'a' + i as u8) as char; 25 | if i == parameters.len() - 1 { 26 | write!(w, "{identifier}: {ty}")?; 27 | } else { 28 | write!(w, "{identifier}: {ty}, ")?; 29 | } 30 | } 31 | let target = simfony::jet::target_type(jet); 32 | match target.is_unit() { 33 | true => writeln!(w, ") {{")?, 34 | false => writeln!(w, ") -> {} {{", simfony::jet::target_type(jet))?, 35 | } 36 | 37 | writeln!(w, " todo!()")?; 38 | writeln!(w, "}}") 39 | } 40 | 41 | /// Write a category of jets as a Rust module to the sink. 42 | fn write_module(category: jet::Category, w: &mut W) -> io::Result<()> { 43 | writeln!(w, "/* This file has been automatically generated. */")?; 44 | writeln!(w)?; 45 | writeln!(w, "{}", category.documentation())?; 46 | writeln!(w)?; 47 | writeln!(w, "#![allow(unused)]")?; 48 | writeln!(w, "#![allow(clippy::complexity)]")?; 49 | writeln!(w)?; 50 | writeln!(w, "use super::*;")?; 51 | 52 | for jet in category.iter().cloned() { 53 | writeln!(w)?; 54 | write_jet(jet, w)?; 55 | } 56 | 57 | Ok(()) 58 | } 59 | 60 | fn main() -> io::Result<()> { 61 | for category in jet::Category::ALL { 62 | let file_name = format!("{category}.rs"); 63 | let mut file = File::create(file_name)?; 64 | write_module(category, &mut file)?; 65 | } 66 | 67 | Ok(()) 68 | } 69 | -------------------------------------------------------------------------------- /doc/context.md: -------------------------------------------------------------------------------- 1 | # Context 2 | 3 | A context Γ maps variable names to Simplicity types: 4 | 5 | Γ = [ `foo` ↦ 𝟙, `bar` ↦ 𝟚^32?, `baz` ↦ 𝟚^32 × 𝟙 ] 6 | 7 | We write Γ(`v`) = A to denote that variable `v` has type A in context Γ. 8 | 9 | We handle free variables inside Simfony expressions via contexts. 10 | 11 | If all free variables are defined in a context, then the context assigns a type to the expression. 12 | 13 | We write Γ ⊩ `a`: A to denote that expression `a` has type A in context Γ. 14 | 15 | Note that contexts handle only the **target type** of an expression! 16 | 17 | Source types are handled by environments and the translation of Simfony to Simplicity. 18 | 19 | We write Γ ⊎ Δ to denote the **disjoint union** of Γ and Δ. 20 | 21 | We write Γ // Δ to denote the **update** of Γ with Δ. The update contains mappings from both contexts. If a variable is present in both, then the mapping from Δ is taken. 22 | 23 | ## Unit literal 24 | 25 | Γ ⊩ `()`: 𝟙 26 | 27 | ## Product constructor 28 | 29 | If Γ ⊩ `b`: B 30 | 31 | If Γ ⊩ `c`: C 32 | 33 | Then Γ ⊩ `(b, c)`: B × C 34 | 35 | ## Left constructor 36 | 37 | If Γ ⊩ `b`: B 38 | 39 | Then Γ ⊩ `Left(b)`: B + C 40 | 41 | For any C 42 | 43 | ## Right constructor 44 | 45 | If Γ ⊩ `c`: c 46 | 47 | Then Γ ⊩ `Right(c)`: B + C 48 | 49 | For any B 50 | 51 | ## Bit string literal 52 | 53 | If `s` is a bit string of 2^n bits 54 | 55 | Then Γ ⊩ `0bs`: 𝟚^(2^n) 56 | 57 | ## Byte string literal 58 | 59 | If `s` is a hex string of 2^n digits 60 | 61 | Then Γ ⊩ `0xs`: 𝟚^(4 * 2^n) 62 | 63 | ## Variable 64 | 65 | If Γ(`v`) = B 66 | 67 | Then Γ ⊩ `v`: B 68 | 69 | ## Witness value 70 | 71 | Γ ⊩ `witness(name)`: B 72 | 73 | For any B 74 | 75 | ## Jet 76 | 77 | If `j` is the name of a jet of type B → C 78 | 79 | If Γ ⊩ `b`: B 80 | 81 | Then Γ ⊩ `jet::j b`: C 82 | 83 | ## Chaining 84 | 85 | If Γ ⊩ `b`: 𝟙 86 | 87 | If Γ ⊩ `c`: C 88 | 89 | Then Γ ⊩ `b; c`: C 90 | 91 | ## Patterns 92 | 93 | Type A and pattern `p` create a context denoted by PCtx(A, `p`) 94 | 95 | PCtx(A, `v`) := [`v` ↦ A] 96 | 97 | PCtx(A, `_`) := [] 98 | 99 | If `p1` and `p2` map disjoint sets of variables 100 | 101 | Then PCtx(A × B, `(p1, p2)`) := PCtx(A, `p1`) ⊎ PCtx(B, `p2`) 102 | 103 | ## Let statement 104 | 105 | If Γ ⊩ `b`: B 106 | 107 | If Γ // PCtx(B, `p`) ⊩ `c`: C 108 | 109 | Then Γ ⊩ `let p: B = b; c`: C 110 | 111 | With alternative syntax 112 | 113 | Then Γ ⊩ `let p = b; c`: C 114 | 115 | ## Match statement 116 | 117 | If Γ ⊩ `a`: B + C 118 | 119 | If Γ // [`x` ↦ B] ⊩ `b`: D 120 | 121 | If Γ // [`y` ↦ C] ⊩ `c`: D 122 | 123 | Then Γ ⊩ `match a { Left(x) => b, Right(y) => c, }`: D 124 | 125 | _(We do not enforce that `x` is used inside `b` or `y` inside `c`. Writing stupid programs is allowed, although there will be a compiler warning at some point.)_ 126 | 127 | ## Left unwrap 128 | 129 | If Γ ⊩ `b`: B + C 130 | 131 | Then Γ ⊩ `b.unwrap_left()`: B 132 | 133 | ## Right unwrap 134 | 135 | If Γ ⊩ `c`: B + C 136 | 137 | Then Γ ⊩ `c.unwrap_right()`: C 138 | -------------------------------------------------------------------------------- /doc/environment.md: -------------------------------------------------------------------------------- 1 | # Environment 2 | 3 | An environment Ξ maps variable names to Simplicity expressions. 4 | 5 | All expressions inside an environment share the same source type A. We say the environment is "from type A". 6 | 7 | ``` 8 | Ξ = 9 | [ foo ↦ unit: (𝟚^32? × 2^32) → 𝟙 10 | , bar ↦ take iden: (𝟚^32? × 𝟚^32) → 𝟚^32? 11 | , baz ↦ drop iden: (𝟚^32? × 𝟚^32) → 𝟚^32 12 | ] 13 | ``` 14 | 15 | We use environments to translate variables inside Simfony expressions to Simplicity. 16 | 17 | The environment tells us the Simplicity expression that returns the value of each variable. 18 | 19 | We translate a Simfony program "top to bottom". Each time a variable is defined, we update the environment to reflect this change. 20 | 21 | During the translation, we can ignore the source type of Simplicity expressions (translated Simfony expressions) entirely. We can focus on producing a Simplicity value of the expected target type. Environments ensure that we get input values for each variable that is in scope. 22 | 23 | Target types are handled by contexts. 24 | 25 | We obtain context Ctx(Ξ) from environment Ξ by mapping each variable `a` from Ξ to the target type of Ξ(`x`): 26 | 27 | Ctx(Ξ)(`x`) = B if Ξ(`x`) = a: A → B 28 | 29 | ## Patterns 30 | 31 | 32 | Patterns occur in let statements `let p := s`. 33 | 34 | Pattern `p` binds the output of Simfony expression `s` to variables. 35 | 36 | As we translate `s` to Simplicity, we need an environment that maps the variables from `p` to Simplicity expressions. 37 | 38 | If `p` is just a variable `p = a`, then the environment is simply [`a` ↦ iden: A → A]. 39 | 40 | If `p` is a product of two variables `p = (a, b)`, then the environment is [`a` ↦ take iden: A × B → A, `b` ↦ drop iden: A × B → B. 41 | 42 | "take" and "drop" are added as we go deeper in the product hierarchy. The pattern `_` is ignored. 43 | 44 | PEnv'(t: A → B, `v`) := [`v` ↦ t] 45 | 46 | PEnv'(t: A → B, `_`) := [] 47 | 48 | If `p1` and `p2` contain disjoint sets of variables 49 | 50 | Then PEnv'(t: A → B × C, `(p1, p2)`) := PEnv'(take t: A → B, p1) ⊎ PEnv'(drop t: A → C, p2) 51 | 52 | PEnv(A, `p`) := PEnv'(iden: A → A, `p`) 53 | 54 | Pattern environments are compatible with pattern contexts: 55 | 56 | Ctx(PEnv(A, `p`)) = PCtx(A, `p`) 57 | 58 | ## Product 59 | 60 | We write Product(ΞA, ΞB) to denote the **product** of environment ΞA from A and environment ΞB from B. 61 | 62 | The product is an environment from type A × B. 63 | 64 | When two Simplicity expressions with environments are joined using the "pair" combinator, then the product of both environments gives us updated bindings for all variables. 65 | 66 | If the same variable is bound in both environments, then the binding from the first environment is taken. 67 | 68 | If ΞA maps `v` to Simplicity expression a: A → C 69 | 70 | Then Product(ΞA, ΞB) maps `v` to take a: A × B → C 71 | 72 | If ΞB maps `v` to Simplicity expression b: B → C 73 | 74 | If ΞA doesn't map `v` 75 | 76 | Then Product(ΞA, ΞB) maps `v` to drop b: A × B → C 77 | 78 | Environment products are compatible with context updates: 79 | 80 | Ctx(Product(ΞA, ΞB)) = Ctx(ΞB) // Ctx(ΞA) 81 | 82 | The order of B and A is reversed: The context of ΞB is updated with the dominant context of ΞA. 83 | -------------------------------------------------------------------------------- /doc/translation.md: -------------------------------------------------------------------------------- 1 | # Translation 2 | 3 | We write ⟦`e`⟧Ξ to denote the translation of Simfony expression `e` using environment Ξ from A. 4 | 5 | The translation produces a Simplicity expression with source type A. 6 | 7 | The target type depends on the Simfony expression `e`. 8 | 9 | ## Unit literal 10 | 11 | ⟦`()`⟧Ξ = unit: A → 𝟙 12 | 13 | ## Product constructor 14 | 15 | If Ctx(Ξ) ⊩ `b`: B 16 | 17 | If Ctx(Ξ) ⊩ `c`: C 18 | 19 | Then ⟦`(b, c)`⟧Ξ = pair ⟦`b`⟧Ξ ⟦`c`⟧Ξ: A → B × C 20 | 21 | ## Left constructor 22 | 23 | If Ctx(Ξ) ⊩ `b`: B 24 | 25 | Then ⟦`Left(b)`⟧Ξ = injl ⟦`b`⟧Ξ: A → B + C 26 | 27 | For any C 28 | 29 | ## Right constructor 30 | 31 | If Ctx(Ξ) ⊩ `c`: C 32 | 33 | Then ⟦`Right(c)`⟧Ξ = injr ⟦`c`⟧Ξ: A → B + C 34 | 35 | For any B 36 | 37 | ## Bit string literal 38 | 39 | If `s` is a bit string of 2^n bits 40 | 41 | Then ⟦`0bs`⟧Ξ = comp unit const 0bs: A → 𝟚^(2^n) 42 | 43 | ## Byte string literal 44 | 45 | If `s` is a hex string of 2^n digits 46 | 47 | Then ⟦`0xs`⟧Ξ = comp unit const 0xs: A → 𝟚^(4 * 2^n) 48 | 49 | ## Variable 50 | 51 | If Ctx(Ξ)(`v`) = B 52 | 53 | Then ⟦`v`⟧Ξ = Ξ(`v`): A → B 54 | 55 | ## Witness value 56 | 57 | Ctx(Ξ) ⊩ `witness(name)`: B 58 | 59 | Then ⟦`witness(name)`⟧Ξ = witness: A → B 60 | 61 | ## Jet 62 | 63 | If `j` is the name of a jet of type B → C 64 | 65 | If Ctx(Ξ) ⊩ `b`: B 66 | 67 | Then ⟦`jet::j b`⟧Ξ = comp ⟦`b`⟧Ξ j: A → C 68 | 69 | ## Chaining 70 | 71 | If Ctx(Ξ) ⊩ `b`: 𝟙 72 | 73 | If Ctx(Ξ) ⊩ `c`: C 74 | 75 | Then ⟦`b; c`⟧Ξ = comp (pair ⟦`b`⟧Ξ ⟦`c`⟧Ξ) (drop iden): A → C 76 | 77 | ## Let statement 78 | 79 | If Ctx(Ξ) ⊩ `b`: B 80 | 81 | If Product(PEnv(B, `p`), Ξ) ⊩ c: C 82 | 83 | Then ⟦`let p: B = b; c`⟧Ξ = comp (pair ⟦`b`⟧Ξ iden) ⟦`c`⟧Product(PEnv(B, `p`), Ξ): A → C 84 | 85 | ## Match statement 86 | 87 | If Ctx(Ξ) ⊩ `a`: B + C 88 | 89 | If Product(PEnv(B, `x`), Ξ) ⊩ `b`: D 90 | 91 | If Product(PEnv(C, `y`), Ξ) ⊩ `c`: D 92 | 93 | Then ⟦`match a { Left(x) => a, Right(y) => b, }`⟧Ξ = comp (pair ⟦a⟧Ξ iden) (case ⟦b⟧Product(PEnv(B, `x`), Ξ) ⟦c⟧Product(PEnv(C, `y`), Ξ)): A → D 94 | 95 | ## Left unwrap 96 | 97 | If Ctx(Ξ) ⊩ `b`: B + C 98 | 99 | Then ⟦`b.unwrap_left()`⟧Ξ = comp (pair ⟦`b`⟧Ξ unit) (assertl iden #{fail 0}): A → B 100 | 101 | ## Right unwrap 102 | 103 | If Ctx(Ξ) ⊩ `c`: B + C 104 | 105 | Then ⟦`c.unwrap_right()`⟧Ξ = comp (pair ⟦`c`⟧Ξ unit) (assertr #{fail 0} iden): A → C 106 | -------------------------------------------------------------------------------- /examples/cat.simf: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let ab: u16 = <(u8, u8)>::into((0x10, 0x01)); 3 | let c: u16 = 0x1001; 4 | assert!(jet::eq_16(ab, c)); 5 | let ab: u8 = <(u4, u4)>::into((0b1011, 0b1101)); 6 | let c: u8 = 0b10111101; 7 | assert!(jet::eq_8(ab, c)); 8 | } 9 | -------------------------------------------------------------------------------- /examples/ctv.simf: -------------------------------------------------------------------------------- 1 | /* 2 | * This program is an emulation of CTV using simplicity 3 | * 4 | * Instead of specifying the template hash as in BIP CTV, 5 | * we require the user to specify all the components of the sighash 6 | * that they want to commit. 7 | */ 8 | fn main() { 9 | let ctx: Ctx8 = jet::sha_256_ctx_8_init(); 10 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::version()); 11 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::lock_time()); 12 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::input_script_sigs_hash()); 13 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::num_inputs()); 14 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::input_sequences_hash()); 15 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::num_outputs()); 16 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::outputs_hash()); 17 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::current_index()); 18 | let ctv_hash: u256 = jet::sha_256_ctx_8_finalize(ctx); 19 | 20 | let expected_hash: u256 = 0xae3d019b30529c6044d2b3d7ee2e0ee5db51a7f05ed5db8f089cd5d455f1fc5d; 21 | assert!(jet::eq_256(ctv_hash, expected_hash)); 22 | } 23 | -------------------------------------------------------------------------------- /examples/escrow_with_delay.simf: -------------------------------------------------------------------------------- 1 | /* 2 | * ESCROW WITH DELAY 3 | * 4 | * An escrow agent can approve the movement of coins in cooperation with the 5 | * sender or the recipient. The escrow agent cannot steal the coins for himself. 6 | * The sender can refund her coins after a timeout. 7 | * 8 | * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#escrowwithdelay 9 | */ 10 | fn not(bit: bool) -> bool { 11 | ::into(jet::complement_1(::into(bit))) 12 | } 13 | 14 | fn checksig(pk: Pubkey, sig: Signature) { 15 | let msg: u256 = jet::sig_all_hash(); 16 | jet::bip_0340_verify((pk, msg), sig); 17 | } 18 | 19 | fn checksig_add(counter: u8, pk: Pubkey, maybe_sig: Option) -> u8 { 20 | match maybe_sig { 21 | Some(sig: Signature) => { 22 | checksig(pk, sig); 23 | let (carry, new_counter): (bool, u8) = jet::increment_8(counter); 24 | assert!(not(carry)); 25 | new_counter 26 | } 27 | None => counter, 28 | } 29 | } 30 | 31 | fn check2of3multisig(pks: [Pubkey; 3], maybe_sigs: [Option; 3]) { 32 | let [pk1, pk2, pk3]: [Pubkey; 3] = pks; 33 | let [sig1, sig2, sig3]: [Option; 3] = maybe_sigs; 34 | 35 | let counter1: u8 = checksig_add(0, pk1, sig1); 36 | let counter2: u8 = checksig_add(counter1, pk2, sig2); 37 | let counter3: u8 = checksig_add(counter2, pk3, sig3); 38 | 39 | let threshold: u8 = 2; 40 | assert!(jet::eq_8(counter3, threshold)); 41 | } 42 | 43 | fn transfer_spend(maybe_sigs: [Option; 3]) { 44 | let sender_pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; // 1 * G 45 | let recipient_pk: Pubkey = 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5; // 2 * G 46 | let escrow_pk: Pubkey = 0xf9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9; // 3 * G 47 | check2of3multisig([sender_pk, recipient_pk, escrow_pk], maybe_sigs); 48 | } 49 | 50 | fn timeout_spend(sender_sig: Signature) { 51 | let sender_pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; // 1 * G 52 | checksig(sender_pk, sender_sig); 53 | let timeout: Distance = 1000; 54 | jet::check_lock_distance(timeout); 55 | } 56 | 57 | fn main() { 58 | match witness::TRANSFER_OR_TIMEOUT { 59 | Left(maybe_sigs: [Option; 3]) => transfer_spend(maybe_sigs), 60 | Right(sender_sig: Signature) => timeout_spend(sender_sig), 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /examples/escrow_with_delay.timeout.wit: -------------------------------------------------------------------------------- 1 | { 2 | "TRANSFER_OR_TIMEOUT": { 3 | "value": "Right(0xedb6865094260f8558728233aae017dd0969a2afe5f08c282e1ab659bf2462684c99a64a2a57246358a0d632671778d016e6df7381293dd5bb9f0999d38640d4)", 4 | "type": "Either<[Option; 3], Signature>" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/hash_loop.simf: -------------------------------------------------------------------------------- 1 | // Add counter to streaming hash and finalize when the loop exists 2 | fn hash_counter_8(ctx: Ctx8, unused: (), byte: u8) -> Either { 3 | let new_ctx: Ctx8 = jet::sha_256_ctx_8_add_1(ctx, byte); 4 | match jet::all_8(byte) { 5 | true => Left(jet::sha_256_ctx_8_finalize(new_ctx)), 6 | false => Right(new_ctx), 7 | } 8 | } 9 | 10 | // Add counter to streaming hash and finalize when the loop exists 11 | fn hash_counter_16(ctx: Ctx8, unused: (), bytes: u16) -> Either { 12 | let new_ctx: Ctx8 = jet::sha_256_ctx_8_add_2(ctx, bytes); 13 | match jet::all_16(bytes) { 14 | true => Left(jet::sha_256_ctx_8_finalize(new_ctx)), 15 | false => Right(new_ctx), 16 | } 17 | } 18 | 19 | fn main() { 20 | // Hash bytes 0x00 to 0xff 21 | let ctx: Ctx8 = jet::sha_256_ctx_8_init(); 22 | let out: Either = for_while::(ctx, ()); 23 | let expected: u256 = 0x40aff2e9d2d8922e47afd4648e6967497158785fbd1da870e7110266bf944880; 24 | assert!(jet::eq_256(expected, unwrap_left::(out))); 25 | 26 | // Hash bytes 0x0000 to 0xffff 27 | // This takes ~10 seconds on my computer 28 | // let ctx: Ctx8 = jet::sha_256_ctx_8_init(); 29 | // let out: Either = for_while::(ctx, ()); 30 | // let expected: u256 = 0x281f79f89f0121c31db2bea5d7151db246349b25f5901c114505c18bfaa50ba1; 31 | // assert!(jet::eq_256(expected, unwrap_left::(out))); 32 | } 33 | -------------------------------------------------------------------------------- /examples/hodl_vault.simf: -------------------------------------------------------------------------------- 1 | /* 2 | * HODL VAULT 3 | * 4 | * Lock your coins until the Bitcoin price exceeds a threshold. 5 | * 6 | * An oracle signs a message with the current block height and the current 7 | * Bitcoin price. The block height is compared with a minimum height to prevent 8 | * the use of old data. The transaction is timelocked to the oracle height, 9 | * which means that the transaction becomes valid after the oracle height. 10 | */ 11 | fn checksig(pk: Pubkey, sig: Signature) { 12 | let msg: u256 = jet::sig_all_hash(); 13 | jet::bip_0340_verify((pk, msg), sig); 14 | } 15 | 16 | fn checksigfromstack(pk: Pubkey, bytes: [u32; 2], sig: Signature) { 17 | let [word1, word2]: [u32; 2] = bytes; 18 | let hasher: Ctx8 = jet::sha_256_ctx_8_init(); 19 | let hasher: Ctx8 = jet::sha_256_ctx_8_add_4(hasher, word1); 20 | let hasher: Ctx8 = jet::sha_256_ctx_8_add_4(hasher, word2); 21 | let msg: u256 = jet::sha_256_ctx_8_finalize(hasher); 22 | jet::bip_0340_verify((pk, msg), sig); 23 | } 24 | 25 | fn main() { 26 | let min_height: Height = 1000; 27 | let oracle_height: Height = witness::ORACLE_HEIGHT; 28 | assert!(jet::le_32(min_height, oracle_height)); 29 | jet::check_lock_height(oracle_height); 30 | 31 | let target_price: u32 = 100000; // laser eyes until 100k 32 | let oracle_price: u32 = witness::ORACLE_PRICE; 33 | assert!(jet::le_32(target_price, oracle_price)); 34 | 35 | let oracle_pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; // 1 * G 36 | let oracle_sig: Signature = witness::ORACLE_SIG; 37 | checksigfromstack(oracle_pk, [oracle_height, oracle_price], oracle_sig); 38 | 39 | let owner_pk: Pubkey = 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5; // 2 * G 40 | let owner_sig: Signature = witness::OWNER_SIG; 41 | checksig(owner_pk, owner_sig); 42 | } 43 | -------------------------------------------------------------------------------- /examples/hodl_vault.wit: -------------------------------------------------------------------------------- 1 | { 2 | "ORACLE_HEIGHT": { 3 | "value": "1000", 4 | "type": "u32" 5 | }, 6 | "ORACLE_PRICE": { 7 | "value": "100000", 8 | "type": "u32" 9 | }, 10 | "ORACLE_SIG": { 11 | "value": "0x90231b8de96a1f940ddcf406fe8389417ca8fb0b03151608e2f94b31b443a7e0d26a12e437df69028f09027c37d5f6742a10c1e8864061d119b8bbce962d26d3", 12 | "type": "Signature" 13 | }, 14 | "OWNER_SIG": { 15 | "value": "0xf2341f571f069216edfc72822f6094b8ec339c2f72dc64aea0eed1e3d60abf4572fdd04618e5b5bc672ccd71cfaf125b6c1b101aeca3a7b938fe83932ab38743", 16 | "type": "Signature" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/htlc.complete.wit: -------------------------------------------------------------------------------- 1 | { 2 | "COMPLETE_OR_CANCEL": { 3 | "value": "Left((0x0000000000000000000000000000000000000000000000000000000000000000, 0xf74b3ca574647f8595624b129324afa2f38b598a9c1c7cfc5f08a9c036ec5acd3c0fbb9ed3dae5ca23a0a65a34b5d6cccdd6ba248985d6041f7b21262b17af6f))", 4 | "type": "Either<(u256, Signature), Signature>" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/htlc.simf: -------------------------------------------------------------------------------- 1 | /* 2 | * HTLC (Hash Time-Locked Contract) 3 | * 4 | * The recipient can spend the coins by providing the secret preimage of a hash. 5 | * The sender can cancel the transfer after a fixed block height. 6 | * 7 | * HTLCs enable two-way payment channels and multi-hop payments, 8 | * such as on the Lightning network. 9 | * 10 | * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#htlc 11 | */ 12 | fn sha2(string: u256) -> u256 { 13 | let hasher: Ctx8 = jet::sha_256_ctx_8_init(); 14 | let hasher: Ctx8 = jet::sha_256_ctx_8_add_32(hasher, string); 15 | jet::sha_256_ctx_8_finalize(hasher) 16 | } 17 | 18 | fn checksig(pk: Pubkey, sig: Signature) { 19 | let msg: u256 = jet::sig_all_hash(); 20 | jet::bip_0340_verify((pk, msg), sig); 21 | } 22 | 23 | fn complete_spend(preimage: u256, recipient_sig: Signature) { 24 | let hash: u256 = sha2(preimage); 25 | let expected_hash: u256 = 0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925; // sha2([0x00; 32]) 26 | assert!(jet::eq_256(hash, expected_hash)); 27 | let recipient_pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; // 1 * G 28 | checksig(recipient_pk, recipient_sig); 29 | } 30 | 31 | fn cancel_spend(sender_sig: Signature) { 32 | let timeout: Height = 1000; 33 | jet::check_lock_height(timeout); 34 | let sender_pk: Pubkey = 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5; // 2 * G 35 | checksig(sender_pk, sender_sig) 36 | } 37 | 38 | fn main() { 39 | match witness::COMPLETE_OR_CANCEL { 40 | Left(preimage_sig: (u256, Signature)) => { 41 | let (preimage, recipient_sig): (u256, Signature) = preimage_sig; 42 | complete_spend(preimage, recipient_sig); 43 | }, 44 | Right(sender_sig: Signature) => cancel_spend(sender_sig), 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/last_will.inherit.wit: -------------------------------------------------------------------------------- 1 | { 2 | "INHERIT_OR_NOT": { 3 | "value": "Left(0x755201bb62b0a8b8d18fd12fc02951ea3998ba42bfc6664daaf8a0d2298cad43cdc21358c7c82f37654275dc2fea8c858adbe97bac92828b498a5a237004db6f)", 4 | "type": "Either>" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/last_will.simf: -------------------------------------------------------------------------------- 1 | /* 2 | * LAST WILL 3 | * 4 | * The inheritor can spend the coins if the owner doesn't move the them for 180 5 | * days. The owner has to repeat the covenant when he moves the coins with his 6 | * hot key. The owner can break out of the covenant with his cold key. 7 | */ 8 | fn checksig(pk: Pubkey, sig: Signature) { 9 | let msg: u256 = jet::sig_all_hash(); 10 | jet::bip_0340_verify((pk, msg), sig); 11 | } 12 | 13 | // Enforce the covenant to repeat in the first output. 14 | // 15 | // Elements has explicit fee outputs, so enforce a fee output in the second output. 16 | // Disallow further outputs. 17 | fn recursive_covenant() { 18 | assert!(jet::eq_32(jet::num_outputs(), 2)); 19 | let this_script_hash: u256 = jet::current_script_hash(); 20 | let output_script_hash: u256 = unwrap(jet::output_script_hash(0)); 21 | assert!(jet::eq_256(this_script_hash, output_script_hash)); 22 | assert!(unwrap(jet::output_is_fee(1))); 23 | } 24 | 25 | fn inherit_spend(inheritor_sig: Signature) { 26 | let days_180: Distance = 25920; 27 | jet::check_lock_distance(days_180); 28 | let inheritor_pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; // 1 * G 29 | checksig(inheritor_pk, inheritor_sig); 30 | } 31 | 32 | fn cold_spend(cold_sig: Signature) { 33 | let cold_pk: Pubkey = 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5; // 2 * G 34 | checksig(cold_pk, cold_sig); 35 | } 36 | 37 | fn refresh_spend(hot_sig: Signature) { 38 | let hot_pk: Pubkey = 0xf9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9; // 3 * G 39 | checksig(hot_pk, hot_sig); 40 | recursive_covenant(); 41 | } 42 | 43 | fn main() { 44 | match witness::INHERIT_OR_NOT { 45 | Left(inheritor_sig: Signature) => inherit_spend(inheritor_sig), 46 | Right(cold_or_hot: Either) => match cold_or_hot { 47 | Left(cold_sig: Signature) => cold_spend(cold_sig), 48 | Right(hot_sig: Signature) => refresh_spend(hot_sig), 49 | }, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/non_interactive_fee_bump.simf: -------------------------------------------------------------------------------- 1 | /* 2 | * NON-INTERACTIVE FEE BUMPING 3 | * 4 | * This feature allows anyone, including miners, to increase a transaction's fee by reducing the change amount, 5 | * following a predefined rule that adds 1 satoshi to the fee every second. 6 | * 7 | * Allowed modifications without affecting the signature: 8 | * - Increase the transaction's nLockTime, delaying its inclusion in a block. 9 | * - Decrease the change output or increase the fee output. 10 | * 11 | * This enables miners to maximize their fees from transactions without needing external fee bumping services like 12 | * sponsors, Child-Pays-For-Parent (CPFP), or anchor outputs, simplifying fee management for transaction inclusion. 13 | */ 14 | 15 | // This function computes a signature hash for transactions that allows non-interactive fee bumping. 16 | // It omits certain fields from the transaction that can be modified by anyone, 17 | // specifically nLockTime and change/fee outputs amounts. 18 | fn sighash_tx_nifb() -> u256 { 19 | let ctx: Ctx8 = jet::sha_256_ctx_8_init(); 20 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::version()); 21 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::inputs_hash()); 22 | // Note that nlocktime is not signed. 23 | // Add the hash of the first output (assumed the ONLY non-change output) 24 | let ctx: Ctx8 = match jet::output_hash(0) { 25 | Some(sighash : u256) => jet::sha_256_ctx_8_add_32(ctx, sighash), 26 | None => panic!(), 27 | }; 28 | // Add all output script pubkeys to the hash, including change and fee outputs script pubkeys 29 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::output_scripts_hash()); 30 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::input_utxos_hash()); 31 | jet::sha_256_ctx_8_finalize(ctx) 32 | } 33 | 34 | // Combines the transaction hash with additional taproot-related data to form the overall transaction signature hash. 35 | fn sighash_nifb() -> u256 { 36 | let ctx: Ctx8 = jet::sha_256_ctx_8_init(); 37 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::genesis_block_hash()); 38 | // Add the transaction-specific hash computed earlier 39 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, sighash_tx_nifb()); 40 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::tap_env_hash()); 41 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::current_index()); 42 | jet::sha_256_ctx_8_finalize(ctx) 43 | } 44 | 45 | // Helper function to ensure the provided boolean value is not negative. 46 | fn check_neg(v : bool) { 47 | assert!(jet::eq_8(jet::left_pad_low_1_8(::into(v)), 0)); 48 | } 49 | 50 | // Enforces a linear increase in transaction fee over time by adjusting the maximum fee allowed before a transaction is mined. 51 | fn total_fee_check() { 52 | let curr_time : u32 = jet::tx_lock_time(); 53 | // [ELEMENTS]:Asset type for the transaction fee (explicitly specifying asset type, typically BTC asset) 54 | let fee_asset : ExplicitAsset = 0x0000000000000000000000000000000000000000000000000000000000000000; 55 | let fees : u64 = jet::total_fee(fee_asset); 56 | let time_at_broadcast : u32 = 1734967235; // Dec 23 ~8:33am PST 57 | let (carry, time_elapsed) : (bool, u32) = jet::subtract_32(curr_time, time_at_broadcast); 58 | check_neg(carry); // Check for negative time difference, which shouldn't happen 59 | let base_fee : u64 = 1000; // Base fee at the time of broadcast 60 | // Calculate the maximum allowed fee as a function of elapsed time 61 | let (carry, max_fee) : (bool, u64) = jet::add_64(base_fee, jet::left_pad_low_32_64(time_elapsed)); 62 | check_neg(carry); // Ensure there's no overflow in fee calculation 63 | // Assert that the current fees are less than the maximum allowed fee 64 | assert!(jet::lt_64(fees, max_fee)); 65 | // Optionally, you could limit the total fee here 66 | } 67 | 68 | fn main() { 69 | let sighash : u256 = sighash_nifb(); 70 | total_fee_check(); 71 | let alice_pk : Pubkey = 0x9bef8d556d80e43ae7e0becb3a7e6838b95defe45896ed6075bb9035d06c9964; 72 | jet::bip_0340_verify((alice_pk, sighash), witness::ALICE_SIGNATURE); 73 | } 74 | -------------------------------------------------------------------------------- /examples/non_interactive_fee_bump.wit: -------------------------------------------------------------------------------- 1 | { 2 | "ALICE_SIGNATURE": { 3 | "value": "0xe6608ceb66f62896ca07661964dd2ab0867df94caaeb089ac09089d13c23cf64ee3a0d42f8f84c2f627d4230c9f357919c48a274117e38c9c3d32a0e87570b45", 4 | "type": "Signature" 5 | } 6 | } 7 | 8 | -------------------------------------------------------------------------------- /examples/p2ms.simf: -------------------------------------------------------------------------------- 1 | /* 2 | * PAY TO MULTISIG 3 | * 4 | * The coins move if 2 of 3 people agree to move them. These people provide 5 | * their signatures, of which exactly 2 are required. 6 | * 7 | * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#lockwithmultisig 8 | */ 9 | fn not(bit: bool) -> bool { 10 | ::into(jet::complement_1(::into(bit))) 11 | } 12 | 13 | fn checksig(pk: Pubkey, sig: Signature) { 14 | let msg: u256 = jet::sig_all_hash(); 15 | jet::bip_0340_verify((pk, msg), sig); 16 | } 17 | 18 | fn checksig_add(counter: u8, pk: Pubkey, maybe_sig: Option) -> u8 { 19 | match maybe_sig { 20 | Some(sig: Signature) => { 21 | checksig(pk, sig); 22 | let (carry, new_counter): (bool, u8) = jet::increment_8(counter); 23 | assert!(not(carry)); 24 | new_counter 25 | } 26 | None => counter, 27 | } 28 | } 29 | 30 | fn check2of3multisig(pks: [Pubkey; 3], maybe_sigs: [Option; 3]) { 31 | let [pk1, pk2, pk3]: [Pubkey; 3] = pks; 32 | let [sig1, sig2, sig3]: [Option; 3] = maybe_sigs; 33 | 34 | let counter1: u8 = checksig_add(0, pk1, sig1); 35 | let counter2: u8 = checksig_add(counter1, pk2, sig2); 36 | let counter3: u8 = checksig_add(counter2, pk3, sig3); 37 | 38 | let threshold: u8 = 2; 39 | assert!(jet::eq_8(counter3, threshold)); 40 | } 41 | 42 | fn main() { 43 | let pks: [Pubkey; 3] = [ 44 | 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798, // 1 * G 45 | 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5, // 2 * G 46 | 0xf9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9, // 3 * G 47 | ]; 48 | check2of3multisig(pks, witness::MAYBE_SIGS); 49 | } 50 | -------------------------------------------------------------------------------- /examples/p2ms.wit: -------------------------------------------------------------------------------- 1 | { 2 | "MAYBE_SIGS": { 3 | "value": "[Some(0xf74b3ca574647f8595624b129324afa2f38b598a9c1c7cfc5f08a9c036ec5acd3c0fbb9ed3dae5ca23a0a65a34b5d6cccdd6ba248985d6041f7b21262b17af6f), None, Some(0x29dbeab5628ae472bce3e08728ead1997ef789d4f04b5be39cc08b362dc229f553fd353f8a0acffdfbddd471d15a0dda3b306842416ff246bc07462e5667eb89)]", 4 | "type": "[Option; 3]" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/p2pk.args: -------------------------------------------------------------------------------- 1 | { 2 | "ALICE_PUBLIC_KEY": { 3 | "value": "0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", 4 | "type": "Pubkey" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/p2pk.simf: -------------------------------------------------------------------------------- 1 | /* 2 | * PAY TO PUBLIC KEY 3 | * 4 | * The coins move if the person with the given public key signs the transaction. 5 | * 6 | * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#lockwithpublickey 7 | */ 8 | fn main() { 9 | jet::bip_0340_verify((param::ALICE_PUBLIC_KEY, jet::sig_all_hash()), witness::ALICE_SIGNATURE) 10 | } 11 | -------------------------------------------------------------------------------- /examples/p2pk.wit: -------------------------------------------------------------------------------- 1 | { 2 | "ALICE_SIGNATURE": { 3 | "value": "0xf74b3ca574647f8595624b129324afa2f38b598a9c1c7cfc5f08a9c036ec5acd3c0fbb9ed3dae5ca23a0a65a34b5d6cccdd6ba248985d6041f7b21262b17af6f", 4 | "type": "Signature" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/p2pkh.simf: -------------------------------------------------------------------------------- 1 | /* 2 | * PAY TO PUBLIC KEY HASH 3 | * 4 | * The coins move if the person with the public key that matches the given hash 5 | * signs the transaction. 6 | * 7 | * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#lockwithpublickeyhash 8 | */ 9 | fn sha2(string: u256) -> u256 { 10 | let hasher: Ctx8 = jet::sha_256_ctx_8_init(); 11 | let hasher: Ctx8 = jet::sha_256_ctx_8_add_32(hasher, string); 12 | jet::sha_256_ctx_8_finalize(hasher) 13 | } 14 | 15 | fn main() { 16 | let pk: Pubkey = witness::PK; 17 | let expected_pk_hash: u256 = 0x132f39a98c31baaddba6525f5d43f2954472097fa15265f45130bfdb70e51def; // sha2(1 * G) 18 | let pk_hash: u256 = sha2(pk); 19 | assert!(jet::eq_256(pk_hash, expected_pk_hash)); 20 | 21 | let msg: u256 = jet::sig_all_hash(); 22 | jet::bip_0340_verify((pk, msg), witness::SIG) 23 | } 24 | -------------------------------------------------------------------------------- /examples/p2pkh.wit: -------------------------------------------------------------------------------- 1 | { 2 | "PK": { 3 | "value": "0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", 4 | "type": "Pubkey" 5 | }, 6 | "SIG": { 7 | "value": "0xf74b3ca574647f8595624b129324afa2f38b598a9c1c7cfc5f08a9c036ec5acd3c0fbb9ed3dae5ca23a0a65a34b5d6cccdd6ba248985d6041f7b21262b17af6f", 8 | "type": "Signature" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/presigned_vault.complete.wit: -------------------------------------------------------------------------------- 1 | { 2 | "HOT_OR_COLD": { 3 | "value": "Left(0xedb6865094260f8558728233aae017dd0969a2afe5f08c282e1ab659bf2462684c99a64a2a57246358a0d632671778d016e6df7381293dd5bb9f0999d38640d4)", 4 | "type": "Either" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/presigned_vault.simf: -------------------------------------------------------------------------------- 1 | /* 2 | * PRESIGNED VAULT 3 | * 4 | * The coins move after a timeout if the hot key signs. 5 | * Alternatively, the cold key can sweep the coins at any time. 6 | * 7 | * This contract can be used to construct a vault that works without covenants: 8 | * 1) The cold key creates a presigned transaction that moves the vaulted coins 9 | * into the contract. 10 | * 2) The presigned transaction is kept alongside the hot key. 11 | * 3) To unvault the coins, the presigned transaction is broadcast. The coins 12 | * move into the contract. The coins cannot be moved until the timeout. 13 | * 4) If the hot key is compromised, then the cold key can cancel the 14 | * unvaulting process and move the coins into a new vault. 15 | * 5) After the timeout, the hot key can withdraw the coins. 16 | * 17 | * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#vaultspend 18 | */ 19 | fn checksig(pk: Pubkey, sig: Signature) { 20 | let msg: u256 = jet::sig_all_hash(); 21 | jet::bip_0340_verify((pk, msg), sig); 22 | } 23 | 24 | fn complete_spend(hot_sig: Signature) { 25 | let timeout: Distance = 1000; 26 | jet::check_lock_distance(timeout); 27 | let hot_pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; // 1 * G 28 | checksig(hot_pk, hot_sig); 29 | } 30 | 31 | fn cancel_spend(cold_sig: Signature) { 32 | let cold_pk: Pubkey = 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5; // 2 * G 33 | checksig(cold_pk, cold_sig) 34 | } 35 | 36 | fn main() { 37 | match witness::HOT_OR_COLD { 38 | Left(hot_sig: Signature) => complete_spend(hot_sig), 39 | Right(cold_sig: Signature) => cancel_spend(cold_sig), 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/reveal_collision.simf: -------------------------------------------------------------------------------- 1 | /* 2 | * REVEAL COLLISION 3 | * 4 | * The coins move if someone is able to provide a SHA2 collision, e.g., 5 | * two distinct byte strings that hash to the same value. 6 | * 7 | * We cannot test this program because no SHA2 collision is known :) 8 | * 9 | * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#revealcollision 10 | */ 11 | fn not(bit: bool) -> bool { 12 | ::into(jet::complement_1(::into(bit))) 13 | } 14 | 15 | fn sha2(string: u256) -> u256 { 16 | let hasher: Ctx8 = jet::sha_256_ctx_8_init(); 17 | let hasher: Ctx8 = jet::sha_256_ctx_8_add_32(hasher, string); 18 | jet::sha_256_ctx_8_finalize(hasher) 19 | } 20 | 21 | fn main() { 22 | let string1: u256 = witness::STRING1; 23 | let string2: u256 = witness::STRING2; 24 | assert!(not(jet::eq_256(string1, string2))); 25 | let hash1: u256 = sha2(string1); 26 | let hash2: u256 = sha2(string2); 27 | assert!(jet::eq_256(hash1, hash2)); 28 | } 29 | -------------------------------------------------------------------------------- /examples/reveal_fix_point.simf: -------------------------------------------------------------------------------- 1 | /* 2 | * REVEAL FIX POINT 3 | * 4 | * The coins move if someone is able to provide a SHA2 fix point, e.g., 5 | * a byte string that hashes to itself. 6 | * 7 | * We cannot test this program because no SHA2 fix point is known :) 8 | * 9 | * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#revealfixedpoint 10 | */ 11 | fn sha2(string: u256) -> u256 { 12 | let hasher: Ctx8 = jet::sha_256_ctx_8_init(); 13 | let hasher: Ctx8 = jet::sha_256_ctx_8_add_32(hasher, string); 14 | jet::sha_256_ctx_8_finalize(hasher) 15 | } 16 | 17 | fn main() { 18 | let string: u256 = witness::STRING; 19 | let hash: u256 = sha2(string); 20 | assert!(jet::eq_256(string, hash)); 21 | } 22 | -------------------------------------------------------------------------------- /examples/sighash_all_anyonecanpay.simf: -------------------------------------------------------------------------------- 1 | /* 2 | * This program verifies a Schnorr signature based on 3 | * SIGHASH_ALL | SIGHASH_ANYONECANPAY. 4 | */ 5 | fn main() { 6 | let ctx: Ctx8 = jet::sha_256_ctx_8_init(); 7 | // Blockchain 8 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::genesis_block_hash()); 9 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::genesis_block_hash()); 10 | // Transaction 11 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::version()); 12 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::lock_time()); 13 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::tap_env_hash()); 14 | // Current input 15 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, unwrap(jet::input_hash(jet::current_index()))); 16 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, unwrap(jet::input_utxo_hash(jet::current_index()))); 17 | // All outputs 18 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::outputs_hash()); 19 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::issuances_hash()); 20 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::output_surjection_proofs_hash()); 21 | // No current index 22 | // Message 23 | let msg: u256 = dbg!(jet::sha_256_ctx_8_finalize(ctx)); 24 | 25 | let pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; 26 | jet::bip_0340_verify((pk, msg), witness::SIG); 27 | } -------------------------------------------------------------------------------- /examples/sighash_all_anyonecanpay.wit: -------------------------------------------------------------------------------- 1 | { 2 | "SIG": { 3 | "value": "0xeae0e02ab94858b5a5712510074f5819fc299d62a6412a6373cbf92eb42a6274fb70827a0d6da84cf549d99b14454f1503df82a14d51419fa4fb08dee6374531", 4 | "type": "Signature" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/sighash_all_anyprevout.simf: -------------------------------------------------------------------------------- 1 | /* 2 | * This program verifies a Schnorr signature based on 3 | * SIGHASH_ALL | SIGHASH_ANYPREVOUT. 4 | */ 5 | fn main() { 6 | let ctx: Ctx8 = jet::sha_256_ctx_8_init(); 7 | // Blockchain 8 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::genesis_block_hash()); 9 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::genesis_block_hash()); 10 | // Transaction 11 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::version()); 12 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::lock_time()); 13 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::tap_env_hash()); 14 | // Current input without outpoint 15 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::current_sequence()); 16 | let ctx: Ctx8 = jet::annex_hash(ctx, jet::current_annex_hash()); 17 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, unwrap(jet::input_utxo_hash(jet::current_index()))); 18 | // All outputs 19 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::outputs_hash()); 20 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::issuances_hash()); 21 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::output_surjection_proofs_hash()); 22 | // No current index 23 | // Message 24 | let msg: u256 = dbg!(jet::sha_256_ctx_8_finalize(ctx)); 25 | 26 | let pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; 27 | jet::bip_0340_verify((pk, msg), witness::SIG); 28 | } -------------------------------------------------------------------------------- /examples/sighash_all_anyprevout.wit: -------------------------------------------------------------------------------- 1 | { 2 | "SIG": { 3 | "value": "0x97474c3eec47373716004a299405ad0a6de6d0b18aeb2d966e4e886f45de2f8d53047fc16b8bffcb45b16dd759a9397c8c6a1f5d5883e2dcbb6d89b90aec5dfd", 4 | "type": "Signature" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/sighash_all_anyprevoutanyscript.simf: -------------------------------------------------------------------------------- 1 | /* 2 | * This program verifies a Schnorr signature based on 3 | * SIGHASH_ALL | SIGHASH_ANYPREVOUTANYSCRIPT. 4 | */ 5 | fn main() { 6 | let ctx: Ctx8 = jet::sha_256_ctx_8_init(); 7 | // Blockchain 8 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::genesis_block_hash()); 9 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::genesis_block_hash()); 10 | // Transaction 11 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::version()); 12 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::lock_time()); 13 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::tap_env_hash()); 14 | // Current input without outpoint nor amounts nor scriptPubKey 15 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::current_sequence()); 16 | let ctx: Ctx8 = jet::annex_hash(ctx, jet::current_annex_hash()); 17 | // All outputs 18 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::outputs_hash()); 19 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::issuances_hash()); 20 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::output_surjection_proofs_hash()); 21 | // No current index 22 | // Message 23 | let msg: u256 = dbg!(jet::sha_256_ctx_8_finalize(ctx)); 24 | 25 | let pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; 26 | jet::bip_0340_verify((pk, msg), witness::SIG); 27 | } 28 | -------------------------------------------------------------------------------- /examples/sighash_all_anyprevoutanyscript.wit: -------------------------------------------------------------------------------- 1 | { 2 | "SIG": { 3 | "value": "0x8287b5c208f7f10469c0b8be133f33464dc2082adf13be80c9cecea15cbef79f112e122533bc1e99de6510591a7d7c32093e3a31bf60d6a684f87f68ac4293ca", 4 | "type": "Signature" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/sighash_none.simf: -------------------------------------------------------------------------------- 1 | /* 2 | * This program verifies a Schnorr signature based on 3 | * SIGHASH_NONE. 4 | */ 5 | fn main() { 6 | let ctx: Ctx8 = jet::sha_256_ctx_8_init(); 7 | // Blockchain 8 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::genesis_block_hash()); 9 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::genesis_block_hash()); 10 | // Transaction 11 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::version()); 12 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::lock_time()); 13 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::tap_env_hash()); 14 | // All inputs 15 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::inputs_hash()); 16 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::input_utxos_hash()); 17 | // No outputs 18 | // Current index 19 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::current_index()); 20 | // Message 21 | let msg: u256 = dbg!(jet::sha_256_ctx_8_finalize(ctx)); 22 | 23 | let pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; 24 | jet::bip_0340_verify((pk, msg), witness::SIG); 25 | } 26 | -------------------------------------------------------------------------------- /examples/sighash_none.wit: -------------------------------------------------------------------------------- 1 | { 2 | "SIG": { 3 | "value": "0x75a0d6ffb1b793bed677968803f15c879b5e53c0d60071264b0f9830ad4d493795637d4e2935c62e3941252a43d05ab2a64ae93dfe8f7622df1001c719a78f91", 4 | "type": "Signature" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/sighash_single.simf: -------------------------------------------------------------------------------- 1 | /* 2 | * This program verifies a Schnorr signature based on 3 | * SIGHASH_SINGLE. 4 | */ 5 | fn main() { 6 | let ctx: Ctx8 = jet::sha_256_ctx_8_init(); 7 | // Blockchain 8 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::genesis_block_hash()); 9 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::genesis_block_hash()); 10 | // Transaction 11 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::version()); 12 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::lock_time()); 13 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::tap_env_hash()); 14 | // All inputs 15 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::inputs_hash()); 16 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, jet::input_utxos_hash()); 17 | // Current output 18 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, unwrap(jet::output_hash(jet::current_index()))); 19 | // Current index 20 | let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, jet::current_index()); 21 | // Message 22 | let msg: u256 = dbg!(jet::sha_256_ctx_8_finalize(ctx)); 23 | 24 | let pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; 25 | jet::bip_0340_verify((pk, msg), witness::SIG); 26 | } 27 | -------------------------------------------------------------------------------- /examples/sighash_single.wit: -------------------------------------------------------------------------------- 1 | { 2 | "SIG": { 3 | "value": "0x8aae6d1a199902e8f19c78b8565118e1eb448dfa26d2439616f68927ea32dd33a964c82cb5d300113107d58b52502f6a7db6bc350dfde5044b49b01a98ee8e8d", 4 | "type": "Signature" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/transfer_with_timeout.simf: -------------------------------------------------------------------------------- 1 | /* 2 | * TRANSFER WITH TIMEOUT 3 | * 4 | * The coins move if the sender and recipient agree to move them. 5 | * If the recipient fails to cooperate, then the sender can recover 6 | * the coins unilaterally after a timeout. 7 | * 8 | * This contract can be used to construct a one-way payment channel: 9 | * The sender keeps increasing the amount of coins to the recipient, 10 | * signing updated transactions with each channel update. The recipient 11 | * broadcasts the transaction on the blockchain when they are ready. 12 | * 13 | * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#transferwithtimeout 14 | */ 15 | fn checksig(pk: Pubkey, sig: Signature) { 16 | let msg: u256 = jet::sig_all_hash(); 17 | jet::bip_0340_verify((pk, msg), sig); 18 | } 19 | 20 | fn transfer_spend(sender_sig: Signature, recipient_sig: Signature) { 21 | let sender_pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; // 1 * G 22 | checksig(sender_pk, sender_sig); 23 | let recipient_pk: Pubkey = 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5; // 2 * G 24 | checksig(recipient_pk, recipient_sig); 25 | } 26 | 27 | fn timeout_spend(sender_sig: Signature) { 28 | let sender_pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; // 1 * G 29 | checksig(sender_pk, sender_sig); 30 | let timeout: Height = 1000; 31 | jet::check_lock_height(timeout); 32 | } 33 | 34 | fn main() { 35 | let sender_sig: Signature = witness::SENDER_SIG; 36 | match witness::TRANSFER_OR_TIMEOUT { 37 | Some(recipient_sig: Signature) => transfer_spend(sender_sig, recipient_sig), 38 | None => timeout_spend(sender_sig), 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/transfer_with_timeout.transfer.wit: -------------------------------------------------------------------------------- 1 | { 2 | "SENDER_SIG": { 3 | "value": "0xf74b3ca574647f8595624b129324afa2f38b598a9c1c7cfc5f08a9c036ec5acd3c0fbb9ed3dae5ca23a0a65a34b5d6cccdd6ba248985d6041f7b21262b17af6f", 4 | "type": "Signature" 5 | }, 6 | "TRANSFER_OR_TIMEOUT": { 7 | "value": "Some(0xf914c88a1ee88b4b7b8eb5603b835fa879386af79628aa9435226f64bdd54313794b18e30437231897dab3861d92437e0efe03a4bb17e60e4369d192b0c61ecf)", 8 | "type": "Option" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1738163270, 24 | "narHash": "sha256-B/7Y1v4y+msFFBW1JAdFjNvVthvNdJKiN6EGRPnqfno=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "59e618d90c065f55ae48446f307e8c09565d5ab0", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "NixOS", 32 | "ref": "nixos-24.11", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "nixpkgs_2": { 38 | "locked": { 39 | "lastModified": 1736320768, 40 | "narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=", 41 | "owner": "NixOS", 42 | "repo": "nixpkgs", 43 | "rev": "4bc9c909d9ac828a039f288cf872d16d38185db8", 44 | "type": "github" 45 | }, 46 | "original": { 47 | "owner": "NixOS", 48 | "ref": "nixpkgs-unstable", 49 | "repo": "nixpkgs", 50 | "type": "github" 51 | } 52 | }, 53 | "root": { 54 | "inputs": { 55 | "flake-utils": "flake-utils", 56 | "nixpkgs": "nixpkgs", 57 | "rust-overlay": "rust-overlay" 58 | } 59 | }, 60 | "rust-overlay": { 61 | "inputs": { 62 | "nixpkgs": "nixpkgs_2" 63 | }, 64 | "locked": { 65 | "lastModified": 1738263856, 66 | "narHash": "sha256-u9nE8Gwc+B3AIy12ZrXXxlFdBouNcB8T6Kf6jX1n2m0=", 67 | "owner": "oxalica", 68 | "repo": "rust-overlay", 69 | "rev": "9efb8a111c32f767d158bba7fa130ae0fb5cc4ba", 70 | "type": "github" 71 | }, 72 | "original": { 73 | "owner": "oxalica", 74 | "repo": "rust-overlay", 75 | "type": "github" 76 | } 77 | }, 78 | "systems": { 79 | "locked": { 80 | "lastModified": 1681028828, 81 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 82 | "owner": "nix-systems", 83 | "repo": "default", 84 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 85 | "type": "github" 86 | }, 87 | "original": { 88 | "owner": "nix-systems", 89 | "repo": "default", 90 | "type": "github" 91 | } 92 | } 93 | }, 94 | "root": "root", 95 | "version": 7 96 | } 97 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Simfony"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | rust-overlay.url = "github:oxalica/rust-overlay"; 8 | }; 9 | 10 | outputs = 11 | { self 12 | , nixpkgs 13 | , flake-utils 14 | , rust-overlay 15 | , ... 16 | }: 17 | flake-utils.lib.eachDefaultSystem 18 | (system: 19 | let 20 | overlays = [ 21 | (import rust-overlay) 22 | ]; 23 | pkgs = import nixpkgs { 24 | inherit system overlays; 25 | }; 26 | mkRust = stable: version: profile: targets: extensions: pkgs.rust-bin.${stable}.${version}.${profile}.override { 27 | inherit targets; 28 | inherit extensions; 29 | }; 30 | defaultRust = mkRust "stable" "latest" "default" ["wasm32-unknown-unknown"] ["rust-src"]; 31 | elementsd-simplicity = pkgs.callPackage ./bitcoind-tests/elementsd-simplicity.nix {}; 32 | CC_wasm32_unknown_unknown = "${pkgs.llvmPackages_16.clang-unwrapped}/bin/clang-16"; 33 | AR_wasm32_unknown_unknown = "${pkgs.llvmPackages_16.libllvm}/bin/llvm-ar"; 34 | CFLAGS_wasm32_unknown_unknown = "-I ${pkgs.llvmPackages_16.libclang.lib}/lib/clang/16/include/"; 35 | gdbSupported = !(pkgs.stdenv.isDarwin && pkgs.stdenv.isAarch64); 36 | default_shell = with_elements: pkgs.mkShell { 37 | buildInputs = [ 38 | defaultRust 39 | pkgs.just 40 | pkgs.cargo-hack 41 | pkgs.mdbook 42 | ] ++ ( 43 | if with_elements then [ elementsd-simplicity ] else [] 44 | ) ++ ( 45 | if gdbSupported then [ pkgs.gdb ] else [] 46 | ); 47 | inherit CC_wasm32_unknown_unknown; 48 | inherit AR_wasm32_unknown_unknown; 49 | inherit CFLAGS_wasm32_unknown_unknown; 50 | # Constants for IDE 51 | RUST_TOOLCHAIN = "${defaultRust}/bin"; 52 | RUST_STDLIB = "${defaultRust}/lib/rustlib/src/rust"; 53 | }; 54 | in 55 | { 56 | devShells = { 57 | default = default_shell false; 58 | elements = default_shell true; 59 | # Temporary shells until CI has its nix derivations 60 | ci = pkgs.mkShell { 61 | buildInputs = [ 62 | (mkRust "stable" "latest" "default" [] []) 63 | pkgs.just 64 | pkgs.cargo-hack 65 | ]; 66 | }; 67 | msrv = pkgs.mkShell { 68 | buildInputs = [ 69 | (mkRust "stable" "1.78.0" "minimal" [] []) 70 | pkgs.just 71 | ]; 72 | }; 73 | fuzz = pkgs.mkShell.override { 74 | stdenv = pkgs.clang16Stdenv; 75 | } { 76 | buildInputs = [ 77 | (mkRust "nightly" "2024-07-01" "minimal" [] ["llvm-tools-preview"]) 78 | pkgs.just 79 | pkgs.cargo-fuzz 80 | pkgs.cargo-binutils 81 | pkgs.rustfilt 82 | ]; 83 | }; 84 | wasm = pkgs.mkShell { 85 | buildInputs = [ 86 | (mkRust "stable" "latest" "default" ["wasm32-unknown-unknown"] []) 87 | pkgs.just 88 | ]; 89 | inherit CC_wasm32_unknown_unknown; 90 | inherit AR_wasm32_unknown_unknown; 91 | inherit CFLAGS_wasm32_unknown_unknown; 92 | }; 93 | book = pkgs.mkShell { 94 | buildInputs = [ 95 | pkgs.mdbook 96 | ]; 97 | }; 98 | }; 99 | } 100 | ); 101 | } 102 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | corpus 3 | artifacts 4 | coverage 5 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "simfony-fuzz" 3 | version = "0.0.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [package.metadata] 8 | cargo-fuzz = true 9 | 10 | [dependencies] 11 | libfuzzer-sys = "0.4" 12 | arbitrary = { version = "1", features = ["derive"] } 13 | simfony = { path = "..", features = ["arbitrary", "serde"]} 14 | itertools = "0.13.0" 15 | serde_json = "1.0.105" 16 | 17 | [[bin]] 18 | name = "compile_text" 19 | path = "fuzz_targets/compile_text.rs" 20 | test = false 21 | doc = false 22 | bench = false 23 | 24 | [[bin]] 25 | name = "compile_parse_tree" 26 | path = "fuzz_targets/compile_parse_tree.rs" 27 | test = false 28 | doc = false 29 | bench = false 30 | 31 | [[bin]] 32 | name = "display_parse_tree" 33 | path = "fuzz_targets/display_parse_tree.rs" 34 | test = false 35 | doc = false 36 | bench = false 37 | 38 | [[bin]] 39 | name = "parse_value_rtt" 40 | path = "fuzz_targets/parse_value_rtt.rs" 41 | test = false 42 | doc = false 43 | bench = false 44 | 45 | [[bin]] 46 | name = "parse_witness_json_rtt" 47 | path = "fuzz_targets/parse_witness_json_rtt.rs" 48 | test = false 49 | doc = false 50 | bench = false 51 | 52 | [[bin]] 53 | name = "parse_witness_module_rtt" 54 | path = "fuzz_targets/parse_witness_module_rtt.rs" 55 | test = false 56 | doc = false 57 | bench = false 58 | 59 | [[bin]] 60 | name = "reconstruct_value" 61 | path = "fuzz_targets/reconstruct_value.rs" 62 | test = false 63 | doc = false 64 | bench = false 65 | -------------------------------------------------------------------------------- /fuzz/README.md: -------------------------------------------------------------------------------- 1 | # Fuzzing 2 | 3 | ## Run a target 4 | 5 | Run a fuzz target indefinitely using the following command: 6 | 7 | ```bash 8 | cargo fuzz run TARGET 9 | ``` 10 | 11 | You can pass arguments to the fuzzer after the `--`, such as a maximum length of 50 bytes or a restriction to use only ASCII bytes. 12 | 13 | ```bash 14 | cargo fuzz run TARGET -- -max_len=50 -ascii_only=1 15 | ``` 16 | 17 | ## Compute code coverage 18 | 19 | Compute the code coverage of the corpus of a given target using the following command: 20 | 21 | ```bash 22 | cargo fuzz coverage TARGET 23 | ``` 24 | 25 | Generate a human-readable HTML coverage report using a command as below. _The exact paths might differ depending on the target architecture._ 26 | 27 | The demangler `rustfilt` must be installed. 28 | 29 | ```bash 30 | cargo cov -- show -Xdemangler=rustfilt target/x86_64-unknown-linux-gnu/coverage/x86_64-unknown-linux-gnu/release/TARGET -instr-profile=fuzz/coverage/TARGET/coverage.profdata -show-line-counts-or-regions -show-instantiations --format html --output-dir=OUTPUT_DIR -ignore-filename-regex="\.cargo" 31 | ``` 32 | 33 | More information is available in the [rustc book](https://doc.rust-lang.org/stable/rustc/instrument-coverage.html#running-the-instrumented-binary-to-generate-raw-coverage-profiling-data). 34 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/compile_parse_tree.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use arbitrary::Arbitrary; 4 | use libfuzzer_sys::fuzz_target; 5 | 6 | use simfony::error::WithFile; 7 | use simfony::{ast, named, parse, ArbitraryOfType, Arguments}; 8 | 9 | fuzz_target!(|data: &[u8]| { 10 | let mut u = arbitrary::Unstructured::new(data); 11 | let parse_program = match parse::Program::arbitrary(&mut u) { 12 | Ok(x) => x, 13 | Err(_) => return, 14 | }; 15 | let ast_program = match ast::Program::analyze(&parse_program) { 16 | Ok(x) => x, 17 | Err(_) => return, 18 | }; 19 | let arguments = match Arguments::arbitrary_of_type(&mut u, ast_program.parameters()) { 20 | Ok(arguments) => arguments, 21 | Err(..) => return, 22 | }; 23 | let simplicity_named_construct = ast_program 24 | .compile(arguments, false) 25 | .with_file("") 26 | .expect("AST should compile with given arguments"); 27 | let _simplicity_commit = named::to_commit_node(&simplicity_named_construct) 28 | .expect("Conversion to commit node should never fail"); 29 | }); 30 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/compile_text.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use arbitrary::Arbitrary; 4 | use libfuzzer_sys::{fuzz_target, Corpus}; 5 | 6 | use simfony::{ArbitraryOfType, Arguments}; 7 | 8 | /// The PEST parser is slow for inputs with many open brackets. 9 | /// Detect some of these inputs to reject them from the corpus. 10 | /// 11 | /// ```text 12 | /// fn n(){ { (s,(( (Ns,(s,(x,(((s,((s,(s,(s,(x,(( {5 13 | /// ``` 14 | fn slow_input(program_text: &str) -> bool { 15 | let mut consecutive_open_brackets = 0; 16 | 17 | for c in program_text.chars() { 18 | if c == '(' || c == '[' || c == '{' { 19 | consecutive_open_brackets += 1; 20 | if consecutive_open_brackets > 3 { 21 | return true; 22 | } 23 | } else if !c.is_whitespace() { 24 | consecutive_open_brackets = 0; 25 | } 26 | } 27 | 28 | false 29 | } 30 | 31 | fuzz_target!(|data: &[u8]| -> Corpus { 32 | let mut u = arbitrary::Unstructured::new(data); 33 | 34 | let program_text = match ::arbitrary(&mut u) { 35 | Ok(x) => x, 36 | Err(..) => return Corpus::Reject, 37 | }; 38 | if slow_input(&program_text) { 39 | return Corpus::Reject; 40 | } 41 | let template = match simfony::TemplateProgram::new(program_text) { 42 | Ok(x) => x, 43 | Err(..) => return Corpus::Keep, 44 | }; 45 | let arguments = match Arguments::arbitrary_of_type(&mut u, template.parameters()) { 46 | Ok(arguments) => arguments, 47 | Err(..) => return Corpus::Reject, 48 | }; 49 | let _ = template.instantiate(arguments, false); 50 | 51 | Corpus::Keep 52 | }); 53 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/display_parse_tree.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | 5 | use simfony::parse::{self, ParseFromStr}; 6 | 7 | fuzz_target!(|parse_program: parse::Program| { 8 | let program_text = parse_program.to_string(); 9 | let restored_parse_program = parse::Program::parse_from_str(program_text.as_str()) 10 | .expect("Output of fmt::Display should be parseable"); 11 | assert_eq!( 12 | parse_program, restored_parse_program, 13 | "Output of fmt::Display should parse to original program" 14 | ); 15 | }); 16 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/parse_value_rtt.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | 5 | use simfony::value::Value; 6 | 7 | fuzz_target!(|value: Value| { 8 | let value_string = value.to_string(); 9 | let parsed_value = 10 | Value::parse_from_str(&value_string, value.ty()).expect("Value string should be parseable"); 11 | assert_eq!( 12 | value, parsed_value, 13 | "Value string should parse to original value" 14 | ); 15 | }); 16 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/parse_witness_json_rtt.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | 5 | use simfony::WitnessValues; 6 | 7 | fuzz_target!(|witness_values: WitnessValues| { 8 | let witness_text = serde_json::to_string(&witness_values) 9 | .expect("Witness map should be convertible into JSON"); 10 | let parsed_witness_text = 11 | serde_json::from_str(&witness_text).expect("Witness JSON should be parseable"); 12 | assert_eq!( 13 | witness_values, parsed_witness_text, 14 | "Witness JSON should parse to original witness map" 15 | ); 16 | }); 17 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/parse_witness_module_rtt.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | 5 | use simfony::parse::ParseFromStr; 6 | use simfony::WitnessValues; 7 | 8 | fuzz_target!(|witness_values: WitnessValues| { 9 | let witness_text = witness_values.to_string(); 10 | let parsed_witness_text = 11 | WitnessValues::parse_from_str(&witness_text).expect("Witness module should be parseable"); 12 | assert_eq!( 13 | witness_values, parsed_witness_text, 14 | "Witness module should parse to original witness values" 15 | ); 16 | }); 17 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/reconstruct_value.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | 5 | use simfony::value::{StructuralValue, Value}; 6 | 7 | fuzz_target!(|value: Value| { 8 | let structural_value = StructuralValue::from(&value); 9 | let reconstructed_value = Value::reconstruct(&structural_value, value.ty()).unwrap(); 10 | assert_eq!(reconstructed_value, value); 11 | }); 12 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | # Format code 2 | fmt: 3 | cargo fmt --all 4 | 5 | # Check if code is formatted 6 | fmtcheck: 7 | cargo fmt --all -- --check 8 | 9 | # Run code linter 10 | lint: 11 | cargo clippy --all-targets --workspace -- --deny warnings 12 | 13 | # Build code with all feature combinations 14 | build_features: 15 | cargo hack check --feature-powerset --no-dev-deps 16 | 17 | # Run unit tests 18 | test: 19 | cargo test --workspace --all-features 20 | 21 | # Check code (CI) 22 | check: 23 | cargo --version 24 | rustc --version 25 | just fmtcheck 26 | just lint 27 | just build_features 28 | just test 29 | 30 | # Run fuzz test for 30 seconds 31 | fuzz target: 32 | cargo-fuzz run {{target}} -- -max_total_time=30 -max_len=50 33 | 34 | # Check fuzz targets (CI; requires nightly) 35 | check_fuzz: 36 | just fuzz compile_parse_tree 37 | just fuzz compile_text 38 | just fuzz display_parse_tree 39 | just fuzz parse_value_rtt 40 | just fuzz parse_witness_json_rtt 41 | just fuzz parse_witness_module_rtt 42 | just fuzz reconstruct_value 43 | 44 | # Build fuzz tests 45 | build_fuzz: 46 | cargo-fuzz check 47 | 48 | # Build integration tests 49 | build_integration: 50 | cargo test --no-run --manifest-path ./bitcoind-tests/Cargo.toml 51 | 52 | # Run integration tests (requires custom elementsd) 53 | check_integration: 54 | cargo test --manifest-path ./bitcoind-tests/Cargo.toml 55 | 56 | # Build code for the WASM target 57 | build_wasm: 58 | cargo check --target wasm32-unknown-unknown 59 | 60 | # Remove all temporary files 61 | clean: 62 | rm -rf target 63 | rm -rf fuzz/target 64 | -------------------------------------------------------------------------------- /src/debug.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::sync::Arc; 3 | 4 | use either::Either; 5 | use hashes::{sha256, Hash, HashEngine}; 6 | use simplicity::{hashes, Cmr}; 7 | 8 | use crate::error::Span; 9 | use crate::types::ResolvedType; 10 | use crate::value::{StructuralValue, Value}; 11 | 12 | /// Tracker of Simfony call expressions inside Simplicity target code. 13 | /// 14 | /// Tracking happens via CMRs that are inserted into the Simplicity target code. 15 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 16 | pub struct DebugSymbols(HashMap); 17 | 18 | /// Intermediate representation of tracked Simfony call expressions 19 | /// that is mutable and that lacks information about the source file. 20 | /// 21 | /// The struct can be converted to [`DebugSymbols`] by providing the source file. 22 | #[derive(Debug, Clone, Eq, PartialEq, Default)] 23 | pub(crate) struct CallTracker { 24 | next_id: u32, 25 | map: HashMap, 26 | } 27 | 28 | /// Call expression with a debug symbol. 29 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 30 | pub struct TrackedCall { 31 | text: Arc, 32 | name: TrackedCallName, 33 | } 34 | 35 | /// Name of a call expression with a debug symbol. 36 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 37 | pub enum TrackedCallName { 38 | Assert, 39 | Panic, 40 | Jet, 41 | UnwrapLeft(ResolvedType), 42 | UnwrapRight(ResolvedType), 43 | Unwrap, 44 | Debug(ResolvedType), 45 | } 46 | 47 | /// Fallible call expression with runtime input value. 48 | #[derive(Debug, Clone, Eq, PartialEq, Hash)] 49 | pub struct FallibleCall { 50 | text: Arc, 51 | name: FallibleCallName, 52 | } 53 | 54 | /// Name of a fallible call expression with runtime input value. 55 | #[derive(Debug, Clone, Eq, PartialEq, Hash)] 56 | pub enum FallibleCallName { 57 | Assert, 58 | Panic, 59 | Jet, 60 | UnwrapLeft(Value), 61 | UnwrapRight(Value), 62 | Unwrap, 63 | } 64 | 65 | /// Debug expression with runtime input value. 66 | #[derive(Debug, Clone, Eq, PartialEq, Hash)] 67 | pub struct DebugValue { 68 | text: Arc, 69 | value: Value, 70 | } 71 | 72 | impl DebugSymbols { 73 | /// Insert a tracked call expression. 74 | /// Use the Simfony source `file` to extract the Simfony text of the expression. 75 | pub(crate) fn insert(&mut self, span: Span, cmr: Cmr, name: TrackedCallName, file: &str) { 76 | let text = remove_excess_whitespace(span.to_slice(file).unwrap_or("")); 77 | let text = text 78 | .strip_prefix("dbg!(") 79 | .and_then(|s| s.strip_suffix(")")) 80 | .unwrap_or(&text); 81 | 82 | self.0.insert( 83 | cmr, 84 | TrackedCall { 85 | text: Arc::from(text), 86 | name, 87 | }, 88 | ); 89 | } 90 | 91 | /// Check if the given CMR tracks any call expressions. 92 | pub fn contains_key(&self, cmr: &Cmr) -> bool { 93 | self.0.contains_key(cmr) 94 | } 95 | 96 | /// Get the call expression that is tracked by the given CMR. 97 | pub fn get(&self, cmr: &Cmr) -> Option<&TrackedCall> { 98 | self.0.get(cmr) 99 | } 100 | } 101 | 102 | fn remove_excess_whitespace(s: &str) -> String { 103 | let mut last_was_space = true; 104 | let is_excess_whitespace = move |c: char| match c { 105 | ' ' => std::mem::replace(&mut last_was_space, true), 106 | '\n' => true, 107 | _ => { 108 | last_was_space = false; 109 | false 110 | } 111 | }; 112 | s.replace(is_excess_whitespace, "") 113 | } 114 | 115 | impl CallTracker { 116 | /// Track a new function call with the given `span`. 117 | /// 118 | /// ## Precondition 119 | /// 120 | /// Different function calls have different spans. 121 | /// 122 | /// This holds true when the method is called on a real source file. 123 | /// The precondition might be broken when this method is called on random input. 124 | pub fn track_call(&mut self, span: Span, name: TrackedCallName) { 125 | let cmr = self.next_id_cmr(); 126 | let _replaced = self.map.insert(span, (cmr, name)); 127 | self.next_id += 1; 128 | } 129 | 130 | /// Get the CMR of the tracked function call with the given `span`. 131 | pub fn get_cmr(&self, span: &Span) -> Option { 132 | self.map.get(span).map(|x| x.0) 133 | } 134 | 135 | fn next_id_cmr(&self) -> Cmr { 136 | let tag_hash = sha256::Hash::hash(b"simfony\x1fdebug\x1f"); 137 | let mut engine = sha256::Hash::engine(); 138 | engine.input(tag_hash.as_ref()); 139 | engine.input(tag_hash.as_ref()); 140 | engine.input(self.next_id.to_be_bytes().as_ref()); 141 | Cmr::from_byte_array(sha256::Hash::from_engine(engine).to_byte_array()) 142 | } 143 | 144 | /// Create debug symbols by attaching information from the source `file`. 145 | pub fn with_file(&self, file: &str) -> DebugSymbols { 146 | let mut debug_symbols = DebugSymbols::default(); 147 | for (span, (cmr, name)) in &self.map { 148 | debug_symbols.insert(*span, *cmr, name.clone(), file); 149 | } 150 | debug_symbols 151 | } 152 | } 153 | 154 | impl TrackedCall { 155 | /// Access the text of the Simfony call expression. 156 | pub fn text(&self) -> &str { 157 | &self.text 158 | } 159 | 160 | /// Access the name of the call. 161 | pub fn name(&self) -> &TrackedCallName { 162 | &self.name 163 | } 164 | 165 | /// Supply the Simplicity input value of the call expression at runtime. 166 | /// Convert the debug call into a fallible call or into a debug value, 167 | /// depending on the kind of debug symbol. 168 | /// 169 | /// Return `None` if the Simplicity input value is of the wrong type, 170 | /// according to the debug symbol. 171 | pub fn map_value(&self, value: &StructuralValue) -> Option> { 172 | let name = match self.name() { 173 | TrackedCallName::Assert => FallibleCallName::Assert, 174 | TrackedCallName::Panic => FallibleCallName::Panic, 175 | TrackedCallName::Jet => FallibleCallName::Jet, 176 | TrackedCallName::UnwrapLeft(ty) => { 177 | Value::reconstruct(value, ty).map(FallibleCallName::UnwrapLeft)? 178 | } 179 | TrackedCallName::UnwrapRight(ty) => { 180 | Value::reconstruct(value, ty).map(FallibleCallName::UnwrapRight)? 181 | } 182 | TrackedCallName::Unwrap => FallibleCallName::Unwrap, 183 | TrackedCallName::Debug(ty) => { 184 | return Value::reconstruct(value, ty) 185 | .map(|value| DebugValue { 186 | text: Arc::clone(&self.text), 187 | value, 188 | }) 189 | .map(Either::Right) 190 | } 191 | }; 192 | Some(Either::Left(FallibleCall { 193 | text: Arc::clone(&self.text), 194 | name, 195 | })) 196 | } 197 | } 198 | 199 | impl FallibleCall { 200 | /// Access the Simfony text of the call expression. 201 | pub fn text(&self) -> &str { 202 | &self.text 203 | } 204 | 205 | /// Access the name of the call. 206 | pub fn name(&self) -> &FallibleCallName { 207 | &self.name 208 | } 209 | } 210 | 211 | impl DebugValue { 212 | /// Access the Simfony text of the debug expression. 213 | pub fn text(&self) -> &str { 214 | &self.text 215 | } 216 | 217 | /// Access the runtime input value. 218 | pub fn value(&self) -> &Value { 219 | &self.value 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/dummy_env.rs: -------------------------------------------------------------------------------- 1 | //! Dummy Elements environment for testing 2 | 3 | use std::sync::Arc; 4 | 5 | use elements::{confidential, taproot::ControlBlock, AssetIssuance}; 6 | use hashes::Hash; 7 | use simplicity::elements::{AssetId, TxOut}; 8 | use simplicity::jet::elements::{ElementsEnv, ElementsUtxo}; 9 | use simplicity::Cmr; 10 | use simplicity::{elements, hashes}; 11 | 12 | /// Return a dummy Elements environment. 13 | pub fn dummy() -> ElementsEnv> { 14 | dummy_with(elements::LockTime::ZERO, elements::Sequence::MAX, false) 15 | } 16 | 17 | /// Returns a default transaction for the Elements network. 18 | fn create_default_transaction( 19 | lock_time: elements::LockTime, 20 | sequence: elements::Sequence, 21 | include_fee_output: bool, 22 | ) -> elements::Transaction { 23 | let mut tx = elements::Transaction { 24 | version: 2, 25 | lock_time, 26 | input: vec![elements::TxIn { 27 | previous_output: elements::OutPoint::default(), 28 | is_pegin: false, 29 | script_sig: elements::Script::new(), 30 | sequence, 31 | asset_issuance: AssetIssuance::default(), 32 | witness: elements::TxInWitness::default(), 33 | }], 34 | output: vec![elements::TxOut { 35 | asset: confidential::Asset::default(), 36 | value: confidential::Value::default(), 37 | nonce: confidential::Nonce::default(), 38 | script_pubkey: elements::Script::default(), 39 | witness: elements::TxOutWitness::default(), 40 | }], 41 | }; 42 | 43 | if include_fee_output { 44 | tx.output.push(TxOut::new_fee(1_000, AssetId::default())); 45 | } 46 | tx 47 | } 48 | 49 | /// Returns a dummy Elements environment with a provided transaction. 50 | pub fn dummy_with_tx(tx: elements::Transaction) -> ElementsEnv> { 51 | let ctrl_blk: [u8; 33] = [ 52 | 0xc0, 0xeb, 0x04, 0xb6, 0x8e, 0x9a, 0x26, 0xd1, 0x16, 0x04, 0x6c, 0x76, 0xe8, 0xff, 0x47, 53 | 0x33, 0x2f, 0xb7, 0x1d, 0xda, 0x90, 0xff, 0x4b, 0xef, 0x53, 0x70, 0xf2, 0x52, 0x26, 0xd3, 54 | 0xbc, 0x09, 0xfc, 55 | ]; 56 | let num_inputs = tx.input.len(); 57 | 58 | ElementsEnv::new( 59 | Arc::new(tx), 60 | vec![ 61 | ElementsUtxo { 62 | script_pubkey: elements::Script::default(), 63 | asset: confidential::Asset::default(), 64 | value: confidential::Value::default(), 65 | }; 66 | num_inputs 67 | ], 68 | 0, 69 | Cmr::from_byte_array([0; 32]), 70 | ControlBlock::from_slice(&ctrl_blk).unwrap(), 71 | None, 72 | elements::BlockHash::all_zeros(), 73 | ) 74 | } 75 | 76 | /// Returns a dummy Elements environment with the given locktime and sequence. 77 | pub fn dummy_with( 78 | lock_time: elements::LockTime, 79 | sequence: elements::Sequence, 80 | include_fee_output: bool, 81 | ) -> ElementsEnv> { 82 | let default_tx = create_default_transaction(lock_time, sequence, include_fee_output); 83 | dummy_with_tx(default_tx) 84 | } 85 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use base64::display::Base64Display; 2 | use base64::engine::general_purpose::STANDARD; 3 | use clap::{Arg, ArgAction, Command}; 4 | 5 | use simfony::{Arguments, CompiledProgram}; 6 | use std::env; 7 | 8 | // Directly returning Result<(), String> prints the error using Debug 9 | // Add indirection via run() to print errors using Display 10 | fn main() { 11 | if let Err(error) = run() { 12 | eprintln!("{error}"); 13 | std::process::exit(1); 14 | } 15 | } 16 | 17 | fn run() -> Result<(), String> { 18 | let command = { 19 | Command::new(env!("CARGO_BIN_NAME")) 20 | .about( 21 | "\ 22 | Compile the given Simfony program and print the resulting Simplicity base64 string.\n\ 23 | If a Simfony witness is provided, then use it to satisfy the program (requires \ 24 | feature 'serde' to be enabled).\ 25 | ", 26 | ) 27 | .arg( 28 | Arg::new("prog_file") 29 | .required(true) 30 | .value_name("PROGRAM_FILE") 31 | .action(ArgAction::Set) 32 | .help("Simfony program file to build"), 33 | ) 34 | }; 35 | 36 | #[cfg(feature = "serde")] 37 | let command = { 38 | command.arg( 39 | Arg::new("wit_file") 40 | .value_name("WITNESS_FILE") 41 | .action(ArgAction::Set) 42 | .help("File containing the witness data"), 43 | ) 44 | }; 45 | 46 | let matches = { 47 | command 48 | .arg( 49 | Arg::new("debug") 50 | .long("debug") 51 | .action(ArgAction::SetTrue) 52 | .help("Include debug symbols in the output"), 53 | ) 54 | .get_matches() 55 | }; 56 | 57 | let prog_file = matches.get_one::("prog_file").unwrap(); 58 | let prog_path = std::path::Path::new(prog_file); 59 | let prog_text = std::fs::read_to_string(prog_path).map_err(|e| e.to_string())?; 60 | let include_debug_symbols = matches.get_flag("debug"); 61 | 62 | let compiled = CompiledProgram::new(prog_text, Arguments::default(), include_debug_symbols)?; 63 | 64 | #[cfg(feature = "serde")] 65 | let witness_opt = { 66 | matches 67 | .get_one::("wit_file") 68 | .map(|wit_file| -> Result { 69 | let wit_path = std::path::Path::new(wit_file); 70 | let wit_text = std::fs::read_to_string(wit_path).map_err(|e| e.to_string())?; 71 | let witness = serde_json::from_str::(&wit_text).unwrap(); 72 | Ok(witness) 73 | }) 74 | .transpose()? 75 | }; 76 | #[cfg(not(feature = "serde"))] 77 | let witness_opt: Option = None; 78 | 79 | if let Some(witness) = witness_opt { 80 | let satisfied = compiled.satisfy(witness)?; 81 | let (program_bytes, witness_bytes) = satisfied.redeem().encode_to_vec(); 82 | println!( 83 | "Program:\n{}", 84 | Base64Display::new(&program_bytes, &STANDARD) 85 | ); 86 | println!( 87 | "Witness:\n{}", 88 | Base64Display::new(&witness_bytes, &STANDARD) 89 | ); 90 | } else { 91 | let program_bytes = compiled.commit().encode_to_vec(); 92 | println!( 93 | "Program:\n{}", 94 | Base64Display::new(&program_bytes, &STANDARD) 95 | ); 96 | } 97 | 98 | Ok(()) 99 | } 100 | -------------------------------------------------------------------------------- /src/minimal.pest: -------------------------------------------------------------------------------- 1 | WHITESPACE = _{ " " | "\t" | "\n" | "\r" } 2 | COMMENT = _{ ("/*" ~ (!"*/" ~ ANY)* ~ "*/") | ("//" ~ (!"\n" ~ ANY)*) } 3 | 4 | program = { SOI ~ item* ~ EOI } 5 | item = { type_alias | function | module } 6 | statement = { assignment | expression } 7 | expression = { block_expression | single_expression } 8 | block_expression = { "{" ~ (statement ~ ";")* ~ expression? ~ "}" } 9 | 10 | identifier = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* } 11 | jet = @{ "jet::" ~ (ASCII_ALPHANUMERIC | "_")+ } 12 | witness_name = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* } 13 | builtin_type = @{ ("Either" | "Option" | "bool" | "List" | unsigned_type) ~ !ASCII_ALPHANUMERIC } 14 | 15 | builtin_function = @{ ("unwrap_left" | "unwrap_right" | "for_while" | "is_none" | "unwrap" | "assert" | "panic" | "match" | "into" | "fold" | "dbg") ~ !ASCII_ALPHANUMERIC } 16 | function_name = { !builtin_function ~ identifier } 17 | typed_identifier = { identifier ~ ":" ~ ty } 18 | function_params = { "(" ~ (typed_identifier ~ ("," ~ typed_identifier)*)? ~ ")" } 19 | function_return = { "->" ~ ty } 20 | fn_keyword = @{ "fn" ~ !ASCII_ALPHANUMERIC } 21 | function = { fn_keyword ~ function_name ~ function_params ~ function_return? ~ block_expression } 22 | 23 | variable_pattern = { identifier } 24 | ignore_pattern = @{ "_" } 25 | tuple_pattern = { "(" ~ ((pattern ~ ",")+ ~ pattern?)? ~ ")" } 26 | array_pattern = { "[" ~ (pattern ~ ("," ~ pattern)* ~ ","?)? ~ "]" } 27 | pattern = { ignore_pattern | tuple_pattern | array_pattern | variable_pattern } 28 | let_keyword = @{ "let" ~ !ASCII_ALPHANUMERIC } 29 | assignment = { let_keyword ~ pattern ~ ":" ~ ty ~ "=" ~ expression } 30 | 31 | left_pattern = { "Left(" ~ identifier ~ ":" ~ ty ~ ")" } 32 | right_pattern = { "Right(" ~ identifier ~ ":" ~ ty ~ ")" } 33 | none_pattern = @{ "None" } 34 | some_pattern = { "Some(" ~ identifier ~ ":" ~ ty ~ ")" } 35 | false_pattern = @{ "false" } 36 | true_pattern = @{ "true" } 37 | match_pattern = { left_pattern | right_pattern | none_pattern | some_pattern | false_pattern | true_pattern } 38 | 39 | sum_type = { "Either<" ~ ty ~ "," ~ ty ~ ">" } 40 | option_type = { "Option<" ~ ty ~ ">" } 41 | boolean_type = @{ "bool" } 42 | unsigned_type = @{ "u128" | "u256" | "u16" | "u32" | "u64" | "u1" | "u2" | "u4" | "u8" } 43 | tuple_type = { "(" ~ ((ty ~ ",")+ ~ ty?)? ~ ")" } 44 | array_size = @{ ASCII_DIGIT+ } 45 | array_type = { "[" ~ ty ~ ";" ~ array_size ~ "]" } 46 | list_bound = @{ ASCII_DIGIT+ } 47 | list_type = { "List<" ~ ty ~ "," ~ list_bound ~ ">" } 48 | ty = { alias_name | builtin_alias | sum_type | option_type | boolean_type | unsigned_type | tuple_type | array_type | list_type } 49 | builtin_alias = @{ "Ctx8" | "Pubkey" | "Message64" | "Message" | "Signature" | "Scalar" | "Fe" | "Gej" | "Ge" | "Point" | "Height" | "Time" | "Distance" | "Duration" | "Lock" | "Outpoint" | "Confidential1" | "ExplicitAsset" | "Asset1" | "ExplicitAmount" | "Amount1" | "ExplicitNonce" | "Nonce" | "TokenAmount1" } 50 | alias_name = { !builtin_type ~ !builtin_alias ~ identifier } 51 | type_keyword = @{ "type" ~ !ASCII_ALPHANUMERIC } 52 | type_alias = { type_keyword ~ alias_name ~ "=" ~ ty ~ ";" } 53 | 54 | left_expr = { "Left(" ~ expression ~ ")" } 55 | right_expr = { "Right(" ~ expression ~ ")" } 56 | none_expr = @{ "None" } 57 | some_expr = { "Some(" ~ expression ~ ")" } 58 | false_expr = @{ "false" } 59 | true_expr = @{ "true" } 60 | unwrap_left = { "unwrap_left::<" ~ ty ~ ">" } 61 | unwrap_right = { "unwrap_right::<" ~ ty ~ ">" } 62 | is_none = { "is_none::<" ~ ty ~ ">" } 63 | unwrap = @{ "unwrap" } 64 | assert = @{ "assert!" } 65 | panic = @{ "panic!" } 66 | type_cast = { "<" ~ ty ~ ">::into" } 67 | debug = @{ "dbg!" } 68 | fold = { "fold::<" ~ function_name ~ "," ~ list_bound ~ ">" } 69 | for_while = { "for_while::<" ~ function_name ~ ">" } 70 | call_name = { jet | unwrap_left | unwrap_right | is_none | unwrap | assert | panic | type_cast | debug | fold | for_while | function_name } 71 | call_args = { "(" ~ (expression ~ ("," ~ expression)*)? ~ ")" } 72 | call_expr = { call_name ~ call_args } 73 | dec_literal = @{ (ASCII_DIGIT | "_")+ } 74 | bin_literal = @{ "0b" ~ (ASCII_BIN_DIGIT | "_")+ } 75 | hex_literal = @{ "0x" ~ (ASCII_HEX_DIGIT | "_")+ } 76 | witness_expr = ${ "witness::" ~ witness_name } 77 | param_expr = ${ "param::" ~ witness_name } 78 | variable_expr = { identifier } 79 | match_arm = { match_pattern ~ "=>" ~ (single_expression ~ "," | block_expression ~ ","?) } 80 | match_keyword = @{ "match" ~ !ASCII_ALPHANUMERIC } 81 | match_expr = { match_keyword ~ expression ~ "{" ~ match_arm ~ match_arm ~ "}" } 82 | tuple_expr = { "(" ~ ((expression ~ ",")+ ~ expression?)? ~ ")" } 83 | array_expr = { "[" ~ (expression ~ ("," ~ expression)* ~ ","?)? ~ "]" } 84 | list_expr = { "list![" ~ (expression ~ ("," ~ expression)* ~ ","?)? ~ "]" } 85 | single_expression = { left_expr | right_expr | none_expr | some_expr | false_expr | true_expr | call_expr | match_expr | tuple_expr | array_expr | list_expr | bin_literal | hex_literal | dec_literal | witness_expr | param_expr | variable_expr | "(" ~ expression ~ ")" } 86 | 87 | mod_keyword = @{ "mod" ~ !ASCII_ALPHANUMERIC } 88 | const_keyword = @{ "const" ~ !ASCII_ALPHANUMERIC } 89 | module_name = @{ "witness" | "param" } 90 | module_assign = { const_keyword ~ witness_name ~ ":" ~ ty ~ "=" ~ expression } 91 | module = { mod_keyword ~ module_name ~ "{" ~ (module_assign ~ ";")* ~ "}" } 92 | -------------------------------------------------------------------------------- /src/num.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::num::NonZeroU32; 3 | use std::str::FromStr; 4 | 5 | /// Implementation for newtypes that wrap a number `u8`, `u16`, ... 6 | /// such that the number has some property. 7 | /// The newtype needs to have a constructor `Self::new(inner) -> Option`. 8 | macro_rules! checked_num { 9 | ( 10 | $wrapper: ident, 11 | $inner: ty, 12 | $description: expr 13 | ) => { 14 | impl $wrapper { 15 | /// Access the value as a primitive type. 16 | pub const fn get(&self) -> usize { 17 | self.0 18 | } 19 | } 20 | 21 | impl std::fmt::Display for $wrapper { 22 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 23 | write!(f, "{}", self.0) 24 | } 25 | } 26 | 27 | impl std::fmt::Debug for $wrapper { 28 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 29 | std::fmt::Display::fmt(self, f) 30 | } 31 | } 32 | 33 | impl std::str::FromStr for $wrapper { 34 | type Err = String; 35 | 36 | fn from_str(s: &str) -> Result { 37 | let n = s.parse::<$inner>().map_err(|e| e.to_string())?; 38 | Self::new(n).ok_or(format!("{s} is not {}", $description)) 39 | } 40 | } 41 | }; 42 | } 43 | 44 | /// An integer that is known to be a power of two with nonzero exponent. 45 | /// 46 | /// The integer is equal to 2^n for some n > 0. 47 | /// 48 | /// The integer is strictly greater than 1. 49 | #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 50 | pub struct NonZeroPow2Usize(usize); 51 | 52 | impl NonZeroPow2Usize { 53 | /// Smallest power of two with nonzero exponent. 54 | // FIXME `std::option::Option::::unwrap` is not yet stable as a const fn 55 | // pub const TWO: Self = Self::new(2).unwrap(); 56 | pub const TWO: Self = Self(2); 57 | 58 | /// Create a power of two with nonzero exponent. 59 | pub const fn new(n: usize) -> Option { 60 | if n.is_power_of_two() && 1 < n { 61 | Some(Self(n)) 62 | } else { 63 | None 64 | } 65 | } 66 | 67 | /// Create a power of two with nonzero exponent. 68 | /// 69 | /// ## Precondition 70 | /// 71 | /// The value must be a power of two with nonzero exponent. 72 | /// 73 | /// ## Panics 74 | /// 75 | /// Panics may occur down the line if the precondition is not satisfied. 76 | pub const fn new_unchecked(n: usize) -> Self { 77 | debug_assert!(n.is_power_of_two() && 1 < n); 78 | Self(n) 79 | } 80 | 81 | /// Return the binary logarithm of the value. 82 | /// 83 | /// The integer is equal to 2^n for some n > 0. Return n. 84 | pub const fn log2(self) -> NonZeroU32 { 85 | let n = self.0.trailing_zeros(); 86 | debug_assert!(0 < n); 87 | // Safety: 0 < n by definition of NonZeroPow2Usize 88 | unsafe { NonZeroU32::new_unchecked(n) } 89 | } 90 | 91 | /// Multiply the value by two. 92 | /// Return the next power of two. 93 | /// 94 | /// The integer is equal to 2^n for some n > 0. Return 2^(n + 1). 95 | pub const fn mul2(self) -> Self { 96 | let n = self.0 * 2; 97 | debug_assert!(n.is_power_of_two() && 1 < n); 98 | Self(n) 99 | } 100 | 101 | /// Divide the value by two. 102 | /// Return the previous power of two with nonzero exponent, if it exists. 103 | /// 104 | /// - If the integer is equal to 2^(n + 1) for some n > 0, then return `Some(2^n)`. 105 | /// - If the integer is equal to 2^1, then return `None`. 106 | pub const fn checked_div2(self) -> Option { 107 | match self.0 / 2 { 108 | 0 => unreachable!(), 109 | 1 => None, 110 | n => { 111 | debug_assert!(n.is_power_of_two() && 1 < n); 112 | Some(Self(n)) 113 | } 114 | } 115 | } 116 | } 117 | 118 | checked_num!(NonZeroPow2Usize, usize, "a power of two greater than 1"); 119 | 120 | #[cfg(feature = "arbitrary")] 121 | impl<'a> arbitrary::Arbitrary<'a> for NonZeroPow2Usize { 122 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 123 | let exp = u.int_in_range(1u32..=8)?; 124 | let num = 2usize.saturating_pow(exp); 125 | Ok(Self::new_unchecked(num)) 126 | } 127 | } 128 | 129 | /// An integer that is known to be a power of two. 130 | /// 131 | /// The integer is equal to 2^n for some n ≥ 0. 132 | #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 133 | pub struct Pow2Usize(usize); 134 | 135 | impl Pow2Usize { 136 | /// Smallest power of two. 137 | pub const ONE: Self = Self(1); 138 | 139 | /// Create a power of two. 140 | pub const fn new(n: usize) -> Option { 141 | if n.is_power_of_two() { 142 | Some(Self(n)) 143 | } else { 144 | None 145 | } 146 | } 147 | 148 | /// Create a power of two. 149 | /// 150 | /// ## Precondition 151 | /// 152 | /// The value must be a power of two. 153 | /// 154 | /// ## Panics 155 | /// 156 | /// Panics may occur down the line if the precondition is not satisfied. 157 | pub const fn new_unchecked(n: usize) -> Self { 158 | debug_assert!(n.is_power_of_two()); 159 | Self(n) 160 | } 161 | 162 | /// Return the binary logarithm of the value. 163 | /// 164 | /// The integer is equal to 2^n for some n ≥ 0. Return n. 165 | pub const fn log2(self) -> u32 { 166 | self.0.trailing_zeros() 167 | } 168 | 169 | /// Multiply the value by two. 170 | /// Return the next power of two. 171 | /// 172 | /// The integer is equal to 2^n for some n ≥ 0. Return 2^(n + 1). 173 | pub const fn mul2(self) -> Self { 174 | let n = self.0 * 2; 175 | debug_assert!(n.is_power_of_two()); 176 | Self(n) 177 | } 178 | 179 | /// Divide the value by two. 180 | /// Return the previous power of two, if it exists. 181 | /// 182 | /// - If the integer is equal to 2^(n + 1) for some n ≥ 0, then return `Some(2^n)`. 183 | /// - If the integer is equal to 2^0, then return `None`. 184 | pub const fn checked_div2(self) -> Option { 185 | match self.0 / 2 { 186 | 0 => None, 187 | n => { 188 | debug_assert!(n.is_power_of_two()); 189 | Some(Self(n)) 190 | } 191 | } 192 | } 193 | } 194 | 195 | checked_num!(Pow2Usize, usize, "a power of two greater than 1"); 196 | 197 | impl From for Pow2Usize { 198 | fn from(value: NonZeroPow2Usize) -> Self { 199 | Self(value.0) 200 | } 201 | } 202 | 203 | #[cfg(feature = "arbitrary")] 204 | impl<'a> arbitrary::Arbitrary<'a> for Pow2Usize { 205 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 206 | let exp = u.int_in_range(0u32..=8)?; 207 | let num = 2usize.saturating_pow(exp); 208 | Ok(Self::new_unchecked(num)) 209 | } 210 | } 211 | 212 | /// A 256-bit unsigned integer. 213 | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 214 | #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] 215 | pub struct U256([u8; 32]); 216 | 217 | impl U256 { 218 | /// The smallest value that can be represented by this integer type (0). 219 | pub const MIN: Self = Self([0; 32]); 220 | /// The largest value that can be represented by this integer type (2²⁵⁶ − 1). 221 | pub const MAX: Self = Self([255; 32]); 222 | /// The number of decimal digits of [`Self::MAX`]. 223 | const MAX_DIGITS: usize = 78; 224 | 225 | /// Create a 256-bit unsigned integer from a byte array. 226 | /// 227 | /// The byte array is in Big Endian order. 228 | pub const fn from_byte_array(bytes: [u8; 32]) -> Self { 229 | Self(bytes) 230 | } 231 | 232 | /// Convert the integer to a byte array. 233 | /// 234 | /// The byte array is in Big Endian order. 235 | pub const fn to_byte_array(self) -> [u8; 32] { 236 | self.0 237 | } 238 | } 239 | 240 | impl AsRef<[u8]> for U256 { 241 | fn as_ref(&self) -> &[u8] { 242 | self.0.as_ref() 243 | } 244 | } 245 | 246 | impl From for U256 { 247 | fn from(value: u8) -> Self { 248 | let mut bytes = [0; 32]; 249 | bytes[31] = value; 250 | Self(bytes) 251 | } 252 | } 253 | 254 | impl From for U256 { 255 | fn from(value: u16) -> Self { 256 | let mut bytes = [0; 32]; 257 | let value_bytes = value.to_be_bytes(); 258 | bytes[30..].copy_from_slice(&value_bytes); 259 | Self(bytes) 260 | } 261 | } 262 | 263 | impl From for U256 { 264 | fn from(value: u32) -> Self { 265 | let mut bytes = [0; 32]; 266 | let value_bytes = value.to_be_bytes(); 267 | bytes[28..].copy_from_slice(&value_bytes); 268 | Self(bytes) 269 | } 270 | } 271 | 272 | impl From for U256 { 273 | fn from(value: u64) -> Self { 274 | let mut bytes = [0; 32]; 275 | let bot_eight = value.to_be_bytes(); 276 | bytes[24..].copy_from_slice(&bot_eight); 277 | Self(bytes) 278 | } 279 | } 280 | 281 | impl From for U256 { 282 | fn from(value: u128) -> Self { 283 | let mut bytes = [0; 32]; 284 | let value_bytes = value.to_be_bytes(); 285 | bytes[16..].copy_from_slice(&value_bytes); 286 | Self(bytes) 287 | } 288 | } 289 | 290 | impl fmt::Display for U256 { 291 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 292 | let mut bytes = self.0; 293 | let mut digits = Vec::with_capacity(Self::MAX_DIGITS); 294 | let mut is_zero = false; 295 | 296 | while !is_zero { 297 | let mut carry = 0; 298 | is_zero = true; 299 | 300 | // Divide by 10, starting at the most significant bytes 301 | for byte in &mut bytes { 302 | let value = carry * 256 + u32::from(*byte); 303 | *byte = (value / 10) as u8; 304 | carry = value % 10; 305 | 306 | if *byte != 0 { 307 | is_zero = false; 308 | } 309 | } 310 | 311 | digits.push(carry as u8); 312 | } 313 | 314 | for digit in digits.iter().rev() { 315 | write!(f, "{}", digit)?; 316 | } 317 | 318 | Ok(()) 319 | } 320 | } 321 | 322 | impl FromStr for U256 { 323 | type Err = ParseIntError; 324 | 325 | fn from_str(s: &str) -> Result { 326 | let decimal = s.trim_start_matches('0'); 327 | if Self::MAX_DIGITS < decimal.chars().count() { 328 | return Err(ParseIntError::PosOverflow); 329 | } 330 | let mut bytes = [0; 32]; 331 | 332 | for ch in decimal.chars() { 333 | let mut carry = ch.to_digit(10).ok_or(ParseIntError::InvalidDigit)?; 334 | 335 | // Add to the least significant bytes first 336 | for byte in bytes.iter_mut().rev() { 337 | let value = u32::from(*byte) * 10 + carry; 338 | *byte = (value % 256) as u8; 339 | carry = value / 256; 340 | } 341 | if 0 < carry { 342 | return Err(ParseIntError::PosOverflow); 343 | } 344 | } 345 | 346 | Ok(Self(bytes)) 347 | } 348 | } 349 | 350 | /// Reimplementation of [`std::num::ParseIntError`] that we can construct. 351 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] 352 | pub enum ParseIntError { 353 | InvalidDigit, 354 | PosOverflow, 355 | } 356 | 357 | impl fmt::Display for ParseIntError { 358 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 359 | match self { 360 | Self::InvalidDigit => write!(f, "Invalid decimal digit"), 361 | Self::PosOverflow => write!(f, "Number too large to fit in target type"), 362 | } 363 | } 364 | } 365 | 366 | impl std::error::Error for ParseIntError {} 367 | 368 | #[cfg(test)] 369 | mod tests { 370 | use super::*; 371 | 372 | #[test] 373 | fn parse_u256_invalid_digit() { 374 | assert_eq!(Err(ParseIntError::InvalidDigit), "a".parse::()); 375 | } 376 | 377 | #[test] 378 | fn parse_u256_overflow() { 379 | let u256_max_plus_one = 380 | "115792089237316195423570985008687907853269984665640564039457584007913129639936"; 381 | assert_eq!( 382 | Err(ParseIntError::PosOverflow), 383 | u256_max_plus_one.parse::() 384 | ); 385 | let u256_max_times_ten = 386 | "1157920892373161954235709850086879078532699846656405640394575840079131296399350"; 387 | assert_eq!( 388 | Err(ParseIntError::PosOverflow), 389 | u256_max_times_ten.parse::() 390 | ); 391 | } 392 | 393 | #[test] 394 | fn parse_u256_leading_zeroes() { 395 | assert_eq!(U256::MIN, "00".parse().unwrap()); 396 | assert_eq!( 397 | U256::MAX, 398 | "0115792089237316195423570985008687907853269984665640564039457584007913129639935" 399 | .parse() 400 | .unwrap() 401 | ); 402 | } 403 | 404 | #[test] 405 | fn parse_u256_ok() { 406 | for n in 0u8..=255 { 407 | let s = n.to_string(); 408 | assert_eq!(U256::from(n), s.parse().unwrap()); 409 | } 410 | assert_eq!( 411 | U256::from(u128::MAX), 412 | "340282366920938463463374607431768211455".parse().unwrap(), 413 | ); 414 | assert_eq!( 415 | U256::MAX, 416 | "115792089237316195423570985008687907853269984665640564039457584007913129639935" 417 | .parse() 418 | .unwrap() 419 | ); 420 | } 421 | 422 | #[test] 423 | fn display_u256() { 424 | for n in 0u8..=255 { 425 | assert_eq!(n.to_string(), U256::from(n).to_string()); 426 | } 427 | assert_eq!(u128::MAX.to_string(), U256::from(u128::MAX).to_string()); 428 | assert_eq!( 429 | "115792089237316195423570985008687907853269984665640564039457584007913129639935", 430 | &U256::MAX.to_string() 431 | ) 432 | } 433 | 434 | #[test] 435 | fn pow2_log2() { 436 | let mut pow = NonZeroPow2Usize::TWO; 437 | 438 | for exp in 1..10 { 439 | assert_eq!(pow.log2().get(), exp); 440 | pow = pow.mul2(); 441 | } 442 | } 443 | 444 | #[test] 445 | fn pow2_div2() { 446 | let mut pow = NonZeroPow2Usize::new(2usize.pow(10)).unwrap(); 447 | 448 | for exp in (2..=10).rev() { 449 | assert_eq!(pow.get(), 2usize.pow(exp)); 450 | pow = pow.checked_div2().unwrap(); 451 | } 452 | assert_eq!(pow, NonZeroPow2Usize::TWO); 453 | assert!(pow.checked_div2().is_none()); 454 | } 455 | } 456 | -------------------------------------------------------------------------------- /src/serde.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fmt; 3 | 4 | use serde::{de, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; 5 | 6 | use crate::parse::ParseFromStr; 7 | use crate::str::WitnessName; 8 | use crate::types::ResolvedType; 9 | use crate::value::Value; 10 | use crate::witness::{Arguments, WitnessValues}; 11 | 12 | struct WitnessMapVisitor; 13 | 14 | impl<'de> de::Visitor<'de> for WitnessMapVisitor { 15 | type Value = HashMap; 16 | 17 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 18 | formatter.write_str("a map with string keys and value-map values") 19 | } 20 | 21 | fn visit_map(self, mut access: M) -> Result 22 | where 23 | M: de::MapAccess<'de>, 24 | { 25 | let mut map = HashMap::new(); 26 | while let Some((key, value)) = access.next_entry::()? { 27 | if map.insert(key.shallow_clone(), value).is_some() { 28 | return Err(de::Error::custom(format!("Name `{key}` is assigned twice"))); 29 | } 30 | } 31 | Ok(map) 32 | } 33 | } 34 | 35 | impl<'de> Deserialize<'de> for WitnessValues { 36 | fn deserialize(deserializer: D) -> Result 37 | where 38 | D: Deserializer<'de>, 39 | { 40 | deserializer 41 | .deserialize_map(WitnessMapVisitor) 42 | .map(Self::from) 43 | } 44 | } 45 | 46 | impl<'de> Deserialize<'de> for Arguments { 47 | fn deserialize(deserializer: D) -> Result 48 | where 49 | D: Deserializer<'de>, 50 | { 51 | deserializer 52 | .deserialize_map(WitnessMapVisitor) 53 | .map(Self::from) 54 | } 55 | } 56 | 57 | struct ValueMapVisitor; 58 | 59 | impl<'de> de::Visitor<'de> for ValueMapVisitor { 60 | type Value = Value; 61 | 62 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 63 | formatter.write_str("a map with \"value\" and \"type\" fields") 64 | } 65 | 66 | fn visit_map(self, mut access: M) -> Result 67 | where 68 | M: de::MapAccess<'de>, 69 | { 70 | let mut value = None; 71 | let mut ty = None; 72 | 73 | while let Some(key) = access.next_key::<&str>()? { 74 | match key { 75 | "value" => { 76 | if value.is_some() { 77 | return Err(de::Error::duplicate_field("value")); 78 | } 79 | value = Some(access.next_value::<&str>()?); 80 | } 81 | "type" => { 82 | if ty.is_some() { 83 | return Err(de::Error::duplicate_field("type")); 84 | } 85 | ty = Some(access.next_value::<&str>()?); 86 | } 87 | _ => { 88 | return Err(de::Error::unknown_field(key, &["value", "type"])); 89 | } 90 | } 91 | } 92 | 93 | let ty = match ty { 94 | Some(s) => ResolvedType::parse_from_str(s).map_err(de::Error::custom)?, 95 | None => return Err(de::Error::missing_field("type")), 96 | }; 97 | match value { 98 | Some(s) => Value::parse_from_str(s, &ty).map_err(de::Error::custom), 99 | None => Err(de::Error::missing_field("value")), 100 | } 101 | } 102 | } 103 | 104 | impl<'de> Deserialize<'de> for Value { 105 | fn deserialize(deserializer: D) -> Result 106 | where 107 | D: Deserializer<'de>, 108 | { 109 | deserializer.deserialize_map(ValueMapVisitor) 110 | } 111 | } 112 | 113 | struct ParserVisitor(std::marker::PhantomData); 114 | 115 | impl<'de, A: ParseFromStr> de::Visitor<'de> for ParserVisitor { 116 | type Value = A; 117 | 118 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 119 | formatter.write_str("a valid string") 120 | } 121 | 122 | fn visit_str(self, value: &str) -> Result 123 | where 124 | E: de::Error, 125 | { 126 | A::parse_from_str(value).map_err(E::custom) 127 | } 128 | } 129 | 130 | impl<'de> Deserialize<'de> for WitnessName { 131 | fn deserialize(deserializer: D) -> Result 132 | where 133 | D: Deserializer<'de>, 134 | { 135 | deserializer.deserialize_str(ParserVisitor::(std::marker::PhantomData)) 136 | } 137 | } 138 | 139 | struct WitnessMapSerializer<'a>(&'a HashMap); 140 | 141 | impl<'a> Serialize for WitnessMapSerializer<'a> { 142 | fn serialize(&self, serializer: S) -> Result 143 | where 144 | S: Serializer, 145 | { 146 | let mut map = serializer.serialize_map(Some(self.0.len()))?; 147 | for (name, value) in self.0 { 148 | map.serialize_entry(name.as_inner(), &ValueMapSerializer(value))?; 149 | } 150 | map.end() 151 | } 152 | } 153 | 154 | struct ValueMapSerializer<'a>(&'a Value); 155 | 156 | impl<'a> Serialize for ValueMapSerializer<'a> { 157 | fn serialize(&self, serializer: S) -> Result 158 | where 159 | S: Serializer, 160 | { 161 | let mut map = serializer.serialize_map(Some(2))?; 162 | map.serialize_entry("value", &self.0.to_string())?; 163 | map.serialize_entry("type", &self.0.ty().to_string())?; 164 | map.end() 165 | } 166 | } 167 | 168 | impl Serialize for WitnessValues { 169 | fn serialize(&self, serializer: S) -> Result 170 | where 171 | S: Serializer, 172 | { 173 | WitnessMapSerializer(self.as_inner()).serialize(serializer) 174 | } 175 | } 176 | 177 | impl Serialize for Arguments { 178 | fn serialize(&self, serializer: S) -> Result 179 | where 180 | S: Serializer, 181 | { 182 | WitnessMapSerializer(self.as_inner()).serialize(serializer) 183 | } 184 | } 185 | 186 | #[cfg(test)] 187 | mod tests { 188 | use super::*; 189 | 190 | #[test] 191 | fn witness_serde_duplicate_assignment() { 192 | let s = r#"{ 193 | "A": { "value": "42", "type": "u32" }, 194 | "A": { "value": "43", "type": "u16" } 195 | }"#; 196 | 197 | match serde_json::from_str::(s) { 198 | Ok(_) => panic!("Duplicate witness assignment was falsely accepted"), 199 | Err(error) => assert!(error.to_string().contains("Name `A` is assigned twice")), 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/str.rs: -------------------------------------------------------------------------------- 1 | //! Types for handling strings with invariants. 2 | 3 | use std::sync::Arc; 4 | 5 | /// Implementations for newtypes that wrap [`Arc`]. 6 | macro_rules! wrapped_string { 7 | ($wrapper:ident, $name:expr) => { 8 | impl $wrapper { 9 | #[doc = "Create a"] 10 | #[doc = $name] 11 | #[doc = ".\n\n"] 12 | #[doc = "## Precondition\n\n"] 13 | #[doc = "The string must be a valid"] 14 | #[doc = $name] 15 | #[doc = ".\n\n"] 16 | #[doc = "## Panics\n\n"] 17 | #[doc = "Panics may occur down the line if the precondition is not satisfied."] 18 | pub fn from_str_unchecked(s: &str) -> Self { 19 | Self(Arc::from(s)) 20 | } 21 | 22 | /// Access the inner string. 23 | pub fn as_inner(&self) -> &str { 24 | self.0.as_ref() 25 | } 26 | 27 | /// Make a cheap copy of the name. 28 | pub fn shallow_clone(&self) -> Self { 29 | Self(Arc::clone(&self.0)) 30 | } 31 | } 32 | 33 | impl std::fmt::Display for $wrapper { 34 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 35 | std::fmt::Display::fmt(&self.0, f) 36 | } 37 | } 38 | 39 | impl std::fmt::Debug for $wrapper { 40 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 41 | std::fmt::Display::fmt(&self.0, f) 42 | } 43 | } 44 | }; 45 | } 46 | 47 | /// Implementation of [`arbitrary::Arbitrary`] for wrapped string types, 48 | /// such that strings of 1 to 10 letters `a` to `z` are generated. 49 | /// 50 | /// The space of lowercase letter strings includes values that are invalid 51 | /// according to the grammar of the particular string type. For instance, 52 | /// keywords are reserved. However, this should not affect fuzzing. 53 | macro_rules! impl_arbitrary_lowercase_alpha { 54 | ($wrapper:ident) => { 55 | #[cfg(feature = "arbitrary")] 56 | impl<'a> arbitrary::Arbitrary<'a> for $wrapper { 57 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 58 | let len = u.int_in_range(1..=10)?; 59 | let mut string = String::with_capacity(len); 60 | for _ in 0..len { 61 | let offset = u.int_in_range(0..=25)?; 62 | string.push((b'a' + offset) as char) 63 | } 64 | Ok(Self::from_str_unchecked(string.as_str())) 65 | } 66 | } 67 | }; 68 | } 69 | 70 | /// The name of a function. 71 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 72 | pub struct FunctionName(Arc); 73 | 74 | impl FunctionName { 75 | /// Return the name of the main function. 76 | pub fn main() -> Self { 77 | Self(Arc::from("main")) 78 | } 79 | } 80 | 81 | wrapped_string!(FunctionName, "function name"); 82 | 83 | #[cfg(feature = "arbitrary")] 84 | impl<'a> arbitrary::Arbitrary<'a> for FunctionName { 85 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 86 | const RESERVED_NAMES: [&str; 11] = [ 87 | "unwrap_left", 88 | "unwrap_right", 89 | "for_while", 90 | "is_none", 91 | "unwrap", 92 | "assert", 93 | "panic", 94 | "match", 95 | "into", 96 | "fold", 97 | "dbg", 98 | ]; 99 | 100 | let len = u.int_in_range(1..=10)?; 101 | let mut string = String::with_capacity(len); 102 | for _ in 0..len { 103 | let offset = u.int_in_range(0..=25)?; 104 | string.push((b'a' + offset) as char) 105 | } 106 | if RESERVED_NAMES.contains(&string.as_str()) { 107 | string.push('_'); 108 | } 109 | 110 | Ok(Self::from_str_unchecked(string.as_str())) 111 | } 112 | } 113 | 114 | /// The identifier of a variable. 115 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 116 | pub struct Identifier(Arc); 117 | 118 | wrapped_string!(Identifier, "variable identifier"); 119 | impl_arbitrary_lowercase_alpha!(Identifier); 120 | 121 | /// The name of a witness. 122 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 123 | pub struct WitnessName(Arc); 124 | 125 | wrapped_string!(WitnessName, "witness name"); 126 | impl_arbitrary_lowercase_alpha!(WitnessName); 127 | 128 | /// The name of a jet. 129 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 130 | pub struct JetName(Arc); 131 | 132 | wrapped_string!(JetName, "jet name"); 133 | 134 | #[cfg(feature = "arbitrary")] 135 | impl<'a> arbitrary::Arbitrary<'a> for JetName { 136 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 137 | u.choose(&simplicity::jet::Elements::ALL) 138 | .map(simplicity::jet::Elements::to_string) 139 | .map(Arc::from) 140 | .map(Self) 141 | } 142 | } 143 | 144 | /// The name of a type alias. 145 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 146 | pub struct AliasName(Arc); 147 | 148 | wrapped_string!(AliasName, "name of a type alias"); 149 | 150 | #[cfg(feature = "arbitrary")] 151 | impl<'a> arbitrary::Arbitrary<'a> for AliasName { 152 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 153 | const RESERVED_NAMES: [&str; 37] = [ 154 | "Either", 155 | "Option", 156 | "bool", 157 | "List", 158 | "u128", 159 | "u256", 160 | "u16", 161 | "u32", 162 | "u64", 163 | "u1", 164 | "u2", 165 | "u4", 166 | "u8", 167 | "Ctx8", 168 | "Pubkey", 169 | "Message64", 170 | "Message", 171 | "Signature", 172 | "Scalar", 173 | "Fe", 174 | "Gej", 175 | "Ge", 176 | "Point", 177 | "Height", 178 | "Time", 179 | "Distance", 180 | "Duration", 181 | "Lock", 182 | "Outpoint", 183 | "Confidential1", 184 | "ExplicitAsset", 185 | "Asset1", 186 | "ExplicitAmount", 187 | "Amount1", 188 | "ExplicitNonce", 189 | "Nonce", 190 | "TokenAmount1", 191 | ]; 192 | 193 | let len = u.int_in_range(1..=10)?; 194 | let mut string = String::with_capacity(len); 195 | for _ in 0..len { 196 | let offset = u.int_in_range(0..=25)?; 197 | string.push((b'a' + offset) as char) 198 | } 199 | if RESERVED_NAMES.contains(&string.as_str()) { 200 | string.push('_'); 201 | } 202 | 203 | Ok(Self::from_str_unchecked(string.as_str())) 204 | } 205 | } 206 | 207 | /// A string of decimal digits. 208 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 209 | pub struct Decimal(Arc); 210 | 211 | wrapped_string!(Decimal, "decimal string"); 212 | 213 | #[cfg(feature = "arbitrary")] 214 | impl<'a> arbitrary::Arbitrary<'a> for Decimal { 215 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 216 | let len = u.int_in_range(1..=10)?; 217 | let mut string = String::with_capacity(len); 218 | for _ in 0..len { 219 | let offset = u.int_in_range(0..=9)?; 220 | string.push((b'0' + offset) as char) 221 | } 222 | Ok(Self::from_str_unchecked(string.as_str())) 223 | } 224 | } 225 | 226 | /// A string of binary digits. 227 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 228 | pub struct Binary(Arc); 229 | 230 | wrapped_string!(Binary, "binary string"); 231 | 232 | #[cfg(feature = "arbitrary")] 233 | impl<'a> arbitrary::Arbitrary<'a> for Binary { 234 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 235 | let len = u.int_in_range(1..=10)?; 236 | let mut string = String::with_capacity(len); 237 | for _ in 0..len { 238 | let offset = u.int_in_range(0..=1)?; 239 | let bin_digit = (b'0' + offset) as char; 240 | string.push(bin_digit); 241 | } 242 | Ok(Self::from_str_unchecked(string.as_str())) 243 | } 244 | } 245 | 246 | /// A string of hexadecimal digits. 247 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 248 | pub struct Hexadecimal(Arc); 249 | 250 | wrapped_string!(Hexadecimal, "hexadecimal string"); 251 | 252 | #[cfg(feature = "arbitrary")] 253 | impl<'a> arbitrary::Arbitrary<'a> for Hexadecimal { 254 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 255 | let len = u.int_in_range(1..=10)?; 256 | let mut string = String::with_capacity(len); 257 | for _ in 0..len { 258 | let offset = u.int_in_range(0..=15)?; 259 | let hex_digit = match offset { 260 | 0..=9 => (b'0' + offset) as char, 261 | 10..=15 => (b'a' + (offset - 10)) as char, 262 | _ => unreachable!(), 263 | }; 264 | string.push(hex_digit); 265 | } 266 | Ok(Self::from_str_unchecked(string.as_str())) 267 | } 268 | } 269 | 270 | /// The name of a module. 271 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 272 | pub struct ModuleName(Arc); 273 | 274 | impl ModuleName { 275 | /// Return the name of the witness module. 276 | pub fn witness() -> Self { 277 | Self(Arc::from("witness")) 278 | } 279 | 280 | /// Return the name of the parameter module. 281 | pub fn param() -> Self { 282 | Self(Arc::from("param")) 283 | } 284 | } 285 | 286 | wrapped_string!(ModuleName, "module name"); 287 | -------------------------------------------------------------------------------- /src/witness.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fmt; 3 | use std::sync::Arc; 4 | 5 | use crate::error::{Error, RichError, WithFile, WithSpan}; 6 | use crate::parse; 7 | use crate::parse::ParseFromStr; 8 | use crate::str::WitnessName; 9 | use crate::types::{AliasedType, ResolvedType}; 10 | use crate::value::Value; 11 | 12 | macro_rules! impl_name_type_map { 13 | ($wrapper: ident) => { 14 | impl $wrapper { 15 | /// Get the type that is assigned to the given name. 16 | pub fn get(&self, name: &WitnessName) -> Option<&ResolvedType> { 17 | self.0.get(name) 18 | } 19 | 20 | /// Create an iterator over all name-type pairs. 21 | pub fn iter(&self) -> impl Iterator { 22 | self.0.iter() 23 | } 24 | 25 | /// Make a cheap copy of the map. 26 | pub fn shallow_clone(&self) -> Self { 27 | Self(Arc::clone(&self.0)) 28 | } 29 | } 30 | 31 | impl From> for $wrapper { 32 | fn from(value: HashMap) -> Self { 33 | Self(Arc::new(value)) 34 | } 35 | } 36 | }; 37 | } 38 | 39 | macro_rules! impl_name_value_map { 40 | ($wrapper: ident, $module_name: expr) => { 41 | impl $wrapper { 42 | /// Access the inner map. 43 | #[cfg(feature = "serde")] 44 | pub(crate) fn as_inner(&self) -> &HashMap { 45 | &self.0 46 | } 47 | 48 | /// Get the value that is assigned to the given name. 49 | pub fn get(&self, name: &WitnessName) -> Option<&Value> { 50 | self.0.get(name) 51 | } 52 | 53 | /// Create an iterator over all name-value pairs. 54 | pub fn iter(&self) -> impl Iterator { 55 | self.0.iter() 56 | } 57 | 58 | /// Make a cheap copy of the map. 59 | pub fn shallow_clone(&self) -> Self { 60 | Self(Arc::clone(&self.0)) 61 | } 62 | } 63 | 64 | impl From> for $wrapper { 65 | fn from(value: HashMap) -> Self { 66 | Self(Arc::new(value)) 67 | } 68 | } 69 | 70 | impl ParseFromStr for $wrapper { 71 | fn parse_from_str(s: &str) -> Result { 72 | parse::ModuleProgram::parse_from_str(s).and_then(|x| Self::analyze(&x)) 73 | } 74 | } 75 | 76 | impl fmt::Display for $wrapper { 77 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 78 | use itertools::Itertools; 79 | 80 | writeln!(f, "mod {} {{", $module_name)?; 81 | for name in self.0.keys().sorted_unstable() { 82 | let value = self.0.get(name).unwrap(); 83 | writeln!(f, " const {name}: {} = {value};", value.ty())?; 84 | } 85 | write!(f, "}}") 86 | } 87 | } 88 | }; 89 | } 90 | 91 | /// Map of witness types. 92 | #[derive(Clone, Debug, Eq, PartialEq, Default)] 93 | pub struct WitnessTypes(Arc>); 94 | 95 | impl_name_type_map!(WitnessTypes); 96 | 97 | /// Map of witness values. 98 | #[derive(Clone, Debug, Eq, PartialEq, Default)] 99 | #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] 100 | pub struct WitnessValues(Arc>); 101 | 102 | impl_name_value_map!(WitnessValues, "witness"); 103 | 104 | impl WitnessValues { 105 | /// Check if the witness values are consistent with the declared witness types. 106 | /// 107 | /// 1. Values that occur in the program are type checked. 108 | /// 2. Values that don't occur in the program are skipped. 109 | /// The witness map may contain more values than necessary. 110 | /// 111 | /// There may be witnesses that are referenced in the program that are not assigned a value 112 | /// in the witness map. These witnesses may lie on pruned branches that will not be part of the 113 | /// finalized Simplicity program. However, before the finalization, we cannot know which 114 | /// witnesses will be pruned and which won't be pruned. This check skips unassigned witnesses. 115 | pub fn is_consistent(&self, witness_types: &WitnessTypes) -> Result<(), Error> { 116 | for name in self.0.keys() { 117 | let Some(declared_ty) = witness_types.get(name) else { 118 | continue; 119 | }; 120 | let assigned_ty = self.0[name].ty(); 121 | if assigned_ty != declared_ty { 122 | return Err(Error::WitnessTypeMismatch( 123 | name.clone(), 124 | declared_ty.clone(), 125 | assigned_ty.clone(), 126 | )); 127 | } 128 | } 129 | 130 | Ok(()) 131 | } 132 | } 133 | 134 | impl ParseFromStr for ResolvedType { 135 | fn parse_from_str(s: &str) -> Result { 136 | let aliased = AliasedType::parse_from_str(s)?; 137 | aliased 138 | .resolve_builtin() 139 | .map_err(Error::UndefinedAlias) 140 | .with_span(s) 141 | .with_file(s) 142 | } 143 | } 144 | 145 | /// Map of parameters. 146 | /// 147 | /// A parameter is a named variable that resolves to a value of a given type. 148 | /// Parameters have a name and a type. 149 | #[derive(Clone, Debug, Eq, PartialEq, Default)] 150 | pub struct Parameters(Arc>); 151 | 152 | impl_name_type_map!(Parameters); 153 | 154 | /// Map of arguments. 155 | /// 156 | /// An argument is the value of a parameter. 157 | /// Arguments have a name and a value of a given type. 158 | #[derive(Clone, Debug, Eq, PartialEq, Default)] 159 | #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] 160 | pub struct Arguments(Arc>); 161 | 162 | impl_name_value_map!(Arguments, "param"); 163 | 164 | impl Arguments { 165 | /// Check if the arguments are consistent with the given parameters. 166 | /// 167 | /// 1. Each parameter must be supplied with an argument. 168 | /// 2. The type of each parameter must match the type of its argument. 169 | /// 170 | /// Arguments without a corresponding parameter are ignored. 171 | pub fn is_consistent(&self, parameters: &Parameters) -> Result<(), Error> { 172 | for (name, parameter_ty) in parameters.iter() { 173 | let argument = self 174 | .get(name) 175 | .ok_or_else(|| Error::ArgumentMissing(name.shallow_clone()))?; 176 | if !argument.is_of_type(parameter_ty) { 177 | return Err(Error::ArgumentTypeMismatch( 178 | name.clone(), 179 | parameter_ty.clone(), 180 | argument.ty().clone(), 181 | )); 182 | } 183 | } 184 | 185 | Ok(()) 186 | } 187 | } 188 | 189 | #[cfg(feature = "arbitrary")] 190 | impl crate::ArbitraryOfType for Arguments { 191 | type Type = Parameters; 192 | 193 | fn arbitrary_of_type( 194 | u: &mut arbitrary::Unstructured, 195 | ty: &Self::Type, 196 | ) -> arbitrary::Result { 197 | let mut map = HashMap::new(); 198 | for (name, parameter_ty) in ty.iter() { 199 | map.insert( 200 | name.shallow_clone(), 201 | Value::arbitrary_of_type(u, parameter_ty)?, 202 | ); 203 | } 204 | Ok(Self::from(map)) 205 | } 206 | } 207 | 208 | #[cfg(test)] 209 | mod tests { 210 | use super::*; 211 | use crate::parse::ParseFromStr; 212 | use crate::value::ValueConstructible; 213 | use crate::{ast, parse, CompiledProgram, SatisfiedProgram}; 214 | 215 | #[test] 216 | fn witness_reuse() { 217 | let s = r#"fn main() { 218 | assert!(jet::eq_32(witness::A, witness::A)); 219 | }"#; 220 | let program = parse::Program::parse_from_str(s).expect("parsing works"); 221 | match ast::Program::analyze(&program).map_err(Error::from) { 222 | Ok(_) => panic!("Witness reuse was falsely accepted"), 223 | Err(Error::WitnessReused(..)) => {} 224 | Err(error) => panic!("Unexpected error: {error}"), 225 | } 226 | } 227 | 228 | #[test] 229 | fn witness_type_mismatch() { 230 | let s = r#"fn main() { 231 | assert!(jet::is_zero_32(witness::A)); 232 | }"#; 233 | 234 | let witness = WitnessValues::from(HashMap::from([( 235 | WitnessName::from_str_unchecked("A"), 236 | Value::u16(42), 237 | )])); 238 | match SatisfiedProgram::new(s, Arguments::default(), witness, false) { 239 | Ok(_) => panic!("Ill-typed witness assignment was falsely accepted"), 240 | Err(error) => assert_eq!( 241 | "Witness `A` was declared with type `u32` but its assigned value is of type `u16`", 242 | error 243 | ), 244 | } 245 | } 246 | 247 | #[test] 248 | fn witness_outside_main() { 249 | let s = r#"fn f() -> u32 { 250 | witness::OUTPUT_OF_F 251 | } 252 | 253 | fn main() { 254 | assert!(jet::is_zero_32(f())); 255 | }"#; 256 | 257 | match CompiledProgram::new(s, Arguments::default(), false) { 258 | Ok(_) => panic!("Witness outside main was falsely accepted"), 259 | Err(error) => { 260 | assert!(error 261 | .contains("Witness expressions are not allowed outside the `main` function")) 262 | } 263 | } 264 | } 265 | 266 | #[test] 267 | fn missing_witness_module() { 268 | match WitnessValues::parse_from_str("") { 269 | Ok(_) => panic!("Missing witness module was falsely accepted"), 270 | Err(error) => assert!(error.to_string().contains("module `witness` is missing")), 271 | } 272 | } 273 | 274 | #[test] 275 | fn redefined_witness_module() { 276 | let s = r#"mod witness {} mod witness {}"#; 277 | match WitnessValues::parse_from_str(s) { 278 | Ok(_) => panic!("Redefined witness module was falsely accepted"), 279 | Err(error) => assert!(error 280 | .to_string() 281 | .contains("Module `witness` is defined twice")), 282 | } 283 | } 284 | 285 | #[test] 286 | fn witness_to_string() { 287 | let witness = WitnessValues::from(HashMap::from([ 288 | (WitnessName::from_str_unchecked("A"), Value::u32(1)), 289 | (WitnessName::from_str_unchecked("B"), Value::u32(2)), 290 | (WitnessName::from_str_unchecked("C"), Value::u32(3)), 291 | ])); 292 | let expected_string = r#"mod witness { 293 | const A: u32 = 1; 294 | const B: u32 = 2; 295 | const C: u32 = 3; 296 | }"#; 297 | assert_eq!(expected_string, witness.to_string()); 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /vscode/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Simfony contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /vscode/README.md: -------------------------------------------------------------------------------- 1 | # Simfony extension for VSCode 2 | 3 | VSCode extension that provides syntax highlighting for the [Simfony](https://docs.simfony.dev/) programming language. 4 | 5 | ## Features 6 | 7 | - Syntax highlighting for .simf files 8 | - Basic language configuration (brackets, comments) 9 | 10 | ## Installation 11 | 12 | Install Node.js (v14 or later recommended). 13 | 14 | ### Local Installation 15 | 16 | 1. Clone this repository 17 | 2. Navigate to the extension directory: 18 | ```bash 19 | cd vscode 20 | ``` 21 | 3. Install dependencies: 22 | ```bash 23 | npm install 24 | ``` 25 | 4. Package the extension: 26 | ```bash 27 | npm install -g @vscode/vsce 28 | vsce package 29 | ``` 30 | This will create a `.vsix` file in the current directory. 31 | 32 | 5. Install the extension in VSCode: 33 | - Launch VS Code 34 | - Go to the Extensions view (Ctrl+Shift+X) 35 | - Click on the "..." menu in the top-right of the Extensions view 36 | - Select "Install from VSIX..." 37 | - Navigate to and select the `.vsix` file you created 38 | 39 | ### Alternative Installation Method 40 | 41 | You can also install the extension directly from the source code: 42 | 43 | 1. Copy the `vscode` folder (rename it if necessary) to your VSCode extensions directory: 44 | - Windows: `%USERPROFILE%\.vscode\extensions` 45 | - macOS/Linux: `~/.vscode/extensions` 46 | 47 | 2. Restart VSCode 48 | 49 | ## Development 50 | 51 | 1. Clone this repository and cd into `vscode` directory 52 | 2. Run `npm install` 53 | 3. Open the project in VS Code 54 | 4. Press F5 to start debugging (this will launch a new VSCode window with the extension loaded) 55 | 5. Make changes to the extension 56 | 6. Reload the debugging window to see your changes (Ctrl+R or Cmd+R) 57 | 58 | ### Reloading the Extension During Development 59 | 60 | When making changes to the extension, you can reload it without uninstalling and reinstalling: 61 | 62 | 1. **Using the Command Palette**: 63 | - Press `Ctrl+Shift+P` (or `Cmd+Shift+P` on macOS) 64 | - Type "Developer: Reload Window" and select it 65 | 66 | 2. **Using keyboard shortcut**: 67 | - Press `Ctrl+R` (or `Cmd+R` on macOS) 68 | 69 | 3. **For extensions installed from folder**: 70 | - Make your changes to the extension files 71 | - Run the "Developer: Reload Window" command as described above 72 | - VSCode will reload with the updated extension 73 | 74 | 4. **For more substantial changes**: 75 | - If you've made significant changes to the extension's structure or manifest 76 | - You may need to restart VSCode completely (close and reopen) 77 | - In some cases, you might need to run the command "Developer: Restart Extension Host" 78 | -------------------------------------------------------------------------------- /vscode/language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "//" 4 | }, 5 | "brackets": [ 6 | ["{", "}"], 7 | ["[", "]"], 8 | ["(", ")"] 9 | ], 10 | "autoClosingPairs": [ 11 | { "open": "{", "close": "}" }, 12 | { "open": "[", "close": "]" }, 13 | { "open": "(", "close": ")" }, 14 | { "open": "\"", "close": "\"" } 15 | ], 16 | "surroundingPairs": [ 17 | ["{", "}"], 18 | ["[", "]"], 19 | ["(", ")"], 20 | ["\"", "\""] 21 | ] 22 | } -------------------------------------------------------------------------------- /vscode/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simfony-language", 3 | "version": "0.0.1", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "simfony-language", 9 | "version": "0.0.1", 10 | "license": "MIT", 11 | "engines": { 12 | "vscode": "^1.85.0" 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /vscode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simfony-language", 3 | "displayName": "Simfony Language Support", 4 | "description": "Syntax highlighting and autocompletion for Simfony language", 5 | "version": "0.0.1", 6 | "publisher": "blockstream", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/BlockstreamResearch/simfony" 10 | }, 11 | "engines": { 12 | "vscode": "^1.85.0" 13 | }, 14 | "categories": [ 15 | "Programming Languages" 16 | ], 17 | "contributes": { 18 | "languages": [{ 19 | "id": "simfony", 20 | "aliases": ["Simfony", "simfony"], 21 | "extensions": [".simf"], 22 | "configuration": "./language-configuration.json" 23 | }], 24 | "grammars": [{ 25 | "language": "simfony", 26 | "scopeName": "source.simfony", 27 | "path": "./syntaxes/simfony.tmLanguage.json" 28 | }] 29 | }, 30 | "license": "MIT" 31 | } -------------------------------------------------------------------------------- /vscode/syntaxes/simfony.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", 3 | "name": "Simfony", 4 | "patterns": [ 5 | { 6 | "include": "#preprocessor" 7 | }, 8 | { 9 | "include": "#comments" 10 | }, 11 | { 12 | "include": "#macros" 13 | }, 14 | { 15 | "include": "#modules" 16 | }, 17 | { 18 | "include": "#keywords" 19 | }, 20 | { 21 | "include": "#functions" 22 | }, 23 | { 24 | "include": "#types" 25 | }, 26 | { 27 | "include": "#literals" 28 | }, 29 | { 30 | "include": "#expressions" 31 | } 32 | ], 33 | "repository": { 34 | "preprocessor": { 35 | "patterns": [ 36 | { 37 | "name": "meta.preprocessor.simfony", 38 | "match": "^\\s*#\\s*(include|define|undef|if|ifdef|ifndef|else|elif|endif|line|error|pragma)\\b", 39 | "captures": { 40 | "1": { 41 | "name": "keyword.other.preprocessor.directive.simfony" 42 | } 43 | } 44 | }, 45 | { 46 | "name": "meta.preprocessor.include.simfony", 47 | "match": "^\\s*#\\s*include\\s+([\"<].*[\">])", 48 | "captures": { 49 | "1": { 50 | "name": "string.quoted.other.lt-gt.include.simfony" 51 | } 52 | } 53 | }, 54 | { 55 | "name": "meta.preprocessor.define.simfony", 56 | "begin": "^\\s*#\\s*(define)\\s+([a-zA-Z_][a-zA-Z0-9_]*)", 57 | "beginCaptures": { 58 | "1": { 59 | "name": "keyword.other.preprocessor.directive.simfony" 60 | }, 61 | "2": { 62 | "name": "entity.name.function.preprocessor.simfony" 63 | } 64 | }, 65 | "end": "(?=(?://|/\\*))|$", 66 | "patterns": [ 67 | { 68 | "include": "#preprocessor-expression" 69 | } 70 | ] 71 | }, 72 | { 73 | "name": "meta.preprocessor.macro.simfony", 74 | "begin": "^\\s*#\\s*(define)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\(", 75 | "beginCaptures": { 76 | "1": { 77 | "name": "keyword.other.preprocessor.directive.simfony" 78 | }, 79 | "2": { 80 | "name": "entity.name.function.preprocessor.simfony" 81 | } 82 | }, 83 | "end": "\\)|(?=(?://|/\\*))|$", 84 | "patterns": [ 85 | { 86 | "name": "variable.parameter.preprocessor.simfony", 87 | "match": "[a-zA-Z_][a-zA-Z0-9_]*" 88 | }, 89 | { 90 | "name": "punctuation.separator.parameters.simfony", 91 | "match": "," 92 | } 93 | ] 94 | }, 95 | { 96 | "name": "meta.preprocessor.conditional.simfony", 97 | "begin": "^\\s*#\\s*(if|ifdef|ifndef|elif)\\b", 98 | "beginCaptures": { 99 | "1": { 100 | "name": "keyword.other.preprocessor.directive.simfony" 101 | } 102 | }, 103 | "end": "(?=(?://|/\\*))|$", 104 | "patterns": [ 105 | { 106 | "include": "#preprocessor-expression" 107 | } 108 | ] 109 | } 110 | ] 111 | }, 112 | "preprocessor-expression": { 113 | "patterns": [ 114 | { 115 | "name": "constant.language.preprocessor.simfony", 116 | "match": "\\b(defined)\\b" 117 | }, 118 | { 119 | "name": "entity.name.function.preprocessor.simfony", 120 | "match": "\\b[a-zA-Z_][a-zA-Z0-9_]*\\b" 121 | }, 122 | { 123 | "include": "#literals" 124 | }, 125 | { 126 | "name": "keyword.operator.preprocessor.simfony", 127 | "match": "&&|\\|\\||==|!=|<=|>=|<|>|!|&&|\\|\\||\\+|\\-|\\*|\\/|%|<<|>>|&|\\||\\^|~" 128 | } 129 | ] 130 | }, 131 | "comments": { 132 | "patterns": [ 133 | { 134 | "name": "comment.line.double-slash.simfony", 135 | "match": "//.*$" 136 | }, 137 | { 138 | "name": "comment.block.simfony", 139 | "begin": "/\\*", 140 | "end": "\\*/" 141 | } 142 | ] 143 | }, 144 | "keywords": { 145 | "patterns": [ 146 | { 147 | "name": "storage.type.function.simfony", 148 | "match": "\\bfn\\b" 149 | }, 150 | { 151 | "name": "storage.type.simfony", 152 | "match": "\\btype\\b" 153 | }, 154 | { 155 | "name": "keyword.other.simfony", 156 | "match": "\\b(mod|const)\\b" 157 | }, 158 | { 159 | "name": "storage.type.simfony", 160 | "match": "\\blet\\b" 161 | }, 162 | { 163 | "name": "keyword.control.simfony", 164 | "match": "\\b(match|if|else|while|for|return)\\b" 165 | }, 166 | { 167 | "name": "keyword.operator.simfony", 168 | "match": "(->|=>|=|:|,|;)" 169 | } 170 | ] 171 | }, 172 | "macros": { 173 | "patterns": [ 174 | { 175 | "match": "\\b(assert|panic)(!)(\\s*\\(|\\s|$)", 176 | "captures": { 177 | "1": { 178 | "name": "keyword.other.preprocessor.directive.simfony" 179 | }, 180 | "2": { 181 | "name": "keyword.other.preprocessor.directive.simfony" 182 | } 183 | } 184 | }, 185 | { 186 | "match": "\\b([a-z][a-zA-Z0-9_]*)(!)(\\s*\\(|\\s|$)", 187 | "captures": { 188 | "1": { 189 | "name": "keyword.other.preprocessor.directive.simfony" 190 | }, 191 | "2": { 192 | "name": "keyword.other.preprocessor.directive.simfony" 193 | } 194 | } 195 | } 196 | ] 197 | }, 198 | "functions": { 199 | "patterns": [ 200 | { 201 | "name": "entity.name.function.simfony", 202 | "match": "\\b(unwrap_left|unwrap_right|for_while|is_none|unwrap|into|fold|dbg)\\b" 203 | }, 204 | { 205 | "match": "\\b(fn)\\s+([a-zA-Z][a-zA-Z0-9_]*)\\s*\\(", 206 | "captures": { 207 | "1": { 208 | "name": "storage.type.function.simfony" 209 | }, 210 | "2": { 211 | "name": "entity.name.function.simfony" 212 | } 213 | } 214 | }, 215 | { 216 | "match": "\\b([a-zA-Z][a-zA-Z0-9_]*)(?!!\\s*)\\s*\\(", 217 | "captures": { 218 | "1": { 219 | "name": "entity.name.function.call.simfony" 220 | } 221 | } 222 | } 223 | ] 224 | }, 225 | "types": { 226 | "patterns": [ 227 | { 228 | "name": "entity.name.type.simfony", 229 | "match": "\\b(Either|Option|bool|List|u128|u256|u16|u32|u64|u1|u2|u4|u8)\\b" 230 | }, 231 | { 232 | "name": "entity.name.type.simfony", 233 | "match": "\\b(Ctx8|Pubkey|Message64|Message|Signature|Scalar|Fe|Gej|Ge|Point|Height|Time|Distance|Duration|Lock|Outpoint|Confidential1|ExplicitAsset|Asset1|ExplicitAmount|Amount1|ExplicitNonce|Nonce|TokenAmount1)\\b" 234 | }, 235 | { 236 | "match": "\\b(type)\\s+([A-Z][a-zA-Z0-9_]*)\\s*=", 237 | "captures": { 238 | "1": { 239 | "name": "storage.type.simfony" 240 | }, 241 | "2": { 242 | "name": "entity.name.type.alias.simfony" 243 | } 244 | } 245 | }, 246 | { 247 | "match": "\\b([A-Z][a-zA-Z0-9_]*)\\b", 248 | "captures": { 249 | "1": { 250 | "name": "entity.name.type.simfony" 251 | } 252 | } 253 | }, 254 | { 255 | "match": ":\\s*([a-zA-Z][a-zA-Z0-9_]*|Either<.*>|Option<.*>|\\(.*\\)|\\[.*\\]|List<.*>)", 256 | "captures": { 257 | "1": { 258 | "name": "entity.name.type.simfony" 259 | } 260 | } 261 | } 262 | ] 263 | }, 264 | "literals": { 265 | "patterns": [ 266 | { 267 | "name": "constant.numeric.decimal.simfony", 268 | "match": "\\b[0-9][0-9_]*\\b" 269 | }, 270 | { 271 | "name": "constant.numeric.binary.simfony", 272 | "match": "\\b0b[01_]+\\b" 273 | }, 274 | { 275 | "name": "constant.numeric.hex.simfony", 276 | "match": "\\b0x[0-9a-fA-F_]+\\b" 277 | }, 278 | { 279 | "name": "constant.language.boolean.simfony", 280 | "match": "\\b(true|false)\\b" 281 | }, 282 | { 283 | "name": "constant.language.simfony", 284 | "match": "\\b(None)\\b" 285 | }, 286 | { 287 | "name": "string.quoted.double.simfony", 288 | "begin": "\"", 289 | "end": "\"", 290 | "patterns": [ 291 | { 292 | "name": "constant.character.escape.simfony", 293 | "match": "\\\\." 294 | } 295 | ] 296 | } 297 | ] 298 | }, 299 | "expressions": { 300 | "patterns": [ 301 | { 302 | "match": "\\b(jet|witness|param)::(\\w+)", 303 | "captures": { 304 | "1": { 305 | "name": "entity.name.namespace.simfony" 306 | }, 307 | "2": { 308 | "name": "entity.name.function.simfony" 309 | } 310 | } 311 | }, 312 | { 313 | "name": "variable.other.simfony", 314 | "match": "\\b[a-z][a-zA-Z0-9_]*\\b" 315 | }, 316 | { 317 | "match": "\\b(Left|Right|Some)\\s*\\(", 318 | "captures": { 319 | "1": { 320 | "name": "support.function.simfony" 321 | } 322 | } 323 | } 324 | ] 325 | }, 326 | "modules": { 327 | "patterns": [ 328 | { 329 | "match": "\\b(mod)\\s+(witness|param)\\b", 330 | "captures": { 331 | "1": { 332 | "name": "keyword.other.simfony" 333 | }, 334 | "2": { 335 | "name": "entity.name.namespace.simfony" 336 | } 337 | } 338 | }, 339 | { 340 | "match": "\\b(jet)\\b", 341 | "name": "entity.name.namespace.simfony" 342 | } 343 | ] 344 | } 345 | }, 346 | "scopeName": "source.simfony" 347 | } --------------------------------------------------------------------------------