# Simplicity > A typed, combinator-based, smart contract language for Bitcoin-like blockchains. ## Get Started ### Quickstart # SimplicityHL Quickstart This is a quickstart document to help you perform your first [transaction](../glossary.md#transaction) on [Liquid](../glossary.md#liquid) testnet using a [Simplicity](../glossary.md#simplicity) [contract](../glossary.md#contract) with a Rust environment. [Make sure you have Rust installed.](https://rust-lang.org/tools/install/) ??? note "Want to try it online with no download?" You can also try an equivalent quickstart (and other Simplicity exercises and demos) online in your browser using the [Simplicity Codespace](https://github.com/Blockstream/simplicity-codespace). ## Demo walkthrough ### 1. Clone the walkthrough git repository Clone the repository: ```bash git clone https://github.com/BlockstreamResearch/simplicity-demo cd simplicity-demo ``` This is a Rust project surrounding a SimplicityHL "Pay-to-Public-Key" (P2PK) smart contract. This smart contract lets anyone who has the matching private key claim the funds held by the contract. Its code (from `crates/simplicity-hl-core/src/source_simf_p2pk.simf`) looks like this: ```rust fn main() { // Authorized public key (fixed at compile time) let pubkey: Pubkey = param::PUBLIC_KEY; // Signature value (provided at spend time) let signature: Signature = witness::SIGNATURE; // Sighash (summary of complete proposed transaction) let sighash: u256 = jet::sig_all_hash(); // Verify supplied signature over proposed transaction details jet::bip_0340_verify((pubkey, sighash), signature); } ``` This contract has a spot for a public key ("`param::PUBLIC_KEY`") of the person authorized to spend the contract's funds. ??? "Using your own wallet instead" If you prefer, you can generate a Liquid testnet wallet of your own and send the tLBTC from the contract to your own wallet instead. You can do this by installing `elementsd` and `elements-cli` and then generating a local wallet with `elements-cli`. Alternatively, you can install a wallet application with Liquid Network support like the [Blockstream App](https://blockstream.com/app/). In the latter case, you'll need to create a Liquid testnet wallet and account. You must provide an [unconfidential](../glossary.md#unconfidential) [address](../glossary.md#address) as the destination address here, not a [confidential](../glossary.md#confidential) address. The command `hal-simplicity address inspect` can derive the unconfidential equivalent of a confidential address if required. ### 2. Create a random seed for a public and private keypair Generate a seed value for generating keys. ```bash openssl rand -hex 32 ``` ??? "Alternative software options" If you don't have `openssl`, you can use one of these methods to generate a random seed value. On Unix-like systems: ```bash head -c 32 /dev/urandom | xxd -p -c 32 ``` On Windows (PowerShell): ```ps1 $bytes = New-Object Byte[] 32 (New-Object System.Security.Cryptography.RNGCryptoServiceProvider).GetBytes($bytes) [System.BitConverter]::ToString($bytes).Replace("-", "").ToLower() ``` Create an `.env.demo` file at the top level of the `simplicity-demo` project. Add a single line with `SEED_HEX=` followed by your random seed value. ### 3. Compile the P2PK contract using the public key ```bash cargo run p2pk compile-to-testnet-address ``` This code: * Derives a private and public keypair from the random seed * Substitutes the public key into the `p2pk.simf` program * Compiles the `p2pk.simf` program * Derives a blockchain address from the compiled Simplicity program The output will look something like this: ``` # Deriving keypair from seed. # Deriving Liquid testnet address. # Compiling SimplicityHL program source_simf/p2pk.simf. SimplicityHL source code: fn main() { // Authorized public key (fixed at compile time) let pubkey: Pubkey = param::PUBLIC_KEY; // Signature value (provided at spend time) let signature: Signature = witness::SIGNATURE; // Sighash (summary of complete proposed transaction) let sighash: u256 = jet::sig_all_hash(); // Verify supplied signature over proposed transaction details jet::bip_0340_verify((pubkey, sighash), signature); } Parameter arguments (compile-time): mod param { const PUBLIC_KEY: u256 = 0x7c37...; } Contract's Liquid testnet address: tex1... ``` The address derived at the bottom, beginning with `tex1...`, can be used to transfer coins to the contract. ```mermaid flowchart LR A[Liquid testnet faucet] -- Funding transaction --> B[P2PK smart contract]; B -- Spending transaction --> C[Wallet]; ``` ### 4. Fund the contract on Liquid testnet Use the Liquid testnet faucet to send some tLBTC (representing Bitcoin on [Liquid](../glossary.md#liquid) testnet) to this contract. Provide the address from the previous step. ```bash cargo run p2pk fund-from-faucet --address tex1... ``` This funds the contract with 100000 sats of tLBTC. Now that the contract controls these coins, its logic decides if and when this value may be spent. You'll see a transaction ID in the output reflecting the transaction that sent the coins from the faucet to the contract. This will be used in the next step in claiming the coins from the contract. ### 5. Create a transaction that spends the tLBTC Now run this command to generate a transaction that spends the assets you sent to your contract (less a network fee of 100 sats). Replace `` with the transaction ID value from the prior step. The address `tex1q9hgs7pj8etd92rw5qz3dymvujffxzylmj6a28h` is a sample wallet address created to receive tLBTC funds from this process. ```bash cargo run p2pk spend-from-p2pk-contract --utxo :0 --to-address tex1q9hgs7pj8etd92rw5qz3dymvujffxzylmj6a28h --send-sats 99900 --fee-sats 100 ``` You'll see output describing steps in the creation of the spending transaction. This transaction proves to the contract that you're entitled to spend the funds it controls. ??? "What's happening here?" This command creates a new Liquid testnet transaction whose [input](../glossary.md#input) comes from the prior contract-funding transaction and whose [output](../glossary.md#output), less a fee, goes to the specified destination address. The Rust program handles various steps in this process. * It derives the private key again (from the seed you created earlier). * It compiles the SimplicityHL program again to obtain all parameters associated with the compiled program. * It creates a [transaction](../glossary.md#transaction) proposing to transfer assets from the contract. * It signs the transaction with the private key, creating a digital signature. * It creates a [witness](../glossary.md#witness) including this digital signature. * It combines all of these elements into a single finalized transaction ready for submission to the blockchain. You'll see each of these steps as it happens, with output something like this: ``` # Deriving keypair from seed. # Creating proposed transaction from UTXO to specified destination. Asset: (tLBTC) # Signing transaction with private key. # Compiling SimplicityHL program source_simf/p2pk.simf. SimplicityHL source code: fn main() { // Authorized public key (fixed at compile time) let pubkey: Pubkey = param::PUBLIC_KEY; // Signature value (provided at spend time) let signature: Signature = witness::SIGNATURE; // Sighash (summary of complete proposed transaction) let sighash: u256 = jet::sig_all_hash(); // Verify supplied signature over proposed transaction details jet::bip_0340_verify((pubkey, sighash), signature); } Parameter arguments (compile-time): mod param { const PUBLIC_KEY: u256 = 0x7c37e620ca2a8e8ba67c7e18f9d9cc6ad53221b1dfbcbc75ba3900c7cea7d75b; } Witness values (spend-time): mod witness { const SIGNATURE: [u8; 64] = 0x6755724721a2ade83c21f1e89c67be1585cc397b0702719bcc58d2a8c5f7ca77ca32321cb22dd73637cff759b248b0c9f816a895478cbd4e77ba79ca86531929; } Transaction: 020000000.... ``` ### 6. Submit the transaction to the Liquid testnet Now run the prior command again with `--broadcast` to submit the transaction to the mempool for inclusion on the blockchain. ```bash cargo run p2pk spend-from-p2pk-contract --utxo :0 --to-address tex1q9hgs7pj8etd92rw5qz3dymvujffxzylmj6a28h --send-sats 99900 --fee-sats 100 --broadcast ``` (Again, `` here should be replaced with the transaction ID from step 4.) You can view your successful transaction [on the Explorer](https://blockstream.info/liquidtestnet/). ### Congratulations You've just compiled a smart contract, sent assets to it on a public blockchain, and then satisfied the contract, allowing you to spend those assets. ??? "See more technical details" The `cargo run` commands above support a `-v` option for verbose output, which includes more technical details about the cryptographic parameters that were calculated by the Rust code. For example, this will display the [CMR](../glossary.md#cmr) and compiled program. #### Next steps ??? note "Using VSCode?" If you're expecting to develop SimplicityHL contracts with Visual Studio Code, you can also install Blockstream's VSCode extension to provide syntax highlighting and other developer features. * Open Extensions View: Click the Extensions icon in the left sidebar. * Search: In the search field, type `SimplicityHL`. * Install: Click the Install button for the extension provided by Blockstream. * See the welcome pages for [Bitcoin](../welcome-bitcoin), [Solidity / EVM](../welcome-evm), and [finance](../welcome-finance) audiences. * Try more tools and sample contracts interactively in the [Simplicity Codespace](https://github.com/Blockstream/simplicity-codespace). * Check out [more complex example contracts](https://github.com/BlockstreamResearch/simplicity-contracts) with similar demos. * See [simple contract source code](https://github.com/BlockstreamResearch/SimplicityHL/tree/master/examples) that demonstrates SimplicityHL language syntax and features. * Read [SimplicityHL language documentation](https://docs.simplicity-lang.org/documentation/execution-model/) to learn more about how to write smart contracts. ### Intro for Bitcoin Developers # Welcome, Bitcoin Developers If you know [Bitcoin Script](../glossary.md#bitcoin-script), you know its constraints: a small, deliberately limited opcode set, no loops, and no built-in way to enforce rules across more than one transaction. [Simplicity](../glossary.md#simplicity) targets the same UTXO model and the same Bitcoin-style transaction semantics, but is built on a small set of functional [combinators](../glossary.md#combinator) instead of an opcode set. It adds [introspection](../glossary.md#introspection), so a program can constrain a transaction's outputs and not just check signatures, which is what makes [covenants](../glossary.md#covenant) and multi-transaction [vault](../glossary.md#vault) protocols possible without the workarounds Script requires today. Every program's execution cost is still statically bounded and known before you fund it, the same guarantee you already rely on in Script. Simplicity provides tools to represent and enforce complex financial agreements and instruments as on-chain [smart contracts](../glossary.md#smart-contract), in line with Bitcoin architecture and philosophy. The [SimplicityHL](../glossary.md#simplicityhl) language expresses this logic in a familiar Rust-like syntax. ## Where to go from here * **Quickstart:** Make a first Simplicity transaction ("pay-to-public-key" implemented in SimplicityHL) with the [quickstart tutorial](../quickstart/). * **Execution model:** [The UTXO model](../../documentation/execution-model) that Simplicity programs execute within, including [introspection](../glossary.md#introspection). * **Covenants, state, and oracles:** [Covenants and state management](../../documentation/state) and [oracles](../../documentation/oracle) cover how to build vaults and advanced constraints. [Jets](../../documentation/jets) expose transaction details and perform calculations efficiently. * **Use cases:** [Simplicity use cases and demos](../../use-cases/). * **Example code:** [Basic contract examples](https://github.com/BlockstreamResearch/SimplicityHL/tree/master/examples) and [more complex example contracts](https://github.com/BlockstreamResearch/simplicity-contracts) demonstrate SimplicityHL syntax and features. The [SimplicityHL language documentation](https://docs.simplicity-lang.org/documentation/) covers the full reference. * **Community:** Join the [Simplicity forum](https://community.simplicity-lang.org/), [Telegram group](https://t.me/simplicity_community), or the [weekly office hours calls](../../office-hours). ### Intro for EVM Developers # Welcome, Ethereum & Solidity Developers Simplicity is a [smart contract](../glossary.md#smart-contract) language built for [UTXO-based](../glossary.md#utxo) blockchains like the [Liquid Network](../glossary.md#liquid), which puts it closer to Bitcoin Script than to the EVM's account model. Instead of a contract address holding shared, global state, you build [covenants](../glossary.md#covenant): rules that govern how a specific output can be spent and how state is passed from one UTXO to the next. Primitives like AMMs and limit order books are buildable this way, just structured differently than their EVM equivalents. The architectural differences from Solidity that matter most are the absence of global state and of unbounded loops. Because there is no shared state, reentrancy does not apply. Every program's execution cost is statically bounded and known before a transaction is ever broadcast. The language's formal semantics also make it suitable for machine-checked proofs of contract behavior. ## Where to go from here * **Quickstart:** Make a first Simplicity transaction with the [quickstart tutorial](../quickstart/). * **Simplicity for EVM developers:** [Introduction to Simplicity for EVM Developers](../documentation/simplicity-for-evm-developers) maps EVM concepts to Simplicity, with FAQs and a video on the architectural differences. * **Use cases:** [Simplicity use cases and demos](../../use-cases/), including complex financial applications built natively on-chain. * **Execution model:** [The UTXO execution model](../../documentation/execution-model) that structures Simplicity contracts, including how spending conditions are enforced without global state or account balances. * **Covenants and state:** [Covenants and state management](../../documentation/state), the UTXO equivalent of updating contract storage. * **Oracles:** How [oracles](../../documentation/oracle) pass off-chain data into Simplicity contracts. * **Example code:** [Basic contract examples](https://github.com/BlockstreamResearch/SimplicityHL/tree/master/examples) and [more complex example contracts](https://github.com/BlockstreamResearch/simplicity-contracts) demonstrate SimplicityHL syntax and features. The [SimplicityHL language documentation](https://docs.simplicity-lang.org/documentation/) covers the full reference. * **Community:** Join the [Simplicity forum](https://community.simplicity-lang.org/), [Telegram group](https://t.me/simplicity_community), or the [weekly office hours calls](../../office-hours). ### Intro for Finance Professionals # Welcome, Fintech Professionals & Architects [Simplicity](../glossary.md#simplicity) is a [smart contract](../glossary.md#smart-contract) language for the [Liquid Network](../glossary.md#liquid) built around [covenants](../glossary.md#covenant): rules that govern exactly how, when, and by whom digital assets move, enforced by the blockchain itself rather than by a counterparty. People are already building programmable [vaults](../glossary.md#vault) with multi-party approval and time-locked withdrawals, atomic swaps, options, and collateralized loans this way: see [use cases](../../use-cases/) for real examples. This is provided natively on Bitcoin's architecture. It enables sophisticated financial products on top of Bitcoin-like chains. Two properties matter most for this audience: every program's execution cost is statically bounded and known before a transaction is broadcast (no surprise fees), and the language's formal semantics support machine-checked proofs of contract behavior. Simplicity supports vaults with multi-party approval workflows, time-locked withdrawals, and complex recovery paths codified directly into the asset; atomic swaps that act as programmable limit orders, with partial fills and dynamic pricing and no settlement risk; and complex derivatives, such as covered call options or collateralized loans, settled directly on-chain without relying on trusted intermediaries or centralized clearinghouses. ## More integration features and options The Simplicity Unchained project will bring Simplicity interpretation to Bitcoin mainnet via oracles and cosignatures, for cases where a native Simplicity integration isn't available. [Oracles can integrate existing business logic](../../documentation/oracle/#oracles-for-business-logic-integration) and databases into blockchain applications built on Simplicity. Simplicity also plugs into existing Blockstream services like [Blockstream Enterprise](https://blockstream.com/enterprise/) and [AMP](https://blockstream.com/amp/). ## Where to go from here * **Use cases:** [Simplicity use cases and demos](../../use-cases/), showing how complex financial applications are built natively on-chain. * **Execution model:** [Simplicity and the UTXO model](../../documentation/execution-model) and how it enables decentralized, non-custodial transaction settlement. * **Oracles:** How [oracles](../../documentation/oracle) securely feed external financial data into covenants. * **Quickstart:** Deploy a first smart contract transaction with the [quickstart tutorial](../quickstart). * **Example code:** [Basic contract examples](https://github.com/BlockstreamResearch/SimplicityHL/tree/master/examples) and [more complex example contracts](https://github.com/BlockstreamResearch/simplicity-contracts), including state management and financial applications, demonstrate SimplicityHL syntax and features. The [SimplicityHL language documentation](https://docs.simplicity-lang.org/documentation/) covers the full reference. * **Community:** Join the [Simplicity forum](https://community.simplicity-lang.org/), [Telegram group](https://t.me/simplicity_community), or the [weekly office hours calls](../../office-hours). ### Weekly Office Hours call # Office Hours The weekly Office Hours call is a chance to talk about what you're working on with Simplicity or to ask questions. This call is hosted by Blockstream staff involved in developing and documenting Simplicity and SimplicityHL. Use this call to * ask all of your questions at any technical level * pass along feature requests and feedback This call will be held on Tuesdays at 8:00 [PDT (UTC-7)](https://www.timeanddate.com/time/zone/usa/san-francisco) and will typically last one hour. It is occasionally cancelled due to U.S. holidays or other events. These Office Hours calls are routinely recorded and published (see below). **Join the Office Hours using Google Meet: [https://meet.google.com/mhx-danz-bwp](https://meet.google.com/mhx-danz-bwp) (or use the link in the calendar below).** You can see all public Simplicity-related events in the calendar: ## Prior Office Hours session recordings * [November 25, 2025](https://youtu.be/Yiiv2UICOPM) * [December 2, 2025](https://youtu.be/c6Uis6VwANU) * [December 9, 2025](https://youtu.be/oEJ1syVf7lg) * [December 16, 2025](https://youtu.be/HpvkMzm8GDc) * [December 23, 2025](https://youtu.be/ry2wQelP8Kc) ([state management](../documentation/state) demo) * [January 6, 2026](https://youtu.be/hCgBdQNPc9c) * [January 13, 2026](https://youtu.be/wknEBcV3HeE) * [January 20, 2026](https://youtu.be/OSa0zMaqGnM) * [January 27, 2026](https://youtu.be/4c8bvD6oomw) ([Simplicity DEX / options contract](https://github.com/Blockstream/simplicity-dex/) demo) * [February 3, 2026](https://youtu.be/lSZeMYx0bnQ) * [February 10, 2026](https://youtu.be/HqDn6-cGcO8) * [February 17, 2026](https://youtu.be/X0TJAnBFSsc) * [February 24, 2026](https://youtu.be/kJnRKI2NuN0) * [March 3, 2026](https://youtu.be/2Rgn4-JIgqU) * [March 10, 2026](https://youtu.be/fnOuWNYFBQg) * [March 17, 2026](https://youtu.be/BL7WDlutjts) ([infix operators](https://github.com/BlockstreamResearch/SimplicityHL/pull/232) demo) * [March 24, 2026](https://youtu.be/1j7uj4kLsvw) (SimplicityHL modules/includes demo; [prediction market contract](https://github.com/Resolvr-io/deadcat) demo) * [March 31, 2026](https://youtu.be/BixsjpS4x6s) ([AMM contract](https://github.com/0ceanSlim/anchor) demo) * [April 7, 2026](https://youtu.be/-pW-hI9Hy8k) ([Simplex](https://github.com/BlockstreamResearch/smplx) demo) * [April 14, 2026](https://youtu.be/Vq7GGTfIr1s) ([Lending contract](https://github.com/BlockstreamResearch/simplicity-lending) demo) * [April 28, 2026](https://youtu.be/pX-trCHS9dk) ([Simplicity codespace](https://github.com/Blockstream/simplicity-codespace) demo) * [May 5, 2026](https://youtu.be/kL8mtxCZOvw) * [May 12, 2026](https://youtu.be/9NlkMdIHeTQ) * [May 26, 2026](https://youtu.be/cvC5l482so4) (Mosaik hackathon project demo) * [June 2, 2026](https://youtu.be/Fk---lRV4us) * [June 9, 2026](https://youtu.be/tdNlrBcwCaQ) * [June 16, 2026](https://youtu.be/i7ls_AQboG0) * [June 23, 2026](https://youtu.be/TswqFCD5z00) (txmanifest demo, part 1) * [June 30, 2026](https://youtu.be/TqZJlpFGbZ0) (txmanifest demo, part 2) * [July 7, 2026](https://youtu.be/A3c3-nGSGzM) * [July 14, 2026](https://youtu.be/N3jxQdHpBkg) (Resolvr [Apogee wallet](https://apogee.resolvr.io/) demo) * [July 21, 2026](https://youtu.be/BKz6gQjMfrw) * [July 28, 2026](https://youtu.be/AhH_t1fPbxY) (wallet integration workshop) * [August 4, 2026](https://youtu.be/FVko8j3HA0I) * [August 11, 2026](https://youtu.be/QijqdTNAnZQ) (enum types demo; Apogee lending contract integration demo) * [August 18, 2026](https://youtu.be/KbIEdYQv6n0) (complete Apogee lending contract integration demo) * [August 25, 2026](https://youtu.be/gtvtogI1V6Y) * [September 1, 2026](https://youtu.be/niE6zMMCXOc) * [September 8, 2026](https://youtu.be/aogCnw8Qx0s) ## Learn ### Execution Model # On-chain Simplicity execution model Simplicity is a special-purpose language. It works in the context of the Bitcoin transaction model on Bitcoin-like blockchains. This is sometimes also called the "[UTXO](../glossary.md#utxo) model". If you're already familiar with Bitcoin Script and the role it plays in the logic of Bitcoin transactions, you can think of Simplicity as a more expressive and more analyzable alternative to Bitcoin Script, useful for writing more complex conditions (such as recursive [covenants](../glossary.md#covenant) that can propagate conditions across multiple subsequent transactions). This document will describe the context in which Simplicity programs run, and what they can and can't do as a result. ## The Simplicity and SimplicityHL environment Developers generally write SimplicityHL, a higher-level language with a Rust-like syntax, which compiles to Simplicity. The SimplicityHL compiler translates SimplicityHL into Simplicity to run on Simplicity's abstract Bit Machine. Simplicity programs are attached to UTXOs via [Taproot](../glossary.md#taproot) and define their spending conditions. However, the Simplicity program is only disclosed when redeeming (claiming) [assets](../glossary.md#asset). As part of the blockchain consensus process, all blockchain [nodes](../glossary.md#node) can confirm that it matches the commitment attached to the UTXO, and can run the program to confirm that it approves the proposed transaction. ## What Simplicity programs are used for The basic task of every Simplicity program is to *consider a proposed blockchain transaction* and determine whether to *approve or disapprove* that transaction. More complex financial logic can be built out of one or more Simplicity programs working together to manage assets and their disposition across multiple related transactions. Together, the logic and rules governing a set of related blockchain transactions can be called a [smart contract](../glossary.md#smart-contract). Designing a smart contract with Simplicity thus includes representing its logic as a series of on-chain transactions, and describing the rules that govern exactly when each transaction is permitted to occur. ## Where and how Simplicity programs run Like Bitcoin Script scripts, Simplicity programs are *attached to [UTXO](../glossary.md#utxo)s* and define spending conditions for the UTXOs to which they are attached. A Simplicity program can, however, have more complex logic and functionality compared to a Bitcoin Script script. The Simplicity program does not initiate or originate the transaction and does not decide anything about what the transaction should be (for instance, it does not calculate or choose destination addresses, although it can *constrain* them by rejecting transactions that specify inappropriate destinations). In Bitcoin and related systems, anyone can propose any transaction at any time; the spending conditions associated with assets, such as those contained in the logic of a Simplicity program, form part of the rules that determine whether or not proposed transactions are valid and hence whether those transactions could eventually be recorded in a block and become part of the blockchain. Whenever a [node](../glossary.md#node) examines a transaction involving UTXO controlled by a Simplicity program, the node will run that program to confirm that the program agrees to allow the transaction. The information available to the program to use in making that decision consists of * whatever details are hard-coded within the program (for example, trusted public key values), * details of all the [input](../glossary.md#input)s and [output](../glossary.md#output)s of the proposed transaction, and * [witness](../glossary.md#witness) data supplied by the creator of the transaction as input to the program. Note that the program can directly check its own cryptographic identity by examining the input UTXO to which it was attached. The [witness](../glossary.md#witness) supplied as part of the transaction by its creator represents inputs meant to provide additional context for the transaction. The form of the expected witness is determined in advance by the contract; a witness might include information such as * choices among different options or contract features (for example, which of several possible actions the transaction is requesting to take) * values of specific parameters (for example, an amount) * asserted state from parties' prior interactions with the contract (see [State Management in SimplicityHL](./state) for more details) * digital signatures from parties approving the contract or confirming other relevant statements (for example, a party's signatures approving the exercise of some ability under the contract, or an oracle's signature asserting the truth of some off-chain fact such as a market price or whether a specific event has occurred) The [Witnesses in SimplicityHL development](./witness) document explains the concept in more detail; the [`.wit` file reference](./witness-format) talks about the practical mechanics of creating one. Because Simplicity is formally specified and fully deterministic, every node that examines that transaction will come to exactly the same conclusion about what the result of running the Simplicity program was, without ambiguity. ## How Simplicity programs are triggered or invoked Simplicity programs are inherently reactive. Because they cannot initiate transactions or other actions independently, they rely entirely on external client software to "drive" the contract state forward. To interact with a Simplicity contract, end-user software (such as wallets or specialized apps) must be built specifically around that individual contract. The client software acts as the active engine for the contract's passive logic by: * Generating transactions: Constructing and submitting on-chain transactions required to trigger specific contract functions. * State tracking: Monitoring on-chain data to determine which actions are valid, and, if necessary, reminding the contract of the relevant state. * User interface: Providing an interface to explain the current state to the user and allows the user to make choices (like a "refund" versus "spend" action) by translating those choices into transactions. In this model, the client software proposes actions, while the Simplicity program judges and confirms whether those actions are actually permissible under the contract rules. Therefore, the client software must know enough about these rules and the contract state to propose transactions that will be accepted. For development purposes, you can also run a Simplicity program locally with `hal-simplicity simplicity pset run` after building a [PSET](../glossary.md#pset) representing the overall transaction within which the program will run. This simulates the Simplicity logic that a node would follow, although nodes can also reject transactions for various other reasons, such as if the input UTXO has already been spent, if sufficient fees are not paid with the transaction, or if spending conditions applicable to some other referenced UTXO are not satisfied. Below, this document presents several examples of applications of SimplicityHL and describe how the contracts they implement must be "driven" by some kind of end-user software generating and submitting appropriate transactions. ## Distinctive features of Simplicity and its environment Simplicity is a deterministic functional programming environment. Simplicity programs [don't have access to any form of I/O or network access](https://delvingbitcoin.org/t/delving-simplicity-part-two-side-effects/2091). They can't display a user interface, read or write files, or call network APIs. In fact, they don't even have direct access to the data of the blockchains with which they are integrated. However, Simplicity does provide programs with the ability to *introspect* the currently-proposed transaction in order to find out details related to input and output asset types, amounts, and addresses. For example, a program can use [introspection](../glossary.md#introspection) to require that an asset is sent back to a copy of that same program; it does so by approving only transactions where an output with exactly the same program code receives the asset that was spent in the program's input. Whenever a user proposes a transaction that would spend (consume as input) an existing UTXO to which a Simplicity program is attached, the proposed transaction makes a claim that the Simplicity program authorizes that UTXO to be spent in the indicated context. Nodes then check this claim by running the program. Most, though not all, programs will check cryptographic information derived from the attached witness data, such as whether one or more digital signatures included there are valid. A Simplicity program can include several alternative paths reflecting different scenarios or outcomes, and different criteria for approving each one. A simple example is a timeout branch, where assets controlled by the program can be refunded to their original senders, but only after a certain amount of time has elapsed. This can serve as an alternative to the originally intended outcome in which a certain transaction is completed by transferring assets elsewhere. This prevents assets from being stuck inside the contract if some party fails to perform its role. ## Why not perform more complex computations in Simplicity? Every Simplicity program is run (albeit in pruned form) by *every node* that validates a block containing a transaction spending assets controlled by the program. The computation to validate transactions is expensive; indeed, creators of transactions may be required to pay for it indirectly via fees. Simplicity programs perform deterministic computations based on publicly-disclosed information. It is useful to have nodes perform computation to validate compliance with financial logic and contractual rules (that determine who is entitled to specific assets). However, computation that isn't necessary for these purposes doesn't need to be done on-chain and replicated by all validators. For example, a loan might charge interest at a specified rate. In principle, a Simplicity program could compute how the loan balance will change over time based on different repayment schedules, but it doesn't *need* to make such hypothetical future projections in order to calculate the actual loan balance. It also wouldn't be able to output the results of these computations for anyone to see them. The same information can just as easily be computed by client-side software, and this is much more efficient. That computation can be done just once, on the device of the interested user. In general, anything that doesn't have to happen on-chain should be handled outside of a Simplicity program. That includes any logic or computations that are relevant to user interface for the contract but not critical to its underlying financial logic and disposition of assets. Many computationally-intensive tasks that must be performed on-chain, like cryptographic operations, can be outsourced to [jets](../glossary.md#jet), allowing the actual calculations to take place in native code. ## Examples The functionality of three kinds of contracts is examined below in order to illustrate how Simplicity programs can make decisions in order to determine whether to approve proposed transactions. These examples do not use introspection features, so they don't demonstrate Simplicity's ability to constrain outputs' destinations. Introspection would also provide an alternative way to implement the refund path in the `htlc` contract (constraining the refund payment to be sent to the address of the original sender of an asset, by asserting that an input address and output address match); this version instead hardcodes a key that can be used to authorize refunds, sent to any chosen address. ### p2ms This program, `p2ms.simf`, is taken from the SimplicityHL examples collection. The [quickstart](/getting-started/quickstart) guide provides a recipe for making a Liquid testnet transaction using this program. ```rust /* * PAY TO MULTISIG * * The coins move if 2 of 3 people agree to move them. These people provide * their signatures, of which exactly 2 are required. * * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#lockwithmultisig */ fn not(bit: bool) -> bool { ::into(jet::complement_1(::into(bit))) } fn checksig(pk: Pubkey, sig: Signature) { let msg: u256 = jet::sig_all_hash(); jet::bip_0340_verify((pk, msg), sig); } fn checksig_add(counter: u8, pk: Pubkey, maybe_sig: Option) -> u8 { match maybe_sig { Some(sig: Signature) => { checksig(pk, sig); let (carry, new_counter): (bool, u8) = jet::increment_8(counter); assert!(not(carry)); new_counter } None => counter, } } fn check2of3multisig(pks: [Pubkey; 3], maybe_sigs: [Option; 3]) { let [pk1, pk2, pk3]: [Pubkey; 3] = pks; let [sig1, sig2, sig3]: [Option; 3] = maybe_sigs; let counter1: u8 = checksig_add(0, pk1, sig1); let counter2: u8 = checksig_add(counter1, pk2, sig2); let counter3: u8 = checksig_add(counter2, pk3, sig3); let threshold: u8 = 2; assert!(jet::eq_8(counter3, threshold)); } fn main() { let pks: [Pubkey; 3] = [ 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798, // 1 * G 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5, // 2 * G 0xf9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9, // 3 * G ]; check2of3multisig(pks, witness::MAYBE_SIGS); } ``` This program includes three hard-coded public keys. Its logic says that any proposed transaction will be approved if, and only if, exactly two digital signatures on the proposed transaction are provided and those signatures were correctly made by the private keys corresponding to any two of those three keys. Since the signatures are made over the transaction data including the specific input(s) and output(s), it can be presumed that the holders of those keys agree with transferring specific assets controlled by the contract to those specific destinations. Once an asset has been sent to this contract (that is, a UTXO identifies it as a spending condition), anyone can propose a transaction that would spend that asset. The contract examines the proposed transaction and decides whether it does or does not contain sufficient evidence (based on the presence or absence of valid signatures provided in the witness). It then approves or rejects the transaction on that basis. ??? "Expand for diagram" ```mermaid flowchart TD A((Claiming transaction)) -->|Witness| B[p2ms contract] B --> C[Valid signature count is 0] C --> D{Sig 1 provided and valid?} D -->|Yes| E[Valid signature count increases by 1] D -->|No| F[Valid signature count unchanged] E --> G{Sig 2 provided and valid?} F --> G G -->|Yes| H[Valid signature count increases by 1] G -->|No| I[Valid signature count unchanged] H --> J{Sig 3 provided and valid?} I --> J J -->|Yes| K[Valid signature count increases by 1] J -->|No| L[Valid signature count unchanged] K --> M{Valid signature count equal to 2?} L --> M M -->|Yes| N((Approve transaction)) M -->|No| O((Reject transaction)) ``` ### htlc This program, `htlc.simf`, is also taken from the SimplicityHL examples collection. It implements a hash-timelock contract, a mechanism often used in cryptocurrency swaps. ```rust /* * HTLC (Hash Time-Locked Contract) * * The recipient can spend the coins by providing the secret preimage of a hash. * The sender can cancel the transfer after a fixed block height. * * HTLCs enable two-way payment channels and multi-hop payments, * such as on the Lightning network. * * https://docs.ivylang.org/bitcoin/language/ExampleContracts.html#htlc */ fn sha2(string: u256) -> u256 { let hasher: Ctx8 = jet::sha_256_ctx_8_init(); let hasher: Ctx8 = jet::sha_256_ctx_8_add_32(hasher, string); jet::sha_256_ctx_8_finalize(hasher) } fn checksig(pk: Pubkey, sig: Signature) { let msg: u256 = jet::sig_all_hash(); jet::bip_0340_verify((pk, msg), sig); } fn complete_spend(preimage: u256, recipient_sig: Signature) { let hash: u256 = sha2(preimage); let expected_hash: u256 = 0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925; // sha2([0x00; 32]) assert!(jet::eq_256(hash, expected_hash)); let recipient_pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; // 1 * G checksig(recipient_pk, recipient_sig); } fn cancel_spend(sender_sig: Signature) { let timeout: Height = 1000; jet::check_lock_height(timeout); let sender_pk: Pubkey = 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5; // 2 * G checksig(sender_pk, sender_sig) } fn main() { match witness::COMPLETE_OR_CANCEL { Left(preimage_sig: (u256, Signature)) => { let (preimage, recipient_sig): (u256, Signature) = preimage_sig; complete_spend(preimage, recipient_sig); }, Right(sender_sig: Signature) => cancel_spend(sender_sig), } } ``` This program incorporates logic supporting two different outcomes, which are two different kinds of transactions that can potentially be approved in different circumstances. One path is called "complete" and represents a transaction that claims to complete the intended asset transfer. If the contract is satisfied (by someone revealing an appropriate hash preimage as "password") that this can occur, it will approve a transaction that effectuates this transfer. The authorized recipient must also provide a digital signature approving this transaction. On the other hand, if the underlying asset being transferred is still controlled by the contract after a specified delay (here, of 1000 blocks since the receipt of the asset), the authenticated original sender can request a refund, and the contract will approve a transaction that effectuates the refund. In each of these cases, the appropriate party must actively make a claim by submitting a transaction and substantiating it with a [witness](../glossary.md#witness) that proves all required conditions are met. Until the recipient explicitly creates and submits this claiming transaction, the assets remain controlled by the contract. The contract does not store any kind of state to record whether one or the other paths has already previously been taken. The reason that one path excludes the other is simply that the underlying asset will already have been spent. In this case, the blockchain's transaction validity logic forbids double-spending the same [UTXO](../glossary.md#utxo). Another way of thinking of this is that, after the asset has been claimed from the contract by someone, the contract no longer controls the disposition of that asset, and therefore it is no longer interesting or relevant whether the contract would "agree" to some other transfer. In a certain sense, Simplicity contracts do not "know" what assets they control, but that information is readily available on the blockchain for inspection by software like wallet apps. ??? "Expand for diagram" ```mermaid flowchart TD A((Claiming transaction)) -->|Witness| B[htlc contract] B -->Q{Which action?} Q -->|Transfer| C{Hash preimage correct?} Q -->|Refund| D{Time 1000 blocks after input transaction?} C -->|Yes| E{Recipient signature valid?} C -->|No| F((Reject transaction)) D -->|Yes| G{Sender signature valid?} D -->|No| F((Reject transaction)) E -->|Yes| H((Approve transaction)) E -->|No| J((Reject transaction)) G -->|Yes| K((Approve transaction)) G -->|No| L((Reject transaction)) ``` ### Prediction market This example discusses a prediction market contract but does not provide an example of SimplicityHL code for this contract. A prediction market contract provides an example of how Simplicity contract updates are "driven" by some kind of end-user software such as a wallet or a contract-specific app, which must generate and submit appropriate transactions and witness data under appropriate circumstances. A typical prediction market issues pairs of tokens called YES and NO with respect to a specific question. The market has functionality that tends to ensure that the YES and NO prices remain consistent with one another. The implementing contract usually provides the following actions: * Issue pair: lock $1 with the contract; receive new YES and NO tokens * Redeem pair: burn existing YES and NO tokens; receive locked $1 * Claim YES: burn existing YES token, provide oracle statement asserting that question resolved YES; receive locked $1 * Claim NO: burn existing NO token, provide oracle statement asserting that question resolved NO; receive locked $1 Users can also directly trade YES and NO tokens with one another, allowing their prices to vary from the assumed "indifference" level of $0.50. At least the final three actions will likely need to be provided by different code paths of the same program, because they all need to be able to release (authorize spending of) some $1 of locked value, and assets controlled by the prediction market ought to be fungible. If the underlying question resolves as YES, the YES token will typically be worth one currency unit (such as $1), while the NO token will not be redeemable for any value. Conversely, if the underlying question resolves as NO, the NO token will be redeemable for $1 and the YES token will not be redeemable. When the question resolves (by the issuance of a signed [oracle](../glossary.md#oracle) statement indicating which side has won), each "winner" holding a token for the successful position on the question must individually proactively claim a reward by explicitly submitting a transaction that claims $1 from the contract in exchange for consuming a token. Therefore, all of the winners need to have, and use, software capable of formulating this claim transaction in order to receive any benefit from their successful bets in the market. In the absence of a specific claim transaction, the contract does not have any inherent notion of who the winners are or the fact that they have won or are entitled to anything. Some implementations may not even "remember" which side has won, and have to be reminded by resubmitting the oracle statement together with each successive claim. ??? "Expand for diagrams" ```mermaid sequenceDiagram participant wallet@{"alias": "User wallet"} participant node@{"alias": "Node"} participant impl@{"alias": "Node's Simplicity implementation"} participant mempool@{"alias": "Mempool"} wallet->>node: New tx spending assets from UTXO Y to address Z, witness W node->>impl: Run program P with UTXO Y to address Z, witness W impl->>node: Success node->>mempool: This tx is valid, can relay it or include it in a block node->>wallet: Your tx is valid ``` ```mermaid sequenceDiagram participant wallet as User wallet participant node as Node participant impl as Node's Simplicity implementation participant mempool as Mempool wallet->>node: New tx spending assets from UTXO Y to address Z, witness W node->>impl: Run program P with UTXO Y to address Z, witness W impl->>node: Failure node->>wallet: Your tx is invalid ``` ### Covenants & State Management # Covenants and state management This document describes concepts and features for enforcing complex financial logic in a Simplicity [*covenant*](../glossary.md#covenant) that manages assets across a series of multiple transactions. ## Covenants Some Simplicity contracts will be "finished" after a single transaction in which some party successfully claims the assets held by the contract. The [Hash Time-Locked Contract](https://github.com/BlockstreamResearch/SimplicityHL/blob/master/examples/htlc.simf) is an example where the contract is complete as soon as an authorized party claims its underlying value. However, a key feature of Simplicity is the ability to implement more complex financial logic by [introspection](../glossary.md#introspection). Introspection allows a [smart contract](../glossary.md#smart-contract) to enforce policies and relationships that last beyond a single transaction. This is usually done by means of [covenants](../glossary.md#covenant), smart contracts that enforce that assets are sent *back to a copy of the same contract*. This allows a contract to "hold onto" assets across a series of transactions, possibly moving some portion of those assets in or out of the contract, or updating the contract's state over time. ```mermaid flowchart LR loop1[ ]:::inv loop2[ ]:::inv Z((User A)) -->|Asset deposit| A[Covenant] Y((User B)) -->|Asset deposit| A A --- loop1 -->|Some update action| A A --- loop2 -->|Some other update action| A A -->|Authorized withdrawal| B((Beneficiary)) A -->|Authorized refund| Y classDef inv display:none,height:0,width:0; ``` In Simplicity, most complex and long-term financial relationships among multiple parties will be modeled as covenants. A simple example is provided in [`last_will.simf`](https://github.com/BlockstreamResearch/SimplicityHL/blob/master/examples/last_will.simf). ???+ "Click to hide source code" ```rust /* * LAST WILL * * The inheritor can spend the coins if the owner doesn't move the them for 180 * days. The owner has to repeat the covenant when he moves the coins with his * hot key. The owner can break out of the covenant with his cold key. */ fn checksig(pk: Pubkey, sig: Signature) { let msg: u256 = jet::sig_all_hash(); jet::bip_0340_verify((pk, msg), sig); } // Enforce the covenant to repeat in the first output. // // Elements has explicit fee outputs, so enforce a fee output in the second output. // Disallow further outputs. fn recursive_covenant() { assert!(jet::eq_32(jet::num_outputs(), 2)); let this_script_hash: u256 = jet::current_script_hash(); let output_script_hash: u256 = unwrap(jet::output_script_hash(0)); assert!(jet::eq_256(this_script_hash, output_script_hash)); assert!(unwrap(jet::output_is_fee(1))); } fn inherit_spend(inheritor_sig: Signature) { let days_180: Distance = 25920; jet::check_lock_distance(days_180); let inheritor_pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; // 1 * G checksig(inheritor_pk, inheritor_sig); } fn cold_spend(cold_sig: Signature) { let cold_pk: Pubkey = 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5; // 2 * G checksig(cold_pk, cold_sig); } fn refresh_spend(hot_sig: Signature) { let hot_pk: Pubkey = 0xf9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9; // 3 * G checksig(hot_pk, hot_sig); recursive_covenant(); } fn main() { match witness::INHERIT_OR_NOT { Left(inheritor_sig: Signature) => inherit_spend(inheritor_sig), Right(cold_or_hot: Either) => match cold_or_hot { Left(cold_sig: Signature) => cold_spend(cold_sig), Right(hot_sig: Signature) => refresh_spend(hot_sig), }, } } ``` This contract allows an heir to claim an inheritance (held by the contract) after the creator has died. It also enforces a mandatory time delay in the claim process so that the heir can only claim the inheritance when the creator hasn't "refreshed" the contract within the past 180 days. ```mermaid flowchart TD Z((Creator's wallet)) -->|Asset deposit| A[Last will covenant] A -->|Hot key signature
Refresh covenant| A A -->|Cold key signature
Creator withdrawal| Z A -->|Inheritor signature
Inherit assets
Only for assets held over 180 days| C((Inheritor's wallet)) ``` The creator is expected to periodically refresh the contract by sending the contract's assets back to the same contract (via the **hot key**). Importantly, the "hot key" is restricted to authorizing this specific form of transaction: it can only authorize sending assets back to the same contract. (This restriction on the hot key's power, the fact that it can't *remove* assets from the covenant's control, is the part of this example that uses covenant logic.) The creator's more secure **cold key** isn't needed routinely, but can be used to authorize arbitrary withdrawals if the contract creator no longer wishes to keep certain assets stored inside the contract. The inheritor's **inheritor key** can authorize arbitrary withdrawals from the contract, but only of [UTXO](../glossary.md#utxo)s that have been held by the contract for at least 180 days. So, whenever the creator refreshes the covenant with a hot key transaction, this period begins anew. The covenant logic in `last_will.simf` is enforced by ensuring that a specific [output](../glossary.md#output) has a script hash matching the script hash of the [input](../glossary.md#input) from which the Simplicity program is being run. This is determined using the relevant [jet](../glossary.md#jet)s. ## SimplicityHL state management The `last_will.simf` example above is **stateless**: it doesn't require the contract to actively remember anything. UTXOs' age is represented on the blockchain itself, and the contract automatically blocks the inheritor from transferring fresh UTXOs. The [execution environment of a Simplicity program](execution-model.md) is highly constrained; specifically, a Simplicity program can't perform any kind of input or output or network access, and can't even directly access the contents of earlier transactions on the blockchain. Still, complex contracts will often need to enforce multiple related transactions and "remember" facts and details over time. For example, they may need to record the existence or size of a debt, or record whether a certain action has already been taken. How can they do so in Simplicity's transaction-based architecture, without being able to save or load anything corresponding to files or database entries? The recommended mechanism uses **cryptographic commitments**. These incorporate the state reference into the on-chain address of a copy of the Simplicity program itself, which the program can confirm by [introspection](../glossary.md#introspection) when it is run. Effectively, this method generates an address for a program in a way that inherently incorporates a cryptographic reference to specific state; when that program is run, it can confirm that the state it was given via a [witness](../glossary.md#witness) matches the state that it expects to have according to its own address. It can immediately reject any proposed transactions that attempt to delete or tamper with the state. This approach provides a way for a contract to maintain state (the values of specific variables) between one transaction and another transaction, enforcing a guarantee that the later transaction has access to the correct values of those variables and that no one has modified them. The contract does so by enforcing that outputs, including address references to versions of the same contract, contain cryptographic references to that state ("save"). A version of the same or a related contract can then enforce in a later transaction that suggested state provided to that new transaction matches up correctly with those cryptographic references ("load"). ## Wallet-side state storage and on-chain verification This means that the actual state data is not directly stored "inside of" the contract; the contract possesses a reliable way to *verify* state that is provided to it, but that state information is typically physically stored inside of a user's wallet software, and passed back to the contract whenever a new transaction involving the contract is constructed. Web developers may recognize this pattern as akin to digitally signed tokens (such as [JWT](https://www.jwt.io/introduction#what-is-json-web-token)) provided by clients to web applications. In the web application setting, the physical storage of the state information can be offloaded to the client, and a digital signature proves that the client didn't modify its contents. The "client" (the wallet or other software that is constructing future transactions) similarly has the responsibility to store and provide the state information back to the contract, under a form of cryptographic authentication preventing modification, although the exact cryptographic details are different from the JWT analogy. In the Simplicity context, the cryptographic commitment to the state is actually used as part of the contract's on-chain address, so performing a state update will actually mean deriving an updated address for the same contract (or a specifically chosen successor contract), and committing a transaction that forwards assets from the contract's prior address to the updated address. Those forwarded assets' spending conditions are then controlled by the updated version of the contract, which is cryptographically bound to the updated version of the state information. Whenever a [covenant](../glossary.md#covenant) performs a state update, it sends assets to a new copy of itself with a *different on-chain address* encoding a reference to the updated state data. A transaction can update state without moving any assets in or out of the contract, merely sending them to the new copy with the new address. Client software meant to interact with the contract must be programmed to observe that this has happened so that new transactions are always performed with the most current updated copy. Although wallet software should generally store contract state in order to provide it back to the contract when performing subsequent transactions, this information could be recalculated if necessary by examining blockchain history, because all Simplicity program execution is deterministic and based on public information. Thus, state information isn't generally confidential; locally replaying the evolution of a contract will ordinarily reveal what the expected state for the next transaction should be. (An initial state commitment when a copy of a program receives assets for the first time could require witness values that have not yet been publicly revealed anywhere, much as the code of the program may not have been publicly revealed. Storing state in a Merkle tree also optionally permits the state to be partially revealed as it is actually needed. For example, state of one branch of the program could be stored in one branch of the Merkle tree and state of another branch could be stored in another branch. The program could be designed so that the witness only reveals the part of the state that is actually used.) ## Basic state management mechanism The modified address is calculated by storing a 256-bit state value in [Taproot](../glossary.md#taproot) alongside a commitment to the Simplicity program's code. Sample code to assert that input state is consistent with the program's address ("load"), and to assert that an output address is consistent with a commitment to a specific updated state value ("store") appears below. The `hal-simplicity simplicity pset update-input` command has also been updated with a `-s` flag that provides an input state value to the program; a copy should also be provided in the witness as `witness::STATE`. The Rust version of this logic is found in [`state_management/bytes32_tr_storage`](https://github.com/BlockstreamResearch/simplicity-contracts/tree/main/crates/contracts/src/state_management/bytes32_tr_storage), including Rust code to build witnesses and transactions. This shows how a wallet can actually track and provide state back to the contract in a subsequent transaction. Currently, this allows a program, if structured as a [covenant](../glossary.md#covenant), to pass itself state updates across subsequent transactions. The state information is always represented as a single uninterpreted `u256` integer value. This is conveniently the size of the output of a SHA256 hash, so a program can choose to interpret this value as a SHA256 hash of specified data items that are provided in a [witness](../glossary.md#witness), in a specific order. The program can then commit to specific values of these chosen data items between one transaction and the next. A more elegant approach would be interpreting this `u256` value as a reference to the root of a [Merkle tree](../glossary.md#merkle-tree). A discussion and demonstration of this approach took place in [the December 23, 2025 Simplicity Office Hours session](https://youtu.be/ry2wQelP8Kc). ## Example with integer counter This example contract, `third_time.simf`, uses the state management mechanism described in the prior section. It enforces the saying "the third time's the charm"; it requires a series of three distinct transactions in order to perform a withdrawal. ???+ "Click to hide source code" ```rust /* * "Third Time's The Charm" covenant demonstrating Simplicity state management * * This covenant requires three transactions in a row in order to release the * locked coins. It counts how many of these transactions have been seen so * far using the witness::STATE value. * * State management: * Computes the "State Commitment" — the expected Script PubKey (address) * for a specific state value. * * HOW IT WORKS: * In Simplicity/Liquid, state is not stored in a dedicated database. Instead, * it is verified via a "Commitment Scheme" inside the Taproot tree of the UTXO. * * This function reconstructs the Taproot structure to validate that the provided * witness data (state_data) was indeed cryptographically embedded into the * transaction output that is currently being spent. * * LOGIC FLOW: * 1. Takes state_data (passed via witness at runtime). * 2. Hashes it as a non-executable TapData leaf. * 3. Combines it with the current program's CMR (tapleaf_hash). * 4. Derives the tweaked_key (Internal Key + Merkle Root). * 5. Returns the final SHA256 script hash (SegWit v1). * * USAGE: * - For load(), we verify: CalculatedHash(witness::STATE) == input_script_hash. * - For store(), we verify: CalculatedHash(updated_state) == output_script_hash. * - This assertion proves that the UTXO is "locked" not just by the code, * but specifically by THIS instance of the state data. So the on-chain address * necessarily changes after each state-updating transaction in order to reflect * a cryptographic commitment to the appropriate state data. * * Surrounding context tracks the state_data, interpreting its low 64 bits as * a counter. The counter can be updated by an "update" transaction sending * value back to the same contract. When the counter is equal to 2 or more, * the "withdraw" transaction is permitted, sending some or all value * to an arbitrary output address instead of back to the contract. Both the * "update" and "withdraw" transactions must include a valid signature by * the authorized key (here hardcoded in check_sig() for demonstration * purposes). * * When initially funding the contract, use witness::STATE = 0. */ fn check_sig(sig: Signature) { let authorized_key: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; // 1 * G let msg: u256 = jet::sig_all_hash(); jet::bip_0340_verify((authorized_key, msg), sig); } fn script_hash_for_input_script(state_data: u256) -> u256 { // This is the bulk of our "compute state commitment" logic from above. let tap_leaf: u256 = jet::tapleaf_hash(); let state_ctx1: Ctx8 = jet::tapdata_init(); let state_ctx2: Ctx8 = jet::sha_256_ctx_8_add_32(state_ctx1, state_data); let state_leaf: u256 = jet::sha_256_ctx_8_finalize(state_ctx2); let tap_node: u256 = jet::build_tapbranch(tap_leaf, state_leaf); // Compute a taptweak using this. let bip0341_key: u256 = 0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0; let tweaked_key: u256 = jet::build_taptweak(bip0341_key, tap_node); // Turn the taptweak into a script hash let hash_ctx1: Ctx8 = jet::sha_256_ctx_8_init(); let hash_ctx2: Ctx8 = jet::sha_256_ctx_8_add_2(hash_ctx1, 0x5120); // Segwit v1, length 32 let hash_ctx3: Ctx8 = jet::sha_256_ctx_8_add_32(hash_ctx2, tweaked_key); jet::sha_256_ctx_8_finalize(hash_ctx3) } fn load(state_data: u256) { // Assert that the input is correct, i.e. "load". assert!(jet::eq_256( script_hash_for_input_script(state_data), unwrap(jet::input_script_hash(jet::current_index())) )); } fn store(new_state: u256) { assert!(jet::eq_256( script_hash_for_input_script(new_state), unwrap(jet::output_script_hash(jet::current_index())) )); } fn update(sig: Signature, state_data: u256) { // In this case, if the signature is correct, we approve the transaction // if the destination is the same contract but with a correct internal // state update that increases the count by 1. // Check that the signature is correct. check_sig(sig); let (state1, state2, state3, count): (u64, u64, u64, u64) = ::into(state_data); let (carry, new_count): (bool, u64) = jet::increment_64(count); // Check for overflow. assert!(jet::eq_1(::into(carry), 0)); // Assert that the output is being sent to a correctly-updated copy of this // specific program, i.e. "store". let new_state: u256 = <(u64, u64, u64, u64)>::into((state1, state2, state3, new_count)); store(new_state); // Assert that there are exactly two outputs in the currently-proposed // transaction (corresponding to the new contract and the network fee // payment). Without this logic, the updater could cause coins to leak out // of the covenant by sending some of the input value to an uncontrolled // output address that is not a copy of this contract. (Even with this // logic, the fee amount itself is not constrained here, and the updater // could choose to give away some or all of the stored value to miners in // the form of an excessive fee.) assert!(jet::eq_32(jet::num_outputs(), 2)); assert!(unwrap(jet::output_is_fee(1))); } fn withdraw(sig: Signature, state_data: u256) { // In this case, if the signature is correct, and the count is already at // least 2, we approve the transaction (allowing the destination(s) and // amount(s) indicated by the proposer). // Check that the signature is correct. check_sig(sig); // Assert that the count from the provided state is already at least 2. let (_, _, _, count): (u64, u64, u64, u64) = ::into(state_data); assert!(jet::le_64(2, count)); } fn main() { // Assert that the provided state_data is correct according to this // program's cryptographic commitment. let state_data: u256 = witness::STATE; load(state_data); match witness::UPDATE_OR_WITHDRAW { Left(sig: Signature) => update(sig, state_data), Right(sig2: Signature) => withdraw(sig2, state_data), } } ``` The use of `jet::le_64` means that one can update *at least* twice before a withdrawal. If this is replaced by `jet::eq_64`, the contract will enforce *exactly* two updates before a withdrawal. As described earlier, each of the intermediate transactions will be sent to a **different on-chain address** representing a commitment to the same Simplicity covenant, but with different state. Thus, the `update` transactions in the diagram below each have corresponding distinct destination addresses. ```mermaid flowchart TD A((Faucet)) -->|Deposit| B[Contract
count=0] B -->|Update| C[Contract
count=1] C -->|Update| D[Contract
count=2] D -->|Withdraw| E((User wallet)) ``` ### Constraining state updates Contracts must include logic to ensure that only authorized parties can perform transactions that cause state updates. The contract above does this with `check_sig()`, requiring that all transactions be signed by an authorized party. The `update()` function in this example enforces further constraints on outputs, such as constraining the total number of outputs to exactly two. The first output must be an updated version of the same contract, while the second output must be a network fee payment. Without this constraint, the updater could add an additional output that leaks contract funds to an unrelated address. A real financial application may need to include logic to enforce a variety of further constraints. For example: * The contract may be designed to deal with a specified [asset](../glossary.md#asset), such as Liquid bitcoin (LBTC). Since [Liquid](../glossary.md#liquid) supports a variety of assets, the contract should inspect the assets being transferred to confirm that they always of the expected kinds. In most contexts, payments should not be allowed to be made with arbitrary assets. * The contract may need to constrain fee amounts in order to prevent parties from intentionally donating the contract's assets to miners via excessive fees. The contract may also require a party performing an update to pay the network fee. In that case, it must allow that party to provide a fee input in the transaction that covers the transaction fee, and may also allow the party to receive change from the fee input. ## Next steps As noted above, a program can store multiple values by interpreting the commitment as a SHA256 hash, where the `load()` and `store()` routines verify that the hash value matches the hash calculated from all the relevant witness values. In this case, the contract developer will create a convention for exactly which values are hashed and in which order; that convention is then used whenever state is stored or loaded. SimplicityHL will have library functions reflecting the `load` and `store` patterns to make it easier for developers to use these mechanisms without writing boilerplate referring to the lower-level details of the cryptographic state commitment. It will also offer a reference implementation of Merkle tree creation and verification to make it easier for developers to load and store multiple values in a standardized and elegant way. ### Witnesses # Witnesses in SimplicityHL development This document describes *witnesses*, which are transaction-time input data for a Simplicity contract provided by the user proposing the transaction. A witness explains what the user wants the contract to do, and convinces the contract that this action is authorized. When you interact with a Simplicity [contract](../glossary.md#contract) on the blockchain, you'll need to build and attach witness data for each [transaction](../glossary.md#transaction). The [execution model](../execution-model) for Simplicity [contract](../glossary.md#contract)s allows the user who is proposing a [transaction](../glossary.md#transaction) to provide input values to the contract. Each contract expects different inputs, but in general they help confirm that the proposed transaction is authorized according to the contract's rules. This is necessary because anyone can propose transactions to spend assets at any time, so a contract needs a clear way to distinguish which transactions are appropriate and which aren't. One can think of a Simplicity program as a function that deterministically answers "yes" or "no" to each proposed transaction. The input data for this function will be the specific transaction details, together with some user-supplied inputs which are collectively known as a *witness*. The form of the expected witness is determined in advance by the Simplicity program, just as any function definition determines what kind of input that function expects. The term "witness" here is adopted from its existing use in other kinds of Bitcoin transactions, and originally from a related meaning in computer science. Among other things, a witness will usually contain digital signatures from some party or parties approving the proposed transaction. It might also include things like * amounts (for example, how much of an asset is requested to be spent or transferred) * oracle statements (confirming some fact about the outside world) * values representing *choices* among several actions that can be taken at a certain moment (for example, whether a payment should proceed or be cancelled and refunded). The witness is directly attached to the transaction and forms a part of it; if the transaction is confirmed, the witness data will be publicly visible on the blockchain as part of the confirmed transaction. In an end-user application, witness data will typically be built by wallet or app software that understands how to interact with a certain contract on the user's behalf. During the contract development process, developers might build it manually. Please note that this document is discussing "inputs" informally in the typical software development sense of [data provided to a function or program](https://en.wikipedia.org/wiki/Parameter_(computer_programming)), not the blockchain-specific sense of the specific [UTXO](../glossary.md#utxo)s consumed by a transaction (which will also be details relevant to many contracts' logic). For the practical mechanics of building `.wit` files, compiling them with `simc`, and formatting every SimplicityHL type as a witness value, see the [`.wit` file reference](witness-format.md). ### Oracles # Oracles in Simplicity [Smart contracts](../glossary.md#smart-contract) in Simplicity, like other on-chain smart contracts, can only directly access or observe [transaction](../glossary.md#transaction) data. However, contracts often refer to off-chain facts, such as a market price or whether an event has happened. * What is the price of Bitcoin in dollars (on a specified exchange)? * What is the price of wheat in Euros (on a specified market with specified delivery terms)? * Who won the 2026 Super Bowl? * Did the insured party suffer a covered loss during the term of an insurance contract? If so, how large was that loss? These facts can be communicated to a contract with the help of an *oracle*, which is simply a trusted party that can make a digitally signed statement that the contract will accept. Any entity that all of the parties interested in a contract can trust, and that's willing to make digital signatures in a prearranged form, can be an oracle. The oracle needs to publish a public key and a well-defined format or structure that its statements will follow. These technical details are then encoded into any contract that will rely on that oracle. When the oracle makes a statement, it communicates the signed statement publicly, or to a party that relies on it. That party then provides the relevant signed oracle statement and signature *in the [witness](../glossary.md#witness)* to a transaction involving a Simplicity contract. The Simplicity contract receives the signed statement and signature via the witness and can verify their correctness and make a decision based on the content of the oracle statement. ## Examples of oracle statements For example, an oracle for a yes-or-no event might say: > We will hash one of the following `u8` values > `0` representing NO > > `1` representing YES > > with SHA256 and we will then sign the result with BIP0340 with the key corresponding to public key `0x292cbaf344fc104ff2307cce72e84f13dbfeb6f603fe94938208c6150733b910`. In this case the oracle could issue the signature `56c9b207945aca49302e26e0f68c7f28ce2801be1c8ed2eb0421d257ab6bf818be854a0a5d08a7346a6ec753c71aaf3c2ec346202ea061f32300f2da81493b40` (a BIP0340 signature of `SHA256(0x00)`) in case the underlying event turns out as NO. Or a price oracle might say > We will construct an oracle statement by hashing a `Height` (representing a [Liquid Network](../glossary.md/#liquid) [block height](../glossary.md#height) followed by a `u32` (representing a number of U.S. dollars that were paid on an exchange for 1 BTC in the most recent exchange transaction) with SHA256. We will sign this statement with the BIP0340 key corresponding to `0x292cbaf344fc104ff2307cce72e84f13dbfeb6f603fe94938208c6150733b910`. The oracle may then publish the values `(height, price, signature)`, representing an instance of such an oracle statement. For example, it might publish > (`3793375, 70664, 0x63decfbfb76f4b549d840d9c1fc95bf2bbe4d82209cf03d27fd4aef7e92d8d20f6844076588058baf70f34ee7e7a7ac2b44d0db2e1be7a792a474cc1d0544a6f`) attesting to a price of $70664 observed at the time of Liquid block 3793375. !!! Note It's important for the oracle to clearly define the format of its statement, including the specific data types of the included values, in order to prevent any ambiguity about the oracle statement's meaning. For example, it must not be possible to reinterpret part of the block height as part of the price, or vice versa. This example avoids ambiguity by specifying that the height is a `Height` (32 bits) and that the price is a `u32` (32 bits). This makes it clear what each individual bit in the signed statement means. Then a Simplicity witness using this statement might include ```json { "oracle_height": { "value": "3793375", "type": "Height" }, "oracle_price": { "value": "70664", "type": "u32" }, "oracle_signature": { "value": "0x63decfbfb76f4b549d840d9c1fc95bf2bbe4d82209cf03d27fd4aef7e92d8d20f6844076588058baf70f34ee7e7a7ac2b44d0db2e1be7a792a474cc1d0544a6f", "type": "Signature" } } ``` A SimplicityHL program that's run with this witness can verify it with ```rust // Minimum required block height (= 2026-03-09). // Block height is a 32-bit value. assert!(jet::lt_32(3790000, witness::oracle_height)); // Hardcoded key of trusted signer let oracle_pubkey: Pubkey = 0x292cbaf344fc104ff2307cce72e84f13dbfeb6f603fe94938208c6150733b910; let ctx: Ctx8 = jet::sha_256_ctx_8_init(); let ctx1: Ctx8 = jet::sha_256_ctx_8_add_32(ctx, witness::oracle_height); let ctx2: Ctx8 = jet::sha_256_ctx_8_add_32(ctx1, witness::oracle_price); let overall_hash: u256 = jet::sha_256_ctx_8_finalize(ctx2); jet::bip_0340_verify((oracle_pubkey, overall_hash), witness::oracle_signature); ``` Note that the program recomputes the SHA256 hash of the asserted data values, then checks that the provided oracle signature is a valid signature for that hash. If a party tried to submit a false or modified oracle statement, the oracle's signature wouldn't verify correctly. The program can then use the validated `witness::oracle_price` value for other logic (for example, calculating a liquidation threshold or a pro-rated refund amount in the [Simplicity Lending Protocol](../../use-cases/lending-protocol), or determining whether it exceeded a threshold for the conditions of a prediction contract). A contract can also allow for multiple oracles by accepting any of several different public keys as trusted, providing a different verification path for each key. ## Prediction market example Resolvr's [Deadcat project](https://github.com/Resolvr-io/deadcat/) includes an example of verifying oracle statements as part of a prediction market implementation. You can see the oracle statement verification logic itself in `verify_oracle_signature()` in [`prediction_market.simf`](https://github.com/Resolvr-io/deadcat/blob/master/src-tauri/crates/deadcat-sdk/contract/prediction_market.simf). A party claiming a redemption from the resolution of a prediction market position needs to provide a matching oracle statement to the contract. ## Oracles for business logic integration In addition to financial applications like prediction markets and options contracts, oracles have applications for letting smart contracts "query" a company's databases or APIs. In this capacity, an internal oracle can bridge the gap between business systems and the blockchain by making signed statements about the results of API calls. When a contract needs access to information kept in a database in order to make a decision, oracle mechanisms help work around [the fact that Simplicity contracts can't directly access off-chain data](../../documentation/execution-model). For example, suppose you're creating an application which is only allowed to send withdrawals to explicitly pre-approved addresses. But the list of such addresses may live in a database, not on the blockchain. An internal oracle can help by 1. performing a database lookup, 2. combining the results with a current timestamp and, 3. digitally signing the results. If a specific address is approved for withdrawals, that oracle statement confirms this fact in a format that can then be provided to the smart contract. If it's not, the oracle statement won't be issued and the transaction won't be completed. Any kind of business may find this useful for adding back-end business logic to a smart contract application (in exchange for imposing dependencies on transactions). It may be especially relevant to regulated entities building regulated financial products and services, where the creators may want to apply additional off-chain criteria for approval of some on-chain actions. ??? Example A user wants to withdraw assets from a smart contract to a user-specified address. The user has previously registered this address with a bank that created the contract and is using it to provide an on-chain service. The user's software contacts the bank and requests an oracle statement to confirm that this transaction is allowed. The bank's systems perform a database lookup, find that the address is approved, and sign a timestamped oracle statement saying so. Now the user's software can provide this oracle statement to the smart contract, along with other user authorization information, to let the contract know that it's allowed to process the withdrawal. If the address isn't found in the database, the bank's systems decline to issue the oracle statement, and instead let the user know what to do in order to get the address authorized to receive withdrawals. For bidirectional communications between back-office business systems and a Simplicity smart contract, an indexing system can track on-chain transactions that affect smart contract state, and report the associated state updates to a traditional database. ## The oracle backend At a technical level, issuing oracle statements for Simplicity contracts requires the following pieces: * Generating a BIP0340 keypair * An unambiguous format for structuring oracle statements * Determining the underlying facts to which the oracle will attest * Generating oracle statements based on the ascertained facts * SHA256 hashing * BIP0340 signing * Publishing the oracle statements and associated signatures SHA256 and BIP0340 should generally be used because they're most compatible with the rest of the ecosystem, and because Simplicity has built-in [jets](../glossary.md#jet) to help verify signatures made with them. !!! warning Operating a public oracle that smart contracts rely on is a serious responsibility, and may expose the operator to attacks meant to steal its keys or induce it to issue false statements. Oracle operators' security and business continuity best practices are beyond the scope of this document. ## Timeouts If an oracle completely ceases to operate, it might be impossible to resolve contracts that rely on that oracle's statements. So, many forms of contract that rely on oracles can benefit from a timeout mechanism as a fail-safe. The smart contract can provide a timeout path so that, after a significant period of time has passed after the contract should have been resolved, unclaimed funds can be claimed as a refund by their original senders, or to some predesignated beneficiary. With this precaution in place, if a relevant oracle statement is never produced, funds are not locked inside the contract permanently. ### Timelocks # Timelocks Timelocks are a scripting feature of Bitcoin and inherited by [Elements](../glossary.md#elements). Timelocks can be used in Simplicity and SimplicityHL to enforce a rule that a [transaction](../glossary.md#transaction) may only happen after a specified amount of time has passed. This is useful for *timeout* logic. Contracts will often have a timeout branch as a fallback mechanism. This timeout branch can allow parties to receive a refund of their assets if the [contract](../glossary.md#contract) is not successfully completed. For example, an atomic swap contract has a timeout where the initiator can be refunded if the other party doesn't complete the swap in a specified period of time. A [vault contract](../glossary.md#vault) can also use timelocks to require a series of transactions, with a specific time delay between them, in order to achieve a withdrawal. ## Timelock measurement units Timelocks can be expressed in various ways. An *absolute* timelock gives an absolute time after which a transaction may occur ("starting next Monday"), while a *relative* timelock specifies how much later a transaction may occur after a prior transaction ("at least one week after this input was confirmed"). Time can be expressed in terms of *blocks* on the blockchain. A blockchain consists of an ever-growing series of blocks, which can be counted to obtain an ever-increasing timescale. In Simplicity, an absolute timelock measured in blocks is [called](../../simplicityhl-reference/type_alias/) a `Height`, while a relative timelock measured in blocks is called a `Distance`. !!! note "Block creation intervals" **[Liquid Network](../glossary.md#liquid) blocks are created once per minute.** This is different from Bitcoin, where a block is created (on average) once every ten minutes. For example, [Liquid block 3800000](https://liquid.network/block/59d271b0df9808eb59e686be30bf0e3c3faf121a72dbd751e90df7bcaed90533) was created at 12:08:10 UTC on March 16, 2026. Time can also be expressed in terms of *real time*. In Simplicity, an absolute timelock measured in real time is called a `Time`, while a relative timelock measured in real time is called a `Duration`. !!! note "Absolute time scale" In both Bitcoin and Liquid, **the *real time* scale for absolute timelock measurement is [*Unix time*](https://en.wikipedia.org/wiki/Unix_time)** (seconds since January 1, 1970). For example, `Time` 1234567890 occurred on February 13, 2009, while `Time` 1800000000 will occur on January 15, 2027. !!! note "Relative time scale" In both Bitcoin and Liquid, **the units of *real time* for relative timelock measurement are *intervals of 512 seconds***. Note that the unit of `Duration` is 512 seconds, while the increment of `Time` is 1 second. For example, one day of real time is slightly less than 169 units of `Duration`. ### Summary | Item | Absolute/Relative | Type name | Unit/scale | | ----| --- | --- | --- | | Blocks
*(specified block height)* | Absolute | `Height` | Blockchain blocks | | Blocks
*(block count interval)* | Relative | `Distance` | Blockchain blocks | | Real time
*(specified time)* | Absolute | `Time` | Unix timestamp (seconds since 1970) | | Real time
*(interval)* | Relative | `Duration` | Units of 512 seconds | You can pick any of these forms of timelock to use in any context in a Simplicity contract. Do not enforce more than one simultaneously, as it may be impossible to verify more than one form of timelock within a single transaction. ## Timelock enforcement mechanisms When a [UTXO](../glossary.md#utxo) enforces a timelock, any transaction that attempts to spend resources from that UTXO must *assert* its compliance with the timelock. This is done by setting the `sequence` or `locktime` fields inside the transaction. The timelock enforcement is a two-step process. (1) The timelock-related [jets](../glossary.md#jet) in Simplicity can read the fields from the transaction and determine whether they obey the applicable timelock requirements. (2) The blockchain consensus rules (applied by [nodes](../glossary.md#node) verifying transactions) will reject a transaction that asserts `sequence` or `locktime` fields that are still in the future compared to the current block. Thus, Simplicity logic checks "is my timelock rule satisfied by this transaction's `sequence` or `locktime`?"; blockchain consensus checks "according to its `sequence` or `locktime`, is this transaction acceptable to include in the blockchain yet?". ??? "Detailed example" Consider a SimplicityHL contract that, at some point, enforces a minimum relative `Distance` of 100 blocks. (A concrete version of the required code appears further below.) If you want to build a transaction claiming assets from this contract, you will need to set the `sequence` field on each input to at least 100. Suppose there is only one input to your transaction. Then... * If you don't set `sequence` at all, it will be assumed to be 0, and the timelock enforcement code further below will fail. * If you set `sequence` to 50, the timelock enforcement code will fail because the `sequence` value is smaller than required. * If you set `sequence` to 150, the timelock condition *succeeds*. However, if you *submit* this transaction to the Liquid Network blockchain less than 150 minutes after the input transaction, the [node](../glossary.md#node) to which you submitted it will reject it with a `non-BIP68-final` (meaning that it's still too early for the proposed transaction to be valid). * If you set `sequence` to 150 and submit the transaction at least 150 blocks after the input transaction, the transaction should be valid and accepted on the blockchain (at least as far as the timelock condition is concerned!). !!! note Note that the timelock jets can't "look up" the real time in the outside world. Rather, they can look up *minimum* times that the transaction creator claimed the transaction would be submitted. In lower-level libraries and documentation, these fields are also called `nSequence` and `nLockTime`. ## Absolute timelock in SimplicityHL Enforcing an absolute timelock in SimplicityHL uses the jets `check_lock_height` (for absolute block height) or `check_lock_time` (for absolute Unix time). `jet::check_lock_height(min_height: Height)`: Assert that the transaction's locktime is a block height greater than or equal to the provided value. Such a transaction cannot be included on the blockchain prior to that block height. `jet::check_lock_time(min_time: Time)`: Assert that the transaction's locktime is a Unix timestamp strictly greater than or equal to the provided value. Such a transaction cannot be included on the blockchain prior to that timestamp. ## Relative timelock in SimplicityHL A relative timelock is calculated based on the time when a particular UTXO was included in a block. !!! warning "Deprecated jets" The Simplicity jets that directly enforce relative timelocks have been deprecated due to an implementation error. However, a workaround is available. This document describes the workaround, not the deprecated jets. This function enforces a relative distance timelock by calling a combination of related jets. ```rust fn enforce_relative_distance(min_distance: Distance) { // Assert that the current input is spent in a transaction that can // only appear a distance of at least min_distance blocks after the input's // UTXO. Panic otherwise. // Transaction version must be at least 2. assert!(jet::le_32(2, jet::version())); // Fetch and parse sequence for current transaction let parsed_seq: Option> = jet::parse_sequence(jet::current_sequence()); match parsed_seq { // Failure condition None => assert!(false), // This is either a distance or a duration, but only a distance is // acceptable here. Some(actual_data: Either) => match actual_data { // Is the actual distance greater than or equal to the specified min_distance? Left(actual_distance: Distance) => assert!(jet::le_16(min_distance, actual_distance)), // A duration is not acceptable in this context. Right(actual_duration: Duration) => assert!(false), }, } } ``` And this is the equivalent function to enforce a relative duration timelock. ```rust fn enforce_relative_duration(min_duration: Duration) { // Assert that the current input is spent in a transaction that can only // appear a duration of at least min_duration units of 512 seconds after // the input's UTXO. Panic otherwise. // Transaction version must be at least 2. assert!(jet::le_32(2, jet::version())); // Fetch and parse sequence for current transaction let parsed_seq: Option> = jet::parse_sequence(jet::current_sequence()); match parsed_seq { // Failure condition None => assert!(false), // This is either a distance or a duration, but only a duration is // acceptable here. Some(actual_data: Either) => match actual_data { // A distance is not acceptable in this context. Left(actual_distance: Distance) => assert!(false), // Is the actual duration greater than or equal to the specified min_duration? Right(actual_duration: Duration) => assert!(jet::le_16(min_duration, actual_duration)), }, } } ``` For real-time-based timelocks, Bitcoin and Elements use a rule called [Median Time Past](https://github.com/bitcoin/bips/blob/master/bip-0113.mediawiki) when determining the effective current time. This rule induces an extra delay (six minutes for Liquid Network, or about one hour for Bitcoin) for the validity of a transaction that using a real-time-based timelock. ## Creating appropriate transactions Transactions that consume UTXOs with timelock conditions must be constructed appropriately to assert the appropriate timelock. The necessary properties can be set as follows: | Kind | Type name | Transaction property | | ---- | --- | --- | | Absolute blocks | `Height` | `nSequence = 0xfffffffe`
`nLockTime < 500000000`
`nLockTime` equal to desired `Height` | | Absolute time | `Time` | `nSequence = 0xfffffffe`
`nLockTime >= 500000000`
`nLockTime` equal to desired `Time` (Unix timestamp) | | Relative blocks | `Distance` | `nSequence < 0x10000`
`nSequence` equal to desired `Distance` (`0` ... `0xffff`) | | Relative time | `Duration` | `nSequence = 0x00400000` + desired `Duration` (`0` ... `0xffff`) | !!! note "More details" The values in the table above are simple heuristics for common timelock assertions and do not include all scenarios and options. For more details on timelock implementation and semantics, please see the Bitcoin timelock specifications in [BIP-65](https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki), [BIP-68](https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki), and [BIP-112](https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki). As noted above, transaction-building tools and APIs may simply refer to the fields as `sequence` and `locktime`. ## No maximum time constraints !!! warning "Timelocks enforce minimum transaction times, not maximum times" In some smart contracts, one might be tempted to use timelock jets to require a transaction to happen *before* a certain time rather than *after*. However, this is not supported. **Architecturally, timelocks in Simplicity contracts can only be usefully used to enforce minimum times, not maximum times, when transactions can occur.** This section further discusses this limitation. Because of the *monotonicity* property of Bitcoin and related blockchain systems, there is no way to directly express a requirement that a transaction occur *before* a certain block height and not after. These systems enforce a rule that a specific transaction that was valid at some point remains valid at all times in the future. Although you can write SimplicityHL code that asserts that a lock distance is *smaller than* rather than *larger than* a specific numerical value, the person creating the transaction can simply assert a small `sequence` value which will be accepted as valid by the blockchain consensus. For example, if a contract was funded with a specific input at block height 5000, and the contract asserts that the relative `Distance` when spending that input should be *less than* 100, a transaction spending that input while asserting `sequence` equal to 50 will still be valid when committed at block height 10000, as the criteria 50<10000-5000 (required by the nodes enforcing blockchain consensus rules in the transaction sequence) and 50<100 (for the contract's own logic) are both true. Asserting the lock distance is small requires the use of a correspondingly small `sequence` value, but this does *not* imply that the resulting transaction is necessarily committed to the blockchain within a short time after the inputs it consumes. ### A workaround: state updates and preemption Because permitted transactions cannot automatically expire or become forbidden, Simplicity requires a different approach to enforce deadlines. Instead, you can achieve an *effective* maximum time for an action using a preemption pattern. The core logic is simple: you can make it impossible to perform an action by removing the assets or state that it relies upon. This requires some party to proactively submit a transaction that performs the preempting action after some time limit has passed. It does not happen automatically. A simple example is a timeout-and-refund mechanism. A contract can provide that, after a certain time period, assets may be transferred back to their original senders. Once a sender proactively claims such a refund, no further actions can be taken with the associated assets because the contract no longer holds them. In general, a preempting transaction transfers the assets elsewhere, or it updates the [covenant's](../glossary.md#covenant) [state](../state.md) to a new version that restricts previously allowed actions. A covenant can be written to permit an authorized party to perform a state update at a certain time which causes previously permitted actions to be forbidden. For example, an authorized update could perform an internal state change to declare a contract "closed" to new claims after a deadline. An effective deadline is enforced provided that a party is incentivized to perform the required transfer or update action. ### Simplex # Simplex Simplex is a development framework and orchestration tool for SimplicityHL smart contracts. It assumes a Rust development environment, and helps you create applications and tools in Rust to interact with the underlying contract. Simplex facilitates creating and running realistic smart contract integration tests, providing a straightforward way to define and run a suite of such tests in Rust. It automatically runs tests against a local instance of `elementsd`, creating a customizable [Liquid Network](../glossary.md#liquid)-like environment for test transactions. Thus, tests can be run without broadcasting transactions publicly on Liquid testnet. A Simplex project's `Simplex.toml` file (automatically created by `simplex init`, as described below) configures the details of this test environment. ## Installing Simplex You can get Simplex from [https://github.com/BlockstreamResearch/smplx](https://github.com/BlockstreamResearch/smplx), or use the auto-install script: ```bash curl -L https://smplx.simplicity-lang.org | bash simplexup ``` When Simplex is installed with `simplexup`, you can rerun `simplexup` at any time to upgrade to the most recent release. ## Using Simplex ### Creating a project Run `simplex init` at the top-level directory of the project. This is ordinarily the same as the top-level directory of the associated Rust project (where the `Cargo.toml` file is found). This step will add a complete starter project in place: `Cargo.toml` (already depending on `smplx-std`), `Simplex.toml`, `src/lib.rs` to let Rust code reference generated [artifacts](../glossary.md#artifacts), and starter contracts and tests. Add or replace SimplicityHL source files in `simf/` as you go so Simplex can find them. ### Building artifacts Run `simplex build` at the top-level directory of the project. This process analyzes the SimplicityHL source code in `simf/` and creates corresponding Rust library files in `src/artifacts`, suitable for including from other Rust code. These library files define functions to build [witnesses](../glossary.md#witness) and blockchain [transactions](../glossary.md#transaction) to drive the individual SimplicityHL contract(s) within the Simplex project. Whenever the SimplicityHL source code changes, re-run `simplex build` to regenerate the artifacts and keep them in sync with the contract's expectations. ???+ "SimplicityHL package dependencies" If your contract's `.simf` import a separate SimplicityHL package (via `use` of an external crate, not just a local module), list it under `[dependencies]` in `Simplex.toml` and run `simplex install` to fetch it before building. ### Creating tests Create `.rs` files in `tests/` containing your integration tests. Each such file defines a series of named integration tests as Rust functions, each with a prototype like ```rust #[simplex::test] fn test_name(context: simplex::TestContext) -> anyhow::Result<()> { // Test code goes here. Ok(()) } ``` ### Running tests Run `simplex test` to execute your complete integration test suite against a local `elementsd`. Adding `-v` will display verbose output from the tests. Pass a name to run just one file's tests. ### Persistent regtest instance `simplex test` normally spins up and tears down its own `elementsd`/Electrs pair for each run. Run `simplex regtest` in a separate terminal to instead start a standalone, long-lived node pair, then point `simplex test` at it via the `[test.rpc]`/`[test.esplora]` entries in `Simplex.toml`. This is useful when you want to inspect chain state between test runs instead of starting from scratch every time. ## Examples `BlockstreamResearch/smplx/examples` contains sample Simplex projects demonstrating how to create integration tests. The current examples are * `examples/basic` (demonstrates a pay-to-public-key contract, called `p2pk.simf`) Each has a `README.md` file describing the project and how to invoke its Simplex test suite. ### Ecosystem & Wallet Integration # Development ecosystem and wallet integration A Simplicity contract enforces financial logic through on-chain transactions, but it is only one piece of a complete financial application. By itself, a contract lacks a user interface or discovery mechanism. To make a Simplicity contract useful, other software, such as a dedicated native app, a web app, or a wallet taught to interact with the contract, needs to be provided. These supporting applications need to handle integration tasks such as finding active instances of a contract on the blockchain and validating that they have the intended or desired functionality, determining the current contract state and the actions a user can take, creating transactions that send or claim assets or that update a contract's state, and representing this information in a meaningful way for an end user. Because Simplicity and Elements are open source, developers have the flexibility to create tools that facilitate these actions in a variety of development environments. ## Tooling (contract compilation and transaction generation) ### Command line [Command-line tools for developing, testing, and exploration](../toolchain/) (`simc` and `hal-simplicity`) are available. ### SimplicityHL, rust-simplicity, and Simplex The recommended development environment for building production applications that directly interact with Simplicity contracts is based on Rust. Three projects are most relevant to Rust developers. * The [SimplicityHL](https://github.com/BlockstreamResearch/SimplicityHL) project includes a Rust library that lets you compile SimplicityHL to low-level Simplicity from inside a Rust program. The compilation logic is the same as in the `simc` compiler, but doesn't require a command-line invocation. * The [`rust-simplicity`](https://github.com/BlockstreamResearch/rust-simplicity) library provides low-level functionality related to building witnesses and transactions. It can also derive on-chain addresses from a compiled Simplicity program. * The [Simplex](../simplex/) orchestration tool can automatically generate code artifacts in Rust, compatible with `rust-simplicity`, that provide basic witness and transaction-building logic for a specified SimplicityHL program. It also provides other useful project management functionality such as dependency management and a test framework. ### LWK The [Liquid Wallet Kit (LWK)](https://github.com/Blockstream/lwk/) provides higher-level functionality in Rust, with bindings also available for several other languages. Simplicity features are still being added in LWK as of April 2026, but are available as an alpha release behind the LWK compile-time feature `simplicity` (build with `cargo build --features simplicity`, `just build-bindings-lib-simplicity`, or `just python-build-bindings-simplicity`). You can use LWK to build a complete wallet or blockchain app that interacts with Liquid Network, including interfacing with Simplicity contracts. LWK can also deal with storing digital assets' private keys directly in-app, and can generate addresses from a supplied seed. LWK's Simplicity support includes higher-level abstractions for transaction building for both commit and redeem transactions. LWK can generate native apps or provide JavaScript bindings, with a WASM compile target, for building web apps. A sample web app built in JavaScript using LWK's WASM target to generate Simplicity transactions is the [lending contract demo](https://demolending.distributedlab.com/). Its source code is available at [`BlockstreamResearch/simplicity-lending`](https://github.com/BlockstreamResearch/simplicity-lending). ## Tooling (blockchain indexing and discovery) The main API for discovering and analyzing transactions on an Elements blockchain, including to determine smart contract state, is [Esplora](https://github.com/Blockstream/esplora). You can use Blockstream's public Esplora instance or run your own Esplora service. If you'd like to run your own Liquid Network node, you can do so with a local instance of [Elements](https://github.com/ElementsProject/elements). The local storage footprint of a Liquid node, as of April 2026, is about 50 gigabytes. ## About transaction-building for interacting with Simplicity contracts Sending an [asset](../glossary.md#asset) to a Simplicity [smart contract](../glossary.md#smart-contract) requires calculating the appropriate on-chain address for the contract, possibly including a [state commitment](./state.md) representing the contract's new state. This is called a *commit* transaction. Determining the appropriate address for a commit transaction may involve moderately complex logic, including [Taproot](../glossary.md#taproot) details (creating a P2TR address where the Simplicity program and state are committed in the Taptree). If the contract does not require updated state commitments, the address of a particular instance of a particular contract will not change from transaction to transaction, and so can be hard-coded or provided by an external tool. The overall form of the commit transaction simply looks like any other P2TR transaction; Simplicity-specific transaction-building logic may not be required. Claiming an asset from a UTXO controlled by a Simplicity contract is called a *redeem* transaction. It requires building a complete transaction with witness data, control block, and possibly appropriate signatures. Simplicity-specific transaction-building logic is usually required for such a transaction. A [covenant](../glossary.md#covenant) transaction, in which assets controlled by a contract are sent back to a version of that same contract, generally includes *both* redeem and commit halves in the same transaction. If an application needs signatures from an existing wallet, a [PSET](../glossary.md#pset) representing a redeem transaction can be built by Simplicity-aware software, and external software can produce an appropriate signature over the proposed PSET transaction. These signatures can be inserted into the [witness](../glossary.md#witness) witness data. ## Wallet connection mechanisms An official wallet connection recommendation is currently in development to connect external wallets with Simplicity clients. Work on this mechanism is built on top of the Bitcoin ecosystem's existing [WalletConnect protocol](https://walletconnect.com/), including a facility for pairing an external wallet with a web application via WebSockets. This permits an existing wallet, with minimal changes, to provide signatures for transactions with Simplicity smart contracts. In this model, the web application (which is developed with LWK targeting WASM) provides the main user interface for a given smart contract, including visualizing contract state and initiating actions with the contract, but users' digital assets can be held in an existent external application or device. The interface is general in order to enable interoperability with any third-party wallet that supports the appropriate wallet connect extensions. However, if the wallet does not provide UI support for a specific contract, the web application must be trusted to accurately represent the meaning and effects of the contract state and requested signatures. A proof of concept of some wallet connect mechanisms is available in the [lending contract demo](https://lending.dev.blockstream.com/). Lightly-customized versions of Blockstream Jade, the Blockstream App, and other wallet applications can pair with this demo and authorize lending contract transactions on Liquid testnet, in both lender and borrower roles. ### txmanifest # txmanifest `txmanifest` is a JSON-based file format for describing Simplicity [contracts](../glossary.md#contract). It provides a reusable, standardized way to *teach wallets* how to describe and interact with a Simplicity contract on-chain. This reduces the need to build custom UI and transaction logic for each contract/wallet pair. When a wallet application imports a trusted [manifest](../glossary.md#manifest) file describing a contract, the wallet application learns how to recognize instances of the described contract on the blockchain, how to describe those details to a user, and how to build new transactions that perform actions in that contract instance. For smart contract developers, `txmanifest` provides a way to describe a contract once and achieve interoperability with a whole ecosystem of wallets. For wallet developers, `txmanifest` provides a way to add UI and transaction functionality once and achieve interoperability with a whole ecosystem of smart contracts. ## Online documentation The `txmanifest` format is work-in-progress and has not yet been frozen as a released specification. Detailed documentation for the `txmanifest` format is available in the [*txmanifest book*](https://stringhandler.github.io/tx_manifest_book/). ## Reference implementation The [`txmanifest-wallet`](https://github.com/stringhandler/txmanifest-wallet/) project provides a reference implementation of `txmanifest` as a developer-oriented wallet application with a textual user interface. You can use `txmanifest-wallet` to debug and experiment with manifest files describing new or existing Simplicity contracts. An interactive online demonstration of this tool, with sample manifests and contracts, is available in the [txw codespace](https://github.com/stringhandler/txw-codespace). ## Security considerations The `txmanifest` format does not include a means to confirm or verify the correctness of descriptions. Trusting an inaccurate or deceptive manifest file can result in wallets misinterpreting the meaning or effect of transactions, and misdescribing them to users. This could cause users' assets to be lost or stolen because the users approve transactions with undesired or unintended effects, including transferring assets to an attacker's control. For example, a manifest file could falsely state that transferring currency to a certain address deposits it as collateral for a loan, which can purportedly be reclaimed by repaying the loan. In reality, the loan could have highly unfavorable terms that the wallet application fails to explain correctly, or the destination address could be some other form of contract that forfeits the user's deposit to an attacker with no further recourse. Users and wallets must only import manifest files from appropriately trusted sources, and not automatically accept manifest files posted online or sent to them by strangers. ### Use Cases Overview # Use Cases Overview Simplicity can be used for a wide range of financial applications. ## Blockstream's reference implementations Examples developed by Blockstream include * [Simplicity DEX](./simplicity-dex/) for financial options * [Simplicity Lending Protocol](./lending-protocol/) for collateralized lending ([implementation](https://github.com/BlockstreamResearch/simplicity-lending)) * [SHRINCS Simplicity verifier](https://github.com/BlockstreamResearch/shrincs-simplicity-verifier), for [Simplicity-based postquantum signature verification](https://blog.blockstream.com/blockstream-research-demonstrates-quantum-resistant-transaction-signing-on-liquid-using-simplicity-smart-contracts/) on Liquid (see also the [PQ Liquid Wallet](https://github.com/smeneguz/pq-liquid-wallet) hackathon project) ## Simplicity projects built by others * [Resolvr](https://resolvr.io/) has created [Astrolabe](https://docs.simplicity-lang.org/news/2026/02/20/video-resolvr-astrolabe-demo/), a Simplicity-based reinsurance investment platform, and [Deadcat](https://github.com/Resolvr-io/deadcat), a Simplicity-based prediction market. * [SideSwap](https://sideswap.io/) has [launched](https://sideswap.io/news/sideswap-press-release/) [Swaption](https://swaption.io/), a Simplicity-based noncustodial binary options marketplace. * [StarkWare](https://starkware.co/) has used Simplicity to [implement a STARK verifier](https://starkware.co/blog/building-starks-in-simplicity/). * [OceanSlim](https://github.com/0ceanslim) has created [anchor](https://github.com/0ceanSlim/anchor), a constant-product automated-market maker (AMM). ## Other examples and ideas Check out the [SimplicityHL examples](https://github.com/BlockstreamResearch/SimplicityHL/tree/master/examples) for other introductory smart contract examples, including [vault](../glossary.md#vault) and [covenant](../glossary.md#covenant) mechanisms. In addition to options, lending, insurance, and prediction markets, Simplicity is expected to support * Cross-chain atomic swaps * Crowdfunding contracts * Bitcoin native smart contracts via Simplicity Unchained * Vaults (multi-stage withdrawals) ### Simplicity DEX # Simplicity DEX The underlying code for this project is found in the [Simplicity DEX repository](https://github.com/Blockstream/simplicity-dex) (other relevant repository links appear below). Despite the name, there is no exchange of Token A for Token B in the traditional sense at the core of the Simplicity DEX. It is a **structured product marketplace** that enables users to create and trade options contracts on-chain on the Liquid Network. Similar techniques can extend it to advertise and perform direct, immediate exchange of pairs of assets in a decentralized way without an intermediary; that functionality is planned for future development. The current protocol facilitates *only* the exchange of "Grantor Tokens" plus a premium in USD for Liquid Bitcoin (LBTC) tokens. Support for other variations will be added in the future. The existing code uses Nostr to publicize the existence of contracts and allow a party to locate a counterparty. This document focuses mainly on the financial logic of the contract rather than the technical mechanisms for representing the contract on Nostr and Liquid. ## Core definitions **Call Option**: A financial contract that gives the holder the right, but not the obligation, to *buy* an underlying asset at a specified strike price before or at a specified expiration date. **Put Option**: A financial contract that gives the holder the right, but not the obligation, to *sell* an underlying asset at a specified strike price before or at a specified expiration date. > **Note**: The underlying [smart contract](../glossary.md#smart-contract) supports both Call and Put options, but the current CLI implementation only supports Call options. **Grantor Token**: A tradable token received by the maker upon funding an options contract. The Grantor Token represents the right to claim assets at settlement - either the LBTC deposited during exercise (if the option is exercised) or the USDt collateral (if the option expires unexercised). In the current version of the protocol, the maker sells this token along with a premium in USD to a taker in exchange for LBTC. ## Implementation The implemented protocol is a variation of a Call Option. The premium to the taker is paid during the exchange of LBTC and collateral tokens (i.e., Grantor Token). ### Participants **Maker ("Option buyer")**: The party that creates the options contract. Upon creation, the maker: * Deposits USDt collateral into the Collateral Covenant * Receives both an **Option Token** and a **Grantor Token** * Keeps the Option Token (gives the right to exercise) * Pays a premium in USD to incentivize the taker to buy the Grantor Token for LBTC. **Taker ("Option seller")**: The party that purchases the Grantor Token and receives the premium in USD. The taker: * Pays LBTC to acquire the Grantor Token from the maker * Holds the Grantor Token, which entitles them to claim assets at settlement ### Resources The core contract of the Simplicity DEX is the Options contract: The concept for this contract was proposed in the following whitepaper: Link to the [Simplicity DEX repository](https://github.com/Blockstream/simplicity-dex). ## Financial Incentive **Maker Profit Condition**: The maker profits when the value of the LBTC received (from selling the Grantor Token) exceeds the value of the USDt collateral deposited and the premium paid. This occurs when the LBTC price rises significantly above the strike price. **Taker Profit Condition**: The taker profits when the premium they receive covers any potential losses, while still retaining a guaranteed claim on either LBTC (if the option is exercised) or the USDt collateral (if the option expires unexercised). ![This graph is a combination of the Long Call by the Maker ("Graph 1") and the Short Call by the Taker ("Graph 3"). Consult Appendix B for more details](/assets/11ad966b-8507-40c2-b29d-0d1e26a6a26a.png){ width="1457" height="888" } !!! info "Option Offer Contract" The [Option Offer contract](https://github.com/BlockstreamResearch/simplicity-contracts/blob/main/crates/contracts/src/finance/option_offer/source_simf/option_offer.simf) enables depositing two assets (collateral + premium) into a single covenant. A counterparty can then swap their settlement asset for both deposited assets in a single atomic transaction, with amounts enforced by configurable ratios (`collateral_per_contract` and `premium_per_collateral`). ## Detailed Step-by-Step Protocol Flow (Current implementation) The maker selects locked-asset as USDt, and claim-asset as LBTC. The contract size is the collateral, e.g., 115,000 USDt. The strike price is $115,000 (LBTC/USDt). The term length is 30 days. The maker creates the "Options Creation" transaction which produces an Option Token Generator and a Grantor Token Generator held in a Generator Covenant. The maker funds a single option using the "Option Funding" transaction, which requires depositing 115,000 USDt into the Collateral Covenant. The maker receives an Option Token and a Grantor Token from the Generator Covenant. Note: at any time, the options contract can be canceled and the collateral retrieved by using both the Option Token and the Grantor Token together to unlock the collateral. The maker deposits their newly minted Grantor Token along with premium (e.g., USDt) into the Option Offer contract. The taker pays LBTC (settlement asset) to the Option Offer contract and receives both the Grantor Token and the premium in a single atomic transaction. The maker keeps their Option Token. **Case A: LBTC ends below $115,000.** Just prior to expiration, the maker uses their Option Token to exercise the Collateral Covenant. This requires depositing LBTC into a Settlement Covenant, which in turn unlocks the Collateral Covenant, whose 115,000 USDt funds the maker gets to keep. Afterwards, the taker can use their Grantor Token to retrieve the LBTC from the Settlement Covenant. **Case B: LBTC ends above $115,000 or the maker lacks adequate LBTC funds.** The maker does nothing and their Option Token is useless. After the expiration date, the taker uses their Grantor Token to retrieve the 115,000 USDt from the Collateral Covenant. ### Key Design Properties This contract design avoids the need for a price oracle, relying instead on the Option Token holder's (i.e., the maker's) natural incentives to choose the appropriate outcome. The taker is required to get the lesser-valued asset of the two, after the expiration date. The choices the maker can make can only improve the taker's outcome. The maker could incorrectly exercise or not exercise their option, which will cause the taker to end up with the more-valuable asset, or the maker could incorrectly exercise their option early, allowing the taker to get access to their funds prior to the expiration date, or both. *It is the responsibility of the maker to optimize their handling of the options* in order to extract the most value for themselves. Any other outcome can only benefit the taker. Avoiding the price oracle also automatically handles the case where the maker defaults: the taker gets the USDt collateral, regardless of the LBTC price. The options design includes extra features beyond the basic specification. In particular, the Grantor and Option Tokens are real tradable assets, and both can be resold. This is particularly useful when the maker is in default (i.e., lacks sufficient funds to exercise the option) but the option is otherwise in-the-money: the maker can sell their Option Token to someone else who does have sufficient funds to exercise it. The design also allows for more than one pair of Grantor/Option Tokens to be generated. For example, the maker can generate 10 token pairs along with 10 Collateral Covenant UTXOs, each holding one tenth of the total collateral. This enables partially filling the order. These sets of Option Tokens and Grantor Tokens are fungible and tradeable. Unfilled orders (i.e., when not all the Grantor Tokens are sold) can be used, along with the same number of Option Tokens, to cancel unused Collateral Covenants and recover the funds immediately. ```mermaid sequenceDiagram participant Maker participant Options as Options Contract participant OptionOffer as Option Offer Contract participant Taker Note over Maker,Taker: Phase 1 & 2: Creation and Funding Maker->>Options: Create Options Contract Options-->>Maker: Generator Covenant created Maker->>Options: Deposit USDt collateral Options-->>Maker: Option Token + Grantor Token Note over Maker,Taker: Phase 3: Token Sale via Option Offer Maker->>OptionOffer: Deposit Grantor Token + Premium (USDt) Taker->>OptionOffer: Pay LBTC (settlement) OptionOffer-->>Taker: Grantor Token + Premium Maker->>OptionOffer: Withdraw settlement (LBTC) OptionOffer-->>Maker: Repay full amount of settlement (LBTC) Note over Maker,Taker: Phase 4a: Settlement (spot < strike) alt LBTC price below strike Maker->>Options: Exercise with Option Token + LBTC Options-->>Maker: Release USDt collateral Taker->>Options: Claim with Grantor Token Options-->>Taker: Release LBTC from Settlement end Note over Maker,Taker: Phase 4b: Settlement (spot >= strike) alt LBTC price above strike (or maker default) Note over Maker: Option expires worthless Taker->>Options: Claim with Grantor Token Options-->>Taker: Release USDt collateral end ``` ## Appendix A: Excalidraw file [profit-loss-diagram(2).excalidraw 28916](/assets/9e9ecabd-7792-4970-a69c-28847b5e9c99.excalidraw) ## Appendix B: Mapping to Traditional Options Positions The Options contract is flexible enough to create all four standard options positions depending on two parameters: the collateral type and which token the maker sells. However, the current contract configuration forces the premium to be of the collateral type, not in USD. ![Standard options profit/loss profiles. The X-axis represents the underlying asset price, and the Y-axis represents profit/loss. The strike price is marked as X, and the breakeven point accounts for the option premium.](/assets/387b82dc-e7ad-4f00-99bb-50a066714ef2.jpg){ width="510" height="323" } ### The Two Determining Factors **Collateral type determines Call vs Put:** * **USDt collateral** → Call-like structure (the option is fundamentally about acquiring LBTC) * **LBTC collateral** → Put-like structure (the option is fundamentally about acquiring USDt) **Which token is sold determines who holds the Long vs Short position:** * **Sell Grantor Token** (keep Option Token) → Maker holds the "long" position (has the right to exercise) * **Sell Option Token** (keep Grantor Token) → Taker holds the "long" position (has the right to exercise) ### The Four Configurations | Configuration | Collateral | Token Sold | Maker's Position | Taker's Position | |----|----|----|----|----| | 1 | USDt | Grantor | Long Call | Short Call | | 2 | USDt | Option | Short Call | Long Call | | 3 | LBTC | Grantor | Long Put | Short Put | | 4 | LBTC | Option | Short Put | Long Put | **Configuration 1 (USDt collateral, sell Grantor)** is the current implementation described in this document. The maker profits when LBTC rises significantly (Long Call profile), while the taker has limited upside but collects the premium spread (Short Call profile). **Configuration 2 (USDt collateral, sell Option)** flips the positions. The taker, now holding the Option Token, has the right to exercise and profits when LBTC falls (Long Call on USDt, effectively). The maker keeps the Grantor Token and has the Short Call profile. **Configuration 3 (LBTC collateral, sell Grantor)** creates a Put-like structure. The maker deposits LBTC as collateral. The Option Token holder can deposit USDt to claim the LBTC. The maker (holding Option Token) profits when LBTC falls (Long Put profile). **Configuration 4 (LBTC collateral, sell Option)** is the inverse of Configuration 3. The taker holds the Option Token and has the Long Put position, profiting when LBTC falls. The maker has the Short Put profile. ### Practical Implications This flexibility means market participants can use the same underlying contract to express different market views: * **Bullish on LBTC**: Use Configuration 1 (maker) or Configuration 4 (taker) * **Bearish on LBTC**: Use Configuration 3 (maker) or Configuration 2 (taker) * **Neutral/yield-seeking**: Take the "short" side of any configuration to collect premium The current CLI implementation supports Configuration 1, but the underlying smart contract is capable of supporting all four configurations. ### Lending Protocol # Simplicity Lending Protocol on Liquid Network [Simplicity](../glossary.md#simplicity) is a deterministic, statically analyzable smart contract language designed for Bitcoin-like systems, with predictable execution costs and a semantics that lends itself to high assurance reasoning and formal verification. This article describes a Simplicity Lending Protocol implemented as Simplicity [smart contracts](../glossary.md#smart-contract) on the [Liquid Network](../glossary.md#liquid). The article focuses on the protocol design, financial logic, and contract decomposition. The Protocol implements on-chain borrowing at interest against collateral, with details agreed and accepted between two parties. The Protocol can be used by any kind of entity, ranging from a financial institution to a private individual. The Protocol handles accounting for a loan involving two different tokens, representing different [assets](../glossary.md#asset). For illustrative purposes, USDT is used as a loaned asset and LBTC as a collateral asset, but the protocol handles any pair of assets that are tokenized on Liquid. The implementation plan consists of steps for the full Protocol implementation. It starts with verified lending primitives and increases the complexity and functionality of the Protocol over time. The existing implementation can be found [on GitHub](https://github.com/BlockstreamResearch/simplicity-lending). ## Simplicity Lending Protocol Roadmap ### P2P Simplified Lending Contract These are the parameters of the lending contract: * **Borrower** - user with collateral asset A. * **Lender** - user with loan asset B. * **Collateral Amount** - pledged by Borrower collateral in asset A. * **Loan Amount** - amount of asset B tokens borrowed by Borrower from Lender. * **Lending Term** - moment in the future (UNIX time, block number, etc.) before which Borrower should repay the Loan Amount in asset B to the Lender. * **Loan Amount** - the amount of the loan asset Borrower would like to borrow. * **Collateral Amount** - locked by the Borrower, the collateral amount for the Loan Amount. * **Liquidation** - if the Loan is not repaid after the Lending Term, the Lender can claim the collateral. * **Loan Fee** - fixed fee paid in loan asset B from Borrower to Lender for the Loan after the loan is originated. Can represent interest payment for a fixed-term loan. * **Origination Fee** - fixed fee paid in collateral asset A from Borrower to Lender for the Loan for loan origination. * **Protocol Fee (Reserve Factor)** - fee paid in loan asset B to the Protocol Address for the Loan after the loan repayment. The fee is calculated as a percentage of the Loan Fee. ```mermaid flowchart TD START((Start)) S_Proposed[State: Collateral Locked] S_Active[State: Loan Active] T_Cancelled([Terminated: Refunded]) T_Settled([Settled: Repaid]) T_Defaulted([Liquidated: Defaulted]) START -->|Borrower proposes terms
Borrower pays collateral| S_Proposed S_Proposed -->|Borrower cancels
Collateral refunded to borrower| T_Cancelled S_Proposed -->|Lender accepts
Lender pays principal
Borrower receives principal| S_Active S_Active -->|Loan repaid
Borrower repays principal
Principal repaid to lender
Collateral refunded to borrower| T_Settled S_Active -->|Timeout reached
Liquidation process invoked
Collateral paid to lender or liquidator| T_Defaulted style S_Proposed fill:#fff4dd,stroke:#d4a017,stroke-width:2px style S_Active fill:#fff4dd,stroke:#d4a017,stroke-width:2px style T_Cancelled fill:#eee,stroke:#999 style T_Settled fill:#d1fae5,stroke:#10b981 style T_Defaulted fill:#fee2e2,stroke:#ef4444 ``` #### Loan Origination ##### Borrower to Lender Borrower locks Collateral Amount and the Origination Fee in the offer transaction. The Borrower also states the following parameters in the transaction: the Loan Fee, Lending Term, Origination Fee, and the Loan Amount the Borrower would like to borrow. After an offer is published with a transaction, any address can accept the offer as a Lender. ##### Lender to Borrower If the Lender is satisfied with the loan conditions in the offer (Origination Fee, Loan Fee, Protocol Fee, Lending Term, Loan Amount, Collateral Amount), the Lender accepts the offer and sends the Loan Amount, receives a fixed Origination Fee in a transaction, and accepts the conditions of the loan, including Origination Fee, Loan Fee, Protocol Fee, Lending Term and Collateral Amount. The Borrower receives the requested Loan Amount in the offer. #### Loan Repayment The Borrower should fully repay the loan plus the Loan Fee at any moment before the Lending Term. Loan Fee is divided into the Protocol Fee paid to the Protocol Address and the rest paid to the Lender. After the repayment, Borrower can claim back the full amount of the collateral asset. #### Liquidation If the Borrower did not repay the full Loan Amount before the Lending Term, the Lender can claim the full collateral amount. ### P2P Lending Contract with Mock Price Oracle and Partial Loan Repayment This contract variant is based upon the contract described in the prior section, with additional functionality and an additional Liquidation Loan-To-Value (LLTV) parameter. Additional definitions: * **Price Oracle** - source of constant verified price information of asset A in terms of asset B, signed by a trusted provider. * **Third Party Liquidator** - any third party address that can fully repay the loan outstanding at the moment of liquidation and get the Collateral Amount. * **Liquidation Protocol Fee** - fee paid to Protocol Address as a percentage (10%, for example) from the Collateral Amount at the liquidation event. #### Loan Origination ##### Borrower to Lender Borrower locks Collateral Amount and the Origination Fee in the transaction. The Borrower also states the transaction Loan Fee, Lending Term, Origination Fee, Loan Amount the Borrower would like to borrow, which should satisfy the following equation: $$ \text{Loan Amount} < \text{LLTV} \times \text{Collateral Amount} \times \text{Price Oracle} $$ ##### Lender to Borrower If the Lender is satisfied with the loan conditions in the offer (Origination Fee, Loan Fee, Protocol Fee, Lending Term, Loan Amount, Collateral Amount, and LLTV), the Lender accepts the offer and sends a transaction with the Loan Amount in asset B and receives a fixed Origination Fee. The Borrower receives the requested Loan Amount in the offer. #### Loan Repayment The Borrower should fully repay the Loan Amount plus Loan Fee at any moment before the expiration of the Lending Term. Loan Fee is divided into the Protocol Fee paid to the Protocol Address and the remainder paid to the Lender. After the repayment, Borrower can claim back the full amount of the collateral asset. During the Lending Term, Borrower can choose a partial loan repayment to avoid a Liquidation event. #### Liquidation In case of a default on the loan (if the Borrower did not repay the full Loan Amount plus the Loan Fee before the Lending Term), the Lender can claim the full collateral amount. (But see below for a partial liquidation alternative.) Liquidation at any moment (before or after Lending Term) by a Third Party Liquidator is possible if the following equation is true: $$ \text{Loan Amount Outstanding} > \text{LLTV} \times \text{Collateral Amount} \times \text{Price Oracle} $$ In this case, Third Party Liquidator receives the Collateral Amount minus the Liquidation Protocol Fee, which goes to the Protocol Address. #### Partial Liquidation Invariant The full collateral liquidation option allows the Lender, after the Lending Term, to liquidate the full amount of collateral even if the loan is significantly repaid. To mitigate this case and give the Borrower partial credit for partial repayment, a partial liquidation invariant is available. This alternative pays the Lender a pro-rated fraction of the collateral in exchange for the outstanding portion of the loan principal. The contract also sets a fixed Liquidation Penalty as an additional amount of collateral obtained by the Lender as a penalty for incomplete repayment. The rest of the collateral is sent back to the Borrower. In this case, liquidation after the Lending Term can be initiated by the Lender or the Borrower. The collateral part received by the Borrower is calculated according to the following equation: $$ \text{Borrower Collateral Payment} = \text{Collateral Amount} - \frac{\text{Loan Amount Outstanding}}{\text{Price Oracle}} - \text{Liquidation Penalty} $$ Liquidation Penalty is also subject to Liquidation Protocol Fee, which goes to the Protocol Address. ### P2P Lending Contract with Integrated Interest Rate This contract variant is based upon the contract described in the prior section, but, instead of a fixed Loan Fee, a fixed Interest Rate is used. Additional definitions: * **Interest Rate** - fixed annual percentage rate (APR), which is a reward for the Lender from the Borrower as an incentive to provide the loan. #### Loan Origination ##### Borrower to Lender Borrower locks Collateral Amount and the Origination Fee in the transaction. The Borrower also states in the transaction the Interest Rate, Lending Term, Origination Fee, Loan Amount the Borrower would like to borrow, which should satisfy the following equation: $$ \text{Loan Amount} < \text{LLTV} \times \text{Collateral Amount} \times \text{Price Oracle} $$ If the Lender is satisfied with the loan conditions in the offer (Origination Fee, Interest Rate, Protocol Fee, Lending Term, Loan Amount, Collateral Amount), the Lender accepts the offer and sends a transaction with the loan in asset B and receives a fixed Origination Fee. ##### Lender to Borrower Lender sends the Loan Amount in a transaction and accepts the conditions of the loan, including Origination Fee, Interest Rate, Protocol Fee, Lending Term, Collateral Amount and LLTV. The Borrower receives the requested Loan Amount in the offer. #### Loan Repayment The Borrower should fully repay the Loan Amount with the accrued Interest Rate at any moment before the Lending Term. Loan Fee is divided into the Protocol Fee paid to the Protocol Address and the rest paid to the Lender. After the repayment Borrower can claim back the full amount of the collateral asset. During the Lending Term Borrower can choose a partial loan repayment to avoid a Liquidation event. #### Liquidation If the Borrower did not repay the full Loan Amount plus the accrued Interest Rate before the Lending Term, the Lender can claim the full collateral amount. Liquidation at any moment (before or after Lending Term) by the Third Party Liquidator is possible provided that the following equation is true: $$ \text{Loan Amount Outstanding} + \text{Accrued Interest Rate} > \text{LLTV} \times \text{Collateral Amount} \times \text{Price Oracle} $$ Third Party Liquidator receives the Collateral Amount minus the Liquidation Protocol Fee, which goes to the Protocol Address. #### Partial Liquidation Invariant The full collateral liquidation option allows the Lender, after the Lending Term, to liquidate the full amount of collateral even if the loan is significantly repaid. To mitigate this case, a partial liquidation mechanism is available. The partial liquidation invariant sets the Liquidation Penalty as a fixed collateral asset amount obtained by the Lender on top of the recovered loan amount. The rest of the collateral is sent back to the Borrower. In this case, liquidation after the Lending Term can be initiated by the Lender or the Borrower. The collateral part received by Borrower is calculated according to the following equation: $$ \text{Borrower Collateral Payment} = \text{Collateral Amount} - \frac{\text{Loan Amount Outstanding}}{\text{Price Oracle}} - \text{Liquidation Penalty} $$ Liquidation Penalty is also subject to Liquidation Protocol Fee, which goes to the Protocol Address. ## First Lending Protocol Implementation The first version of the Protocol is being finalized with a P2P Simplified Lending Contract solution, which will be deployed on the Liquid Network and allow lending USDT against LBTC for a fixed term. The first lending protocol implementation is carried out by multiple [covenants](../glossary.md#covenant) that are constructed continuously by both Borrower and Lender in the following order: ### Creation of Utility Tokens Both Lender's and Borrower's financial interests in the contract are represented by special "one-time" assets ([tokens](../glossary.md#token)) that enable the existence of secondary markets where these tokens can be sold/traded/exchanged. To ensure the integrity of lending offer parameters across multiple covenants, specialized Parameter Tokens are utilized. These assets act as data carriers by encoding the protocol parameters directly within their amount fields. **Important:** To ensure compatibility with Liquid/Elements consensus rules, the encoded value must not exceed the maximum allowed supply limit ([`MAX_MONEY`](https://github.com/ElementsProject/rust-elements/blob/f6ffc7800df14b81c0f5ae1c94368a78b99612b9/src/blind.rs#L471)). This effectively restricts the available data space within the token's amount field to 51 bits. Two distinct tokens are required to store the current protocol state: 1. `FIRST_PARAMETERS_NFT`: Encodes the `PRINCIPAL_INTEREST_RATE`, `LOAN_DURATION`, `COLLATERAL_DECIMALS_MANTISSA`, and `PRINCIPAL_DECIMALS_MANTISSA`. | Bits | Field name | Description | | :---- | :---- | :---- | | \[0..15\] | Interest Rate | 16-bit value for the loan interest (percentage) | | \[16..42\] | Loan Expiration Time | 27-bit value for the loan expiration time in blocks (block height) | | \[43..46\] | Collateral Decimals Mantissa | 4-bit exponent for collateral amount calculation | | \[47..50\] | Principal Decimals Mantissa | 4-bit exponent for principal amount calculation | 2. `SECOND_PARAMETERS_NFT`: Encodes the `BASE_COLLATERAL_AMOUNT` and `BASE_PRINCIPAL_AMOUNT`. | Bits | Field name | Description | | :---- | :---- | :---- | | \[0..24\] | Base Collateral Amount | 25-bit integer representing the collateral amount without decimals | | \[25..49\] | Base Principal Amount | 25-bit integer representing the principal amount without decimals | | \[50\] | Free Space | - | Since the amount in a single [UTXO](../glossary.md#utxo) cannot exceed $21,000,000 \times 10^8$, the number of bits required to encode the parameters can be reduced: - Decimals mantissa (4 bits): This field occupies only 4 bits because assets with more than 8 decimals can't be created on Liquid. - Base amount (25 bits): The base amount occupies 25 bits, allowing for a maximum value of 33,554,432. When combined with the decimals mantissa, it can represent any value from 0 up to the maximum possible supply. - Interest Rate (16 bits): This field requires a minimum of 14 bits to express percentages using a scale where 10,000 equals 100%. Using 16 bits achieves a maximum interest rate of 655.36%. - Loan expiration time in blocks: Setting this value to 27 bits allows the end block of the loan to be 134,217,728. This is currently ~248 years in the future with a 1-minute block rate. ### Lock Collateral After calculating the Utility asset IDs, the "lock collateral" covenant can be constructed. ### Set up Lending After the Borrower has constructed a "lock collateral" covenant, a Lender can then initiate the loan by sending the lending covenant transaction. ### Settle Lending After the lending contract is set up, there are two ways it can be settled. Either the Borrower provides the principal with interest to the Lender and takes the collateral back, or the Lending Term expires, and the Lender liquidates the position, claiming the collateral. ## Indexing and Discovery Mechanism The indexing mechanism is a critical component of the protocol's UX. To make the lending workflow functional, Lenders must be able to view all active loan offers from Borrowers. Simultaneously, Borrowers require real-time updates on the status of their offers to monitor repayment deadlines and obligations. The following indexing strategy is used to aggregate and display this data. ### Pre-lock Transaction Discovery and Verification To monitor all active orders, an indexer scans the blockchain to identify transactions that initialize Pre-lock covenants. Once these transactions are identified, an indexer monitors the lifecycle of the resulting UTXOs to extract and update information regarding the loan terms. An indexer recognizes Pre-lock initialization transactions by matching them against a specific transaction template: - The transaction must contain a minimum of 5 inputs and 6 outputs. - The transaction must include an `OP_RETURN` output containing a valid [BIP340](https://en.bitcoin.it/wiki/BIP_0340) public key. - The first and second inputs must contain Parameter UTXOs, where the encoded amounts match the bit-packed lending protocol structure. If a transaction meets the criteria above, the indexer performs a verification: 1. It attempts to compile the Pre-lock covenant using the parameters extracted from the transaction data. 2. It compares the resulting script hash ([CMR](../glossary.md#cmr)) with the script hash of output 0. 3. If the hashes match, the transaction is confirmed as a valid Pre-lock initialization. ### State Management An indexer maintains a database of all existing offers. It continuously monitors the blockchain for spending transactions that target protocol-controlled outputs, allowing it to update the status of each loan (e.g., Active, Repaid, Cancelled, or Liquidated) in near real-time. ## Reference ### Introduction # Introduction SimplicityHL is a high-level language for writing Bitcoin smart contracts. In other words, SimplicityHL is a language for expressing spending conditions of [UTXOs](../glossary.md#utxo) on the Bitcoin blockchain. SimplicityHL looks and feels like [Rust](https://www.rust-lang.org/). Developers write SimplicityHL, Bitcoin full nodes run Simplicity. # Types and Values - [Types and Values](./type.md) - [Type Aliases](./type_alias.md) - [Type Casting](./type_casting.md) # Writing a Program - [Let Statements](./let_statement.md) - [Match Expression](./match_expression.md) - [Functions](./function.md) - [Programs](./program.md) ### Types and Values # Types and Values SimplicityHL mostly uses a subset of Rust's types. It extends Rust in some ways to make it work better with Simplicity and with the blockchain. ## Boolean Type | Type | Description | Values | |--------|-----------------|-----------------| | `bool` | Boolean | `false`, `true` | Values of type `bool` are truth values, which are either `true` or `false`. ## Integer Types | Type | Description | Values | |--------|-----------------|--------------------------------------------------------| | `u1` | 1-bit integer | `0`, `1` | | `u2` | 2-bit integer | `0`, `1`, `2`, `3` | | `u4` | 4-bit integer | `0`, `1`, …, `15` | | `u8` | 8-bit integer | `0`, `1`, …, `255` | | `u16` | 16-bit integer | `0`, `1`, …, `65535` | | `u32` | 32-bit integer | `0`, `1`, …, `4294967295` | | `u64` | 64-bit integer | `0`, `1`, …, `18446744073709551615` | | `u128` | 128-bit integer | `0`, `1`, …, `340282366920938463463374607431768211455` | | `u256` | 256-bit integer | `0`, `1`, …, 2256 - 1 | Unsigned integers range from 1 bit to 256 bits. [`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. `u1`, `u2`, `u4` and `u256` are new to SimplicityHL. Integer values can be written in decimal notation `123456`, binary notation `0b10101010` or hexadecimal notation `0xdeadbeef`. The number of bits or hex digits (for binary or hex notation) must correspond to the bit width of the type. There are no signed integers. The maximal `u256` value is `115792089237316195423570985008687907853269984665640564039457584007913129639935`. ## Tuple Types | Type | Description | Values | |--------------|-------------|---------------------------------------------------| | `()` | 0-tuple | `()` | | `(A)` | 1-tuple | `(a0,)`, `(a1,)`, … | | `(A, B)` | 2-tuple | `(a0, b0)`, `(a1, b1)`, `(a2, b2)`, `(a3, b3)`, … | | … | … | … | | `(A, B, …)` | n-tuple | `(a0, b0, …)`, … | [Tuples work just like in Rust](https://doc.rust-lang.org/std/primitive.tuple.html). The empty tuple `()` contains no information. It is also called the "unit". It is mostly used as the return type of functions that don't return anything. Singletons `(a0,)` must be written with an extra comma `,` to differentiate them from function calls. Bigger tuples `(a0, b0, …)` work like in most other programming languages. Each tuple type `(A1, A2, …, AN)` defines a sequence `A1`, `A2`, …, `AN` of types. Values of that type must mirror the sequence of types: 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. Tuples are always finite in length. > Tuples are different from arrays: > Each element of a tuple can have a different type. > Each element of an array must have the same type. ## Array Types | Type | Description | Values | |----------|-------------|---------------------------------------------------| | `[A; 0]` | 0-array | `[]` | | `[A; 1]` | 1-array | `[a0]`, `[a1]`, … | | `[A; 2]` | 2-array | `[a0, a1]`, `[a2, a3]`, `[a4, a5]`, `[a6, a7]`, … | | … | … | … | | `[A; N]` | n-array | `[a0, …, aN]`, … | [Arrays work just like in Rust](https://doc.rust-lang.org/std/primitive.array.html). The empty array `[]` is basically useless, but is included for completeness. Arrays `[a0, …, aN]` work like in most other programming languages. Each array type `[A; N]` defines an element type `A` and a length `N`. An array value `[a0, …, aN]` of that type consists of `N` many elements `a0`, …, `aN` that are each of type `A`. Arrays are always of finite length. > Arrays are different from tuples: > Each element of an array must have the same type. > Each element of a tuple can have a different type. ## List Types **SimplicityHL List types work differently from what you might expect from other languages. Please see their details and limitations below.** Notably, a List type must be declared with a length that is an exact power of two, and the List can never hold the full number of items indicated by its declared length. | Type | Description | Values | |---------------------------|---------------------|------------------------------------------------------| | `List` | <2-list | `list![]`, `list![a1]` | | `List` | <4-list | `list![]`, …, `list![a1, a2, a3]` | | `List` | <8-list | `list![]`, …, `list![a1, …, a7]` | | `List` | <16-list | `list![]`, …, `list![a1, …, a15]` | | `List` | <32-list | `list![]`, …, `list![a1, …, a31]` | | `List` | <64-list | `list![]`, …, `list![a1, …, a62]` | | `List` | <128-list | `list![]`, …, `list![a1, …, a127]` | | `List` | <256-list | `list![]`, …, `list![a1, …, a255]` | | `List` | <512-list | `list![]`, …, `list![a1, …, a511]` | | … | … | … | | `List` | <2^N-list | `list![]`, …, `list![a1, …, a_{2^N - 1}]` | Lists hold a variable number of elements of the same type. This is similar to [Rust vectors](https://doc.rust-lang.org/std/vec/struct.Vec.html), but SimplicityHL doesn't have a heap. In SimplicityHL, lists exist on the stack, which is why the maximum list length is bounded. <2-lists hold fewer than 2 elements, so zero or one element. <4-lists hold fewer than 4 elements, so zero to three elements. <8-lists hold fewer than 8 elements, so zero to seven elements. And so on. For technical reasons, the list bound is always a power of two. The bound 1 is not supported, because it would only allow empty lists, which is useless. > Lists are different from arrays: > List values hold a variable number of elements. > Array values hold a fixed number of elements. On the blockchain, you pay for every byte that you use. If you use an array, then you pay for every single element. For example, values of type `[u8; 512]` cost roughly as much as 512 individual `u8` values. However, if you use a list, then you only pay for the elements that you actually use. For example, the type `List` allows for up to 511 elements. If you only use three elements `list![1, 2, 3]`, then you pay for exactly three elements. You **don't** pay for the remaining 508 unused elements. ## Option Types | Type | Values | |-------------|-----------------------------------| | `Option` | `None`, `Some(a0)`, `Some(a1)`, … | Options represent values that might not be present. [They work just like in Rust](https://doc.rust-lang.org/std/option/index.html). An option type is generic over a type `A`. The value `None` is empty. The value `Some(a)` contains an inner value `a` of type `A`. In Rust, we implement options as follows. ```rust enum Option { None, Some(A), } ``` ## Either Types | Type | Values | |----------------|--------------------------------------------------------| | `Either` | `Left(a0)`, `Left(a1)`, …, `Right(b0)`, `Right(b1)`, … | Sum types represent values that are of some "left" type in some cases and that are of another "right" type in other cases. [They work just like in the either crate](https://docs.rs/either/latest/either/enum.Either.html). [The Result type from Rust is very similar, too](https://doc.rust-lang.org/std/result/index.html). A sum type is generic over two types, `A` and `B`. The value `Left(a)` contains an inner value `a` of type `A`. The value `Right(b)` contains an inner value `b` of type `B`. In Rust, we implement sum types as follows. ```rust enum Either { Left(A), Right(B), } ``` ## Enum Types Enum types are available since SimplicityHL 0.7.0 (compiling with `-Z enums`). Enum types are declared with a stanza like ```rust enum A { B, C, D, E, } ``` This declaration allows the type `A` to take on any of four listed values, called `A::B`, `A::C`, `A::D`, and `A::E`. The declaration stanza belongs at the top level of the program, outside of any executable code block. Enum type values can also wrap objects of other types, when this is appropriately declared in the initial enum declaration. The `Enum` instances then contain inner variables of the specified types. ```rust enum A { B, C(T), D, E(U), } ``` In this declaration, `A::C` wraps another object of type `T`, while `A::E` wraps another object of type `U`. Enum types are intended for use in matching witness values with the `match` statement, so that one of a list of pre-specified actions or choices can be selected easily. By wrapping other values where needed, the additional values appropriate to a specific action or choice can also be provided in the witness. | Type | Values (pursuant to declaration of a specific Enum) | |----------------|--------------------------------------------------------| | `EnumX` | `EnumX::A(T)`, `EnumX::B(U)`, `EnumX::C(V)` …, | ### Type Aliases # Type Aliases SimplicityHL currently doesn't support Rust-like `struct`s for organizing data. ```rust struct User { active: bool, id: u256, sign_in_count: u64, } ``` SimplicityHL programmers have to handle long tuples of unlabeled data, which can get messy. ```rust (bool, u256, u64) ``` To help with the situation, programmers can define custom type aliases. Aliases define a new name for an existing type. In contrast, `struct`s define an entirely new type, so aliases are different from `struct`s. However, aliases still make the code more readable. ```rust type User = (bool, u256, u64); ``` There is also a list of builtin type aliases. These aliases can be used without defining them. | Builtin Alias | Definition | |------------------|-------------------------------| | `Amount1` | `Either<(u1, u256), u64>` | | `Asset1` | `Either<(u1, u256), u256>` | | `Confidential1` | `(u1, u256)` | | `Ctx8` | `(List, (u64, u256))` | | `Distance` | `u16` | | `Duration` | `u16` | | `ExplicitAmount` | `u256` | | `ExplicitAsset` | `u256` | | `ExplicitNonce` | `u256` | | `Fe` | `u256` | | `Ge` | `(u256, u256)` | | `Gej` | `((u256, u256), u256)` | | `Height` | `u32` | | `Lock` | `u32` | | `Message` | `u256` | | `Message64` | `[u8; 64]` | | `Nonce` | `Either<(u1, u256), u256>` | | `Outpoint` | `(u256, u32)` | | `Point` | `(u1, u256)` | | `Pubkey` | `u256` | | `Scalar` | `u256` | | `Signature` | `[u8; 64]` | | `Time` | `u32` | | `TokenAmount1` | `Either<(u1, u256), u64>` | ### Type Casting # Casting A SimplicityHL type can be cast into another SimplicityHL type if both types share the same structure. The structure of a type has to do with how the type is implemented on the Simplicity "processor". Below is a table of types that can be cast into each other. | Type | Casts To (And Back) | |----------------|------------------------------------| | `bool` | `Either<(), ()>` | | `Option` | `Either<(), A>` | | `u1` | `bool` | | `u2` | `(u1, u1)` | | `u4` | `(u2, u2)` | | `u8` | `(u4, u4)` | | `u16` | `(u8, u8)` | | `u32` | `(u16, u16)` | | `u64` | `(u32, u32)` | | `u128` | `(u64, u64)` | | `u256` | `(u128, u128)` | | `(A)` | `A` | | `(A, B, C)` | `(A, (B, C))` | | `(A, B, C, D)` | `((A, B), (C, D))` | | … | … | | `[A; 0]` | `()` | | `[A; 1]` | `A` | | `[A; 2]` | `(A, A)` | | `[A; 3]` | `(A, (A, A))` | | `[A; 4]` | `((A, A), (A, A))` | | … | … | | `List` | `Option` | | `List` | `(Option<[A; 2]>, List)` | | `List` | `(Option<[A; 4]>, List)` | | `List` | `(Option<[A; 8]>, List)` | | `List` | `(Option<[A; 16]>, List)` | | `List` | `(Option<[A; 32]>, List)` | | `List` | `(Option<[A; 64]>, List)` | | `List` | `(Option<[A; 128]>, List)` | | `List` | `(Option<[A; 256]>, List)` | | … | … | ## Casting Rules Type `A` can be cast into itself (reflexivity). If type `A` can be cast into type `B`, then type `B` can be cast into type `A` (symmetry). 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). ## Casting Expression All casting in SimplicityHL happens explicitly through a casting expression. ```rust ::into(input) ``` The above expression casts the value `input` of type `Input` into some output type. The input type of the cast is explicit while the output type is implicit. In SimplicityHL, the output type of every expression is known. ```rust let x: u32 = 1; ``` In the above example, the meaning of the expression `1` is clear because of the type `u32` of variable `x`. Here, `1` means a string of 31 zeroes and 1 one. _In other contexts, `1` could mean something different, like a string of 255 zeroes and 1 one._ The SimplicityHL compiler knows the type of the outermost expression, and it tries to infer the types of inner expressions based on that. When it comes to casting expressions, the compiler has no idea about the input type of the cast. The programmer needs to supply this information by annotating the cast with its input type. ```rust let x: u32 = <(u16, u16)>::into((0, 1)); ``` In the above example, the tuple `(0, 1)` of type `(u16, u16)` is cast into type `u32`. Consult the table above to verify this is a valid cast. ### Let Statements # Let Statement Variables are defined in let statements, [just like in Rust](https://doc.rust-lang.org/std/keyword.let.html). ```rust let x: u32 = 1; ``` The above let statement defines a variable called `x`. The variable is of type `u32` and it is assigned the value `1`. ```rust let x: u32 = f(1337); ``` Variables can be assigned to the output value of any expression, such as function calls. ## Explicit typing In SimplicityHL, the type of a defined variable **always** has to be written. This is different from Rust, which has better type inference. ## Immutability SimplicityHL variables are **always** immutable. There are no mutable variables. ## Redefinition and scoping The same variable can be defined twice in the same scope. The later definition overrides the earlier definition. ```rust let x: u32 = 1; let x: u32 = 2; assert!(jet::eq_32(x, 2)); // x == 2 ``` Normal scoping rules apply: Variables from outer scopes are available inside inner scopes. A variable defined in an inner scope shadows a variable of the same name from an outer scope. ```rust let x: u32 = 1; let y: u32 = 2; let z: u32 = { let x: u32 = 3; assert!(jet::eq_32(y, 2)); // y == 2 x }; assert!(jet::eq_32(z, 3)); // z == 3 ``` ## Pattern matching There is limited pattern matching support inside let statements. ```rust let (x, y, _): (u8, u16, u32) = (1, 2, 3); let [x, _, z]: [u32; 3] = [1, 2, 3]; ``` In the first line, the tuple `(1, 2, 3)` is deconstructed into the values `1`, `2` and `3`. These values are assigned to the variable names `x`, `y` and `_`. The variable name `_` is a special name that ignores its value. In the end, two variables are created: `x: u32 = 1` and `y: u16 = 2`. Similarly, arrays can be deconstructed element by element and assigned to a variable each. ### Match Expression # Match Expression A match expression conditionally executes code branches. Which branch is executed depends on the input to the match expression. ```rust let result: u32 = match f(42) { Left(x: u32) => x, Right(x: u16) => jet::left_pad_low_16_32(x), }; ``` In the above example, the output of the function call `f(42)` is matched. `f` returns an output of type `Either`. If `f(42)` returns a value that matches the pattern `Left(x: u32)`, then the first match arm is executed. This arm simply returns the value `x`. Alternatively, if `f(42)` returns a value that matches the pattern `Right(x: u16)`, then the second match arm is executed. This arm extends the 16-bit number `x` to a 32-bit number by padding its left with zeroes. Because of type constraints, the output of `f` must match one of these two patterns. The whole match expression returns a value of type `u32`, from one of the two arms. ## Explicit typing In SimplicityHL, the type of variables inside match arms must **always** be written. This is different from Rust, which has better type inference. ## Pattern matching There is limited support for pattern matching inside match expressions. Boolean values can be matched. The Boolean match expression is the replacement for an "if-then-else" in SimplicityHL. ```rust let bit_flip: bool = match false { false => true, true => false, }; ``` Optional values can be matched. The `Some` arm introduces a variable which must be explicitly typed. ```rust let unwrap_or_default: u32 = match Some(42) { None => 0, Some(x: u32) => x, }; ``` Finally, `Either` values can be matched. Again, variables that are introduced in match arms must be explicitly typed. ```rust let map_either: u32 = match Left(1337) { Left(x: u32) => f(x), Right(y: u32) => f(y), }; ``` Since SimplicityHL 0.5.0, the match expression also supports further pattern matching, similar to Rust. ```rust let unwrap_or_default: u32 = match Some((4, 2)) { None => 0, Some((y, z): (u16, u16)) => <(u16, u16)>::into((y, z)), }; ``` This is more concise than including code to perform the deconstruction inside the match arm. For example, the code above is a more concise alternative to this version that subsequently deconstructes the tuple `x` of type `(u16, u16)` into two integers `y` and `z` of type `u16`. ```rust let unwrap_or_default: u32 = match Some((4, 2)) { None => 0, Some(x: (u16, u16)) => { let (y, z): (u16, u16) = x; <(u16, u16)>::into((y, z)) } }; ``` The match arm can also contain match expressions for further deconstruction. For example, the sum value `x` of type `Either` can be matched as either `Left(y: u32)` or `Right(z: u32)`. ```rust let unwrap_or_default: u32 = match Some(Left(42)) { None => 0, Some(x: Either) => match x { Left(y: u32) => y, Right(z: u32) => z, }, }; ``` ## Enum matching Since SimplicityHL 0.7.0 (compiling with `-Z enums`), you can match on all values of an `enum` type. This is useful for *actions* at the top level of a contract's `main()` function. Matching an `enum` lets a [witness](../glossary.md#witness) choose from among several predefined actions. ```rust enum Action { Inherit(Signature), ColdSpend(Signature), HotSpend(Signature), } // ... match witness::ACTION { Action::Inherit(sig: Signature) => inherit_spend(sig), Action::ColdSpend(sig: Signature) => cold_spend(sig), Action::HotSpend(sig: Signature) => refresh_spend(sig), } ``` In this example, `witness::ACTION` is an object of type `Action` (with the explicit value either `Inherit(sig)`, `ColdSpend(sig)`, or `HotSpend(sig)`). The SimplicityHL program uses this value to learn which action the proposed transaction is attempting to take, and call the appropriate function to validate the conditions for that action. ## Only two branches per `match` Differently from Rust, the `match` expression only allows two match arms (branches) per `match` (except for `enum` types). For example, it is currently not possible to match `Left(Left(x: u8))`, `Left(Right(x: u8)`, `Right(Left(x: u8))`, and `Right(Right(x: u8))` as arms of a single `match` expression. Matching these four possibilities instead requires two nested `match` expressions. This limitation does not apply to the `enum` matching described above. A `match` on an `enum` type should contain exactly one branch for each declared value of that `enum` type, regardless of how many values the `enum` type can take on. ### Defining Functions # Functions Functions are defined and called [just like in Rust](https://doc.rust-lang.org/std/keyword.fn.html). ```rust fn add(x: u32, y: u32) -> u32 { let (carry, sum): (bool, u32) = jet::add_32(x, y); match carry { true => panic!(), // overflow false => {}, // ok }; sum } ``` 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`. The body of the function is a block expression `{ ... }` that is executed from top to bottom. The function returns on the final line _(note the missing semicolon `;`)_. In the above example, `x` and `y` are added via the `add_32` jet. The function then checks if the carry is true, signaling an overflow, in which case it panics. On the last line, the value of `sum` is returned. The above function is called by writing its name `add` followed by a list of arguments `(40, 2)`. Each parameter needs an argument, so the list of arguments is as long as the list of parameters. Here, `x` is assigned the value `40` and `y` is assigned the value `2`. ```rust let z: u32 = add(40, 2); ``` ## No early returns SimplicityHL has no support for an early return via a "return" keyword. The only branching that is available is via [match expressions](./match_expression.md). ## No recursion SimplicityHL has no support for recursive function calls. A function can be called inside a function body if it has been defined before. This means that a function cannot call itself. Loops, where `f` calls `g` and `g` calls `f`, are also impossible. 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. ```rust fn level_0() -> u32 { 0 } fn level_1() -> u32 { let (_, next) = jet::increment_32(level_0()); next } fn level_2() -> u32 { let (_, next) = jet::increment_32(level_1()); next } ``` ## Order matters If function `g` calls function `f`, then `f` **must** be defined before `g`. ```rust fn f() -> u32 { 42 } fn g() -> u32 { f() } ``` ## Main function The `main` function is the entry point of each SimplicityHL program. Running a program means running its `main` function. Other functions are called from the `main` function. ```rust fn main() { // ... } ``` The `main` function is a reserved name and must exist in every program. SimplicityHL programs are always "binaries". There is no support for "libraries". ## Jets Jets are predefined and optimized functions for common use cases. ```rust jet::add_32(40, 2) ``` Jets live inside the namespace `jet`, which is why they are prefixed with `jet::`. They can be called without defining them manually. It is usually more efficient to call a jet than to manually compute a value. [The jet documentation](https://docs.rs/simfony-as-rust/latest/simfony_as_rust/jet/index.html) lists each jet and explains what it does. ### Programs # Programs A SimplicityHL program consists of a `main` [function](./function.md). A program may also have [type aliases](./type_alias.md) or custom [function definitions](./function.md). The `main` function comes last in the program, because everything it calls must be defined before it. ```rust type Furlong = u32; type Mile = u32; fn to_miles(distance: Either) -> Mile { match distance { Left(furlongs: Furlong) => jet::divide_32(furlongs, 8), Right(miles: Mile) => miles, } } fn main() { let eight_furlongs: Either = Left(8); let one_mile: Either = Right(1); assert!(jet::eq_32(1, to_miles(eight_furlongs))); assert!(jet::eq_32(1, to_miles(one_mile))); } ``` ### Built-in Functions # Built-in functions [SimplicityHL](../glossary.md#simplicityhl) offers several built-in functions and macros. These are additional to the hundreds of [jets](../../documentation/jets/) provided by the Simplicity Elements integration. |
Function
| Description | |-------------|-----------------------------------| | `array_fold()` | Repeatedly apply a function to each element of an array, starting with an initial accumulator value. | | `assert!()` | Require that a boolean value is `true`. Panic otherwise. | | `dbg!()` | Do-nothing function used as a debugger marker. | | `fold()` | Repeatedly apply a function to each element of a list, starting with an initial accumulator value. | | `for_while()` | Perform a bounded loop by repeatedly calling a function with an incrementing counter variable. Permits an explicit early exit from the loop.

**Note: can be computationally expensive.** | | `::into()` | Perform native type conversions. See [type casting](../type_casting/) for more details. | | `is_none()` | Check whether an `Option` value is `None`, returning `true` or `false`. | | `panic!` | Immediately abort the current program, rejecting the currently proposed transaction. | | `unwrap` | Require that a value of type `Option` is `Some`, extracting the underlying element of type `T`. Panics if the given value is `None` instead. | | `unwrap_left` | Require that a value of type `Either` is `Left`, extracting the underlying element of type `T`. Panics if the given value is `Right` instead. | | `unwrap_right` | Require that a value of type `Either` is `Right`, extracting the underlying element of type `U`. Panics if the given value is `Left` instead. | ## Examples ### `array_fold` ```rust fn sum(elt: u32, acc: u32) -> u32 { let (_, acc): (bool, u32) = jet::add_32(elt, acc); acc } fn main() { let arr: [u32; 7] = [1, 2, 3, 4, 5, 6, 7]; let sum: u32 = array_fold::(arr, 0); assert!(jet::eq_32(sum, 28)); } ``` ### `assert!` ```rust fn main(){ let (_, total): (bool, u32) = jet::add_32(1, 1); assert!(jet::eq_32(total, 2)); } ``` ### `fold` ```rust fn sum(elt: u32, acc: u32) -> u32 { let (_, acc): (bool, u32) = jet::add_32(elt, acc); acc } fn main() { let xs: List = list![1, 2, 3]; let s: u32 = fold::(xs, 0); assert!(jet::eq_32(s, 6)); } ``` ### `for_while` Detailed example of calculating a hash over an entire array (from `SimplicityHL/examples/hash_loop.simf`). ??? "Click to show" ```rust // Add counter to streaming hash and finalize when the loop exists fn hash_counter_8(ctx: Ctx8, unused: (), byte: u8) -> Either { let new_ctx: Ctx8 = jet::sha_256_ctx_8_add_1(ctx, byte); match jet::all_8(byte) { true => Left(jet::sha_256_ctx_8_finalize(new_ctx)), false => Right(new_ctx), } } // Add counter to streaming hash and finalize when the loop exists fn hash_counter_16(ctx: Ctx8, unused: (), bytes: u16) -> Either { let new_ctx: Ctx8 = jet::sha_256_ctx_8_add_2(ctx, bytes); match jet::all_16(bytes) { true => Left(jet::sha_256_ctx_8_finalize(new_ctx)), false => Right(new_ctx), } } fn main() { // Hash bytes 0x00 to 0xff let ctx: Ctx8 = jet::sha_256_ctx_8_init(); let out: Either = for_while::(ctx, ()); let expected: u256 = 0x40aff2e9d2d8922e47afd4648e6967497158785fbd1da870e7110266bf944880; assert!(jet::eq_256(expected, unwrap_left::(out))); // Hash bytes 0x0000 to 0xffff // This takes ~10 seconds on my computer // let ctx: Ctx8 = jet::sha_256_ctx_8_init(); // let out: Either = for_while::(ctx, ()); // let expected: u256 = 0x281f79f89f0121c31db2bea5d7151db246349b25f5901c114505c18bfaa50ba1; // assert!(jet::eq_256(expected, unwrap_left::(out))); } ``` ### `into` ```rust fn not(bit: bool) -> bool { ::into(jet::complement_1(::into(bit))) } ``` See [type casting](../type_casting/) for more details. ### `is_none` Many uses of `is_none` are more simply handled with `unwrap()` or `match`, but it is available where desired. ```rust let existing_key: Pubkey = get_existing_pubkey(); if is_none::(witness::NEW_PUBKEY) { require_pubkey(existing_key) } else { require_pubkey(unwrap(witness::NEW_PUBKEY)) }; ``` This is equivalent to the `match` version ```rust let existing_key: Pubkey = get_existing_pubkey(); match witness::NEW_PUBKEY { None => require_pubkey(existing_key), Some(new_key: Pubkey) => require_pubkey(new_key), } ``` ### `panic!` ```rust // This requires that the specified witness include a Left(Signature) // rather than a Right(Signature). fn main(){ match witness::LEFT_OR_RIGHT { Left(s: Signature) => validate_signature(s), Right(_: Signature) => panic!(), } } ``` ### `unwrap`, `unwrap_left`, `unwrap_right` A simple example of `unwrap()` appears in [`last_will.simf`](https://github.com/BlockstreamResearch/SimplicityHL/blob/master/examples/last_will.simf). ```rust assert!(unwrap(jet::output_is_fee(1))); ``` `jet::output_is_fee()` returns `Option` to handle the case where the specified output does not exist. That is, the jet returns `None` if the specified output doesn't exist, `Some(true)` if the specified output is a fee output, and `Some(false)` if the specified output is a non-fee output. The code above requires that the return value is `Some(true)`. `unwrap()` panics if the return value is `None`, while `assert!()` panics if the unwrapped value returned by `unwrap()` is `false`. More examples of `unwrap()`, as well as `unwrap_left()` and `unwrap_right()`, can be found in the [options contract](https://github.com/BlockstreamResearch/simplicity-contracts/blob/main/crates/contracts/src/finance/options/source_simf/options.simf). ### Jets Reference # Jets reference Simplicity jets are built-in functions which you can call to efficiently perform various computations, including some related to arithmetic, logic, and cryptography. ## Uses of jets Jet calls are currently used in SimplicityHL to perform many operations, such as integer comparisons or arithmetic, that have dedicated notation in other programming languages. * For example, in SimplicityHL, you check whether two integers are equal with a call to a jet such as `jet::eq_32`. Some jets allow a Simplicity program to refuse a proposed transaction by performing a mandatory assertion (these jets' return type is `()` below). The "panic" or failure effect produced by these jets is the *only* way to decline a transaction, so every program will need to call one or more of these jets directly or indirectly. * For example, `jet::bip_0340_verify` checks a digital signature and refuses the transaction if the signature cannot be verified. Jets also provide [introspection](../glossary.md#introspection) of the currently proposed transaction's inputs and outputs. * For example, `jet::output_script_hash` provides the cryptographic identity of the program that controls a specified output of the proposed transaction. This can be used to require that assets are sent back to a copy of a specific [program](../glossary.md#program) (a [covenant](../glossary.md#covenant)). ## Jet list Here is a complete list of the available jets in the Elements Simplicity integration on Liquid Network, their [type signatures](../../simplicityhl-reference/type/), and a description of what they do. ### Multi-bit logic ???+ "Click to hide" |
Jet
| Description | | ----------------------------------- | ----------- | | `all_8(u8) -> bool` | Check if the value is `u8::MAX`. | | `all_16(u16) -> bool` | Check if the value is `u16::MAX`. | | `all_32(u32) -> bool` | Check if the value is `u32::MAX`. | | `all_64(u64) -> bool` | Check if the value is `u64::MAX`. | | `and_1(u1, u1) -> u1` | Bitwise AND of two 1-bit values. | | `and_8(u8, u8) -> u8` | Bitwise AND of two 8-bit values. | | `and_16(u16, u16) -> u16` | Bitwise AND of two 16-bit values. | | `and_32(u32, u32) -> u32` | Bitwise AND of two 32-bit values | | `and_64(u64, u64) -> u64` | Bitwise AND of two 64-bit values | | `ch_1(u1, u1, u1) -> u1` | Bitwise CHOICE of a bit and two 1-bit values. If the bit is true, then take the first value, else take the second value. | | `ch_8(u8, u8, u8) -> u8` | Bitwise CHOICE of a bit and two 8-bit values. If the bit is true, then take the first value, else take the second value. | | `ch_16(u16, (u16, u16)) -> u16` | Bitwise CHOICE of a bit and two 16-bit values. If the bit is true, then take the first value, else take the second value. | | `ch_32(u32, (u32, u32)) -> u32` | Bitwise CHOICE of a bit and two 32-bit values. If the bit is true, then take the first value, else take the second value. | | `ch_64(u64, (u64, u64)) -> u64` | Bitwise CHOICE of a bit and two 64-bit values. If the bit is true, then take the first value, else take the second value. | | `complement_1(u1) -> u1` | Bitwise NOT of a 1-bit value. | | `complement_8(u8) -> u8` | Bitwise NOT of an 8-bit value. | | `complement_16(u16) -> u16` | Bitwise NOT of a 16-bit value. | | `complement_32(u32) -> u32` | Bitwise NOT of a 32-bit value. | | `complement_64(u64) -> u64` | Bitwise NOT of a 64-bit value. | | `eq_1(u1, u1) -> bool` | Check if two 1-bit values are equal. | | `eq_8(u8, u8) -> bool` | Check if two 8-bit values are equal. | | `eq_16(u16, u16) -> bool` | Check if two 16-bit values are equal. | | `eq_32(u32, u32) -> bool` | Check if two 32-bit values are equal. | | `eq_64(u64, u64) -> bool` | Check if two 64-bit values are equal. | | `eq_256(u256, u256) -> bool` | Check if two 256-bit values are equal. | | `full_left_shift_16_1(u16, u1) -> (u1, u16)` | Helper for left-shifting bits. The bits are shifted from a 1-bit value into a 16-bit value. Return the shifted value and the 1 bit that was shifted out. | | `full_left_shift_16_2(u16, u2) -> (u2, u16)` | Helper for left-shifting bits. The bits are shifted from a 2-bit value into a 16-bit value. Return the shifted value and the 2 bits that were shifted out. | | `full_left_shift_16_4(u16, u4) -> (u4, u16)` | Helper for left-shifting bits. The bits are shifted from a 4-bit value into a 16-bit value. Return the shifted value and the 4 bits that were shifted out. | | `full_left_shift_16_8(u16, u8) -> (u8, u16)` | Helper for left-shifting bits. The bits are shifted from an 8-bit value into a 16-bit value. Return the shifted value and the 8 bits that were shifted out. | | `full_left_shift_32_1(u32, u1) -> (u1, u32)` | Helper for left-shifting bits. The bits are shifted from a 1-bit value into a 32-bit value. Return the shifted value and the 1 bit that was shifted out. | | `full_left_shift_32_2(u32, u2) -> (u2, u32)` | Helper for left-shifting bits. The bits are shifted from a 2-bit value into a 32-bit value. Return the shifted value and the 2 bits that were shifted out. | | `full_left_shift_32_4(u32, u4) -> (u4, u32)` | Helper for left-shifting bits. The bits are shifted from a 4-bit value into a 32-bit value. Return the shifted value and the 4 bits that were shifted out. | | `full_left_shift_32_8(u32, u8) -> (u8, u32)` | Helper for left-shifting bits. The bits are shifted from an 8-bit value into a 32-bit value. Return the shifted value and the 8 bits that were shifted out. | | `full_left_shift_32_16(u32, u16) -> (u16, u32)` | Helper for left-shifting bits. The bits are shifted from a 16-bit value into a 32-bit value. Return the shifted value and the 16 bits that were shifted out. | | `full_left_shift_64_1(u64, u1) -> (u1, u64)` | Helper for left-shifting bits. The bits are shifted from a 1-bit value into a 64-bit value. Return the shifted value and the 1 bit that was shifted out. | | `full_left_shift_64_2(u64, u2) -> (u2, u64)` | Helper for left-shifting bits. The bits are shifted from a 2-bit value into a 64-bit value. Return the shifted value and the 2 bits that were shifted out. | | `full_left_shift_64_4(u64, u4) -> (u4, u64)` | Helper for left-shifting bits. The bits are shifted from a 4-bit value into a 64-bit value. Return the shifted value and the 4 bits that were shifted out. | | `full_left_shift_64_8(u64, u8) -> (u8, u64)` | Helper for left-shifting bits. The bits are shifted from an 8-bit value into a 64-bit value. Return the shifted value and the 8 bits that were shifted out. | | `full_left_shift_64_16(u64, u16) -> (u16, u64)` | Helper for left-shifting bits. The bits are shifted from a 16-bit value into a 64-bit value. Return the shifted value and the 16 bits that were shifted out. | | `full_left_shift_64_32(u64, u32) -> (u32, u64)` | Helper for left-shifting bits. The bits are shifted from a 32-bit value into a 64-bit value. Return the shifted value and the 32 bits that were shifted out. | | `full_left_shift_8_1(u8, u1) -> (u1, u8)` | Helper for left-shifting bits. The bits are shifted from a 1-bit value into an 8-bit value. Return the shifted value and the 1 bit that was shifted out. | | `full_left_shift_8_2(u8, u2) -> (u2, u8)` | Helper for left-shifting bits. The bits are shifted from a 2-bit value into an 8-bit value. Return the shifted value and the 2 bits that were shifted out. | | `full_left_shift_8_4(u8, u4) -> (u4, u8)` | Helper for left-shifting bits. The bits are shifted from a 4-bit value into an 8-bit value. Return the shifted value and the 4 bits that were shifted out. | | `full_right_shift_16_1(u1, u16) -> (u16, u1)` | Helper for right-shifting bits. The bits are shifted from a 1-bit value into a 16-bit value. Return the shifted value and the 1 bit that was shifted out. | | `full_right_shift_16_2(u2, u16) -> (u16, u2)` | Helper for right-shifting bits. The bits are shifted from a 2-bit value into a 16-bit value. Return the shifted value and the 2 bits that were shifted out. | | `full_right_shift_16_4(u4, u16) -> (u16, u4)` | Helper for right-shifting bits. The bits are shifted from a 4-bit value into a 16-bit value. Return the shifted value and the 4 bits that were shifted out. | | `full_right_shift_16_8(u8, u16) -> (u16, u8)` | Helper for right-shifting bits. The bits are shifted from an 8-bit value into a 16-bit value. Return the shifted value and the 8 bits that were shifted out. | | `full_right_shift_32_1(u1, u32) -> (u32, u1)` | Helper for right-shifting bits. The bits are shifted from a 1-bit value into a 32-bit value. Return the shifted value and the 1 bit that was shifted out. | | `full_right_shift_32_2(u2, u32) -> (u32, u2)` | Helper for right-shifting bits. The bits are shifted from a 2-bit value into a 32-bit value. Return the shifted value and the 2 bits that were shifted out. | | `full_right_shift_32_4(u4, u32) -> (u32, u4)` | Helper for right-shifting bits. The bits are shifted from a 4-bit value into a 32-bit value. Return the shifted value and the 4 bits that were shifted out. | | `full_right_shift_32_8(u8, u32) -> (u32, u8)` | Helper for right-shifting bits. The bits are shifted from an 8-bit value into a 32-bit value. Return the shifted value and the 8 bits that were shifted out. | | `full_right_shift_32_16(u16, u32) -> (u32, u16)` | Helper for right-shifting bits. The bits are shifted from a 16-bit value into a 32-bit value. Return the shifted value and the 16 bits that were shifted out. | | `full_right_shift_64_1(u1, u64) -> (u64, u1)` | Helper for right-shifting bits. The bits are shifted from a 1-bit value into a 64-bit value. Return the shifted value and the 1 bit that was shifted out. | | `full_right_shift_64_2(u2, u64) -> (u64, u2)` | Helper for right-shifting bits. The bits are shifted from a 2-bit value into a 64-bit value. Return the shifted value and the 2 bits that were shifted out. | | `full_right_shift_64_4(u4, u64) -> (u64, u4)` | Helper for right-shifting bits. The bits are shifted from a 4-bit value into a 64-bit value. Return the shifted value and the 4 bits that were shifted out. | | `full_right_shift_64_8(u8, u64) -> (u64, u8)` | Helper for right-shifting bits. The bits are shifted from an 8-bit value into a 64-bit value. Return the shifted value and the 8 bits that were shifted out. | | `full_right_shift_64_16(u16, u64) -> (u64, u16)` | Helper for right-shifting bits. The bits are shifted from a 16-bit value into a 64-bit value. Return the shifted value and the 16 bits that were shifted out. | | `full_right_shift_64_32(u32, u64) -> (u64, u32)` | Helper for right-shifting bits. The bits are shifted from a 32-bit value into a 64-bit value. Return the shifted value and the 32 bits that were shifted out. | | `full_right_shift_8_1(u1, u8) -> (u8, u1)` | Helper for right-shifting bits. The bits are shifted from a 1-bit value into an 8-bit value. Return the shifted value and the 1 bit that was shifted out. | | `full_right_shift_8_2(u2, u8) -> (u8, u2)` | Helper for right-shifting bits. The bits are shifted from a 2-bit value into an 8-bit value. Return the shifted value and the 2 bits that were shifted out. | | `full_right_shift_8_4(u4, u8) -> (u8, u4)` | Helper for right-shifting bits. The bits are shifted from a 4-bit value into an 8-bit value. Return the shifted value and the 4 bits that were shifted out. | | `high_1() -> u1` | Return `u1::MAX` = 1. | | `high_8() -> u8` | Return `u8::MAX`. | | `high_16() -> u16` | Return `u16::MAX`. | | `high_32() -> u32` | Return `u32::MAX`. | | `high_64() -> u64` | Return `u64::MAX`. | | `left_extend_16_32(u16) -> u32` | Extend a 16-bit value to a 32-bit value by padding its left with the MSB. | | `left_extend_16_64(u16) -> u64` | Extend a 16-bit value to a 64-bit value by padding its left with the MSB. | | `left_extend_1_8(u1) -> u8` | Extend a 1-bit value to an 8-bit value by padding its left with the MSB. | | `left_extend_1_16(u1) -> u16` | Extend a 1-bit value to a 16-bit value by padding its left with the MSB. | | `left_extend_1_32(u1) -> u32` | Extend a 1-bit value to a 32-bit value by padding its left with the MSB. | | `left_extend_1_64(u1) -> u64` | Extend a 1-bit value to a 64-bit value by padding its left with the MSB. | | `left_extend_32_64(u32) -> u64` | Extend a 32-bit value to a 64-bit value by padding its left with the MSB. | | `left_extend_8_16(u8) -> u16` | Extend an 8-bit value to a 16-bit value by padding its left with the MSB. | | `left_extend_8_32(u8) -> u32` | Extend an 8-bit value to a 32-bit value by padding its left with the MSB. | | `left_extend_8_64(u8) -> u64` | Extend an 8-bit value to a 64-bit value by padding its left with the MSB. | | `left_pad_high_16_32(u16) -> u32` | Extend a 16-bit value to a 32-bit value by padding its left with ones. | | `left_pad_high_16_64(u16) -> u64` | Extend a 16-bit value to a 64-bit value by padding its left with ones. | | `left_pad_high_1_8(u1) -> u8` | Extend a 1-bit value to an 8-bit value by padding its left with ones. | | `left_pad_high_1_16(u1) -> u16` | Extend a 1-bit value to a 16-bit value by padding its left with ones. | | `left_pad_high_1_32(u1) -> u32` | Extend a 1-bit value to a 32-bit value by padding its left with ones. | | `left_pad_high_1_64(u1) -> u64` | Extend a 1-bit value to a 64-bit value by padding its left with ones. | | `left_pad_high_32_64(u32) -> u64` | Extend a 32-bit value to a 64-bit value by padding its left with ones. | | `left_pad_high_8_16(u8) -> u16` | Extend an 8-bit value to a 16-bit value by padding its left with ones. | | `left_pad_high_8_32(u8) -> u32` | Extend an 8-bit value to a 32-bit value by padding its left with ones. | | `left_pad_high_8_64(u8) -> u64` | Extend an 8-bit value to a 64-bit value by padding its left with ones. | | `left_pad_low_16_32(u16) -> u32` | Extend a 16-bit value to a 32-bit value by padding its left with zeroes. | | `left_pad_low_16_64(u16) -> u64` | Extend a 16-bit value to a 64-bit value by padding its left with zeroes. | | `left_pad_low_1_8(u1) -> u8` | Extend a 1-bit value to an 8-bit value by padding its left with zeroes. | | `left_pad_low_1_16(u1) -> u16` | Extend a 1-bit value to a 16-bit value by padding its left with zeroes. | | `left_pad_low_1_32(u1) -> u32` | Extend a 1-bit value to a 32-bit value by padding its left with zeroes. | | `left_pad_low_1_64(u1) -> u64` | Extend a 1-bit value to a 64-bit value by padding its left with zeroes. | | `left_pad_low_32_64(u32) -> u64` | Extend a 32-bit value to a 64-bit value by padding its left with zeroes. | | `left_pad_low_8_16(u8) -> u16` | Extend an 8-bit value to a 16-bit value by padding its left with zeroes. | | `left_pad_low_8_32(u8) -> u32` | Extend an 8-bit value to a 32-bit value by padding its left with zeroes. | | `left_pad_low_8_64(u8) -> u64` | Extend an 8-bit value to a 64-bit value by padding its left with zeroes. | | `left_rotate_8(u4, u8) -> u8` | Left-rotate an 8-bit value by the given amount. | | `left_rotate_16(u4, u16) -> u16` | Left-rotate a 16-bit value by the given amount. | | `left_rotate_32(u8, u32) -> u32` | Left-rotate a 32-bit value by the given amount. | | `left_rotate_64(u8, u64) -> u64` | Left-rotate a 64-bit value by the given amount. | | `left_shift_8(u4, u8) -> u8` | Left-shift an 8-bit value by the given amount. Bits are filled with zeroes. | | `left_shift_16(u4, u16) -> u16` | Left-shift a 16-bit value by the given amount. Bits are filled with zeroes. | | `left_shift_32(u8, u32) -> u32` | Left-shift a 32-bit value by the given amount. Bits are filled with zeroes. | | `left_shift_64(u8, u64) -> u64` | Left-shift a 64-bit value by the given amount. Bits are filled with zeroes. | | `left_shift_with_8(u1, u4, u8) -> u8` | Left-shift an 8-bit value by the given amount. Bits are filled with the given bit. | | `left_shift_with_16(u1, u4, u16) -> u16` | Left-shift a 16-bit value by the given amount. Bits are filled with the given bit. | | `left_shift_with_32(u1, u8, u32) -> u32` | Left-shift a 32-bit value by the given amount. Bits are filled with the given bit. | | `left_shift_with_64(u1, u8, u64) -> u64` | Left-shift a 64-bit value by the given amount. Bits are filled with the given bit. | | `leftmost_16_1(u16) -> u1` | Return the most significant 1 bit of a 16-bit value. | | `leftmost_16_2(u16) -> u2` | Return the most significant 2 bits of a 16-bit value. | | `leftmost_16_4(u16) -> u4` | Return the most significant 4 bits of a 16-bit value. | | `leftmost_16_8(u16) -> u8` | Return the most significant 8 bits of a 16-bit value. | | `leftmost_32_1(u32) -> u1` | Return the most significant 1 bit of a 32-bit value. | | `leftmost_32_2(u32) -> u2` | Return the most significant 2 bits of a 32-bit value. | | `leftmost_32_4(u32) -> u4` | Return the most significant 4 bits of a 32-bit value. | | `leftmost_32_8(u32) -> u8` | Return the most significant 8 bits of a 32-bit value. | | `leftmost_32_16(u32) -> u16` | Return the most significant 16 bits of a 32-bit value. | | `leftmost_64_1(u64) -> u1` | Return the most significant 1 bit of a 64-bit value. | | `leftmost_64_2(u64) -> u2` | Return the most significant 2 bits of a 64-bit value. | | `leftmost_64_4(u64) -> u4` | Return the most significant 4 bits of a 64-bit value. | | `leftmost_64_8(u64) -> u8` | Return the most significant 8 bits of a 64-bit value. | | `leftmost_64_16(u64) -> u16` | Return the most significant 16 bits of a 64-bit value. | | `leftmost_64_32(u64) -> u32` | Return the most significant 32 bits of a 64-bit value. | | `leftmost_8_1(u8) -> u1` | Return the most significant 1 bit of an 8-bit value. | | `leftmost_8_2(u8) -> u2` | Return the most significant 2 bits of an 8-bit value. | | `leftmost_8_4(u8) -> u4` | Return the most significant 4 bits of an 8-bit value. | | `low_1() -> u1` | Return `u1::MIN` = 0. | | `low_8() -> u8` | Return `u8::MIN` = 0. | | `low_16() -> u16` | Return `u16::MIN` = 0. | | `low_32() -> u32` | Return `u32::MIN` = 0. | | `low_64() -> u64` | Return `u64::MIN` = 0. | | `maj_1(u1, u1, u1) -> u1` | Bitwise MAJORITY of three 1-bit values. The output bit is false if two or more input bits are false, and true otherwise. | | `maj_8(u8, u8, u8) -> u8` | Bitwise MAJORITY of three 8-bit values. The output bit is false if two or more input bits are false, and true otherwise. | | `maj_16(u16, (u16, u16)) -> u16` | Bitwise MAJORITY of three 16-bit values. The output bit is false if two or more input bits are false, and true otherwise. | | `maj_32(u32, (u32, u32)) -> u32` | Bitwise MAJORITY of three 32-bit values. The output bit is false if two or more input bits are false, and true otherwise. | | `maj_64(u64, (u64, u64)) -> u64` | Bitwise MAJORITY of three 64-bit values. The output bit is false if two or more input bits are false, and true otherwise. | | `or_1(u1, u1) -> u1` | Bitwise OR of two 1-bit values. | | `or_8(u8, u8) -> u8` | Bitwise OR of two 8-bit values. | | `or_16(u16, u16) -> u16` | Bitwise OR of two 16-bit values. | | `or_32(u32, u32) -> u32` | Bitwise OR of two 32-bit values. | | `or_64(u64, u64) -> u64` | Bitwise OR of two 64-bit values. | | `right_extend_16_32(u16) -> u32` | Extend a 16-bit value to a 32-bit value by padding its right with the MSB. | | `right_extend_16_64(u16) -> u64` | Extend a 16-bit value to a 64-bit value by padding its right with the MSB. | | `right_extend_32_64(u32) -> u64` | Extend a 16-bit value to a 64-bit value by padding its right with the MSB. | | `right_extend_8_16(u8) -> u16` | Extend an 8-bit value to a 16-bit value by padding its right with the MSB. | | `right_extend_8_32(u8) -> u32` | Extend an 8-bit value to a 32-bit value by padding its right with the MSB. | | `right_extend_8_64(u8) -> u64` | Extend an 8-bit value to a 64-bit value by padding its right with the MSB. | | `right_pad_high_16_32(u16) -> u32` | Extend a 16-bit value to a 32-bit value by padding its right with ones. | | `right_pad_high_16_64(u16) -> u64` | Extend a 16-bit value to a 64-bit value by padding its right with ones. | | `right_pad_high_1_8(u1) -> u8` | Extend a 1-bit value to an 8-bit value by padding its right with ones. | | `right_pad_high_1_16(u1) -> u16` | Extend a 1-bit value to a 16-bit value by padding its right with ones. | | `right_pad_high_1_32(u1) -> u32` | Extend a 1-bit value to a 32-bit value by padding its right with ones. | | `right_pad_high_1_64(u1) -> u64` | Extend a 1-bit value to a 64-bit value by padding its right with ones. | | `right_pad_high_32_64(u32) -> u64` | Extend a 32-bit value to a 64-bit value by padding its right with ones. | | `right_pad_high_8_16(u8) -> u16` | Extend an 8-bit value to a 16-bit value by padding its right with ones. | | `right_pad_high_8_32(u8) -> u32` | Extend an 8-bit value to a 32-bit value by padding its right with ones. | | `right_pad_high_8_64(u8) -> u64` | Extend a 1-bit value to a 64-bit value by padding its right with ones. | | `right_pad_low_16_32(u16) -> u32` | Extend a 16-bit value to a 32-bit value by padding its right with zeroes. | | `right_pad_low_16_64(u16) -> u64` | Extend a 16-bit value to a 64-bit value by padding its right with zeroes. | | `right_pad_low_1_8(u1) -> u8` | Extend a 1-bit value to an 8-bit value by padding its right with zeroes. | | `right_pad_low_1_16(u1) -> u16` | Extend a 1-bit value to a 16-bit value by padding its right with zeroes. | | `right_pad_low_1_32(u1) -> u32` | Extend a 1-bit value to a 32-bit value by padding its right with zeroes. | | `right_pad_low_1_64(u1) -> u64` | Extend a 1-bit value to a 64-bit value by padding its right with zeroes. | | `right_pad_low_32_64(u32) -> u64` | Extend a 32-bit value to a 64-bit value by padding its right with zeroes. | | `right_pad_low_8_16(u8) -> u16` | Extend an 8-bit value to a 16-bit value by padding its right with zeroes. | | `right_pad_low_8_32(u8) -> u32` | Extend an 8-bit value to a 32-bit value by padding its right with zeroes. | | `right_pad_low_8_64(u8) -> u64` | Extend an 8-bit value to a 64-bit value by padding its right with zeroes. | | `right_rotate_8(u4, u8) -> u8` | Right-rotate an 8-bit value by the given amount. | | `right_rotate_16(u4, u16) -> u16` | Right-rotate a 16-bit value by the given amount. | | `right_rotate_32(u8, u32) -> u32` | Right-rotate a 32-bit value by the given amount. | | `right_rotate_64(u8, u64) -> u64` | Right-rotate a 64-bit value by the given amount. | | `right_shift_8(u4, u8) -> u8` | Right-shift an 8-bit value by the given amount. Bits are filled with zeroes. | | `right_shift_16(u4, u16) -> u16` | Right-shift a 16-bit value by the given amount. Bits are filled with zeroes. | | `right_shift_32(u8, u32) -> u32` | Right-shift a 32-bit value by the given amount. Bits are filled with zeroes. | | `right_shift_64(u8, u64) -> u64` | Right-shift a 64-bit value by the given amount. Bits are filled with zeroes. | | `right_shift_with_8(u1, u4, u8) -> u8` | Right-shift an 8-bit value by the given amount. Bits are filled with the given bit. | | `right_shift_with_16(u1, u4, u16) -> u16` | Right-shift a 16-bit value by the given amount. Bits are filled with the given bit. | | `right_shift_with_32(u1, u8, u32) -> u32` | Right-shift a 32-bit value by the given amount. Bits are filled with the given bit. | | `right_shift_with_64(u1, u8, u64) -> u64` | Right-shift a 64-bit value by the given amount. Bits are filled with the given bit. | | `rightmost_16_1(u16) -> u1` | Return the least significant 1 bit of a 16-bit value. | | `rightmost_16_2(u16) -> u2` | Return the least significant 2 bits of a 16-bit value. | | `rightmost_16_4(u16) -> u4` | Return the least significant 4 bits of a 16-bit value. | | `rightmost_16_8(u16) -> u8` | Return the least significant 8 bits of a 16-bit value. | | `rightmost_32_1(u32) -> u1` | Return the least significant 1 bit of a 32-bit value. | | `rightmost_32_2(u32) -> u2` | Return the least significant 2 bits of a 32-bit value. | | `rightmost_32_4(u32) -> u4` | Return the least significant 4 bits of a 32-bit value. | | `rightmost_32_8(u32) -> u8` | Return the least significant 8 bits of a 32-bit value. | | `rightmost_32_16(u32) -> u16` | Return the least significant 16 bits of a 32-bit value. | | `rightmost_64_1(u64) -> u1` | Return the least significant 1 bit of a 64-bit value. | | `rightmost_64_2(u64) -> u2` | Return the least significant 2 bits of a 64-bit value. | | `rightmost_64_4(u64) -> u4` | Return the least significant 4 bits of a 64-bit value. | | `rightmost_64_8(u64) -> u8` | Return the least significant 8 bits of a 64-bit value. | | `rightmost_64_16(u64) -> u16` | Return the least significant 16 bits of a 64-bit value. | | `rightmost_64_32(u64) -> u32` | Return the least significant 32 bits of a 64-bit value. | | `rightmost_8_1(u8) -> u1` | Return the least significant 1 bit of an 8-bit value. | | `rightmost_8_2(u8) -> u2` | Return the least significant 2 bits of an 8-bit value. | | `rightmost_8_4(u8) -> u4` | Return the least significant 4 bits of an 8-bit value. | | `some_1(u1) -> bool` | Check if a 1-bit value is nonzero. | | `some_8(u8) -> bool` | Check if an 8-bit value is nonzero. | | `some_16(u16) -> bool` | Check if a 16-bit value is nonzero. | | `some_32(u32) -> bool` | Check if a 32-bit value is nonzero. | | `some_64(u64) -> bool` | Check if a 64-bit value is nonzero. | | `xor_1(u1, u1) -> u1` | Bitwise XOR of two 1-bit values. | | `xor_8(u8, u8) -> u8` | Bitwise XOR of two 8-bit values. | | `xor_16(u16, u16) -> u16` | Bitwise XOR of two 16-bit values. | | `xor_32(u32, u32) -> u32` | Bitwise XOR of two 32-bit values. | | `xor_64(u64, u64) -> u64` | Bitwise XOR of two 64-bit values. | | `xor_xor_1(u1, u1, u1) -> u1` | Bitwise XOR of three 1-bit values. | | `xor_xor_8(u8, u8, u8) -> u8` | Bitwise XOR of three 8-bit values. | | `xor_xor_16(u16, (u16, u16)) -> u16` | Bitwise XOR of three 16-bit values. | | `xor_xor_32(u32, (u32, u32)) -> u32` | Bitwise XOR of three 32-bit values. | | `xor_xor_64(u64, (u64, u64)) -> u64` | Bitwise XOR of three 64-bit values. | ### Arithmetic ???+ "Click to hide" |
Jet
| Description | | ----------------------------------- | ----------- | | `add_8(u8, u8) -> (bool, u8)` | Add two integers and return the carry. | | `add_16(u16, u16) -> (bool, u16)` | Add two integers and return the carry. | | `add_32(u32, u32) -> (bool, u32)` | Add two integers and return the carry. | | `add_64(u64, u64) -> (bool, u64)` | Add two integers and return the carry. | | `decrement_8(u8) -> (bool, u8)` | Decrement an integer by one and return the borrow bit. | | `decrement_16(u16) -> (bool, u16)` | Decrement an integer by one and return the borrow bit. | | `decrement_32(u32) -> (bool, u32)` | Decrement an integer by one and return the borrow bit. | | `decrement_64(u64) -> (bool, u64)` | Decrement an integer by one and return the borrow bit. | | `div_mod_8(u8, u8) -> (u8, u8)` | Divide the first integer by the second integer, and return the remainder. | | `div_mod_16(u16, u16) -> (u16, u16)` | Divide the first integer by the second integer, and return the remainder. | | `div_mod_32(u32, u32) -> (u32, u32)` | Divide the first integer by the second integer, and return the remainder. | | `div_mod_64(u64, u64) -> (u64, u64)` | Divide the first integer by the second integer, and return the remainder. | | `div_mod_128_64(u128, u64) -> (u64, u64)` | Divide the 128-bit integer `a` by the 64-bit integer `b`.
Return a tuple of the quotient `q` and the remainder `r`.

Use this jet to recursively define wide integer divisions.

## Preconditions
1. `q` < 2^64
2. 2^63 ≤ `b`

Return `(u64::MAX, u64::MAX)` when the preconditions are not satisfied. | | `divide_8(u8, u8) -> u8` | Divide the first integer by the second integer. | | `divide_16(u16, u16) -> u16` | Divide the first integer by the second integer. | | `divide_32(u32, u32) -> u32` | Divide the first integer by the second integer. | | `divide_64(u64, u64) -> u64` | Divide the first integer by the second integer. | | `divides_8(u8, u8) -> bool` | Check if the first integer is divisible by the second integer. | | `divides_16(u16, u16) -> bool` | Check if the first integer is divisible by the second integer. | | `divides_32(u32, u32) -> bool` | Check if the first integer is divisible by the second integer. | | `divides_64(u64, u64) -> bool` | Check if the first integer is divisible by the second integer. | | `full_add_8(bool, u8, u8) -> (bool, u8)` | Add two integers. Take a carry-in and return a carry-out. | | `full_add_16(bool, u16, u16) -> (bool, u16)` | Add two integers. Take a carry-in and return a carry-out. | | `full_add_32(bool, u32, u32) -> (bool, u32)` | Add two integers. Take a carry-in and return a carry-out. | | `full_add_64(bool, u64, u64) -> (bool, u64)` | Add two integers. Take a carry-in and return a carry-out. | | `full_decrement_8(bool, u8) -> (bool, u8)` | Decrement an integer by one. Take a borrow-in and return a borrow-out. | | `full_decrement_16(bool, u16) -> (bool, u16)` | Decrement an integer by one. Take a borrow-in and return a borrow-out. | | `full_decrement_32(bool, u32) -> (bool, u32)` | Decrement an integer by one. Take a borrow-in and return a borrow-out. | | `full_decrement_64(bool, u64) -> (bool, u64)` | Decrement an integer by one. Take a borrow-in and return a borrow-out. | | `full_increment_8(bool, u8) -> (bool, u8)` | Increment an integer by one. Take a carry-in and return a carry-out. | | `full_increment_16(bool, u16) -> (bool, u16)` | Increment an integer by one. Take a carry-in and return a carry-out. | | `full_increment_32(bool, u32) -> (bool, u32)` | Increment an integer by one. Take a carry-in and return a carry-out. | | `full_increment_64(bool, u64) -> (bool, u64)` | Increment an integer by one. Take a carry-in and return a carry-out. | | `full_multiply_8((u8, u8), (u8, u8)) -> u16` | Helper for multiplying integers. Take the product of the first pair of integers and add the sum of the second pair. | | `full_multiply_16((u16, u16), (u16, u16)) -> u32` | Helper for multiplying integers. Take the product of the first pair of integers and add the sum of the second pair. | | `full_multiply_32((u32, u32), (u32, u32)) -> u64` | Helper for multiplying integers. Take the product of the first pair of integers and add the sum of the second pair. | | `full_multiply_64((u64, u64), (u64, u64)) -> u128` | Helper for multiplying integers. Take the product of the first pair of integers and add the sum of the second pair. | | `full_subtract_8(bool, u8, u8) -> (bool, u8)` | Subtract the second integer from the first integer. Take a borrow-in and return a borrow-out. | | `full_subtract_16(bool, u16, u16) -> (bool, u16)` | Subtract the second integer from the first integer. Take a borrow-in and return a borrow-out. | | `full_subtract_32(bool, u32, u32) -> (bool, u32)` | Subtract the second integer from the first integer. Take a borrow-in and return a borrow-out. | | `full_subtract_64(bool, u64, u64) -> (bool, u64)` | Subtract the second integer from the first integer. Take a borrow-in and return a borrow-out. | | `increment_8(u8) -> (bool, u8)` | Increment an integer by one and return the carry. | | `increment_16(u16) -> (bool, u16)` | Increment an integer by one and return the carry. | | `increment_32(u32) -> (bool, u32)` | Increment an integer by one and return the carry. | | `increment_64(u64) -> (bool, u64)` | Increment an integer by one and return the carry. | | `is_one_8(u8) -> bool` | Check if an integer is one. | | `is_one_16(u16) -> bool` | Check if an integer is one. | | `is_one_32(u32) -> bool` | Check if an integer is one. | | `is_one_64(u64) -> bool` | Check if an integer is one. | | `is_zero_8(u8) -> bool` | Check if an integer is zero. | | `is_zero_16(u16) -> bool` | Check if an integer is zero. | | `is_zero_32(u32) -> bool` | Check if an integer is zero. | | `is_zero_64(u64) -> bool` | Check if an integer is zero. | | `le_8(u8, u8) -> bool` | Check if an integer is less than or equal to another integer. | | `le_16(u16, u16) -> bool` | Check if an integer is less than or equal to another integer. | | `le_32(u32, u32) -> bool` | Check if an integer is less than or equal to another integer. | | `le_64(u64, u64) -> bool` | Check if an integer is less than or equal to another integer. | | `lt_8(u8, u8) -> bool` | Check if an integer is less than another integer. | | `lt_16(u16, u16) -> bool` | Check if an integer is less than another integer. | | `lt_32(u32, u32) -> bool` | Check if an integer is less than another integer. | | `lt_64(u64, u64) -> bool` | Check if an integer is less than another integer. | | `max_8(u8, u8) -> u8` | Return the bigger of two integers. | | `max_16(u16, u16) -> u16` | Return the bigger of two integers. | | `max_32(u32, u32) -> u32` | Return the bigger of two integers. | | `max_64(u64, u64) -> u64` | Return the bigger of two integers. | | `median_8(u8, u8, u8) -> u8` | Return the median of three integers. | | `median_16(u16, u16, u16) -> u16` | Return the median of three integers. | | `median_32(u32, u32, u32) -> u32` | Return the median of three integers. | | `median_64(u64, u64, u64) -> u64` | Return the median of three integers. | | `min_8(u8, u8) -> u8` | Return the smaller of two integers. | | `min_16(u16, u16) -> u16` | Return the smaller of two integers. | | `min_32(u32, u32) -> u32` | Return the smaller of two integers. | | `min_64(u64, u64) -> u64` | Return the smaller of two integers. | | `modulo_8(u8, u8) -> u8` | Compute the remainder after dividing both integers. | | `modulo_16(u16, u16) -> u16` | Compute the remainder after dividing both integers. | | `modulo_32(u32, u32) -> u32` | Compute the remainder after dividing both integers. | | `modulo_64(u64, u64) -> u64` | Compute the remainder after dividing both integers. | | `multiply_8(u8, u8) -> u16` | Multiply two integers. The output is a 16-bit integer. | | `multiply_16(u16, u16) -> u32` | Multiply two integers. The output is a 32-bit integer. | | `multiply_32(u32, u32) -> u64` | Multiply two integers. The output is a 64-bit integer. | | `multiply_64(u64, u64) -> u128` | Multiply two integers. The output is a 128-bit integer. | | `negate_8(u8) -> (bool, u8)` | Negate the integer (modulo 2⁸) and return the borrow bit. | | `negate_16(u16) -> (bool, u16)` | Negate the integer (modulo 2¹⁶) and return the borrow bit. | | `negate_32(u32) -> (bool, u32)` | Negate the integer (modulo 2³²) and return the borrow bit. | | `negate_64(u64) -> (bool, u64)` | Negate the integer (modulo 2⁶⁴) and return the borrow bit. | | `one_8() -> u8` | Return 1 as an 8-bit integer. | | `one_16() -> u16` | Return 1 as a 16-bit integer. | | `one_32() -> u32` | Return 1 as a 32-bit integer. | | `one_64() -> u64` | Return 1 as a 64-bit integer. | | `subtract_8(u8, u8) -> (bool, u8)` | Subtract the second integer from the first integer, and return the borrow bit. | | `subtract_16(u16, u16) -> (bool, u16)` | Subtract the second integer from the first integer, and return the borrow bit. | | `subtract_32(u32, u32) -> (bool, u32)` | Subtract the second integer from the first integer, and return the borrow bit. | | `subtract_64(u64, u64) -> (bool, u64)` | Subtract the second integer from the first integer, and return the borrow bit. | ### Hash functions ???+ "Click to hide" |
Jet
| Description | | ----------------------------------- | ----------- | | `sha_256_block(u256, u256, u256) -> u256` | Update the given 256-bit midstate by running the SHA256 block compression function, using the given 512-bit block. | | `sha_256_ctx_8_add_1(Ctx8, u8) -> Ctx8` | Add 1 byte to the SHA256 hash engine. | | `sha_256_ctx_8_add_2(Ctx8, u16) -> Ctx8` | Add 2 bytes to the SHA256 hash engine. | | `sha_256_ctx_8_add_4(Ctx8, u32) -> Ctx8` | Add 4 bytes to the SHA256 hash engine. | | `sha_256_ctx_8_add_8(Ctx8, u64) -> Ctx8` | Add 8 bytes to the SHA256 hash engine. | | `sha_256_ctx_8_add_16(Ctx8, u128) -> Ctx8` | Add 16 bytes to the SHA256 hash engine. | | `sha_256_ctx_8_add_32(Ctx8, u256) -> Ctx8` | Add 32 bytes to the SHA256 hash engine. | | `sha_256_ctx_8_add_64(Ctx8, [u8; 64]) -> Ctx8` | Add 64 bytes to the SHA256 hash engine. | | `sha_256_ctx_8_add_128(Ctx8, [u8; 128]) -> Ctx8` | Add 128 bytes to the SHA256 hash engine. | | `sha_256_ctx_8_add_256(Ctx8, [u8; 256]) -> Ctx8` | Add 256 bytes to the SHA256 hash engine. | | `sha_256_ctx_8_add_512(Ctx8, [u8; 512]) -> Ctx8` | Add 512 bytes to the SHA256 hash engine. | | `sha_256_ctx_8_add_buffer_511(Ctx8, List) -> Ctx8` | Add a list of less than 512 bytes to the SHA256 hash engine. | | `sha_256_ctx_8_finalize(Ctx8) -> u256` | Produce a hash from the current state of the SHA256 hash engine. | | `sha_256_ctx_8_init() -> Ctx8` | Initialize a default SHA256 hash engine. | | `sha_256_iv() -> u256` | Return the SHA256 initial value. | ### Elliptic curve functions ???+ "Click to hide" |
Jet
| Description | | ----------------------------------- | ----------- | | `decompress(Point) -> Option` | Decompress a point into affine coordinates.

- Return `None` if the x-coordinate is not on the curve.
- Return `Some(ge)` even if the x-coordinate is not normalized. | | `fe_add(Fe, Fe) -> Fe` | Add two field elements. | | `fe_invert(Fe) -> Fe` | Compute the modular inverse of a field element. | | `fe_is_odd(Fe) -> bool` | Check if the canonical representative of the field element is odd. | | `fe_is_zero(Fe) -> bool` | Check if the field element represents zero. | | `fe_multiply(Fe, Fe) -> Fe` | Multiply two field elements. | | `fe_multiply_beta(Fe) -> Fe` | Multiply a field element by the canonical primitive cube root of unity (beta). | | `fe_negate(Fe) -> Fe` | Negate a field element. | | `fe_normalize(Fe) -> Fe` | Return the canonical representation of a field element. | | `fe_square(Fe) -> Fe` | Square a field element. | | `fe_square_root(Fe) -> Option` | Compute the modular square root of a field element if it exists. | | `ge_is_on_curve(Ge) -> bool` | Check if the given point satisfies the curve equation y² = x³ + 7. | | `ge_negate(Ge) -> Ge` | Negate a point. | | `gej_add(Gej, Gej) -> Gej` | Add two points. | | `gej_double(Gej) -> Gej` | Double a point. If the result is the point at infinity, it is returned in canonical form. | | `gej_equiv(Gej, Gej) -> bool` | Check if two points represent the same point. | | `gej_ge_add(Gej, Ge) -> Gej` | Add two points. If the result is the point at infinity, it is returned in canonical form. | | `gej_ge_add_ex(Gej, Ge) -> (Fe, Gej)` | Add two points. Also return the ratio of `a`'s z-coordinate and the result's z-coordinate. If the result is the point at infinity, it is returned in canonical form. | | `gej_ge_equiv(Gej, Ge) -> bool` | Check if two points represent the same point. | | `gej_infinity() -> Gej` | Return the canonical representation of the point at infinity. | | `gej_is_infinity(Gej) -> bool` | Check if the point represents infinity. | | `gej_is_on_curve(Gej) -> bool` | Check if the given point satisfies the curve equation y² = x³ + 7. | | `gej_negate(Gej) -> Gej` | Negate a point. | | `gej_normalize(Gej) -> Option` | Convert the point into affine coordinates with canonical field representatives. If the result is the point at infinity, it is returned in canonical form. | | `gej_rescale(Gej, Fe) -> Gej` | Change the representatives of a point by multiplying the z-coefficient by the given value. | | `gej_x_equiv(Fe, Gej) -> bool` | Check if the point represents an affine point with the given x-coordinate. | | `gej_y_is_odd(Gej) -> bool` | Check if the point represents an affine point with odd y-coordinate. | | `generate(Scalar) -> Gej` | Multiply the generator point with the given scalar. | | `hash_to_curve(u256) -> Ge` | A cryptographic hash function that results in a point on the secp256k1 curve.

This matches the hash function used to map asset IDs to asset commitments. | | `linear_combination_1((Scalar, Gej), Scalar) -> Gej` | Compute the linear combination `b * a + c * g` for point `b` and scalars `a` and `c`, where `g` is the generator point. | | `linear_verify_1(((Scalar, Ge), Scalar), Ge) -> ()` | Assert that a point `b` is equal to the linear combination `a.0 * a.1 + a.2 * g`, where `g` is the generator point.

## Panics
The assertion fails. | | `point_verify_1(((Scalar, Point), Scalar), Point) -> ()` | Assert that a point `b` is equal to the linear combination `a.0 * a.1 + a.2 * g`, where `g` is the generator point.

## Panics
- The assertion fails.
- Fails if the points cannot be decompressed. | | `scalar_add(Scalar, Scalar) -> Scalar` | Add two scalars. | | `scalar_invert(Scalar) -> Scalar` | Compute the modular inverse of a scalar. | | `scalar_is_zero(Scalar) -> bool` | Check if the scalar represents zero. | | `scalar_multiply(Scalar, Scalar) -> Scalar` | Multiply two scalars. | | `scalar_multiply_lambda(Scalar) -> Scalar` | Multiply a scalar with the canonical primitive cube of unity (lambda) | | `scalar_negate(Scalar) -> Scalar` | Negate a scalar. | | `scalar_normalize(Scalar) -> Scalar` | Return the canonical representation of the scalar. | | `scalar_square(Scalar) -> Scalar` | Square a scalar. | | `scale(Scalar, Gej) -> Gej` | Multiply a point by a scalar. | | `swu(Fe) -> Ge` | Algebraically distribute a field element over the secp256k1 curve as defined in ["Indifferentiable Hashing to Barreto-Naehrig Curves"](https://inria.hal.science/hal-01094321/file/FT12.pdf) by Pierre-Alain Fouque and Mehdi Tibouchi.

While this by itself is not a cryptographic hash function, it can be used as a subroutine in a `hash_to_curve` function. However, the distribution only approaches uniformity when it is called twice. | ### Digital signatures ???+ "Click to hide" |
Jet
| Description | | ----------------------------------- | ----------- | | `bip_0340_verify((Pubkey, Message), Signature) -> ()` | Assert that a Schnorr signature matches a public key and message.

## Panics
The assertion fails. | ### Bitcoin ???+ "Click to hide" |
Jet
| Description | | ----------------------------------- | ----------- | | `parse_lock(u32) -> Either` | Parse an integer as a consensus-encoded Bitcoin lock time. | | `parse_sequence(u32) -> Option>` | Parse an integer as a consensus-encoded Bitcoin sequence number. | | `tapdata_init() -> Ctx8` | Create a SHA256 context, initialized with a `TapData` tag. | ### Elements signature hash modes ???+ "Click to hide" |
Jet
| Description | | ----------------------------------- | ----------- | | `annex_hash(Ctx8, Option) -> Ctx8` | Continue a SHA256 hash with an optional hash by appending the following:
- If there is no hash, then the byte `0x00`.
- If there is a hash, then the byte `0x01` followed by the given hash (32 bytes). | | `asset_amount_hash(Ctx8, Asset1, Amount1) -> Ctx8` | Continue a SHA256 hash with the serialization of a confidential asset followed by the serialization of a amount. | | `build_tapbranch(u256, u256) -> u256` | Return the SHA256 hash of the following:
- The hash of the ASCII string `TapBranch/elements` (32 bytes).
- The lexicographically smaller of the two inputs (32 bytes).
- The hash of the ASCII string `TapBranch/elements` again (32 bytes).
- The lexicographically larger of the two inputs (32 bytes).

This builds a taproot from two branches. | | `build_tapleaf_simplicity(u256) -> u256` | Return the SHA256 hash of the following:
- The hash of the ASCII string `TapLeaf/elements` (32 bytes).
- The hash of the ASCII string `TapLeaf/elements` again (32 bytes).
- The Simplicity leaf version `0xbe` (1 byte).
- The byte `0x20` (1 byte).
- The input CMR (32 bytes).

This builds a tapleaf hash for a Simplicity program. | | `build_taptweak(Pubkey, u256) -> u256` | Implementation of `taproot_tweak_pubkey` from BIP-0341.

## Panics
1. The input x-only public key is off curve or exceeds the field size.
2. The internal hash value `t` exceeds the secp256k1 group order.
3. The generated tweaked point is infinity, and thus has no valid x-only public key.

Note that situations 2 and 3 are cryptographically impossible to occur. | | `input_amounts_hash() -> u256` | Return the SHA256 hash of the serialization of each input UTXO's asset and amount fields. | | `input_annexes_hash() -> u256` | Return the SHA256 hash of the concatenation of the following for every input:
- If the input has no annex, or isn't a taproot spend, then the byte `0x00`.
- If the input has an annex, then the byte `0x01` followed by the SHA256 hash of the annex (32 bytes). | | `input_hash(u32) -> Option` | Return the SHA256 hash of the following:
- If the input is not a pegin, then the byte `0x00`.
- If the input is a pegin, then the byte `0x01` followed by the parent chain's genesis hash (32 bytes).
- The input's serialized previous transaction ID (32 bytes).
- The input's previous transaction index in big endian format (4 bytes).
- The input's sequence number in big endian format (4 bytes).
- If the input has no annex, or isn't a taproot spend, then the byte `0x00`.
- If the input has an annex, then the byte `0x01` followed by the SHA256 hash of the annex (32 bytes).

Return `None` if the input does not exist. | | `input_outpoints_hash() -> u256` | Return the SHA256 hash of the concatenation of the following for every input:
- If the input is not a pegin, then the byte `0x00`.
- If the input is a pegin, then the byte `0x01` followed by the parent chain's genesis hash (32 bytes).
- The input's serialized previous transaction ID (32 bytes).
- The input's previous transaction index in big endian format (4 bytes).

IMPORTANT: the index is serialized in big endian format rather than little endian format. | | `input_script_sigs_hash() -> u256` | Return the SHA256 hash of the concatenation of the SHA256 hash of each input's scriptSig.

Note that if an input's UTXO uses segwit, then it's scriptSig will necessarily be the empty string. In such cases we still use the SHA256 hash of the empty string. | | `input_scripts_hash() -> u256` | Return the SHA256 hash of the concatenation of the SHA256 hash of each input UTXO's scriptPubKey. | | `input_sequences_hash() -> u256` | Return the SHA256 hash of the concatenation of the following for every input:
- The input's sequence number in big endian format (4 bytes).

IMPORTANT: the sequence number is serialized in big endian format rather than little endian format. | | `input_utxo_hash(u32) -> Option` | Return the SHA256 hash of the following:
- The serialization of the input UTXO's asset and amount fields.
- The SHA256 hash of the input UTXO's scriptPubKey.

Return `None` if the input does not exist. | | `input_utxos_hash() -> u256` | Return the SHA256 hash of the following:
- The result of `input_amounts_hash` (32 bytes).
- The result of `input_scripts_hash` (32 bytes). | | `inputs_hash() -> u256` | Return the SHA256 hash of the following:
- The result of `input_outpoints_hash` (32 bytes).
- The result of `input_sequences_hash` (32 bytes).
- The result of `input_annexes_hash` (32 bytes). | | `issuance_asset_amounts_hash() -> u256` | Return the SHA256 hash of the concatenation of the following for every input:
- If the input has no issuance then two bytes `0x00 0x00`.
- If the input is has a new issuance then the byte `0x01` followed by a serialization of the calculated issued asset id (32 bytes) followed by the serialization of the (possibly confidential) issued asset amount (9 bytes or 33 bytes).
- If the input is has a reissuance then the byte `0x01` followed by a serialization of the issued asset id (32 bytes), followed by the serialization of the (possibly confidential) issued asset amount (9 bytes or 33 bytes).

IMPORTANT: If there is an issuance but there are no asset issued (i.e. the amount is null) we serialize the value as the explicit 0 amount, (i.e. `0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00`).

Note, the issuance asset id is serialized in the same format as an explicit asset id would be. | | `issuance_blinding_entropy_hash() -> u256` | Return the SHA256 hash of the concatenation of the following for every input:
- If the input has no issuance then the byte `0x00`.
- If the input is has a new issuance then the byte `0x01` followed by 32 `0x00` bytes and the new issuance's contract hash field (32 bytes).
- If the input is has reissuance then the byte `0x01` followed by a serialization of the reissuance's blinding nonce field (32 bytes) and the reissuance's entropy field (32 bytes).

Note that if the issuance is a new issuance then the blinding nonce field is 32 `0x00` bytes and new issuance's contract hash. | | `issuance_hash(u32) -> Option` | Return the SHA256 hash of the following:
1. The asset issuance:
- If the input has no issuance then two bytes `0x00 0x00`.
- If the input is has a new issuance then the byte `0x01` followed by a serialization of the calculated issued asset id (32 bytes) followed by the serialization of the (possibly confidential) issued asset amount (9 bytes or 33 bytes).
- If the input is has a reissuance then the byte `0x01` followed by a serialization of the issued asset id (32 bytes), followed by the serialization of the (possibly confidential) issued asset amount (9 bytes or 33 bytes).
2. The token issuance:
- If the input has no issuance then two bytes `0x00 0x00`.
- If the input is has a new issuance then the byte `0x01` followed by a serialization of the calculated issued token id (32 bytes) followed by the serialization of the (possibly confidential) issued token amount (9 bytes or 33 bytes).
- If the input is has a reissuance then the byte `0x01` followed by a serialization of the issued token id (32 bytes), followed by the serialization of the explicit 0 amount (i.e `0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00`) (9 bytes).
3. The range proofs:
- The SHA256 hash of the range proof of the input's issuance asset amount (32 bytes).
- The SHA256 hash of the range proof of the input's issuance token amount (32 bytes).
4. The blinding entropy:
- If the input has no issuance then the byte `0x00`.
- If the input is has a new issuance then the byte `0x01` followed by 32 `0x00` bytes and the new issuance's contract hash field (32 bytes).
- If the input is has reissuance then the byte `0x01` followed by a serialization of the reissuance's blinding nonce field (32 bytes) and the reissuance's entropy field (32 bytes).

Return `None` if the input does not exist. | | `issuance_range_proofs_hash() -> u256` | Return the SHA256 hash of the concatenation of the following for every input:
- The SHA256 hash of the range proof of the input's issuance asset amount (32 bytes).
- The SHA256 hash of the range proof of the input's issuance token amount (32 bytes).

Note that each the range proof is considered to be the empty string in the case there is no issuance, or if the asset or token amount doesn't exist (i.e is null). The SHA256 hash of the empty string is still used in these cases. | | `issuance_token_amounts_hash() -> u256` | Return the SHA256 hash of the concatenation of the following for every input:
- If the input has no issuance then two bytes `0x00 0x00`.
- If the input is has a new issuance then the byte `0x01` followed by a serialization of the calculated issued token id (32 bytes) followed by the serialization of the (possibly confidential) issued token amount (9 bytes or 33 bytes).
- If the input is has a reissuance then the byte `0x01` followed by a serialization of the issued token id (32 bytes), followed by the serialization of the explicit 0 amount (i.e `0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00`) (9 bytes).

IMPORTANT: If there is an issuance but there are no tokens issued (i.e. the amount is null) we serialize the value as the explicit 0 amount, (i.e. `0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00`).

Note, the issuance token id is serialized in the same format as an explicit asset id would be. | | `issuances_hash() -> u256` | Return the SHA256 hash of the following:
- The result of `issuance_asset_amounts_hash` (32 bytes).
- The result of `issuance_token_amounts_hash` (32 bytes).
- The result of `issuance_range_proofs_hash` (32 bytes).
- The result of `issuance_blinding_entropy_hash` (32 bytes). | | `nonce_hash(Ctx8, Option) -> Ctx8` | Continue the SHA256 hash with the serialization of an optional nonce. | | `outpoint_hash(Ctx8, Option, Outpoint) -> Ctx8` | Continue the SHA256 hash with an optional pegin and an outpoint by appending the following:
- If the input is not a pegin, then the byte `0x00`.
- If the input is a pegin, then the byte `0x01` followed by the given parent genesis hash (32 bytes).
- The input's previous transaction ID (32 bytes).
- The input's previous transaction index in big endian format (4 bytes). | | `output_amounts_hash() -> u256` | Return the SHA256 hash of the serialization of each output's asset and amount fields. | | `output_hash(u32) -> Option` | Return the SHA256 hash of the following:
- The serialization of the output's asset and amount fields.
- The serialization of the output's nonce field.
- The SHA256 hash of the output's scriptPubKey.
- The SHA256 hash of the output's range proof.

Return `None` if the output does not exist.

Note: the result of `output_surjection_proofs_hash` is specifically excluded because surjection proofs are dependent on the inputs as well as the output. | | `output_nonces_hash() -> u256` | Return the SHA256 hash of the serialization of each output's nonce field. | | `output_range_proofs_hash() -> u256` | Return the SHA256 hash of the concatenation of the SHA256 hash of each output's range proof.

Note that if the output's amount is explicit then the range proof is considered the empty string. | | `output_scripts_hash() -> u256` | Return the SHA256 hash of the concatenation of the SHA256 hash of each output's scriptPubKey. | | `output_surjection_proofs_hash() -> u256` | Return the SHA256 hash of the concatenation of the SHA256 hash of each output's surjection proof.

Note that if the output's asset is explicit then the surjection proof is considered the empty string. | | `outputs_hash() -> u256` | Return the SHA256 hash of the following:
- The result of `output_amounts_hash` (32 bytes).
- The result of `output_nonces_hash` (32 bytes).
- The result of `output_scripts_hash` (32 bytes).
- The result of `output_range_proofs_hash` (32 bytes).

Note: the result of `output_surjection_proofs_hash` is specifically excluded because surjection proofs are dependent on the inputs as well as the output. See also `tx_hash`. | | `sig_all_hash() -> u256` | Return the SHA256 hash of the following:
- The result of `genesis_block_hash` (32 bytes).
- The result of `genesis_block_hash` again (32 bytes).
- The result of `tx_hash` (32 bytes).
- The result of `tap_env_hash` (32 bytes).
- The result of `current_index` (Note: this is in big endian format) (4 bytes).

Note: the two copies of the `genesis_block_hash` values effectively makes this result a BIP-340 style tagged hash. | | `tap_env_hash() -> u256` | Return the SHA256 hash of the following:
- The result of `tapleaf_hash` (32 bytes).
- The result of `tappath_hash` (32 bytes).
- The result of `internal_key` (32 bytes). | | `tapleaf_hash() -> u256` | Return the SHA256 hash of the following:
- The hash of the ASCII string `TapLeaf/elements` (32 bytes).
- The hash of the ASCII string `TapLeaf/elements` again (32 bytes).
- The result of `tapleaf_version` (1 byte).
- The byte `0x20` (1 byte).
- The result of `script_cmr` (32 bytes).

Note: this matches Elements' modified BIP-0341 definition of tapleaf hash. | | `tappath_hash() -> u256` | Return a hash of the current input's control block excluding the leaf version and the taproot internal key.

Using the notation of BIP-0341, it returns the SHA256 hash of c[33: 33 + 32m]. | | `tx_hash() -> u256` | Return the SHA256 hash of the following:
- The result of `version` (Note: this is in big endian format) (4 bytes).
- The result of `tx_lock_time` (Note: this is in big endian format) (4 bytes).
- The result of `inputs_hash` (32 bytes).
- The result of `outputs_hash` (32 bytes).
- The result of `issuances_hash` (32 bytes).
- The result of `output_surjection_proofs_hash` (32 bytes).
- The result of `input_utxos_hash` (32 bytes). | ### Time locks ???+ "Click to hide" |
Jet
| Description | | ----------------------------------- | ----------- | | `check_lock_height(Height) -> ()` | Assert that the value returned by `tx_lock_height` is greater than or equal to the given value.

## Panics
The assertion fails. | | `check_lock_time(Time) -> ()` | Assert that the value returned by `tx_lock_time` is greater than or equal to the given value.

## Panics
The assertion fails. | | `tx_is_final() -> bool` | Check if the sequence numbers of all transaction inputs are at their maximum value. | | `tx_lock_height() -> Height` | If `tx_is_final` returns false, then try to parse the transaction's lock time as a `Height` value. Return zeroes otherwise. | | `tx_lock_time() -> Time` | If `tx_is_final` returns false, then try to parse the transaction's lock time as a `Time` value. Return zeroes otherwise. | ### Issuance ???+ "Click to hide" |
Jet
| Description | | ----------------------------------- | ----------- | | `calculate_asset(u256) -> ExplicitAsset` | Calculate the issued asset id from a given entropy value. | | `calculate_confidential_token(u256) -> ExplicitAsset` | Calculate the reissuance token id from a given entropy value for assets with confidential issued amounts. | | `calculate_explicit_token(u256) -> ExplicitAsset` | Calculate the reissuance token id from a given entropy value for assets with explicit issued amounts. | | `calculate_issuance_entropy(Outpoint, u256) -> u256` | Calculate the entropy value from a given outpoint and contract hash.

This entropy value is used to compute issued asset and token IDs. | | `issuance(u32) -> Option>` | Return the kind of issuance of the input at the given index:
- Return `Some(Some(false))` if the input has new issuance.
- Return `Some(Some(true))` if the input has reissuance.
- Return `Some(None)` if the input has no issuance.
- Return `None` if the input does not exist. | | `issuance_asset(u32) -> Option>` | Return the ID of the issued asset of the input at the given index:
- Return `Some(Some(x))` if the input has issuance with asset id `x`.
- Return `Some(None)` if the input has no issuance.
- Return `None` if the input does not exist. | | `issuance_entropy(u32) -> Option>` | Return the issuance entropy of the input at the given index:
- Return `Some(Some(x))` if the input has reissuance with entropy `x` or if there is new issuance whose computed entropy is `x`.
- Return `Some(None)` if the input has no issuance.
- Return `None` if the input does not exist. | | `issuance_token(u32) -> Option>` | Return the reissuance token of the input at the given index:
- Return `Some(Some(x))` if the input has issuance with the reissuance token ID `x`.
- Return `Some(None)` if the input has no issuance.
- Return `None` if the input does not exist. | | `lbtc_asset() -> u256` | Return the asset for Liquid bitcoin. | ### Transaction ???+ "Click to hide" |
Jet
| Description | | ----------------------------------- | ----------- | | `current_amount() -> (Asset1, Amount1)` | Return the `input_amount` at the `current_index`. | | `current_annex_hash() -> Option` | Return the `input_annex_hash` at the `current_index`. | | `current_asset() -> Asset1` | Return the `input_asset` at the `current_index`. | | `current_index() -> u32` | Return the index of the current txin. | | `current_issuance_asset_amount() -> Option` | Return the `issuance_asset_amount` at the `current_index`. | | `current_issuance_asset_proof() -> u256` | Return the `issuance_asset_proof` at the `current_index`. | | `current_issuance_token_amount() -> Option` | Return the `issuance_token_amount` at the `current_index`. | | `current_issuance_token_proof() -> u256` | Return the `issuance_token_proof` at the `current_index`. | | `current_new_issuance_contract() -> Option` | Return the `new_issuance_contract` at the `current_index`. | | `current_pegin() -> Option` | Return the `input_pegin` at the `current_index`. | | `current_prev_outpoint() -> Outpoint` | Return the previous outpoint of the current txin. | | `current_reissuance_blinding() -> Option` | Return the `reissuance_blinding` at the `current_index`. | | `current_reissuance_entropy() -> Option` | Return the `reissuance_entropy` at the `current_index`. | | `current_script_hash() -> u256` | Return the SHA256 hash of the scriptPubKey of the UTXO of the current txin. | | `current_script_sig_hash() -> u256` | Return the SHA256 hash of the scriptSig of the current txin.

SegWit UTXOs enforce scriptSig to be the empty string. In such cases, we return the SHA256 hash of the empty string. | | `current_sequence() -> u32` | Return the nSequence of the current txin.

Use this jet to obtain the raw, encoded sequence number. | | `genesis_block_hash() -> u256` | Return the SHA256 hash of the genesis block. | | `input_amount(u32) -> Option<(Asset1, Amount1)>` | Return the asset id and the asset amount at the given input index.

Return `None` if the input does not exist. | | `input_annex_hash(u32) -> Option>` | Return the SHA256 hash of the annex at the given input:
- Return `Some(Some(x))` if the input has an annex that hashes to `x`.
- Return `Some(None)` if the input has no annex.
- Return `None` if the input does not exist. | | `input_asset(u32) -> Option` | Return the asset id of the input at the given index.

Return `None` if the input does not exist. | | `input_pegin(u32) -> Option>` | Return the parent genesis block hash if the input at the given index is a peg-in.

- Return `Some(None)` if the input is not a peg-in.
- Return `None` if the input does not exist. | | `input_prev_outpoint(u32) -> Option` | Return the previous outpoint of the input at the given index.

Return `None` if the input does not exist. | | `input_script_hash(u32) -> Option` | Return the SHA256 hash of the scriptPubKey of the UTXO of the input at the given index.

Return `None` if the input does not exist. | | `input_script_sig_hash(u32) -> Option` | Return the SHA256 hash of the scriptSig of the input at the given index.

Return `None` if the input does not exist.

SegWit UTXOs enforce scriptSig to be the empty string. In such cases, we return the SHA256 hash of the empty string. | | `input_sequence(u32) -> Option` | Return the nSequence of the input at the given index.

Return `None` if the input does not exist. | | `internal_key() -> Pubkey` | Return the internal key of the current input.

We assume that Simplicity can be spent in Taproot outputs only, so there always exists an internal key. | | `issuance_asset_amount(u32) -> Option>` | Return the possibly confidential amount of the issuance if the input at the given index has an issuance.

- Return `Some(None)` if the input does not have an issuance.
- Return `None` if the input does not exist. | | `issuance_asset_proof(u32) -> Option` | Return the SHA256 hash of the range proof for the amount of the issuance at the given input index.

- Return the hash of the empty string if the input does not have an issuance.
- Return `None` if the input does not exist. | | `issuance_token_amount(u32) -> Option>` | Return the possibly confidential amount of the reissuance tokens if the input at the given index has an issuance.

- Return `Some(Some(Right(0)))` if the input is itself a reissuance.
- Return `Some(None)` if the input does not have an issuance.
- Return `None` if the input does not exist. | | `issuance_token_proof(u32) -> Option` | Return the SHA256 hash of the range proof for the amount of the reissuance tokens at the given input index.

- Return the hash of the empty string if the input does not have an issuance.
- Return `None` if the input does not exist. | | `lock_time() -> Lock` | Return the lock time of the transaction. | | `new_issuance_contract(u32) -> Option>` | Return the contract hash for the new issuance at the given input index.

- Return `Some(None)` if the input does not have a new issuance.
- Return `None` if the input does not exist. | | `num_inputs() -> u32` | Return the number of inputs of the transaction. | | `num_outputs() -> u32` | Return the number of outputs of the transaction. | | `output_amount(u32) -> Option<(Asset1, Amount1)>` | Return the asset amount of the output at the given index.

Return `None` if the output does not exist. | | `output_asset(u32) -> Option` | Return the asset id of the output at the given index.

Return `None` if the output does not exist. | | `output_is_fee(u32) -> Option` | Check if the output at the given index is a fee output.

Return `None` if the output does not exist. | | `output_nonce(u32) -> Option>` | Return the nonce of the output at the given index.

- Return `Some(None)` if the output does not have a nonce.
- Return `None` if the output does not exist. | | `output_null_datum(u32, u32) -> Option>>>` | Return the `b`-th entry of a null data (`OP_RETURN`) output at index `a`.

- Return `Some(Some(Right(Right(x-1))))` if the entry is `OP_x` for `x` in the range 1..=16.
- Return `Some(Some(Right(Left(0))))` if the entry is `OP_1NEGATE`.
- Return `Some(Some(Right(Left(1))))` if the entry is `OP_RESERVED`.
- Return `Some(Some(Left((x, hash))))` if the entry is pushed data. `hash` is the SHA256 hash of the data pushed and `x` indicates how the data was pushed:
- `x == 0` means the push was an immediate 0 to 75 bytes.
- `x == 1` means the push was an `OP_PUSHDATA1`.
- `x == 2` means the push was an `OP_PUSHDATA2`.
- `x == 3` means the push was an `OP_PUSHDATA4`.
- Return `Some(None)` if the null data has fewer than `b` entries.
- Return `None` if the output is not a null data output.

Use this jet to read peg-out data from an output. | | `output_range_proof(u32) -> Option` | Return the SHA256 hash of the range proof of the output at the given index.

Return `None` if the output does not exist. | | `output_script_hash(u32) -> Option` | Return the SHA256 hash of the scriptPubKey of the output at the given index.

Return `None` if the output does not exist. | | `output_surjection_proof(u32) -> Option` | Return the SHA256 hash of the surjection proof of the output at the given index.

Return `None` if the output does not exist. | | `reissuance_blinding(u32) -> Option>` | Return the blinding factor used for the reissuance at the given input index.

- Return `Some(None)` if the input does not have a reissuance.
- Return `None` if the input does not exist. | | `reissuance_entropy(u32) -> Option>` | Return the entropy used for the reissuance at the given input index.

- Return `Some(None)` if the input does not have a reissuance.
- Return `None` if the input does not exist. | | `script_cmr() -> u256` | Return the CMR of the Simplicity program in the current input.

This is the CMR of the currently executed Simplicity program. | | `tapleaf_version() -> u8` | Return the tap leaf version of the current input.

We assume that Simplicity can be spent in Taproot outputs only, so there always exists a tap leaf. | | `tappath(u8) -> Option` | Return the SHA256 hash of the tap path of the current input.

We assume that Simplicity can be spent in Taproot outputs only, so there always exists a tap path. | | `total_fee(ExplicitAsset) -> ExplicitAmount` | Return the total amount of fees paid to the given asset id.

Return zero for any asset without fees. | | `transaction_id() -> u256` | Return the transaction ID. | | `version() -> u32` | Return the version number of the transaction. | ## Deprecated jets Four jets related to time locks have been deprecated. ???+ "Click to hide" |
Jet
| Description | | ----------------------------------- | ----------- | | `check_lock_distance(Distance) -> ()` | **Deprecated; do not use.** Assert that the value returned by `tx_lock_distance` is greater than or equal to the given value.

## Panics
The assertion fails. | | `check_lock_duration(Duration) -> ()` | **Deprecated; do not use.** Assert that the value returned by `tx_lock_duration` is greater than or equal to the given value.

## Panics
The assertion fails | | `tx_lock_distance() -> Distance` | **Deprecated; do not use.** If `version` returns 2 or greater, then return the greatest valid `Distance` value of any transaction input. Return zeroes otherwise. | | `tx_lock_duration() -> Duration` | **Deprecated; do not use.** If `version` returns 2 or greater, then return the greatest valid `Duration` value of any transaction input. Return zeroes otherwise. | These jets' names may have been changed in some tools and libraries to discourage their use. ## Notation There are three styles of writing jet names that you may encounter in Simplicity-related tooling or source code. * SimplicityHL writes jets like `jet::eq_32`. * Disassembly of low-level Simplicity writes them like `jet_eq_32`. * Rust source code writes them like `Elements::Eq32`. The reference list above is aimed at SimplicityHL developers, so it presents jet names in SimplicityHL format. Remember to include `jet::` before the name of the jet when calling it from a SimplicityHL program. ## More about jet implementation The list of jets is fixed when Simplicity is integrated with a specific blockchain, because their details become part of the consensus rules for each Simplicity-enabled blockchain. A complete list of jets must be predefined so different verifiers can agree on what a particular Simplicity program means and what its exact behavior is when it is run. Jet implementations are available in native code to allow miners and other node operators to run these functions more quickly and efficiently. Calling jets, where available, makes your Simplicity program smaller and faster. A few jets [provide behaviors that could not be achieved directly with low-level Simplicity combinators alone](https://delvingbitcoin.org/t/delving-simplicity-part-two-side-effects/2091), such as transaction introspection. Jets that can fail (those whose return type is `()`) are the expected and only way for a Simplicity program to disapprove a proposed transaction. ### Standard Library Reference # SimplicityHL standard library reference The SimplicityHL standard library provides various functions useful in developing smart contracts. Here is a complete list of the available library functions, their [type signatures](../../simplicityhl-reference/type/), and a description of what they do. Some library functions can fail or panic. This allows a Simplicity program to refuse a proposed transaction by performing a mandatory assertion; these functions' return type is `()` below. The failure or panic effect produced by these functions, or the corresponding behavior of jets, is ultimately the *only* way to decline a transaction. For more built-in SimplicityHL functions, see the [jets reference](jets.md). ## Asserts ???+ "Click to hide" |
Standard library function
| Description | | ----------------------------------- | ----------- | | `assert_eq_1(u1, u1) -> ()` | Assert that two `u1` values are equal.

## Panics
The assertion fails. | | `assert_eq_8(u8, u8) -> ()` | Assert that two `u8` values are equal.

## Panics
The assertion fails. | | `assert_eq_16(u16, u16) -> ()` | Assert that two `u16` values are equal.

## Panics
The assertion fails. | | `assert_eq_32(u32, u32) -> ()` | Assert that two `u32` values are equal.

## Panics
The assertion fails. | | `assert_eq_64(u64, u64) -> ()` | Assert that two `u64` values are equal.

## Panics
The assertion fails. | | `assert_eq_128(u128, u128) -> ()` | Assert that two `u128` values are equal.

## Panics
The assertion fails. | | `assert_eq_256(u256, u256) -> ()` | Assert that two `u256` values are equal.

## Panics
The assertion fails. | | `assert_none_1(Option) -> ()` | Assert that the given `Option` is `None`.

## Panics
The assertion fails. | | `assert_none_8(Option) -> ()` | Assert that the given `Option` is `None`.

## Panics
The assertion fails. | | `assert_none_16(Option) -> ()` | Assert that the given `Option` is `None`.

## Panics
The assertion fails. | | `assert_none_32(Option) -> ()` | Assert that the given `Option` is `None`.

## Panics
The assertion fails. | | `assert_none_64(Option) -> ()` | Assert that the given `Option` is `None`.

## Panics
The assertion fails. | | `assert_none_128(Option) -> ()` | Assert that the given `Option` is `None`.

## Panics
The assertion fails. | | `assert_none_256(Option) -> ()` | Assert that the given `Option` is `None`.

## Panics
The assertion fails. | | `assert_eq_bool(bool, bool) -> ()` | Assert that two `bool` values are equal.

## Panics
The assertion fails. | ## Binary logic ???+ "Click to hide" |
Standard library function
| Description | | ----------------------------------- | ----------- | | `not(bool) -> bool` | Return the logical NOT of the given value. | | `or(bool, bool) -> bool` | Return the logical OR of the two given values. | | `and(bool, bool) -> bool` | Return the logical AND of the two given values. | | `xor(bool, bool) -> bool` | Return the logical XOR of the two given values. | ## OP_RETURN ???+ "Click to hide" |
Standard library function
| Description | | ----------------------------------- | ----------- | | `is_output_op_return(u32) -> bool` | Return `true` if the output at the given index is an OP_RETURN (null data) output, `false` otherwise (including if the output does not exist). | | `assert_output_is_op_return(u32) -> ()` | Assert that the output at the given index is an OP_RETURN (null data) output.

## Panics
The assertion fails. | ## secp256k1 operations ???+ "Click to hide" |
Standard library function
| Description | | ----------------------------------- | ----------- | | `ge_to_point(Ge) -> Point` | Compress an affine point to `(parity, x)`, where `parity = 1` if and only if `y` is odd. | | `point_to_gej(Point) -> Gej` | Decompress a compressed `Point` into a Jacobian point with `z = 1`.

## Panics
Panics if the compressed point does not decode to a valid curve point. | | `safe_gej_normalize(Gej) -> Ge` | Convert a Jacobian point into affine coordinates.

## Panics
Panics if the point is the point at infinity, which has no affine representation. | | `fe_sub(Fe, Fe) -> Fe` | Subtract two field elements. | | `scalar_sub(Scalar, Scalar) -> Scalar` | Subtract two scalars. | | `gej_sub(Gej, Gej) -> Gej` | Subtract two Jacobian points. | | `fe_eq(Fe, Fe) -> bool` | Check field-element equality modulo `p`. | | `scalar_eq(Scalar, Scalar) -> bool` | Check scalar equality modulo the curve order `n`. | | `ge_eq(Ge, Ge) -> bool` | Check whether two affine points are equal. | | `point_point_eq(Point, Point) -> bool` | Check whether two compressed `Point` values are equal (same parity and same x-coordinate). | | `gej_point_eq(Gej, Point) -> bool` | Check whether a Jacobian point and a compressed `Point` represent the same curve point.

## Panics
Panics if the compressed point does not decode to a valid curve point. | | `assert_fe_eq(Fe, Fe) -> ()` | Assert field-element equality modulo `p`.

## Panics
The assertion fails. | | `assert_scalar_eq(Scalar, Scalar) -> ()` | Assert scalar equality modulo the curve order `n`.

## Panics
The assertion fails. | | `assert_ge_eq(Ge, Ge) -> ()` | Assert that two affine points are equal.

## Panics
The assertion fails. | | `assert_point_eq(Point, Point) -> ()` | Assert that two compressed `Point` values are equal (same parity and same x-coordinate).

## Panics
The assertion fails. | | `assert_gej_point_eq(Gej, Point) -> ()` | Assert that a Jacobian point equals the point encoded by a compressed `Point`.

## Panics
The assertion fails, or the compressed point does not decode to a valid curve point. | | `assert_gej_eq(Gej, Gej) -> ()` | Assert that two Jacobian points represent the same curve point, without normalizing either one first.

## Panics
The assertion fails. | | `assert_gej_ge_eq(Gej, Ge) -> ()` | Assert that a Jacobian point equals an affine point, without normalizing the Jacobian point first.

## Panics
The assertion fails. | ## `u1` conversions ???+ "Click to hide" |
Standard library function
| Description | | ----------------------------------- | ----------- | | `u1_to_u8(u1) -> u8` | Widen a `u1` value to a `u8` value, zero-extending the high bits. | | `u1_to_u16(u1) -> u16` | Widen a `u1` value to a `u16` value, zero-extending the high bits. | | `u1_to_u32(u1) -> u32` | Widen a `u1` value to a `u32` value, zero-extending the high bits. | | `u1_to_u64(u1) -> u64` | Widen a `u1` value to a `u64` value, zero-extending the high bits. | | `u1_to_u128(u1) -> u128` | Widen a `u1` value to a `u128` value, zero-extending the high bits. | | `u1_to_u256(u1) -> u256` | Widen a `u1` value to a `u256` value, zero-extending the high bits. | | `u1_to_bool(u1) -> bool` | Convert a `u1` value to `bool`. | ## `u8` arithmetic ???+ "Click to hide" |
Standard library function
| Description | | ----------------------------------- | ----------- | | `checked_add_8(u8, u8) -> Option` | Add two `u8` values. Return `Some` of the sum, or `None` if the result overflows `u8`. | | `safe_add_8(u8, u8) -> u8` | Add two `u8` values.

## Panics
Panics if the result overflows `u8`. | | `checked_sub_8(u8, u8) -> Option` | Subtract the second `u8` value from the first. Return `Some` of the difference, or `None` if the result would underflow `u8`. | | `safe_sub_8(u8, u8) -> u8` | Subtract the second `u8` value from the first.

## Panics
Panics if the result would underflow `u8`. | | `checked_mul_8(u8, u8) -> Option` | Multiply two `u8` values. Return `Some` of the product, or `None` if the result overflows `u8`. | | `safe_mul_8(u8, u8) -> u8` | Multiply two `u8` values.

## Panics
Panics if the result overflows `u8`. | | `checked_div_8(u8, u8) -> Option` | Divide the first `u8` value by the second. Return `Some` of the quotient, or `None` if the divisor is zero. | | `safe_div_8(u8, u8) -> u8` | Divide the first `u8` value by the second.

## Panics
Panics if the divisor is zero. | | `gt_8(u8, u8) -> bool` | Check if the first `u8` value is greater than the second. | | `ge_8(u8, u8) -> bool` | Check if the first `u8` value is greater than or equal to the second. | ## `u8` conversions ???+ "Click to hide" |
Standard library function
| Description | | ----------------------------------- | ----------- | | `u8_to_u16(u8) -> u16` | Widen a `u8` value to a `u16` value, zero-extending the high bits. | | `u8_to_u32(u8) -> u32` | Widen a `u8` value to a `u32` value, zero-extending the high bits. | | `u8_to_u64(u8) -> u64` | Widen a `u8` value to a `u64` value, zero-extending the high bits. | | `u8_to_u128(u8) -> u128` | Widen a `u8` value to a `u128` value, zero-extending the high bits. | | `u8_to_u256(u8) -> u256` | Widen a `u8` value to a `u256` value, zero-extending the high bits. | | `split_u8_into_u1(u8) -> (u1, u1, u1, u1, u1, u1, u1, u1)` | Split a `u8` value into eight `u1` words, most-significant first. | | `safe_u8_to_u1(u8) -> u1` | Narrow a `u8` value to `u1`.

## Panics
Panics if the value does not fit in `u1`. | ## `u16` arithmetic ???+ "Click to hide" |
Standard library function
| Description | | ----------------------------------- | ----------- | | `checked_add_16(u16, u16) -> Option` | Add two `u16` values. Return `Some` of the sum, or `None` if the result overflows `u16`. | | `safe_add_16(u16, u16) -> u16` | Add two `u16` values.

## Panics
Panics if the result overflows `u16`. | | `checked_sub_16(u16, u16) -> Option` | Subtract the second `u16` value from the first. Return `Some` of the difference, or `None` if the result would underflow `u16`. | | `safe_sub_16(u16, u16) -> u16` | Subtract the second `u16` value from the first.

## Panics
Panics if the result would underflow `u16`. | | `checked_mul_16(u16, u16) -> Option` | Multiply two `u16` values. Return `Some` of the product, or `None` if the result overflows `u16`. | | `safe_mul_16(u16, u16) -> u16` | Multiply two `u16` values.

## Panics
Panics if the result overflows `u16`. | | `checked_div_16(u16, u16) -> Option` | Divide the first `u16` value by the second. Return `Some` of the quotient, or `None` if the divisor is zero. | | `safe_div_16(u16, u16) -> u16` | Divide the first `u16` value by the second.

## Panics
Panics if the divisor is zero. | | `gt_16(u16, u16) -> bool` | Check if the first `u16` value is greater than the second. | | `ge_16(u16, u16) -> bool` | Check if the first `u16` value is greater than or equal to the second. | ## `u16` conversions ???+ "Click to hide" |
Standard library function
| Description | | ----------------------------------- | ----------- | | `u16_to_u32(u16) -> u32` | Widen a `u16` value to a `u32` value, zero-extending the high bits. | | `u16_to_u64(u16) -> u64` | Widen a `u16` value to a `u64` value, zero-extending the high bits. | | `u16_to_u128(u16) -> u128` | Widen a `u16` value to a `u128` value, zero-extending the high bits. | | `u16_to_u256(u16) -> u256` | Widen a `u16` value to a `u256` value, zero-extending the high bits. | | `split_u16_into_u8(u16) -> (u8, u8)` | Split a `u16` value into two `u8` words, most-significant first. | | `safe_u16_to_u1(u16) -> u1` | Narrow a `u16` value to `u1`.

## Panics
Panics if the value does not fit in `u1`. | | `safe_u16_to_u8(u16) -> u8` | Narrow a `u16` value to `u8`.

## Panics
Panics if the value does not fit in `u8`. | ## `u32` arithmetic ???+ "Click to hide" |
Standard library function
| Description | | ----------------------------------- | ----------- | | `checked_add_32(u32, u32) -> Option` | Add two `u32` values. Return `Some` of the sum, or `None` if the result overflows `u32`. | | `safe_add_32(u32, u32) -> u32` | Add two `u32` values.

## Panics
Panics if the result overflows `u32`. | | `checked_sub_32(u32, u32) -> Option` | Subtract the second `u32` value from the first. Return `Some` of the difference, or `None` if the result would underflow `u32`. | | `safe_sub_32(u32, u32) -> u32` | Subtract the second `u32` value from the first.

## Panics
Panics if the result would underflow `u32`. | | `checked_mul_32(u32, u32) -> Option` | Multiply two `u32` values. Return `Some` of the product, or `None` if the result overflows `u32`. | | `safe_mul_32(u32, u32) -> u32` | Multiply two `u32` values.

## Panics
Panics if the result overflows `u32`. | | `checked_div_32(u32, u32) -> Option` | Divide the first `u32` value by the second. Return `Some` of the quotient, or `None` if the divisor is zero. | | `safe_div_32(u32, u32) -> u32` | Divide the first `u32` value by the second.

## Panics
Panics if the divisor is zero. | | `gt_32(u32, u32) -> bool` | Check if the first `u32` value is greater than the second. | | `ge_32(u32, u32) -> bool` | Check if the first `u32` value is greater than or equal to the second. | ## `u32` conversions ???+ "Click to hide" |
Standard library function
| Description | | ----------------------------------- | ----------- | | `u32_to_u64(u32) -> u64` | Widen a `u32` value to a `u64` value, zero-extending the high bits. | | `u32_to_u128(u32) -> u128` | Widen a `u32` value to a `u128` value, zero-extending the high bits. | | `u32_to_u256(u32) -> u256` | Widen a `u32` value to a `u256` value, zero-extending the high bits. | | `split_u32_into_u8(u32) -> (u8, u8, u8, u8)` | Split a `u32` value into four `u8` words, most-significant first. | | `split_u32_into_u16(u32) -> (u16, u16)` | Split a `u32` value into two `u16` words, most-significant first. | | `safe_u32_to_u1(u32) -> u1` | Narrow a `u32` value to `u1`.

## Panics
Panics if the value does not fit in `u1`. | | `safe_u32_to_u8(u32) -> u8` | Narrow a `u32` value to `u8`.

## Panics
Panics if the value does not fit in `u8`. | | `safe_u32_to_u16(u32) -> u16` | Narrow a `u32` value to `u16`.

## Panics
Panics if the value does not fit in `u16`. | ## `u64` arithmetic ???+ "Click to hide" |
Standard library function
| Description | | ----------------------------------- | ----------- | | `checked_add_64(u64, u64) -> Option` | Add two `u64` values. Return `Some` of the sum, or `None` if the result overflows `u64`. | | `safe_add_64(u64, u64) -> u64` | Add two `u64` values.

## Panics
Panics if the result overflows `u64`. | | `checked_sub_64(u64, u64) -> Option` | Subtract the second `u64` value from the first. Return `Some` of the difference, or `None` if the result would underflow `u64`. | | `safe_sub_64(u64, u64) -> u64` | Subtract the second `u64` value from the first.

## Panics
Panics if the result would underflow `u64`. | | `checked_mul_64(u64, u64) -> Option` | Multiply two `u64` values. Return `Some` of the product, or `None` if the result overflows `u64`. | | `safe_mul_64(u64, u64) -> u64` | Multiply two `u64` values.

## Panics
Panics if the result overflows `u64`. | | `checked_div_64(u64, u64) -> Option` | Divide the first `u64` value by the second. Return `Some` of the quotient, or `None` if the divisor is zero. | | `safe_div_64(u64, u64) -> u64` | Divide the first `u64` value by the second.

## Panics
Panics if the divisor is zero. | | `gt_64(u64, u64) -> bool` | Check if the first `u64` value is greater than the second. | | `ge_64(u64, u64) -> bool` | Check if the first `u64` value is greater than or equal to the second. | ## `u64` conversions ???+ "Click to hide" |
Standard library function
| Description | | ----------------------------------- | ----------- | | `u64_to_u128(u64) -> u128` | Widen a `u64` value to a `u128` value, zero-extending the high bits. | | `u64_to_u256(u64) -> u256` | Widen a `u64` value to a `u256` value, zero-extending the high bits. | | `split_u64_into_u8(u64) -> (u8, u8, u8, u8, u8, u8, u8, u8)` | Split a `u64` value into eight `u8` words, most-significant first. | | `split_u64_into_u16(u64) -> (u16, u16, u16, u16)` | Split a `u64` value into four `u16` words, most-significant first. | | `split_u64_into_u32(u64) -> (u32, u32)` | Split a `u64` value into two `u32` words, most-significant first. | | `safe_u64_to_u1(u64) -> u1` | Narrow a `u64` value to `u1`.

## Panics
Panics if the value does not fit in `u1`. | | `safe_u64_to_u8(u64) -> u8` | Narrow a `u64` value to `u8`.

## Panics
Panics if the value does not fit in `u8`. | | `safe_u64_to_u16(u64) -> u16` | Narrow a `u64` value to `u16`.

## Panics
Panics if the value does not fit in `u16`. | | `safe_u64_to_u32(u64) -> u32` | Narrow a `u64` value to `u32`.

## Panics
Panics if the value does not fit in `u32`. | ## `u128` bit logic ???+ "Click to hide" |
Standard library function
| Description | | ----------------------------------- | ----------- | | `and_128(u128, u128) -> u128` | Bitwise AND of two `u128` values. | | `or_128(u128, u128) -> u128` | Bitwise OR of two `u128` values. | | `eq_128(u128, u128) -> bool` | Check if two `u128` values are equal. | | `left_shift_128(u8, u128) -> u128` | Left-shift a `u128` value by the given amount. Bits shifted out are discarded; vacated low bits are filled with zeroes. | | `right_shift_128(u8, u128) -> u128` | Right-shift a `u128` value by the given amount. Bits shifted out are discarded; vacated high bits are filled with zeroes. | ## `u128` comparisons ???+ "Click to hide" |
Standard library function
| Description | | ----------------------------------- | ----------- | | `is_zero_128(u128) -> bool` | Check if a `u128` value is zero. | | `lt_128(u128, u128) -> bool` | Check if the first `u128` value is strictly less than the second. | | `le_128(u128, u128) -> bool` | Check if the first `u128` value is less than or equal to the second. | | `gt_128(u128, u128) -> bool` | Check if the first `u128` value is strictly greater than the second. | | `ge_128(u128, u128) -> bool` | Check if the first `u128` value is greater than or equal to the second. | ## `u128` conversions ???+ "Click to hide" |
Standard library function
| Description | | ----------------------------------- | ----------- | | `u128_to_u256(u128) -> u256` | Widen a `u128` value to a `u256` value, zero-extending the high bits. | | `split_u128_into_u8(u128) -> (u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8)` | Split a `u128` value into sixteen `u8` words, most-significant first. | | `split_u128_into_u16(u128) -> (u16, u16, u16, u16, u16, u16, u16, u16)` | Split a `u128` value into eight `u16` words, most-significant first. | | `split_u128_into_u32(u128) -> (u32, u32, u32, u32)` | Split a `u128` value into four `u32` words, most-significant first. | | `split_u128_into_u64(u128) -> (u64, u64)` | Split a `u128` value into two `u64` words, most-significant first. | | `safe_u128_to_u1(u128) -> u1` | Narrow a `u128` value to `u1`.

## Panics
Panics if the value does not fit in `u1`. | | `safe_u128_to_u8(u128) -> u8` | Narrow a `u128` value to `u8`.

## Panics
Panics if the value does not fit in `u8`. | | `safe_u128_to_u16(u128) -> u16` | Narrow a `u128` value to `u16`.

## Panics
Panics if the value does not fit in `u16`. | | `safe_u128_to_u32(u128) -> u32` | Narrow a `u128` value to `u32`.

## Panics
Panics if the value does not fit in `u32`. | | `safe_u128_to_u64(u128) -> u64` | Narrow a `u128` value to `u64`.

## Panics
Panics if the value does not fit in `u64`. | ## `u128` arithmetic ???+ "Click to hide" |
Standard library function
| Description | | ----------------------------------- | ----------- | | `add_128(u128, u128) -> (bool, u128)` | Add two `u128` values. Return the carry bit and the sum. | | `add_128_64(u128, u64) -> (bool, u128)` | Add a `u64` value to a `u128` value. Return the carry bit and the sum. | | `checked_add_128(u128, u128) -> Option` | Add two `u128` values. Return `Some` of the sum, or `None` if the result overflows `u128`. | | `safe_add_128(u128, u128) -> u128` | Add two `u128` values.

## Panics
Panics if the result overflows `u128`. | | `sub_128(u128, u128) -> (bool, u128)` | Subtract the second `u128` value from the first. Return the borrow bit and the difference. | | `checked_sub_128(u128, u128) -> Option` | Subtract the second `u128` value from the first. Return `Some` of the difference, or `None` if the result would underflow `u128`. | | `safe_sub_128(u128, u128) -> u128` | Subtract the second `u128` value from the first.

## Panics
Panics if the result would underflow `u128`. | | `mul_128(u128, u128) -> u256` | Multiply two `u128` values. The full, non-truncated product is returned as a `u256`, so this operation can never overflow. | | `checked_mul_128(u128, u128) -> Option` | Multiply two `u128` values. Return `Some` of the product, or `None` if the result overflows `u128`. | | `safe_mul_128(u128, u128) -> u128` | Multiply two `u128` values.

## Panics
Panics if the result overflows `u128`. | | `div_mod_128_64(u128, u64) -> (u128, u64)` | Divide a `u128` value by a `u64` value, returning the `u128` quotient and the `u64` remainder.

## Panics
Panics if the divisor is zero. | | `div_mod_128(u128, u128) -> (u128, u128)` | Divide the first `u128` value by the second, returning the quotient and the remainder.

## Panics
Panics if the divisor is zero. | | `div_128(u128, u128) -> u128` | Divide the first `u128` value by the second, returning the quotient.

## Panics
Panics if the divisor is zero. | | `checked_div_128(u128, u128) -> Option` | Divide the first `u128` value by the second. Return `Some` of the quotient, or `None` if the divisor is zero. | | `safe_div_128(u128, u128) -> u128` | Divide the first `u128` value by the second.

## Panics
Panics if the divisor is zero. | | `full_add_128(bool, u128, u128) -> (bool, u128)` | Add two `u128` values, taking an incoming carry bit. Return the outgoing carry bit and the sum. | | `full_sub_128(bool, u128, u128) -> (bool, u128)` | Subtract the second `u128` value from the first, taking an incoming borrow bit. Return the outgoing borrow bit and the difference. | | `mul_128_64(u128, u64) -> u256` | Multiply a `u128` value by a `u64` value. The full, non-truncated product is returned as a `u256`, so this operation can never overflow. | | `calculate_normalizer_base_64(u128, bool) -> u64` | Helper for `jet::div_mod_128_64`-based division algorithms. Returns the factor by which `b` should be multiplied so that its most-significant non-zero word is at least `2^63`, as required by those algorithms (which operate in base `2^64`). Set `is_b_u128` to `true` if `b`'s upper 64 bits may be non-zero, or `false` if `b` is known to fit in `u64` (in which case its upper 64 bits must already be zero).

## Panics
The assertion fails if `is_b_u128` is `false` but `b`'s upper 64 bits are non-zero, or if `b` is zero. | | `estimate_quotient_digit_base_64(u64, u64, u64, u64, u64) -> u64` | Helper for Algorithm D division. Estimates and corrects the next base-`2^64` quotient digit from the three most-significant dividend words (`u2`, `u1`, `u0`) and the two most-significant divisor words (`v1`, `v0`). | ## `u256` bit logic ???+ "Click to hide" |
Standard library function
| Description | | ----------------------------------- | ----------- | | `and_256(u256, u256) -> u256` | Bitwise AND of two `u256` values. | | `or_256(u256, u256) -> u256` | Bitwise OR of two `u256` values. | | `left_shift_256(u8, u256) -> u256` | Left-shift a `u256` value by the given amount. Bits shifted out are discarded; vacated low bits are filled with zeroes. | | `right_shift_256(u8, u256) -> u256` | Right-shift a `u256` value by the given amount. Bits shifted out are discarded; vacated high bits are filled with zeroes. | ## `u256` comparisons ???+ "Click to hide" |
Standard library function
| Description | | ----------------------------------- | ----------- | | `is_zero_256(u256) -> bool` | Check if a `u256` value is zero. | | `lt_256(u256, u256) -> bool` | Check if the first `u256` value is strictly less than the second. | | `le_256(u256, u256) -> bool` | Check if the first `u256` value is less than or equal to the second. | | `gt_256(u256, u256) -> bool` | Check if the first `u256` value is strictly greater than the second. | | `ge_256(u256, u256) -> bool` | Check if the first `u256` value is greater than or equal to the second. | ## `u256` conversions ???+ "Click to hide" |
Standard library function
| Description | | ----------------------------------- | ----------- | | `split_u256_into_u8(u256) -> (u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8)` | Split a `u256` value into thirty-two `u8` words, most-significant first. | | `split_u256_into_u16(u256) -> (u16, u16, u16, u16, u16, u16, u16, u16, u16, u16, u16, u16, u16, u16, u16, u16)` | Split a `u256` value into sixteen `u16` words, most-significant first. | | `split_u256_into_u32(u256) -> (u32, u32, u32, u32, u32, u32, u32, u32)` | Split a `u256` value into eight `u32` words, most-significant first. | | `split_u256_into_u64(u256) -> (u64, u64, u64, u64)` | Split a `u256` value into four `u64` words, most-significant first. | | `split_u256_into_u128(u256) -> (u128, u128)` | Split a `u256` value into two `u128` words, most-significant first. | | `safe_u256_to_u1(u256) -> u1` | Narrow a `u256` value to `u1`.

## Panics
Panics if the value does not fit in `u1`. | | `safe_u256_to_u8(u256) -> u8` | Narrow a `u256` value to `u8`.

## Panics
Panics if the value does not fit in `u8`. | | `safe_u256_to_u16(u256) -> u16` | Narrow a `u256` value to `u16`.

## Panics
Panics if the value does not fit in `u16`. | | `safe_u256_to_u32(u256) -> u32` | Narrow a `u256` value to `u32`.

## Panics
Panics if the value does not fit in `u32`. | | `safe_u256_to_u64(u256) -> u64` | Narrow a `u256` value to `u64`.

## Panics
Panics if the value does not fit in `u64`. | | `safe_u256_to_u128(u256) -> u128` | Narrow a `u256` value to `u128`.

## Panics
Panics if the value does not fit in `u128`. | ## `u256` arithmetic ???+ "Click to hide" |
Standard library function
| Description | | ----------------------------------- | ----------- | | `add_256(u256, u256) -> (bool, u256)` | Add two `u256` values. Return the carry bit and the sum. | | `add_256_128(u256, u128) -> (bool, u256)` | Add a `u128` value to a `u256` value. Return the carry bit and the sum. | | `checked_add_256(u256, u256) -> Option` | Add two `u256` values. Return `Some` of the sum, or `None` if the result overflows `u256`. | | `safe_add_256(u256, u256) -> u256` | Add two `u256` values.

## Panics
Panics if the result overflows `u256`. | | `sub_256(u256, u256) -> (bool, u256)` | Subtract the second `u256` value from the first. Return the borrow bit and the difference. | | `checked_sub_256(u256, u256) -> Option` | Subtract the second `u256` value from the first. Return `Some` of the difference, or `None` if the result would underflow `u256`. | | `safe_sub_256(u256, u256) -> u256` | Subtract the second `u256` value from the first.

## Panics
Panics if the result would underflow `u256`. | | `mul_256(u256, u256) -> (u256, u256)` | Multiply two `u256` values. The full, non-truncated product is returned as a pair of `u256` values, most-significant first, so this operation can never overflow. | | `mul_256_64(u256, u64) -> (u64, u256)` | Multiply a `u256` value by a `u64` value. The full, non-truncated product is returned as a `u64`/`u256` pair, most-significant first, so this operation can never overflow. | | `mul_256_128(u256, u128) -> (u128, u256)` | Multiply a `u256` value by a `u128` value. The full, non-truncated product is returned as a `u128`/`u256` pair, most-significant first, so this operation can never overflow. | | `checked_mul_256(u256, u256) -> Option` | Multiply two `u256` values. Return `Some` of the product, or `None` if the result overflows `u256`. | | `safe_mul_256(u256, u256) -> u256` | Multiply two `u256` values.

## Panics
Panics if the result overflows `u256`. | | `div_mod_256_64(u256, u64) -> (u256, u64)` | Divide a `u256` value by a `u64` value, returning the `u256` quotient and the `u64` remainder.

## Panics
Panics if the divisor is zero. | | `div_mod_256_128(u256, u128) -> (u256, u128)` | Divide a `u256` value by a `u128` value, returning the `u256` quotient and the `u128` remainder.

## Panics
Panics if the divisor is zero. | | `div_mod_256(u256, u256) -> (u256, u256)` | Divide the first `u256` value by the second, returning the quotient and the remainder.

## Panics
Panics if the divisor is zero. | | `div_256(u256, u256) -> u256` | Divide the first `u256` value by the second, returning the quotient.

## Panics
Panics if the divisor is zero. | | `checked_div_256(u256, u256) -> Option` | Divide the first `u256` value by the second. Return `Some` of the quotient, or `None` if the divisor is zero. | | `safe_div_256(u256, u256) -> u256` | Divide the first `u256` value by the second.

## Panics
Panics if the divisor is zero. | ### .wit File Reference # `.wit` File Reference This reference covers the practical mechanics of building witness data for a SimplicityHL contract: the `.wit` file format, compiling it with `simc`, other ways to build witness data, and how to format every SimplicityHL type as a witness value. For an explanation of what a witness is and why it exists, see [Witnesses in SimplicityHL development](witness.md). ## Command-line development with `.wit` files The [simc](../glossary.md#simc) compiler is able to compile (in this context, "serialize") a witness using a contract-specific text file called a `.wit` file. The output is a base64 string which can then be provided to other tools like `hal-simplicity pset finalize` to be incorporated into a complete transaction. A `.wit` file is a JSON file consisting of key-value string pairs. Each entry name corresponds to a variable name expected by the SimplicityHL program, and the string value contains the Rust-like data representation: ```json { "amount": "100", "x": "3", "yes_or_no": "false" } ``` This witness provides two integer values, available to a SimplicityHL program as `witness::amount` and `witness::x`, and a boolean value available as `witness::yes_or_no`. The `simc` compiler infers the required type for each entry automatically from the program source code. Note that all values, including numbers and booleans, are represented as JSON strings within the `.wit` file (`"100"`, not `100`; `"false"`, not `false`). !!! note "Explicit type annotations (optional)" By default, `simc` infers types directly from your contract logic. If you want to explicitly declare or enforce a type for primitive values, tuples, arrays, alias types, or sum types in your `.wit` file, you can use an explicit type annotation syntax, providing a JSON object with `"type"` and `"value"` fields: ```json { "amount": { "value": "100", "type": "u32" }, "when": { "value": "50", "type": "Distance" } } ``` (Note: Custom `enum` types defined in your program **cannot** use explicit type annotations in a witness file; they must always be provided as bare variant strings.) An important type very frequently used in witnesses is `Signature`, which represents a [BIP 0340-style digital signature](https://en.bitcoin.it/wiki/BIP_0340), the main kind of signature used in Simplicity programs. ```json { "ALICE_SIG": "0x16f0f70b1aa9afaf1ee656a038d896c0b6199e33d5c2328fe5d7cc3f1b67af269ac6352f3486e552e966f62f7bcb75dbfa872920be00adb1c3a35d2f307f189c", "BOB_SIG": "0xcaa328e73c3a1c5bba7e606f5fdd9c993eba361c2cfb9beb3cc62f192b4d348913446d9f79ebbee8d15b83872db3903ad1b8ee2cc3cdc78c8d2f289ab7f1e8f0" } ``` This witness provides `witness::ALICE_SIG` and `witness::BOB_SIG`, representing two BIP-0340 signatures from two parties approving a transaction. A SimplicityHL program can use `jet::bip_0340_verify()` to verify a signature over a provided `u256` value or over transaction details (a [sighash](../glossary.md#sighash)). If you need hard-code a specific BIP-0340 public key in your contract or pass it in a witness, you can provide it as type `Pubkey`. `.wit` files may be written manually during the contract development process or generated by wallet software or an SDK. You can find examples corresponding to sample contracts in [`SimplicityHL/examples`](https://github.com/BlockstreamResearch/SimplicityHL/tree/master/examples). ## Compiling (serializing) `.wit` files with `simc` `simc` produces a serialized base64 form of a `.wit` file to incorporate into a transaction. The witness file is specified with the `-w` option: ```bash $ simc --json p2ms.simf -w p2ms.wit {"program": "5lk2l5vmZ++dy7rFWgYpXOhwsHApv82y3OKNlZ8oFbFvgXmARacYEf5RB7X1tMEVAbpXAfNhcd45LjO88p6usCblccJ7lBgtPyYRQDJLGGIJJonwvxOqRTamOQiwbfM2EMA+InecBt8gyCoWRAoQY4oNggUIOOQKE2AACEGGHIMMFgFpHxOQKEGHG4AccgwVJ4CBOKD8JNwsUH1HCrYwEJFB+NQQDaBwIfhWmNBCBQgwzMAMAKwCD8UGCo/FYIBuC4IAwDxcBxkBxuQKDcam5BnGHHG5CHGHCxC1gOAIFBuQh+SRxhxxx+ShxhxwsgtoDiFAoTYAQIQKDcmjjcnBRm2ggDjpIoA5GA1gcBA4jA4zA5cgcvwOYMkUAclgcxY=", "witness": "+6WeUroyP8LKsSWJSZJX0XnFrMVODj5+L4RU4Bt2LWaeB93Pae1y5RHQUy0aWutmZutdEkTC6wIPvZCTFYvXt6U7fVasUVyOV5x8EOUdWjMv3vE6nglrfHOYEWbFuEU+qn+mp/FBWf+/e7qOOitBu0dmDQhILf5I14DoxcrM/XEg", ...} ``` By including both the program source code `p2ms.simf` and the witness file `p2ms.wit`, the compiler automatically verifies that all required witness values are present and match their expected types. ## Other tools for building witness data Witness data doesn't necessarily need to be written to disk as a `.wit` file; it can be assembled in memory by client applications. * **Rust Integration:** Developers using Rust can build witness structures directly. Tools like [Simplex](../simplex/) generate native Rust witness-building code directly from `.simf` files. Explicit witness-building examples are also available in [`simplicity-contracts`](https://github.com/BlockstreamResearch/simplicity-contracts/tree/main/crates/contracts/src). * **Liquid Wallet Kit (LWK):** The [Liquid Wallet Kit](https://github.com/Blockstream/lwk) SDK ("`lwk`") allows building witnesses in Python, JavaScript, and Rust without creating intermediate `.wit` files. ## Types and Formatting The compiler infers expected types directly from your SimplicityHL contract logic. The sections below describe how to format `.wit` string values for specific data literals. ### Primitive types Primitive types include unsigned integers (`u1`, `u2`, `u4`, `u8`, `u16`, `u32`, `u64`, `u128`, `u256`) and booleans (`bool`). Integers can be provided in base 10 or hexadecimal (`0x` prefix). **Contract expectation (`contract.simf`):** ```rust let quantity: u16 = witness::QUANTITY; let is_valid: bool = witness::YES_OR_NO; ``` **Witness file (`witness.wit`):** ```json { "QUANTITY": "5", "YES_OR_NO": "true" } ``` Some alias types like `Signature` and `Pubkey` are also written as scalar strings: **Contract expectation (`contract.simf`):** ```rust let sig: Signature = witness::ALICE_SIGNATURE; ``` **Witness file (`witness.wit`):** ```json { "ALICE_SIGNATURE": "0x7eef1115a87adc14ff7d99aea2e9501bc27f6dcc05e4720de1212732158fd94ab82f219b8f54bc07c761b38cbbafee5bd0697481ac96b819768559e31e06fe40" } ``` ### Named enumeration types Enumeration (`enum`) types declared in your `.simf` contract represent explicit, named choices. In the `.wit` file, an enum choice is passed directly as a string matching the variant name. Enum values can be *unit variants* (simple names) or *compound variants* that carry path-specific argument data. **Contract expectation (`contract.simf`):** ```rust enum Action { Update, Claim(u64, u64), } fn main() { // ... let user_choice: Action = witness::ACTION; // ... } ``` In this example, `Update` is a simple name, while `Claim` is a compound variant that wraps two additional values. **Witness file choosing `Update` (`witness.wit`):** ```json { "ACTION": "Action::Update" } ``` **Witness file choosing `Claim` with parameters (`witness.wit`):** ```json { "ACTION": "Action::Claim(17041427052385644731, 18305655359241496139)" } ``` !!! warning Type annotations for `enum` values are forbidden inside `.wit` files. They *must* be bare value strings, and their types *must* be inferred by the compiler. ### Compound types Tuples and arrays group multiple values into a single witness item. * **Tuples `(T1, T2, ...)`:** Formatted as comma-separated values inside parentheses `(...)`. * **Arrays `[T; n]`:** Formatted as comma-separated values inside square brackets `[...]`. **Contract expectation (`contract.simf`):** ```rust let mypair: (bool, u16) = witness::MYPAIR; let four_sigs: [Signature; 4] = witness::FOUR_SIGS; ``` **Witness file (`witness.wit`):** ```json { "MYPAIR": "(true, 376)", "FOUR_SIGS": "[0x8a3584f8..., 0xf74b3ca5..., 0xdf5dc2e2..., 0x29dbeab5...]" } ``` Values inside tuples and arrays can be accessed in SimplicityHL via tuple destructuring (`let (a, b): (bool, u16) = witness::mypair;`) or array indexing (`witness::FOUR_SIGS[0]`). ### Tagged sum types Tagged sum types express conditional data structures that are unwrapped inside the contract using `match` statements (which explicitly handle both possibilities) or `unwrap` macros (which assert that a specific expected version is present). * **Option Types (`Option`):** Represent optional witness data. Formatted as `"Some(...)"` or `"None"`. * **Either Types (`Either`):** Represent structural two-way choices. Formatted as `"Left(...)"` or `"Right(...)"`. #### Example: `Option` **Contract expectation (`contract.simf`):** ```rust let alice_sig: Option = witness::MAYBE_ALICE_SIG; let bob_sig: Option = witness::MAYBE_BOB_SIG; ``` (The signatures' actual presence or absence could be handled later on in the contract with `match alice_sig` and `match bob_sig` statements.) **Witness file (`witness.wit`):** ```json { "MAYBE_ALICE_SIG": "Some(0x27fe61d4e263cb2732da0b9dcd8ed27f400a40d7959901fae7ccdda896373c0fa2ecfda7168f4a200ffa5d52d7b4463453aad9c95a3ba65bccd788a8e72eb07e)", "MAYBE_BOB_SIG": "None" } ``` #### Example: `Either` While custom `enum` types are generally preferred for named contract actions, `Either` remains useful for generic structural branching. **Contract expectation (`contract.simf`):** ```rust let auth: Either = witness::SIGNATURE_OR_PUBKEY_AND_AMOUNT; ``` **Witness file taking the `Right` path (`witness.wit`):** ```json { "SIGNATURE_OR_PUBKEY_AND_AMOUNT": "Right((0xd7a2a84507129b63908bc38d27bb96fa3a55536ad3b025b95205c4a8e92c9bd2, 52119))" } ``` ## More built-in types Domain-specific [alias types](../../simplicityhl-reference/type_alias.md) are available for clarity, including, among others, `Pubkey`, `Signature`, and the four [timelock](../glossary.md#timelock) types `Distance`, `Duration`, `Height`, and `Time`. These names are capitalized in SimplicityHL signatures, and their parameter requirements are detailed in [the jet documentation](../jets.md). ### Toolchain Reference # SimplicityHL toolchain The SimplicityHL toolchain consists of the command-line tools `simc` (the SimplicityHL compiler) and `hal-simplicity` (a multipurpose command-line utility for inspecting and creating objects relevant to Simplicity programs and on-chain transactions). !!! Note The toolchain is useful for learning about Simplicity and SimplicityHL and for testing and debugging purposes. However, **most application developers will use a workflow that does not emphasize the use of these tools**. A typical smart contract development workflow is focused on a high-level language (usually Rust) in which application software is developed alongside the SimplicityHL contract in a single project. See [simplicity-contracts](https://github.com/BlockstreamResearch/simplicity-contracts) for examples of such projects. In this workflow, SimplicityHL programs are compiled, addresses are derived, and witnesses and transactions are built from within Rust applications using other libraries and tooling, not `simc` and `hal-simplicity`. (The other libraries and tooling share some of their code with these command-line tools.) The [Liquid Wallet Kit (LWK)](https://docs.liquid.net/docs/lwk-overview-and-examples) also provides tools for building witnesses and transactions from high-level languages other than Rust, facilitating an analogous workflow for those languages. !!! note "Try these tools in the Simplicity Codespace" You can try out these tools interactively in the [Simplicity Codespace](https://github.com/Blockstream/simplicity-codespace). !!! note "VSCode users" If you're expecting to develop SimplicityHL contracts with Visual Studio Code, you can also install Blockstream's VSCode extension to provide syntax highlighting and other developer features. * Open Extensions View: Click the Extensions icon in the left sidebar. * Search: In the search field, type `SimplicityHL`. * Install: Click the Install button for the extension provided by Blockstream. ## Typical workflow **As noted above, most SimplicityHL developers will use a different workflow using other tools.** The most basic workflow for on-chain transactions with the command-line toolchain is: *Commit-time* (that is, when sending assets *to* the contract): 1. compile the program with `simc` and note the compiled program data 2. derive the on-chain destination address of the compiled program with `hal-simplicity simplicity info` (optionally including `-s` to commit to 256 bits of specified state data) 3. send assets to the calculated on-chain address *Redeem-time* (that is, when spending assets *from* the contract): 1. update the `.wit` file with the witness data for the redemption transaction 2. compile the program with `simc` and note the compiled program data and serialized witness data 3. create a skeleton [PSET](../glossary.md#pset) with `hal-simplicity simplicity pset create`, indicating the [UTXO](../glossary.md#utxo) to be spent 4. attach more details about the UTXO to be spent to the PSET with `hal-simplicity simplicity pset update-input` 5. attach the compiled program and serialized witness data to the PSET with `hal-simplicity simplicity pset finalize` 6. convert the PSET to a serialized transaction with `hal-simplicity simplicity pset extract` 7. submit the resulting transaction on the blockchain You can see a complete worked example of the actions above, both commit-time and redeem-time, in the old [bash quickstart](../getting-started/bash-quickstart.md), which is no longer suggested for most beginning users. There are also several demonstrations in the Simplicity Codespace, linked in the note above, which demonstrate both actions using `simc` and `hal-simplicity`. ## simc Install with: `cargo install simplicityhl` ```text Compile the given SimplicityHL program and print the resulting Simplicity base64 string. Usage: simc [OPTIONS] Arguments: SimplicityHL program file to build Options: -w, --wit File containing the witness data -a, --args File containing the arguments data --debug Include debug symbols in the output --json Output in JSON --abi Additional ABI .simf contract types -h, --help Print help -Z, --unstable-feature Enable unstable features. ``` The compiled program, its corresponding [Commitment Merkle Root](../glossary.md#cmr), and optionally the serialized witness file when it is provided with `-w`, are printed in base64 format on the standard output, with text labels identifying each output item. The base64 form of the program is a representation of its low-level Simplicity code, and could be considered the "binary". If the option `--json` is provided, produce JSON output instead. The JSON object fields `program` and `witness` contain the base64-encoded compiled program and base64-encoded serialized witness, respectively. Using `--json` is recommended whenever the output of `simc` will be parsed by other programs or scripts. The compiled program is needed in order to derive addresses and parameters at commit-time (that is, when sending assets *to* the contract). The witness is additionally needed in order to derive addresses and parameters at redeem-time (that is, when spending assets *from* the contract). A [covenant](../glossary.md#covenant) is used in both ways at once, as it may be both the origin and the destination of assets inside the same transaction. ## hal-simplicity Install with: `cargo install hal-simplicity` `hal-simplicity` is based on [`hal-elements`](https://github.com/ElementsProject/hal-elements) and also includes the subcommands from `hal-elements`. A future release may merge both tools into one. The `hal-simplicity` subcommands that differ from `hal-elements` are `hal-simplicity simplicity info`, `hal-simplicity simplicity pset`, and `hal-simplicity simplicity sighash`. These are used, respectively, for constructing *on-chain addresses*, *transactions*, and *signatures* for use with Simplicity programs. Some additional information and sample invocations appears in the [`hal-simplicity` README file](https://github.com/BlockstreamResearch/hal-simplicity/blob/master/README.md). ### hal-simplicity simplicity info ```text Parse a base64-encoded Simplicity program and decode it USAGE: hal-simplicity simplicity info [FLAGS] [OPTIONS] [witness] FLAGS: -r, --elementsregtest run in elementsregtest mode -h, --help Prints help information --liquid run in liquid mode -v, --verbose print verbose logging output to stderr -y, --yaml print output in YAML instead of JSON OPTIONS: -s, --state 32-byte state commitment to put alongside the program when generating addresess (hex) ARGS: a Simplicity program in base64 [witness] a hex encoding of all the witness data for the program ``` The optional state commitment via `-s` is noteworthy here, as it is needed to derive an address reflecting a commitment to contract [state](state.md) information. Without `-s`, no state commitment is included. The output is a JSON object which contains some of the following fields (most of them only in case a witness was provided): * `jets`: currently always `core`. * `commit_base64`: the base64 low-level Simplicity program (as provided as input). * `commit_decode`: a representation of the low-level Simplicity code as a sequence of [combinator](../glossary.md#combinator) and [jet](../glossary.md#jet) invocations. This is one practical way to visualize what low-level Simplicity code looks like. * `type_arrow`: currently always `1 → 1`. * `cmr`: the [CMR](../glossary.md#cmr) of the program. * `liquid_address_unconf`: the [Liquid](../glossary.md#liquid) on-chain address of the program, in unconfidential format * `liquid_testnet_address_unconf`: the [Liquid](../glossary.md#liquid) testnet on-chain address of the program, in unconfidential format * `is_redeem`: whether a witness was provided (for redeem-time) or not (for commit-time) * `redeem_base64`: a redemption-time version of the program with pruning performed (removing unused program branches) and witness data attached * `witness_hex`: the serialized witness data in hex (rather than base64) format * `amr`: annotated Merkle root (internal cryptographic parameter) * `ihr`: identity hash of the root (internal cryptographic parameter) ### hal-simplicity simplicity pset This tool is used in the redemption phase to create a transaction capable of claiming existing on-chain assets from [UTXOs](../glossary.md#utxo) that are controlled by a specified Simplicity program. The transaction is created in [PSET](../glossary.md#pset) format by sequentially attaching multiple forms of required information to a skeleton transaction. ```text manipulate PSETs for spending from Simplicity programs USAGE: hal-simplicity simplicity pset [FLAGS] FLAGS: -h, --help Prints help information -v, --verbose print verbose logging output to stderr SUBCOMMANDS: create create an empty PSET extract extract a raw transaction from a completed PSET finalize Attach a Simplicity program and witness to a PSET input run Run a Simplicity program in the context of a PSET input. update-input Attach UTXO data to a PSET input ``` #### hal-simplicity simplicity pset create This command creates a new empty PSET. It needs to know where the asset to be spent is coming from, and where it should be sent to. The first argument is a JSON string consisting of a list of *outpoints*, each a JSON object containing a `txid` and a `vout`, like this: ```json [ { "txid": "", "vout": }, { "txid": "", "vout": }, { "txid": "", "vout": } ] ``` where each `` is a txid in hex form and each `` is an integer index. The second argument is a JSON string consisting of a list of JSON objects mapping *destination addresses to assets and amounts*, where each destination address is an on-chain address and each amount is a floating-point value. The assets are specified as 64-character hexadecimal values (Liquid asset IDs). One of the JSON objects can also optionally contain a mapping from the string `fee` to an amount, indicating payment of a fee. ```json [ { "address": "", "asset": "", "amount": }, { "fee": } ] ``` where `` is a destination address, `` is a hexadecimal Liquid asset ID, `` is a floating-point amount, and `` is a floating-point amount. If the asset is not specified, it is assumed by default to be the asset corresponding to Liquid bitcoin (LBTC). Network fees are currently always automatically paid in LBTC. The output of the command is a JSON object whose attribute `pset` contains the new PSET in base64 format. #### hal-simplicity simplicity pset update-input This command modifies an existing PSET by attaching required redeem-time details to an input that is a [UTXO](../glossary.md#utxo) controlled by a Simplicity program. `hal-simplicity simplicity pset update-input -i :: -c -p ` where `` is the existing base64-encoded PSET to modify, `` is the index of the input to modify, `` is the scriptPubKey of the input, `asset` is the Liquid asset ID of the input, `value` is the numerical amount of the input, `` is the [CMR](../glossary.md#cmr) of the program to spend from, and `-p` is the program's internal key (commonly the fixed value `50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0` by default). The output of the command is a JSON object whose attribute `pset` contains the updated PSET in base64 format. #### hal-simplicity simplicity pset finalize This command attaches a Simplicity program and witness data to a PSET for redemption purposes. `hal-simplicity simplicity pset finalize ` where `` is the existing base64-encoded PSET to modify, `` is the index of the input to modify, `` is the base64-encoded low-level Simplicity program, and `` is the serialized witness data. The output of the command is a JSON object whose attribute `pset` contains the updated PSET in base64 format. #### hal-simplicity simplicity pset run `hal-simplicity simplicity pset run ` This command simulates running a Simplicity program in the context of a transaction built up as a PSET. You can see whether the transaction succeeds and some the details of individual jet invocations, their inputs, and their outputs. This helps with debugging to confirm whether a program will complete successfully and approve a transaction, as well as understanding why it accepted or rejected a specific proposed transaction. The command has the same arguments as `hal-simplicity simplicity pset finalize` but *runs* the program in the context of the resulting transaction, instead of outputting a PSET representing that transaction. The output is a list of events in the execution of the program. #### hal-simplicity simplicity pset extract `hal-simplicity simplicity pset extract ` where `` is the existing base64-encoded PSET to transform into a broadcastable transaction. This command serializes a PSET in a hexadecimal form suitable for submission to the blockchain. The output is the complete hex data for the new transaction. ### hal-simplicity simplicity sighash Generate (or validate) signatures over Simplicity transactions using a private key. ```text hal-simplicity simplicity sighash [FLAGS] [OPTIONS] [--] [control-block] FLAGS: -r, --elementsregtest run in elementsregtest mode -g, --genesis-hash genesis hash of the blockchain the transaction belongs to (hex) -h, --help Prints help information --liquid run in liquid mode -v, --verbose print verbose logging output to stderr -y, --yaml print output in YAML instead of JSON OPTIONS: -i, --input-utxo ... an input UTXO, without witnesses, in the form :: (should be used multiple times, one for each transaction input) (hex:hex:BTC decimal or hex) -p, --public-key public key which is checked against secret-key (if provided) and the signature (if provided) (hex) -x, --secret-key secret key to sign the transaction with (hex) -s, --signature signature to validate (if provided, public-key must also be provided) (hex) ARGS: transaction to sign (hex) the index of the input to sign (decimal) CMR of the input program (hex) Taproot control block of the input program (hex) ``` In signing applications, the basic syntax is `hal-simplicity simplicity sighash -x ` where `` is the base64-encoded PSET containing the transaction to be signed, `` is the numeric input index, `` is the [CMR](../glossary.md#cmr) of the Simplicity program that controls the UTXO, and `privkey` is the hex-encoded private key with which the signature should be made. The output is a JSON object containing signature details, in which the `signature` attribute contains a signature suitable for inclusion in a Simplicity program's witness data. Note that the version of the signature used in a `.wit` must begin with `0x` before the hexadecimal signature. `hal-simplicity simplicity sighash` can also *verify* signatures that have already been created, if the expected public key is specified with `-p` and the existing signature with `-s`. ### Context # Context A context Γ maps variable names to Simplicity types: Γ = [ `foo` ↦ 𝟙, `bar` ↦ 𝟚^32?, `baz` ↦ 𝟚^32 × 𝟙 ] We write Γ(`v`) = A to denote that variable `v` has type A in context Γ. We handle free variables inside SimplicityHL expressions via contexts. If all free variables are defined in a context, then the context assigns a type to the expression. We write Γ ⊩ `a`: A to denote that expression `a` has type A in context Γ. Note that contexts handle only the **target type** of an expression! Source types are handled by environments and the translation of SimplicityHL to Simplicity. We write Γ ⊎ Δ to denote the **disjoint union** of Γ and Δ. 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. ## Unit literal Γ ⊩ `()`: 𝟙 ## Product constructor If Γ ⊩ `b`: B If Γ ⊩ `c`: C Then Γ ⊩ `(b, c)`: B × C ## Left constructor If Γ ⊩ `b`: B Then Γ ⊩ `Left(b)`: B + C For any C ## Right constructor If Γ ⊩ `c`: C Then Γ ⊩ `Right(c)`: B + C For any B ## Bit string literal If `s` is a bit string of 2^n bits Then Γ ⊩ `0bs`: 𝟚^(2^n) ## Byte string literal If `s` is a hex string of 2^n digits Then Γ ⊩ `0xs`: 𝟚^(4 * 2^n) ## Variable If Γ(`v`) = B Then Γ ⊩ `v`: B ## Witness value Γ ⊩ `witness(name)`: B For any B ## Jet If `j` is the name of a jet of type B → C If Γ ⊩ `b`: B Then Γ ⊩ `jet::j b`: C ## Chaining If Γ ⊩ `b`: 𝟙 If Γ ⊩ `c`: C Then Γ ⊩ `b; c`: C ## Patterns Type A and pattern `p` create a context denoted by PCtx(A, `p`) PCtx(A, `v`) := [`v` ↦ A] PCtx(A, `_`) := [] If `p1` and `p2` map disjoint sets of variables Then PCtx(A × B, `(p1, p2)`) := PCtx(A, `p1`) ⊎ PCtx(B, `p2`) ## Let statement If Γ ⊩ `b`: B If Γ // PCtx(B, `p`) ⊩ `c`: C Then Γ ⊩ `let p: B = b; c`: C With alternative syntax Then Γ ⊩ `let p = b; c`: C ## Match statement If Γ ⊩ `a`: B + C If Γ // [`x` ↦ B] ⊩ `b`: D If Γ // [`y` ↦ C] ⊩ `c`: D Then Γ ⊩ `match a { Left(x) => b, Right(y) => c, }`: D _(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.)_ ## Left unwrap If Γ ⊩ `b`: B + C Then Γ ⊩ `b.unwrap_left()`: B ## Right unwrap If Γ ⊩ `c`: B + C Then Γ ⊩ `c.unwrap_right()`: C ### Environment # Environment An environment Ξ maps variable names to Simplicity expressions. All expressions inside an environment share the same source type A; the environment is said to be "from type A". ```text Ξ = [ foo ↦ unit: (𝟚^32? × 2^32) → 𝟙 , bar ↦ take iden: (𝟚^32? × 𝟚^32) → 𝟚^32? , baz ↦ drop iden: (𝟚^32? × 𝟚^32) → 𝟚^32 ] ``` Environments translate variables inside SimplicityHL expressions to Simplicity. The environment gives the Simplicity expression that returns the value of each variable. A SimplicityHL program is translated "top to bottom". Each time a variable is defined, the environment is updated to reflect this change. During the translation, the source type of Simplicity expressions (translated SimplicityHL expressions) can be ignored entirely. Translation focuses on producing a Simplicity value of the expected target type. Environments ensure that input values are available for each variable that is in scope. Target types are handled by contexts. Context Ctx(Ξ) is obtained from environment Ξ by mapping each variable `x` from Ξ to the target type of Ξ(`x`): Ctx(Ξ)(`x`) = B if Ξ(`x`) = a: A → B ## Patterns Patterns occur in let statements `let p := s`. Pattern `p` binds the output of SimplicityHL expression `s` to variables. Translating `s` to Simplicity requires an environment that maps the variables from `p` to Simplicity expressions. If `p` is just a variable `p = a`, then the environment is simply [`a` ↦ iden: A → A]. 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]. "take" and "drop" are added when going deeper in the product hierarchy. The pattern `_` is ignored. PEnv'(t: A → B, `v`) := [`v` ↦ t] PEnv'(t: A → B, `_`) := [] If `p1` and `p2` contain disjoint sets of variables Then PEnv'(t: A → B × C, `(p1, p2)`) := PEnv'(take t: A → B, p1) ⊎ PEnv'(drop t: A → C, p2) PEnv(A, `p`) := PEnv'(iden: A → A, `p`) Pattern environments are compatible with pattern contexts: Ctx(PEnv(A, `p`)) = PCtx(A, `p`) ## Product Product(ΞA, ΞB) denotes the **product** of environment ΞA from A and environment ΞB from B. The product is an environment from type A × B. When two Simplicity expressions with environments are joined using the "pair" combinator, the product of both environments gives updated bindings for all variables. If the same variable is bound in both environments, then the binding from the first environment is taken. If ΞA maps `v` to Simplicity expression a: A → C Then Product(ΞA, ΞB) maps `v` to take a: A × B → C If ΞB maps `v` to Simplicity expression b: B → C If ΞA doesn't map `v` Then Product(ΞA, ΞB) maps `v` to drop b: A × B → C Environment products are compatible with context updates: Ctx(Product(ΞA, ΞB)) = Ctx(ΞB) // Ctx(ΞA) The order of B and A is reversed: The context of ΞB is updated with the dominant context of ΞA. ### Translation # Translation We write ⟦`e`⟧Ξ to denote the translation of SimplicityHL expression `e` using environment Ξ from A. The translation produces a Simplicity expression with source type A. The target type depends on the SimplicityHL expression `e`. ## Unit literal ⟦`()`⟧Ξ = unit: A → 𝟙 ## Product constructor If Ctx(Ξ) ⊩ `b`: B If Ctx(Ξ) ⊩ `c`: C Then ⟦`(b, c)`⟧Ξ = pair ⟦`b`⟧Ξ ⟦`c`⟧Ξ: A → B × C ## Left constructor If Ctx(Ξ) ⊩ `b`: B Then ⟦`Left(b)`⟧Ξ = injl ⟦`b`⟧Ξ: A → B + C For any C ## Right constructor If Ctx(Ξ) ⊩ `c`: C Then ⟦`Right(c)`⟧Ξ = injr ⟦`c`⟧Ξ: A → B + C For any B ## Bit string literal If `s` is a bit string of 2^n bits Then ⟦`0bs`⟧Ξ = comp unit const 0bs: A → 𝟚^(2^n) ## Byte string literal If `s` is a hex string of 2^n digits Then ⟦`0xs`⟧Ξ = comp unit const 0xs: A → 𝟚^(4 * 2^n) ## Variable If Ctx(Ξ)(`v`) = B Then ⟦`v`⟧Ξ = Ξ(`v`): A → B ## Witness value Ctx(Ξ) ⊩ `witness(name)`: B Then ⟦`witness(name)`⟧Ξ = witness: A → B ## Jet If `j` is the name of a jet of type B → C If Ctx(Ξ) ⊩ `b`: B Then ⟦`jet::j b`⟧Ξ = comp ⟦`b`⟧Ξ j: A → C ## Chaining If Ctx(Ξ) ⊩ `b`: 𝟙 If Ctx(Ξ) ⊩ `c`: C Then ⟦`b; c`⟧Ξ = comp (pair ⟦`b`⟧Ξ ⟦`c`⟧Ξ) (drop iden): A → C ## Let statement If Ctx(Ξ) ⊩ `b`: B If Product(PEnv(B, `p`), Ξ) ⊩ `c`: C Then ⟦`let p: B = b; c`⟧Ξ = comp (pair ⟦`b`⟧Ξ iden) ⟦`c`⟧Product(PEnv(B, `p`), Ξ): A → C ## Match statement If Ctx(Ξ) ⊩ `a`: B + C If Product(PEnv(B, `x`), Ξ) ⊩ `b`: D If Product(PEnv(C, `y`), Ξ) ⊩ `c`: D Then ⟦`match a { Left(x) => b, Right(y) => c, }`⟧Ξ = comp (pair ⟦`a`⟧Ξ iden) (case ⟦`b`⟧Product(PEnv(B, `x`), Ξ) ⟦`c`⟧Product(PEnv(C, `y`), Ξ)): A → D ## Left unwrap If Ctx(Ξ) ⊩ `b`: B + C Then ⟦`b.unwrap_left()`⟧Ξ = comp (pair ⟦`b`⟧Ξ unit) (assertl iden #{fail 0}): A → B ## Right unwrap If Ctx(Ξ) ⊩ `c`: B + C Then ⟦`c.unwrap_right()`⟧Ξ = comp (pair ⟦`c`⟧Ξ unit) (assertr #{fail 0} iden): A → C ## Resources ### FAQ # Simplicity FAQ ## Simplicity is so simple it fits on a [T-shirt](https://store.blockstream.com/products/simplicity-t-shirt). Does that mean it's as limited as Bitcoin Script? No, the "simplicity" refers to its foundational design and formal semantics, not its expressiveness. - Bitcoin Script is deliberately limited; Simplicity is finitarily complete, meaning it can program any finite computation. - Complex off-chain (even Turing-complete) computations can be verified on-chain with Simplicity. ## Is Simplicity Turing-complete like EVM? No. Non-Turing-completeness is a deliberate design choice. Costs are known at compile time, so there are no "out of gas" failures. Programs contain no unbounded loops or recursion, so every program halts. Programs are also analyzable; this property supports formal reasoning about program behavior. ## How does Simplicity handle state? Does it have global state like Ethereum? Simplicity has no global state. It is a purely functional language: each program is just a function that maps inputs to outputs. Contracts run within the Bitcoin [UTXO](../glossary.md#utxo) model: 1. Contracts are small programs attached to UTXOs that guard the associated coins. 2. Spending a UTXO means providing witness data so the contract evaluates to true. 3. State is carried forward explicitly by committing data into the next UTXO. This design avoids shared mutable state (as in Ethereum). Instead, every transition is localized: a UTXO is consumed, and the updated state is re-committed into the new UTXO. ## How do I prove my Simplicity contract is correct? Formal verification happens in [Coq/Rocq](https://rocq-prover.org/), not directly in Simplicity. Process: 1. Simplicity semantics are modeled in Coq. 2. You prove correctness properties (safety, termination, resource use). 3. Proofs give guarantees before deployment. ## Simplicity is low-level. Do I write contracts directly in it? Not usually. Developers ordinarily write in high-level SimplicityHL, which compiles down to Simplicity. ## What are Jets, and how do they make programs efficient? A [Simplicity jet](../documentation/jets.md) is a pre-defined, optimized function that replaces an equivalent Simplicity expression to speed up execution without changing its meaning. Benefits: - Programs remain formally verifiable. - Heavy operations run in optimized C instead of interpreted combinators. - Keeps execution fast, compact, and analyzable. ## How does Simplicity exist alongside Bitcoin script? With [Taproot](../glossary.md#taproot)'s versioned leaves, a single Taproot output can include both standard Script/Miniscript leaves and a Simplicity leaf. This allows mixing policies: simple paths can remain in Script while advanced paths use Simplicity, preserving flexibility and privacy under one Taptree. ## How do I track the value of a Simplicity contract with partial payouts when different strike prices are being matched? Simplicity contracts operate on UTXO-committed state. Each contract output carries forward a table of strikes together with their remaining notionals. At every settlement event, the contract: 1. Reads the current reference price. 2. Applies partial payouts to any strikes that are matched. 3. Updates the strike table by reducing the notional amounts that have been settled. The value of the contract at any point is given by the piecewise payoff function, evaluated against the latest UTXO state. By inspecting the most recent UTXO, participants can determine both their current position and their outstanding exposure. (see also: Bitcoin Optech [comment from AJ Towns](https://bitcoinops.org/en/newsletters/2024/11/29/#flexible-coin-earmarks) on flexible coin earmarks) ### Glossary # Glossary These are terms likely to appear within Simplicity documentation and other educational materials. ## Address An identifier to which [assets](./glossary.md#asset) may be sent on a blockchain. Each address is associated with one or more scripts, such as Simplicity [contracts](./glossary.md#contract), which control access to funds sent to that address. Given a Simplicity contract and an "unspendable [internal key](./glossary.md#internal-key)", it is possible to derive a unique address for that contract, which refers to an instance of the program's code. ## Artifacts Rust library functions, automatically generated by [Simplex](../documentation/simplex/), that facilitate building [witnesses](./glossary.md#witness) and [transactions](./glossary.md#transaction) matching the requirements and expectations of an individual [contract](./glossary.md#contract). ## Asset A specific abstract or virtual possession whose ownership is tracked on a blockchain according to the blockchain's rules. In Bitcoin, there is only one asset directly tracked on the blockchain, although there are indirect ways to represent ownership and transfer of others. In Elements, [anyone can create a new asset at any time](https://elementsproject.org/features/issued-assets), and a single [transaction](./glossary.md#transaction) can natively involve the transfer of multiple assets at once. Simplicity allows [introspection](./glossary.md#introspection) of [input](./glossary.md#input) and [output](./glossary.md#output) data in order to allow a program to determine which asset or assets are proposed to be transferred in a specific transaction, and where the assets are proposed to be sent. The program can then use this information to constrain the transaction according to its logic, such as by requiring certain assets to be sent only to a specific destination, or even requiring assets to be sent back to the same contract. See also "[token](./glossary.md#token)". ## Bitcoin Script A programming language included in Bitcoin since its inception, allowing some policies to be applied to an [output](./glossary.md#output) of a [transaction](./glossary.md#transaction). Like Simplicity, intentionally not [Turing complete](./glossary.md#turing-complete); more limited than Simplicity, particularly with regard to [introspection](./glossary.md#introspection) and [covenants](./glossary.md#covenant). See also [Elements Script](./glossary.md#elements-script) (Also just "Script".) ## CMR Commitment Merkle Root. A cryptographic representation of the identity of a specific Simplicity [contract](./glossary.md#contract) as a [Merkle tree](./glossary.md#merkle-tree). This provides a way to refer to that contract, and eventually to confirm that a partially revealed (pruned) contract posted on a blockchain was properly derived from a specified original contract. ## Combinator A low-level operation in Simplicity, the approximate equivalent of an opcode in [Bitcoin Script](./glossary.md#bitcoin-script) and other low-level programming languages. Simplicity is designed using [combinatory logic](https://en.wikipedia.org/wiki/Combinatory_logic); a low-level Simplicity program is made up of a series of combinator invocations as well as invocations of [jets](./glossary.md#jet). In combinatory logic, a combinator is a function that takes functions as input and returns a function as output, so that functions are the underlying objects that the logical system deals with. Architecturally, Simplicity combinators follow this pattern. ## Confidential On the [Liquid Network](./glossary.md#liquid), transactions may be [confidential](https://blog.liquid.net/guide-to-confidential-transactions/), preventing third parties from determining the amount and asset of a transaction. To use this feature, the transaction must be sent to a "confidential address" which includes a cryptographic blinding key. If the destination address does not include such a key, it may be called "[unconfidential](./glossary.md#unconfidential)". For more information, see also [Confidential Transactions on Liquid](https://docs.liquid.net/docs/liquid-features-and-benefits#confidential-transactions-on-liquid). ## Contract Sometimes used interchangeably with "program". Often, a specific instance of Simplicity code that can receive [assets](./glossary.md#asset) on a blockchain and make decisions about how to dispose of those assets in accordance with its internal logic. The broader concept of a [smart contract](./glossary.md#smart-contract) might in turn refer either narrowly to a specific Simplicity program or broadly to a whole set of interactions and relationships realized through code, of which that Simplicity program could be only one component. In this view a smart contract as a whole potentially includes several Simplicity programs, possibly as well as other related technical arrangements. ## Cost In Simplicity blockchain integrations, a metric for the computational resources used in verifying a [transaction](./glossary.md#transaction) that invokes a Simplicity [contract](./glossary.md#contract). Cost is a measurement of CPU usage (to avoid multidimensional optimization problems, the other major resource, memory, is simply capped at a fixed value). Cost is converted to a minimum weight that a transaction input must carry, which is then paid for via transaction [fees](./glossary.md#fee). See "[weight](./glossary.md#weight)" for more information. ## Covenant A covenant is a condition or behavior in a [contract](./glossary.md#contract) related to restrictions on the [output](./glossary.md#output) destination to which an [asset](./glossary.md#asset) may be transferred. Covenants allow a contract to enforce various rules that form useful building blocks for higher-level mechanisms and guarantees about contract behavior. Simplicity supports highly general covenant mechanisms by means of its [introspection](./glossary.md#introspection) features. For example, covenants in Simplicity can enforce... * rules like multi-step spending processes, or preprogrammed delays in spending under some circumstances * rules providing for cases in which some assets must be refunded to or retained by the same contract * requirements that some assets be sent only to specific recipients or other Simplicity contract instances * policies authorizing progressively increasing [fee](./glossary.md#fee) amounts as a transaction involving the contract becomes older. ## Elements A blockchain software system derived from Bitcoin and developed primarily by Blockstream. Elements allows the creation of Bitcoin-like blockchains with enhanced functionality. It is the software architecture underlying the [Liquid](./glossary.md#liquid) Network. ## Elements Script An extension of [Bitcoin Script](./glossary.md#bitcoin-script) which includes several new opcodes for 64-bit arithmetic and transaction introspection (covenants). See [tapscript_opcodes.md](https://github.com/ElementsProject/elements/blob/master/doc/tapscript_opcodes.md) in the Elements source tree. Still not [Turing complete](./glossary.md#turing-complete) or as expressive as Simplicity. (Also just "Script", when it is clear or irrelevant whether Bitcoin or Elements Script is meant.) ## elements-cli The standard command line user interface for creating and querying blocks, transactions, and other objects within a network based on [Elements](./glossary.md#elements), including the [Liquid](./glossary.md#liquid) Network. ## elementsd The software used to create an [Elements](./glossary.md#elements) network node, including a [Liquid](./glossary.md#liquid) Network node, which maintains and verifies an up-to-date copy of the blockchain of the network in question. ## Fee Resources intentionally paid to miners as part of a [transaction](./glossary.md#transaction) in order to compensate them for producing blocks. In Elements, block production is practically free so the fee market serves as an anti-denial-of-service measure and as a way to prioritize transactions for inclusion in blocks. ## hal-simplicity A software tool that provides various pieces of Simplicity-related functionality, including those needed to build Simplicity-related [transactions](./glossary.md#transaction). ## Height (Also "block height".) The number of blocks that currently exist on a specific blockchain, or that existed as of a [transaction](./glossary.md#transaction) of interest. Since blockchains normally add blocks at a predictable rate, the height can be used as a measurement of the current date and time, providing one mechanism for [contracts](./glossary.md#contract) to refer to and enforce conditions related to the dates before or after which certain events may or must occur. For example, Liquid mainnet block height 3634700 occurred at 2025-11-21 02:23:10 UTC, and Liquid adds blocks at a rate of 1 per minute, so the date 2026-05-01 can be indicated by adding about 232000 minutes to this height, giving height 3866700 as a reference to a time a little later in the morning on that date. ## Input In Bitcoin or Elements, a funding source that contributes [assets](./glossary.md#asset) to a particular [transaction](./glossary.md#transaction). ## Internal key In Taproot, every output can be spent in two ways: by signing a transaction with a public key, or by revealing a Script or Simplicity program embedded in the key as a Taproot commitment, along with a satisfying witness. The "internal key" is the component of a Taproot commitment which defines which party or parties is able to sign. Commonly, an unspendable key is used, such as 50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 (taken from [BIP 0341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki), which also specifes a method to blind this key for privacy reasons). This disables the key-spending path. With Simplicity contracts, the typical construction is to use a Taproot tree with an unspendable internal key and a single leaf denoting the program, or two leaves denoting the program and its state commitment. Changing the internal key or state commitment changes the [address](./glossary.md#address) of the program without changing its code. ## Introspection In Simplicity, the ability for a [contract](./glossary.md#contract) to examine the details of the context of a proposed [transaction](./glossary.md#transaction) (via introspection [jets](./glossary.md#jet)) in order to make decisions about whether to approve the transaction, particularly the control of [outputs](./glossary.md#output) in order to enforce "[covenant](./glossary.md#covenant)" conditions. ## Jet An optimized native-code implementation of a Simplicity expression, such as arithmetic, logic, bit manipulations, or cryptographic operations. Jets are faster and therefore have a lower [cost](./glossary.md#cost) than their equivalent Simplicity code. Validating nodes are assumed to be executing the optimized code rather than their Simplicity specification, justifying this cost reduction. The list of jets and their specific behaviors is fixed at the time of integration of Simplicity into a particular blockchain. In the [Elements](./glossary.md#elements) integration, there are 469 jets. ## Liquid A specific [Elements](./glossary.md#elements)-based network, the [Liquid Network](https://liquid.net/), that is the first blockchain to have native support for Simplicity. Most Simplicity examples as of 2026 assume that a program is running on the Liquid mainnet or Liquid testnet, although other integrations are planned. ## Manifest (Also "`txmanifest`", the full name of the manifest file format.) A JSON file that describes an individual [smart contract](./glossary.md#smart-contract) in detail, helping wallet applications identify instances of the contract on-chain and build [transactions](./glossary.md#transaction) to interact with it, thereby invoking user-selected actions within the contract's logic. ## Merkle tree A cryptographic mechanism for representing a potentially large amount of data concisely in a way that ensures that none of the data can be changed (a "commitment"). The Merkle tree also allows that data to be revealed selectively, so that some portions can be disclosed and verified, while continuing to hide other portions. A Merkle tree is represented by its root, which is a single cryptographic hash that commits to every object in the tree. A Merkle tree is used in creating an [address](./glossary.md#address) for a Simplicity program, as well as in enabling [pruning](./glossary.md#pruning) of that program when it is run. See also [CMR](./glossary.md#cmr) (the root of a Merkle tree describing a specific Simplicity program). ## Multisig A transaction architecture (or other application of digital signatures) in which a specified number or combination of signatures from several distinct signing keys is required in order to approve a [transaction](./glossary.md#transaction) or other event or statement. Often specified as k-of-n multisig, e.g. a 7-of-10 multisig design would require that any 7 of 10 specified entities provide their approval in order for a transaction as a whole to go ahead. This can be used as a precaution to mitigate the impact of mistakes, compromise, or misbehavior by individual signers or groups of signers, much as an offline action or transaction could require prior approval by multiple distinct parties. Outside of the blockchain space, the term "threshold signature" is more commonly used, while "multisignature" is reserved for the case when all signers are required to generate a signature. ## Node An entity that participates in the verification of [transactions](./glossary.md#transaction) on a blockchain. In most blockchains, anyone can operate a node just by running a copy of the blockchain's verification software. The node will typically download a complete copy of the blockchain data. Nodes typically validate all transactions in all blocks, including any Scripts or Simplicity programs that appear in them. Sometimes the term "full node" is used to emphasize that all parts of all transactions are validated. An "archival node" refers to a full node which retains all data after it has been verified. You can run your own local [Liquid](./glossary.md#liquid) (mainnet or testnet) node with the [elementsd](./glossary.md#elementsd) software. ## Oracle An entity that is trusted (in the Simplicity context, by users of a [contract](./glossary.md#contract)) to make accurate digitally-signed statements about some fact or situation that exists outside of a blockchain, such as a current market price, or whether or not some real-world event has occurred as of a specified date. Oracle statements intended for use in conjunction with blockchains can often include a specific block [height](./glossary.md#height) to indicate the time as of which the oracle certifies that its statement was true. Most kinds of oracle statements should include some form of dating mechanism for confirming whether the statement is still recent, so that old oracle statements can't be misleadingly reused in the future. For example, a price oracle would normally say something more like "We observed wheat for delivery in December 2025 trade for USD 5.2225 per bushel on 2025-11-25" (though in a more machine-readable form) rather than "The price of wheat is USD 5.2225 per bushel" (as this statement is not true in general). On a blockchain the oracle could likely express this statement in terms of block [heights](./glossary.md#height). Relying on an oracle creates some risks, both that the oracle may appear to issue an inaccurate statement (for example, due to loss of control over its private key), and that the oracle may cease to operate eventually (for example, due to a bankruptcy of a company that was operating it) and fail to make expected statements after a certain point in time. Some of these risks could be reduced by requiring a quorum of several oracles (like a [multisig](./glossary.md#multisig) mechanism), albeit at the cost of needing to ensure the existence of more oracle operators. ## Output In Bitcoin or [Elements](./glossary.md#elements), a funding destination that receives a quantity of an [asset](./glossary.md#asset) from a particular [transaction](./glossary.md#transaction) and that specifies an associated future condition for subsequent transfer (or "redemption") of that asset. The conditions associated with an output are ultimately enforced by the logic of [Bitcoin Script](./glossary.md#bitcoin-script) or [Simplicity](./glossary.md#simplicity) programs. An unspent output is a [UTXO](./glossary.md#utxo). ## Parameter (1) A value (e.g. a trusted public key) attached to an instance of a [SimplicityHL](./glossary.md#simplicityhl) [contract](./glossary.md#contract) at compile-time. (2) A value attached to a Bitcoin or [Elements](./glossary.md#elements) [transaction](./glossary.md#transaction). ## Private key In public-key cryptography, a secret value corresponding to a specific [public key](./glossary.md#public-key). The possessor of the private key can use it to create digital signatures indicating agreement with specific assertions, such as proposed Bitcoin or [Elements](./glossary.md#elements) transactions, or [oracle](./glossary.md#oracle) assertions. Anyone can verify those digital signatures using the corresponding public key. ## Program Sometimes used interchangeably with "contract". A specific instance of [Simplicity](./glossary.md#simplicity) code that can receive [assets](./glossary.md#asset) on a blockchain and make decisions about how to dispose of those assets in accordance with its internal logic. A distinction between a "program" and a "contract" is that a "contract" can refer to a larger system for controlling disposition of [assets](./glossary.md#asset) across multiple [transactions](./glossary.md#transaction), each of which is individually controlled by a program. The contract can thus potentially consist of more than instance of a program, or even of several related programs that respectively handle different portions of the contracts functionality. A program’s creator could choose to publish its code (outside of a blockchain) in order to allow other people to learn of its existence and interact with it. A reference to the program’s address appears on a blockchain when a [transaction](./glossary.md#transaction) includes an [output](./glossary.md#output) controlled by the program. A copy of the program’s code (in pruned form) appears on the blockchain only when a later transaction spends such an output. ## Pruning A transformation of a [Simplicity](./glossary.md#simplicity) program before publication as part of a [transaction](./glossary.md#transaction), so that the modified program includes only the code paths that actually executed as part of that transaction. This specifically applies to conditional branches ([SimplicityHL](./glossary.md#simplicityhl) `match` statement; [Simplicity](./glossary.md#simplicity) `case` [combinator](./glossary.md#combinator)) where only one path of several will be used in any specific instance. This pruning process means that, for example, a contract that supports several different outcome scenarios, with code logic for each of them, will not be published in full as part of any specific transaction. Instead, only the relevant portion of the contract will be published. The pruning mechanism typically reduces the amount of data that must be stored on the blockchain, which miners and other [node](./glossary.md#node) operators consider important. It can also provide a degree of privacy by not unnecessarily publicly revealing the details of what would have happened in some counterfactual scenarios (although this is only relevant to applications in which the contract logic is not made available to the general public, and in which a specific instance of contract is used infrequently enough that some of its outcomes never occur at all). This can be compared to a legal contract with various chapters covering various contingencies. When a particular contingency does not occur, the chapters related to it do not have to be considered in connection with enforcing the contract's terms, and their details do not have to be cited or consulted. This could relate, for example, to a power that some party possessed but did not invoke on some occasion. [Taproot](./glossary.md#taproot) also includes its own pruning mechanism, but references to pruning in Simplicity documentation typically relate to Simplicity pruning rather than Taproot pruning. ## PSET Partially-Signed Elements Transaction. The [Elements](./glossary.md#elements) equivalent of a PSBT (Partially-Signed Bitcoin Transaction), an object representing an incomplete [transaction](./glossary.md#transaction) that is still in the process of being created by having additional parameters and data attached to it. The bulk of the changes to PSET versus PSBT have to do with Confidential Transactions. When the PSET is complete, it will be finalized, yielding a complete transaction that can be submitted to the blockchain for inclusion in a block. A PSET is useful for incremental creation by software and can also be circulated to one or more external prospective signers for signature with their [private keys](./glossary.md#private-key). PSET is based on [PSBT v2](https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki), whose multi-party transaction facilities are needed to make Confidential Transactions work in the PSBT model. PSBT v2 is not widely-deployed in the Bitcoin space, which may increase the perceived difference between PSET and PSBT. ## Public key In public-key cryptography, a public value to which a specific [private key](./glossary.md#private-key) corresponds. Anyone can use the public key to verify the authenticity of statements that have purportedly been signed by the possessor of the private key. ## Recursive covenant A [covenant](./glossary.md#covenant) that, in at least some circumstances, requires an [asset](./glossary.md#asset) to be sent back to the same contract, or to a [contract](./glossary.md#contract) that continues to enforce a particular rule on downstream [transactions](./glossary.md#transaction). ## Sighash A cryptographic hash of relevant data to be signed as part of a Bitcoin or [Elements](./glossary.md#elements) [transaction](./glossary.md#transaction) by some party in order to approve the transaction. For example, this may be a hash of a list of all inputs and outputs of the transaction. In some contexts, the term "sighash" is used to describe the set of relevant data that goes into computing this hash, rather than the hashed value itself. ## simc Blockstream's Simplicity compiler, which translates [SimplicityHL](./glossary.md#simplicityhl) to [Simplicity](./glossary.md#simplicity), as well as serializing [witness](./glossary.md#witness)es for inclusion on a blockchain. ## Simplex A development tool for [SimplicityHL](./glossary.md#simplicityhl) projects that supports dependency management, automatic generation of Rust [artifacts](./glossary.md#artifacts) for [transaction](./glossary.md#transaction) and [witness](./glossary.md#witness) building, and integration testing via [elementsd](./glossary.md#elementsd). ## Simplicity A financial programming language for high-assurance [smart contract](./glossary.md#smart-contract) and financial instrument development. A low-level language created by Blockstream and natively available on the [Liquid](./glossary.md#liquid) Network, Simplicity makes it easier and safer to write complex conditions and behaviors for automated provision of financial services. Simplicity occupies a similar role to [Bitcoin Script](./glossary.md#bitcoin-script), operating in a comparable context to it, while providing greater functionality and making practical the development of significantly more sophisticated on-chain smart contract logic. Developers ordinarily don't write programs in Simplicity directly, instead writing in SimplicityHL. ## SimplicityHL A high-level programming language with a Rust-like syntax that was created in conjunction with [Simplicity](./glossary.md#simplicity) to facilitate writing Simplicity programs. SimplicityHL compiles to Simplicity, which is actually run by [node](./glossary.md#node) operators. ## simply An alternative [SimplicityHL](./glossary.md#simplicityhl) compiler maintained by Starkware. ## Smart contract A mechanism by which computer code directly specifies and determines the conditions for disposition of [assets](./glossary.md#asset) (particularly tokenized assets on a blockchain). Analogized to a contract in the legal sense because it may represent an understanding and agreement between parties that governs a part of their future behavior or the results of that behavior. Unlike a traditional offline contract, the smart contract is not drafted in natural language and is not interpreted or enforced by human beings. See also [smart contract](https://en.wikipedia.org/wiki/Smart_contract) on Wikipedia. Some sources may use "smart contract" to refer to the overall combination of technologies and protocols that govern or realize a particular commercial relationship or interaction, of which an individual Simplicity program might be only one component. In general, there might be several related Simplicity programs that form part of an overall smart contract system or arrangement. ## Taproot A Bitcoin and Elements feature where the overall structure of the conditions for spending a [UTXO](./glossary.md#utxo) are represented as a [Merkle tree](./glossary.md#merkle-tree). (This tree is also known as a "taptree" and its leaves as "tapleaves.") On-chain Simplicity [programs](./glossary.md#program) are represented within such a tree, which ultimately derives an address to which [assets](./glossary.md#asset) can be sent. Among other effects, this results in the presence of the [internal key](./glossary.md#internal-key). The details of Simplicity programs' Taproot representations also play a critical role in [state management](../documentation/state/). In the recommended approach, a cryptographic hash representing the current state of a smart contract instance is stored in a Taproot leaf alongside the associate Simplicity program's Taproot leaf. This provides a means of cryptographically verifying [witness](./glossary.md#witness) assertions about what the contract's state should be. Because of the use of cryptographic hashes to commit to the content of the whole tree, changing any part of the Taproot structure, including any part of the program code or its stored state, results in a completely different derived address. ## Timelock A spending condition for a [UTXO](./glossary.md#utxo) that only permits certain transfers after a specified time. For example, a timelock condition can restrict an asset so it can't be transferred for a specified number of seconds or a specified number of blocks. Timelocks can be used to implement "maturity" for claims so that they can be exercised only after a specified date. They're also used as part of a timeout-and-refund pattern, so that assets can eventually be refunded if a contract does not complete. They also appear as part of many [vault](./glossary.md#vault) designs. In Simplicity, timelock conditions are enforced by calling appropriate [jets](./glossary.md#jet) to check timelock assertions in a proposed [transaction](./glossary.md#transaction). The logic of a timelock can be combined with other conditions, for example to allow one key to authorize transfers immediately, but require a delay when authorizing them with another key. ## Token An [asset](./glossary.md#asset) on a blockchain which represents a specific right, claim, or ability, often due to agreement by particular organizations to accept it for a specific purpose, or references in [smart contracts](./glossary.md#smart-contract) that cause its possession or transfer to have a specific effect. (Sometimes, a monetary or financial asset whose ownership is tracked on a blockchain, such as a virtual currency.) ## Transaction A payment or proposed payment on a blockchain that confirms the transfer of certain specified [assets](./glossary.md#asset), setting new conditions for the future transfer of those assets. On Bitcoin and Liquid, transactions consist of a set of inputs, each with independent spending conditions, along with a set of outputs. The assets from the set of inputs are reassigned to the set of outputs according to the transaction specification. If any input is controlled by a [Simplicity](./glossary.md#simplicity) program which enforces the logic of a [smart contract](./glossary.md#smart-contract) and includes [witness](./glossary.md#witness) data, the transaction may be referred to as a "Simplicity transaction". Simplicity transactions are validated by full [nodes](./glossary.md#node) according to consensus rules that are extended to include details of Simplicity and its integration into a particular blockchain. The full nodes must run a pruned Simplicity program when it is proposed for inclusion in a block in order to confirm both that the referenced program has proper authority to approve the transaction, and that it actually does approve it. ## Turing complete In computer science, Turing completeness is a phenomenon where a very large number of models of computing devices or systems all turn out to be equivalent in power (ultimately able to perform exactly the same computations, albeit with what could be seen as different degrees of efficiency). Neglecting some details of the computer science formalism, most programming languages and computing devices approximate Turing completeness, and so are informally called Turing complete. Some programming languages, including [Bitcoin Script](./glossary.md#bitcoin-script) and [Simplicity](./glossary.md#simplicity), are intentionally not Turing complete; they are simpler and intentionally cannot perform certain computations, including those that under some circumstances never complete. Bitcoin Script and Simplicity programs, by contrast, are mathematically guaranteed to finish running within a finite (in fact, predictable) amount of time. By giving up some amount of expressive power, Simplicity also improves predictability of a program’s behavior. Unlike Turing-complete languages, Simplicity programs can conceivably have aspects of their behavior automatically analyzed in a way that is always valid for every input. Simplicity programs can never "get stuck" and fail to decide on an answer for whether a transaction is approved. This means that Simplicity does not include loops or recursion, and SimplicityHL cannot perform an unbounded loop (such as a while loop, as in other programming languages). SimplicityHL provides bounded looping mechanisms: if a program contains a loop, the maximum number of loop iterations must be known in advance, and the built-in `for_while` function can only repeat a given loop up to 65536 times. These limitations aid the analysis of the correctness of Simplicity and SimplicityHL programs’ behavior, while still permitting the implementation of complex and useful smart contract functionality. ## Unconfidential An address or transaction on the [Liquid Network](./glossary.md#liquid) is called unconfidential, or non-confidential, if it isn't [confidential](./glossary.md#confidential). Should you need to derive an unconfidential address from a confidential address, you can do so with [elements-cli](./glossary.md#elements-cli). The unconfidential address and confidential address are distinguished by the absence or presence of a cryptographic blinding key. ## UTXO Unspent Transaction Output. A statement, registered on a blockchain, that the owner of some [asset](./glossary.md#asset) has authorized its transfer under certain conditions (a transaction), when that asset has not yet been claimed (spent) by a subsequent [transaction](./glossary.md#transaction). Specifically, UTXOs are those outputs of prior transactions that have not yet been claimed as inputs of any subsequent transaction. UTXOs represent assets that are available to a particular recipient or recipients (whether an individual, program, organization, or an entity described by some set of conditions). Most UTXOs specify that they can be claimed by the owner of a specific private key (which is the simplest sense of what it means for a private key to "own" or "control" assets on a blockchain). However, significantly more detailed conditions can be applied (whether by means of [Bitcoin Script](./glossary.md#bitcoin-script) programs, or, in blockchains with Simplicity integration, Simplicity programs). Assets that are controlled by a Simplicity contract exist on the blockchain in the form of UTXOs referencing those assets where the recipient, or authorized spender, is an address of that Simplicity contract. Claiming assets from a Simplicity contract is done by creating a new transaction which asserts that those assets should be sent to a new recipient address or addresses. The new transaction references outputs of one or more existing UTXOs in order to consume them as its inputs. This new transaction will only be valid, and hence will only be recorded on the blockchain, if the Simplicity contract's logic approves it, within the specific additional context of that transaction (e.g. [inputs](./glossary.md#input), [outputs](./glossary.md#output), block [height](./glossary.md#height), and [witness](./glossary.md#witness)). UTXOs are also one means of storing state information directly on the blockchain, by encoding the relevant information within some UTXO parameter. Once a particular UTXO has successfully been spent (used as the input of a new transaction recorded on the blockchain), it is no longer considered a UTXO, because it is no longer available to be spent by other transactions. ## Vault A [smart contract](./glossary.md#smart-contract) that deliberately adds delays or additional conditions to the process of withdrawing [assets](./glossary.md#asset). A vault will enforce a delayed or multi-stage withdrawal process, often by requiring a series of [transactions](./glossary.md#transaction), not just one, before assets can move. It may also provide a means for an authorized party to cancel the withdrawal during the delay period. Simplicity provides [introspection](./glossary.md#introspection) tools that make it easy to create vaults that enforce multi-stage withdrawal rules directly on-chain. ## Weight A measure of the quantity of resources consumed by a proposed [transaction](./glossary.md#transaction) as a means of determining the [fee](./glossary.md#fee) that must be paid to miners. Roughly equivalent to the transaction's size when encoded on the wire. Traditional Bitcoin transaction weight was [introduced in BIP 0141](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki) as part of the SegWit mechanism, to replace "size" with a more flexible metric. In [BIP 0341](https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki) the concept of a "budget" was introduced, in which each transaction has a minimum weight associated with it, depending on its number of signature checks, to ensure that the weight metric accounts for the CPU resources demanded of nodes. (It is important to collapse all resource requirements into a single metric so miners do not need to do multidimensional optimization, which is NP-complete.) Simplicity extends this concept: each program has a [cost](./glossary.md#cost) to execute which contributes to the total budget of any transaction it appears in. In BIP 0342, the budget for a signature check is typically covered by the weight of the signature itself. However, in Simplicity, many jets have a much smaller encoding than the weight implied by their cost. As a result, transactions involving more computationally-intensive Simplicity programs may be expected to pad the transactions to a larger size (and hence a larger weight) in order to meet the budget requirements. ## Witness An input provided to a specific program on a specific occasion to help it confirm that it should authorize a [transaction](./glossary.md#transaction), including the evidence that justifies why the transaction is a legitimate one according to the rules of the smart contract. This may include digital signatures from parties that are participating in the contract in some way, or from [oracles](./glossary.md#oracle) that are making assertions about information or events outside of the blockchain. The format and contents of a witness, as well as the interpretation of those contents, are specified by the program that consumes it. A witness must be serialized (converted into a sequence of bytes) and attached to a transaction so that verifiers have access to the witness data in order to confirm that a specific program approves a specific transaction when run with a specific input. The witness data is constructed by whoever proposes the transaction, but some portions of its contents will often be provided by other parties (to confirm their own approvals on the transaction, for example). This process is specific to an individual Simplicity program; it will probably be performed in real deployments by "driver" software, within wallets or other client applications, that understands what each program expects in its input and helps to create an appropriate input. In computer science, a witness is a specific example that allows a program to confirm that some set of conditions is, or can be, satisfied. In the Simplicity context, those conditions are the rules allowing assets to be transferred by the contract, and the witness input allows the program to confirm that those rules were met with respect to a proposed transaction. ### Simplicity for EVM Developers # Simplicity for EVM Developers *Michael from [Boltz](https://boltz.exchange/) explaining EVM and Liquid differences.*
Welcome, Solidity and EVM developers! You're used to a world of account-based models, Turing-complete languages, and sophisticated state management. Simplicity, a new programming language from Blockstream Research, offers a fundamentally different approach, bringing high-assurance smart contracting to Bitcoin-style [UTXO](../glossary.md#utxo)-based blockchains like Liquid. While it operates on different principles, Simplicity's design provides expressiveness and reliability together, letting you build sophisticated smart contracts and be confident in their outcomes. This guide will help you navigate the key conceptual shifts as you transition into the Simplicity world. (You can also check out [https://medium.com/@Arvolear/evm-vs-simplicity-6b96bd64b987](a blog post by Artem Chystiakov) comparing the two environments and ecosystems.) ## Key Differences: Simplicity vs. EVM/Solidity | Aspect | Simplicity (UTXO) | EVM / Solidity (Account) | Practical Migration Tip | |-----------------------|------------------|--------------------------|--------------------------| | **State Model** | UTXO-based | Account-based | Replace global variables with UTXOs carrying contract data. To update state, consume the old UTXO and create a new one with the new state. | | **Language** | Simplicity (low-level, combinator-based) | Solidity (high-level, imperative) | Simplicity programs are pure, side-effect-free functions. Build logic by composing small operations instead of writing imperative code. Shift from “writing instructions” to “connecting transformations.” | | **Turing Completeness** | No (bounded programs only) | Yes (limited by gas) | Rewrite loops as fixed-size unrolled logic; precompute where possible. | | **Persistence** | Stateless; data in UTXOs only | Contracts have permanent storage | Store state as commitments inside UTXOs. Carry forward data explicitly via transactions. | | **Smart Contract Calls** | No native inter-contract calls | Inter-contract calls, libraries | Use transaction chaining to compose logic; each step handled by a different UTXO. | | **Gas Model** | Static resource bounds | Runtime gas estimation | Bounds (e.g. bit usage) are known at compile time → predictability. | | **Determinism** | Fully deterministic | Some non-deterministic values (`block.timestamp`) | Use pre-committed data and covenant-based enforcement. | | **Security Model** | No shared state; no reentrancy | Reentrancy, shared mutable state | The UTXO model has no shared mutable state, so reentrancy doesn't apply. | | **Verification** | Built-in formal verification support | Formal verification tooling exists separately from the language (e.g. Certora, KEVM) | Coq-based proofs are part of the standard Simplicity development workflow, conducted before deployment. | | **Deployment** | Commit code hashes only | Deploy full code on-chain | Prepare full expressions off-chain; deploy only program commitments. | | **Data Input/Output** | All inputs explicit | Calldata, storage reads | Define inputs/outputs clearly; all data must come via inputs/UTXOs. | | **Oracles** | Pre-committed or covenant-enforced | Chainlink & external calls | Use pre-signed data or UTXO conditions for oracle inputs. | ## Simplicity and EVM differences ### Simplicity's expressiveness despite a minimal core language While a complete description of Simplicity's core language does fit on a T-shirt, this simplicity refers to its foundational design and formal semantics, not its expressiveness. Unlike Bitcoin Script, which is limited by design and lacks expressiveness for complex smart contracts, Simplicity aims to provide complete expressiveness for whatever computations you need. It is finitarily complete, meaning it can program all finite computations required for a smart contract system. You can even verify Turing-complete off-chain computations on the main chain using Simplicity. ### Turing-incompleteness No, Simplicity is intentionally Turing-incomplete. This design choice enables static analysis, with three consequences. The cost to run any Simplicity program can be determined before committing funds to it. This prevents programs from consuming excessive memory or computation time, guarding against denial-of-service attacks, and contrasts with Ethereum's "out of gas" issues, where pre-paid fees can be lost if a program runs out of gas unexpectedly. Without unbounded loops and recursion, all programs are guaranteed to terminate. Bounded loops are achieved by unrolling the loop, with sub-expression sharing preventing unreasonable impacts on program size. The lack of Turing completeness contributes to the language's simplicity and analyzability, making it amenable to formal reasoning. ### State model Unlike EVM contracts that can access key-value data stores to maintain state across transactions, Simplicity has no state. Simplicity is a purely functional, expression-based language. Every Simplicity expression fundamentally denotes a function mapping input values to output values. It cannot directly express values; instead, it expresses constant functions that always produce the same output. Simplicity operates within the Bitcoin UTXO (Unspent Transaction Output) model. In this model, funds are controlled by small programs. When you spend coins, you essentially provide evidence (witness data) that the program guarding those funds evaluates to true, allowing the transaction to proceed. ### Formal verification workflow You do not perform formal proofs in Haskell or Simplicity directly. Simplicity's formal specification and verification of its core language and semantics take place in the Coq proof assistant. The proof process works as follows: * Simplicity's semantics have a precise mathematical model defined in Coq, which allows for rigorous proofs of correctness. * You can directly prove correctness properties about your specific smart contract written in Simplicity using formal methods within the Coq framework. This means you can formally verify every step. * This approach allows developers to create formal proofs of correctness for their smart contracts before deployment, addressing the immutability problem of blockchain smart contracts where mistakes cannot be corrected once deployed. For instance, you can prove that coins cannot be moved without a specific signature, or that a program won't exceed a memory threshold. * The Haskell implementation is primarily used for constructing and prototyping Simplicity programs. These programs are then the *subject* of formal proofs conducted in Coq. There is no formalized connection between the Haskell library and Simplicity's formal semantics in Coq, so the Haskell library is intended for experimental development, not production where formal proofs are critical. ### Higher-level language abstractions While Simplicity is an extremely low-level language, akin to assembler, you are not expected to write contracts directly in it for most applications. Higher-level languages and tools are available. SimplicityHL is a developer-friendly "front-end" language that compiles down to Simplicity assembly; it has a syntax similar to Rust, abstracting away some of Simplicity's functional programming details to make it more accessible. The [SimplicityHL Codespace](https://github.com/Blockstream/simplicity-codespace) lets you start experimenting with SimplicityHL in your browser, and includes example programs and pre-installed developer tools. The Haskell implementation provides a way to construct Simplicity programs in a tagless-final style, which transparently handles sharing of subexpressions. The [`Haskell-Examples`](https://github.com/BlockstreamResearch/simplicity/tree/master/Haskell-Examples) folder in the Simplicity repository contains various Simplicity expressions written in Haskell. The longer-term goal is for developers to write contracts in various higher-level languages that compile down to Simplicity code alongside proofs of their correct operation. ### Jets Jets are a concept for efficiency and extensibility in Simplicity. * A jet is a single combinator that replaces a larger Simplicity expression, known as its "specification". * While the core Simplicity language is concise, complex programs built solely from its basic combinators could be kilobytes of code and take minutes to execute. * Jets solve this by allowing the Simplicity interpreter to evaluate the jet either by evaluating its Simplicity specification or by using optimized machine code (often C implementations) that has the same effect. * Crucially, these optimised implementations are formally proven equivalent to their Simplicity specifications in Coq. For example, the SHA-256 compression function and libsecp256k1 elliptic curve operations have been reimplemented and formally verified in Simplicity. * This approach opens a clear path for introducing new features and optimisations without constant soft forks. Simplicity's comprehensive "catalog of jets" includes cryptographic functions, arithmetic operations, and Bitcoin-related operations like timelocks. ### Inputs and external data Simplicity programs interact with data through explicit mechanisms. The `witness` combinator returns a value provided at evaluation time, serving as input to Simplicity programs; this is analogous to Bitcoin Script's input stack in its `sigScript` or SegWit's witness. Type inference ensures witness data only contains nominally useful data and prevents padding with unused bits. Simplicity also includes primitive expressions that allow programs to read data from the transaction context where they are executed, including details about the transaction's inputs and outputs, locktime, and the commitment Merkle root of the program itself. The `assert` and `fail` expressions allow programs to halt execution if certain conditions are not met, similar to Bitcoin Script's `OP_VERIFY` or Ethereum's `STOP` opcode, and are used for checks like digital signature verification. ### Program structure Simplicity's structure is deeply rooted in functional programming and designed for efficiency and privacy. Simplicity expressions are constructed from a small set of basic combinators (such as `comp`, `pair`, `witness`, `iden`, `unit`, `injl`, `injr`, `take`, `drop`, `case`) which build up expressions from smaller ones. Simplicity natively integrates Merkelized Abstract Syntax Trees (MASTs): programs are arranged into trees, and only the portions necessary for redemption are revealed, pruning away unused parts. This increases privacy and decreases block space requirements. Simplicity also enables transparent sharing of identical subexpressions within a program, letting complex logic such as bounded loops be expressed more compactly. Even though shared subexpressions can make a program appear smaller than the work it performs, Simplicity's static analysis ensures that all programs have a known upper bound on resource usage, maintaining predictable and safe execution limits. ### Type system Simplicity's type system is fundamental and rigorously defined. Unlike Bitcoin's existing scripting language, Simplicity enforces strict typing rules, which helps eliminate certain classes of bugs and vulnerabilities. All types in Simplicity are combinations of just three basic forms: * **Unit type (1)**: defines exactly one possible value, representing an "empty output". * **Product type (A × B)**: composes a pair of types using an "and" operation, similar to tuples or records. * **Sum type (A + B)**: combines two types in an "or" operation, similar to `Either` types in functional languages or tagged unions. All types in Simplicity are finite. This means infinite or recursive types are not possible, ensuring termination and enabling rigorous analysis and verification. Simplicity has neither function types nor higher-order functions. It also has no named variables, relying on combinators to avoid binders and environments for bound variables. Simplicity uses first-order unification to perform type inference on Simplicity expressions, replacing any remaining type variables with the unit type. Because the types of pruned branches are discarded, the inferred types may end up smaller than in the originally committed program. ## Comparing Simplicity and Solidity Scripts Here's an example that shows the differences between both languages. An oracle signs a message with the current block height and the current price. The block height is compared with a minimum height to prevent the use of old data. The transaction is timelocked to the oracle height, which means that the transaction becomes valid after the oracle height. ### Simplicity Example ([source](https://github.com/BlockstreamResearch/SimplicityHL/blob/master/examples/hodl_vault.simf)) ```rust fn checksig(pk: Pubkey, sig: Signature) { let msg: u256 = jet::sig_all_hash(); jet::bip_0340_verify((pk, msg), sig); } fn checksigfromstack(pk: Pubkey, bytes: [u32; 2], sig: Signature) { let [word1, word2]: [u32; 2] = bytes; let hasher: Ctx8 = jet::sha_256_ctx_8_init(); let hasher: Ctx8 = jet::sha_256_ctx_8_add_4(hasher, word1); let hasher: Ctx8 = jet::sha_256_ctx_8_add_4(hasher, word2); let msg: u256 = jet::sha_256_ctx_8_finalize(hasher); jet::bip_0340_verify((pk, msg), sig); } fn main() { let min_height: Height = 1000; let oracle_height: Height = witness::ORACLE_HEIGHT; assert!(jet::le_32(min_height, oracle_height)); jet::check_lock_height(oracle_height); let target_price: u32 = 100000; // laser eyes until 100k let oracle_price: u32 = witness::ORACLE_PRICE; assert!(jet::le_32(target_price, oracle_price)); let oracle_pk: Pubkey = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798; // 1 * G let oracle_sig: Signature = witness::ORACLE_SIG; checksigfromstack(oracle_pk, [oracle_height, oracle_price], oracle_sig); let owner_pk: Pubkey = 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5; // 2 * G let owner_sig: Signature = witness::OWNER_SIG; checksig(owner_pk, owner_sig); } ``` ### Solidity Example ```solidity pragma solidity ^0.8.0; contract HodlVault { address public oracle; address payable public recipient; uint256 public priceThreshold; // e.g. $70,000 => 70000 * 1e8 if using 8 decimals uint256 public minBlockHeight; bool public claimed; event Claimed(address recipient, uint256 price, uint256 blockHeight); constructor( address _oracle, address payable _recipient, uint256 _priceThreshold, uint256 _minBlockHeight ) payable { oracle = _oracle; recipient = _recipient; priceThreshold = _priceThreshold; minBlockHeight = _minBlockHeight; } // Oracle-signed message format: // price: uint256 (e.g., 7000000000 for $70,000.0000) // blockHeight: uint256 // v, r, s: ECDSA signature function claim(uint256 price, uint256 blockHeight, uint8 v, bytes32 r, bytes32 s) external { require(!claimed, "Already claimed"); require(price >= priceThreshold, "Price below threshold"); require(blockHeight >= minBlockHeight, "Oracle block too old"); require(block.number >= blockHeight, "Timelock: wait for oracle block"); // Reconstruct the message the oracle signed bytes32 message = keccak256(abi.encodePacked(price, blockHeight)); bytes32 ethSignedMessage = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", message)); address recovered = ecrecover(ethSignedMessage, v, r, s); require(recovered == oracle, "Invalid oracle signature"); claimed = true; recipient.transfer(address(this).balance); emit Claimed(recipient, price, blockHeight); } } ``` ### Differences from Rust # Differences between Rust and SimplicityHL SimplicityHL syntax is directly based on Rust and should be familiar to Rust programmers. This document describes some differences between the two languages, ways that SimplicityHL is *not* the same as Rust. ## No mutable variables All SimplicityHL variables are immutable. There is no way to declare a variable as mutable or to change its value after it's been declared. However, variables can be *shadowed* (redefining the same name within a single scope). Allowed: ```rust let ctx: Ctx8 = jet::sha_256_ctx_8_init(); let ctx: Ctx8 = jet::sha_256_ctx_8_add_1(ctx, 0x68); let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, 0x656c6c6f); let hash: u256 = jet::sha_256_ctx_8_finalize(ctx); assert!(jet::eq_256(hash, 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)); ``` Not useful: ```rust // This function does not do what the developer may have expected. It does not modify x. fn increment(x: u8){ let (carry, x2): (bool, u8) = jet::increment_8(x); let x: u8 = x2; } ``` ## No infix and unary operators Currently, SimplicityHL does not support common infix and unary operators such as `!=`, `==`, `<=`, `>=`, `+`, `-`, `*`, `/`, `&`, `|`, `^`, `!`, and others that are found in Rust and in other languages whose syntax descends from C's. Instead, each of these operations requires an explicit call to an appropriate [jet](../jets) to perform the comparison. (It may be possible for a future version of the SimplicityHL compiler to support these notations as syntactic sugar for the corresponding jet calls.) For example, code that might look like ```rust if (counter3 != threshold) { assert!(0); } ``` in other languages is written in current versions of SimplicityHL as ```rust assert!(jet::eq_8(counter3, threshold)); ``` Code that might look like ```rust let x: u8 = 17; let y: u8 = x + 1; ``` in Rust is written in current SimplicityHL as ```rust let x: u8 = 17; let (carry, y): (bool, u8) = jet::add_8(x, 1); ``` (as the `add_8` jet returns an explicit carry flag indicating whether the addition caused an integer overflow; the carry flag value is required to be assigned somewhere). ## No unbounded loops or recursion Simplicity is intentionally not Turing-complete, and SimplicityHL accordingly does not have a `while` loop or a `for` loop in its native syntax. Nor is a function permitted to call itself recursively. For bounded loops, there is a built-in function called `for_while` which can execute a code block up to a specified maximum number of times (limited to 65536 iterations per `for_while` loop). SimplicityHL also offers `fold` and `array_fold` functions to help with some tasks that could traditionally be performed with iteration or recursion in other languages. ## No `if` statement SimplicityHL does not include an `if` statement. Conditional branching is performed with the `match` keyword (sometimes by `match`ing on a boolean value returned from a jet or function, or on the constructor of a value passed in a [witness](../glossary.md#witness) input). ## No run-time I/O or interactivity Simplicity's execution model allows a witness to be provided at runtime as a form of input to the program. There is no provision for interacting with users or services, or for reading other data from elsewhere, during the program's execution. A Simplicity program is also expected to provide a yes-or-no answer about whether to approve a proposed blockchain transaction, based on data optionally supplied via a witness. The program is not able to give other answers, output, or side effects. Thus, there are no functions to perform any form of file, network, or terminal I/O. ## Simpler type system SimplicityHL's type system is much simpler than Rust's. It isn't possible to declare traits or implementations of traits. ## Simpler `match` expression The [`match` expression](../../simplicityhl-reference/match_expression/) in SimplicityHL is more limited than Rust's. ## Simpler module import notation The [module import syntax](../../simplicityhl-reference/modules/) in SimplicityHL is more limited than Rust's. For example, you cannot use `*` as a wildcard to import multiple functions at once. ### Simplicity Compared # Simplicity compared to other languages Simplicity is a low-level, formally verifiable functional language for expressing spending conditions and covenants on Bitcoin-like blockchains. The table below compares it with other approaches to writing on-chain spend conditions and smart contracts. | Aspect | **Bitcoin Script** | **Miniscript** | **Solidity** | **Simplicity** | **Comments** | |---|---|---|---|---|---| | **Primary purpose** | Minimal spend conditions for Bitcoin UTXOs | Safer, structured way to write Bitcoin Script policies | General-purpose smart contracts on Ethereum-like blockchains | Formally verifiable contracts in Bitcoin-like settings | | | **Expressiveness** | Limited by design | More composable than Bitcoin Script | Very high | High within strict rules | More features increase the number of ways a program can be misused or misconfigured. | | **Execution model** | Stack-based, no global state | Policy → Script, tree-structured, stack-based | Runs on EVM with global state | Combinator-based, no loops or mutable state | The execution model determines how contracts interact and how directly their behavior can be audited. | | **Turing-completeness** | No | No | Yes | No | Non-Turing-completeness means every program terminates in bounded time; gas metering is unnecessary because execution cost is bounded statically. | | **Typical use cases** | Payments, multisig, timelocks | Advanced wallet policies, thresholds with fallbacks | DeFi, tokens, DAOs, dApps | High-assurance financial logic on Liquid | Each language targets a different point on the tradeoff between expressiveness and constraint. | | **Safety approach** | Minimal opcodes, deterministic | Constrained grammar with static checks | Security via patterns, audits, and tooling | Formal proofs and deterministic execution | Static and formal methods catch a different class of bugs than runtime testing and auditing. | | **State model** | [UTXO](../glossary.md#utxo) (local) | UTXO (via Script) | Account/global state | UTXO-style | A local (UTXO) model isolates each contract's state from others. A global-state model lets contracts read and write state shared with other contracts directly. | | **Formal verification** | Limited | Better static analysis | Possible but complex | Core feature | The extent and rigor of available formal-verification tooling varies across these ecosystems. | | **Performance/resource bounds** | Bounded by consensus | Bounded by consensus | Gas-limited execution | Strict static bounds | How resource bounds are enforced (by consensus limits, by a gas mechanism, or statically by the language) affects how predictable execution cost is before deployment. | | **Interoperability** | Bitcoin-native | Bitcoin-native | EVM-wide standards | Liquid ecosystem | Interoperability depends on which wallets, tooling, and standards a contract can integrate with directly. | ### Roadmap Overview # Simplicity Roadmap For announcements on progress on this and other Simplicity and SimplicityHL work, you can join the [Simplicity Community Telegram group](https://t.me/simplicity_community). ## Upcoming SimplicityHL features SimplicityHL continues to gain additional notation and convenience features. The `simc` compiler will be updated to support additional syntax for these features. SimplicityHL language enhancement priorities include * Infix operators: familiar notations like `==`, `<`, `>`, `+`, `-`, `*`, `/`, `&`, `|` for comparisons, arithmetic, and logic operations * More integer types: specific-width integers; signed integers (supporting negative numbers) * `if` and `return` statements * A more conventional loop syntax (for bounded loops) * Modules/namespaces * Library functions ## State management Simplicity programs can track persistent state via cryptographic commitments. This provides proper support for general [covenants](../glossary.md#covenant) that need to keep track of arbitrary history as users interact with them over time. (You can see a brief demonstration of this approach in the [December 23, 2025 Office Hours session](https://youtu.be/ry2wQelP8Kc).) Documention and sample contracts demonstrating this are forthcoming. Library code is also under development to support maintaining arbitrary quantities of state information (with selective revelation and efficient updates) via a [Merkle tree](../glossary.md#merkle-tree). ## Standard library A standard library for SimplicityHL is [under development](https://github.com/BlockstreamResearch/simplicityhl-std). ## Developer tool improvements Improvements are planned to various developer tools, including the existing [language server](https://github.com/distributed-lab/simplicityhl-lsp) for VSCode integration, and the `hal-simplicity` command-line tool. ## Type-based SimplicityHL A future version of SimplicityHL using type theory foundations is in preparation. In the short term, `simc` will receive a new type inference engine in based on this work, which will relax existing requirements for mandatory type annotations. Over time, extensions for dependent type mechanisms will be exposed in SimplicityHL. ## Mutinynet integration An integration of Simplicity in [Mutinynet](https://github.com/MutinyWallet/mutiny-net), a signet (test network) that remains architecturally closer to Bitcoin Core, is in progress. This will demonstrate the potential for development with Simplicity on a Bitcoin-like chain without Elements extensions. ## AMP and LWK integrations Integrations of Simplicity with [AMP](https://blockstream.com/amp/) and [LWK](https://github.com/Blockstream/lwk) are underway, in order to offer financial application developers more power when building on Liquid Network. ## Documentation updates In addition to documentation on features mentioned above, significant new and updated documentation is in preparation, including material on design patterns and avoiding pitfalls in smart contract design. ## Simplicity Unchained For blockchains where a native Simplicity integration isn't present, like the Bitcoin Mainnet, an [oracle](../glossary.md#oracle)-based Simplicity interpreter called Simplicity Unchained will offer Simplicity scripting support. The oracle can make statements confirming when contract conditions (expressed in Simplicity programs) have been met and thereby approve transactions permitted by those conditions. This indirectly brings the power and determinism of Simplicity scripting to other chains. ## Oracle tools Tools and examples are being created for integrating other data sources into Simplicity contract logic via signed oracle statements. This can support a range of use cases, from price oracles to confirm off-chain asset price data, to importing data from financial companies' existing back-office databases, making them a source of truth for portions of contracts' logic. ## Sample applications Prototypes of several real financial applications on Simplicity are being specified and implemented. Work in progress on some of these is available in [the `simplicity-contracts` repository on GitHub](https://github.com/BlockstreamResearch/simplicity-contracts). ## Ecosystem Tools and standards for building financial applications to interact with Simplicity contracts, as well as for connecting existing wallet apps to contract flows, are in development. See "[Road to Ecosystem](../../documentation/road-to-ecosystem)" for an overview of the requirements for safely connecting wallets. ### Ecosystem/Wallet Connect Roadmap # Road to Ecosystem Wide adoption of the Simplicity language in the Liquid ecosystem means that a variety of protocols can be built without requiring every wallet to be directly integrated with every protocol. There could be useful protocols that enable better saving strategies, life insurance, payments, lending, options, tokenization, and many other on-chain financial applications. At the same time, an open ecosystem also introduces malicious websites and misleading interfaces. In an open ecosystem, malicious protocols cannot be prevented from appearing, because no single party controls the ecosystem. Someone can always create a contract and misrepresent its nature or effects, or even present a malicious contract to a counterparty. The best available approach is to help the user understand what is going on before they sign and pay. This document is built around a simple motto: > I understand for what I am paying. ## The Tree The motto can be thought of as the root of a Merkle tree. The root is simple and user-facing. The leaves are technical details. ```mermaid flowchart TD classDef leftAlign text-align:left; %% Main vertical flow nodes Protocols["Third-party protocols
• Payments
• Saving
• Lending
• Options
• DEX
• Insurance
• Bridges
• Tokenization
• Other contracts"]:::leftAlign TxFlow["Transaction flow
1. Web-to-wallet communication
2. Transaction construction
   ↳ Wallet ABI
   ↳ wallet-owned coin selection
3. Transaction interpretation
   ↳ clear signing
4. User approval
5. Signing
6. Broadcast"]:::leftAlign %% Subgraph forcing Left-to-Right rendering for the sentence subgraph Horizontal_Sentence [" "] direction LR I((I)) -- "I (the user)" --- Wallet["Wallet"] Wallet --- Core["[ I understand for what I am paying ]"] Core -- "understand" --- Explanation["Explanation"] end %% Vertical cross-axis connections Protocols -- "for what" --- Core Core -- "am paying" --- TxFlow %% Attach the collapsed sub-trees below the horizontal nodes W_Features["Wallet Functions
• Displays balances
• Sends / receives funds
• Syncs with blockchain
• Keeps funds secure
• Decides if safe to sign"]:::leftAlign W_Priv["Owns private state
• UTXOs
• balances
• blinding keys
• signing keys"]:::leftAlign Wallet --> W_Features Wallet --> W_Priv E_Clear["Clear signing
• Parses transaction
• Interprets inputs / outputs
• Validates assets / amounts
• Explains fees
• Explains protocol metadata
• Explains Simplicity covenants
• Rejects what it cannot understand"]:::leftAlign E_UI["User interface
• Shows what will happen
• Shows what user gives
• Shows what user receives
• Shows why transaction is needed"]:::leftAlign Explanation --> E_Clear Explanation --> E_UI %% Styling classDef highlight fill:#e1f5fe,stroke:#0277bd,stroke-width:3px,color:#000; class Core highlight; classDef default fill:#f4f4f9,stroke:#555,stroke-width:1px; %% Hide the subgraph border so it appears completely seamless style Horizontal_Sentence fill:none,stroke:none,color:none; ``` The tree grows over time rather than being fixed once and for all. ## Wallets Wallets are the core part of the ecosystem. They do the heavy lifting for the user. They display balances, send and receive funds, sync with the blockchain, manage keys, select coins, construct transactions, and protect wallet-private information. They are also the last line of defense before user funds move. A protocol can ask the wallet to sign something, but the wallet should decide whether that request is understandable and safe enough to show to the user. A wallet has two responsibilities at the same time: 1. It must be useful enough to participate in protocols. 2. It must be strict enough to protect the user from signing something they do not understand. This creates the first major branch of the ecosystem: * Wallet * Syncs with the blockchain * Keeps funds secure * Owns wallet-private state * Signs transactions * Explains transactions before signing ## Understanding What Is Being Signed The second part of the motto is: > understand This branch is directly connected to paying money and sending transactions. When a user signs a transaction, they should understand what the transaction does. This is the role of clear signing. Clear signing is a set of instructions, checks, and validations that a wallet performs to ensure that a transaction received from a third party is understandable before the wallet signs it. A third-party protocol may prepare a request for the wallet. That protocol could be a lending application, an options protocol, a payment application, a savings strategy, a DEX, or any other contract that can be implemented on-chain. The wallet should not blindly sign the transaction just because the request came through a supported transport layer. The wallet should interpret the transaction. By default, if the wallet cannot interpret an input or output, it should reject the whole transaction. This is the safest default. If a transaction cannot be explained, it should not be signed. When the wallet can interpret everything, it should perform checks and validations to verify that the transaction matches what the user intended. For example, the wallet should display asset details correctly, show the amounts involved, explain fees, identify the protocol, and describe what the user is receiving in exchange. * Clear signing * Explain transaction from a third party * Parse transaction structure * Interpret inputs * Interpret outputs * Identify assets * Verify amounts * Explain fees * Explain protocol-specific metadata * Explain Simplicity covenant behavior * Check that the request matches user intent * Sign only if the transaction is understandable This becomes especially important for Simplicity contracts. Simplicity covenants are complex, which makes wallet display much harder. A transaction locked behind a Simplicity covenant can encode behavior that is not obvious from the transaction shape alone. The wallet needs additional structure to explain what the covenant means, what the user is allowed to do, and what the user is committing to. Therefore, clear signing has another branch: * Clear signing * Explain the transaction to the best of the wallet's ability * What is being spent? * What is being received? * What asset is involved? * What covenant controls this transaction? * What protocol does this belong to? * What conditions will exist after signing? * What risks or unknowns remain? The motivation is simple: the user should understand, without reasonable doubt, that the action being performed is the action they intended. ## Standards Around Wallet Interaction To make this possible, the ecosystem needs shared standards. The relevant work is happening around Elements Improvement Proposals, or ELIPs. The [ElementsProject/ELIPs](https://github.com/ElementsProject/ELIPs) repository contains proposals for Elements and Liquid-related standards. Two relevant draft proposals are already available: * [Wallet ABI Transaction Creation Protocol](https://github.com/ElementsProject/ELIPs/pull/35) * [Liquid Wallet RPC Profile](https://github.com/ElementsProject/ELIPs/pull/36) A follow-up profile for Liquid clear signing is expected to define how wallets should parse, validate, and display clear-signing metadata. Together, these standards describe different parts of the same tree: * Web application * Communicates with wallet * Liquid Wallet RPC Profile * Asks wallet to construct or complete a transaction * Wallet ABI Transaction Creation Protocol * Provides metadata for interpretation * Liquid Clear Signing Profile * Receives a signed transaction only after user approval These standards are not separate ideas; they are different layers of one user-safety model. The application wants the user to participate in a protocol. The wallet wants to protect the user. The standards define how those two parties communicate without destroying privacy, safety, or usability. ## Third-Party Protocols The phrase: > for what points to the reason the user is paying. The user is not paying because a website asked for a signature. The user is paying for something: a good, a service, a position in a protocol, a contract, a transfer, or a financial action. Examples include: * Third-party protocols * Payments * Saving strategies * Life insurance * Lending * Options * DEX protocols * Bridges * Tokenization * Other on-chain applications These protocols are outside the wallet. The wallet does not need to implement every protocol internally, and it should not be expected to understand every website by default. However, the wallet must still understand enough to protect the user before signing. That is the central tension of the ecosystem: * Open ecosystem * Many protocols * Many websites * Many contract types * Many assets * One wallet responsibility: * explain the transaction before signing The ecosystem becomes useful only if applications can innovate without waiting for every wallet to hard-code their protocol. But the ecosystem becomes safe only if wallets can reject unknown, ambiguous, or misleading signing requests. ## Transport Layer For third-party protocols to work, websites and wallets need a way to communicate. This is the transport layer. A protocol website needs to ask the wallet for some action: connect an account, construct a transaction, sign a [PSET](../glossary.md#pset), sign a message, or send funds. The wallet needs to receive that request, evaluate it, and show the user what is happening. By definition, this requires an open API that allows the connection to be established. * Third-party protocol * Transport layer * Establishes a connection with the wallet * Requests wallet capabilities * Sends protocol requests * Sends transaction-construction requests * Sends signing requests * Receives wallet responses * Preserves user privacy as much as possible Liquid adds an important complication: confidentiality. Liquid supports Confidential Transactions. Confidentiality means that funds can be transferred without revealing the asset ID and amount to the public blockchain observer. Because of this, a wallet should not disclose balances, [UTXOs](../glossary.md#utxo), or view material to a counterparty unless the user has explicitly agreed to that disclosure. The best privacy-preserving path is described by the Wallet ABI approach. The Wallet ABI Transaction Creation Protocol allows an application to express an application-level intent while keeping wallet-owned UTXOs, balances, and internal selection state private to the wallet. In this model, the application does not need to know everything about the wallet. Instead, the application tells the wallet what kind of transaction is needed, and the wallet constructs or completes the transaction using its own private state. * Wallet ABI * Application describes intent * Wallet keeps private state private * Wallet performs coin selection * Wallet constructs or completes the transaction * Wallet prepares the signing view * Clear signing explains the result to the user This is the preferred model when confidentiality matters. ## User-Approved Disclosure There is also a second use case. Maybe the user wants to share more information with a website. Maybe the application needs balances or UTXOs, and the user explicitly agrees to disclose them. In that case, the general shape of Bitcoin wallet RPC methods can be adapted for Liquid. The [WalletConnect Bitcoin JSON-RPC methods](https://docs.walletconnect.network/wallet-sdk/chain-support/bitcoin) provide a useful reference point for wallet-to-application RPC methods in the Bitcoin ecosystem. For Liquid, the corresponding work is the [Liquid Wallet RPC Profile](https://github.com/ElementsProject/ELIPs/pull/36). The Liquid profile needs to account for Liquid-specific details: * Liquid Wallet RPC Profile * Liquid accounts * user-approved balances * user-approved UTXOs * descriptor-change events This approach is useful when the user chooses interoperability and convenience over maximum confidentiality. Disclosure should be explicit. A wallet should not leak wallet-private information merely to make an application easier to build. ## Clear Signing as the Center The transport layer lets the website talk to the wallet. The Wallet ABI lets the website ask the wallet to construct a transaction without learning private wallet state. The Liquid Wallet RPC Profile lets the website request wallet information when the user agrees to disclose it. But clear signing is what ties everything back to the motto. * Transport layer * delivers the request * Wallet ABI / RPC profile * structures the request * Clear signing * explains the request * Wallet UI * asks the user for approval * User * understands for what they are paying Without clear signing, the ecosystem becomes unsafe. The user may technically approve a transaction, but they do not know what they approved. With clear signing, the wallet becomes an interpreter between complex protocol logic and human understanding. ## Conclusion This document started with a simple motto: > I understand for what I am paying. This motto provides the structure of the ecosystem. The "I" is represented by the wallet: the user's agent, keeper of funds, balances, keys, and private state. The "understand" part is represented by clear signing: the procedure that allows the wallet to parse, validate, and explain a transaction before the user signs. The "for what" part is represented by third-party protocols: payments, lending, options, savings, insurance, DEXs, tokenization, and other applications that can be built with Simplicity. The "am paying" part is represented by transaction construction, signing, and broadcast. Making this work requires a transport layer that allows websites and wallets to communicate, wallet APIs that preserve Liquid confidentiality by default, RPC methods for cases where the user explicitly agrees to disclose wallet information, and clear signing metadata that lets the wallet explain complex Simplicity contracts to the user. Clear signing is the most ambitious part of this vision. It requires more than good UI. It requires standards, registries, validation logic, contract metadata, asset metadata, and wallet implementations that reject what they cannot understand. This is the secure road to an open Simplicity ecosystem: > introduce as many verifiable checks as possible, so the wallet can explain to the user, with high confidence, for what they are paying. ### Projects # Simplicity-related projects !!! tip For beginners and quick experimentation, start with the [quickstart](../../getting-started/quickstart). These are open source projects in the Simplicity ecosystem. | Project link | Description | | ------------ | ----------- | | [SimplicityHL](https://github.com/BlockstreamResearch/SimplicityHL) | SimplicityHL high-level programming language compiler. | | [simplicity](https://github.com/BlockstreamResearch/simplicity) | Low-level Simplicity language interpreter and blockchain integrations. | | [rust-simplicity](https://github.com/BlockstreamResearch/rust-simplicity) | Simplicity Rust application development libraries. | | [smplx](https://github.com/BlockstreamResearch/smplx) | Simplex SDK / orchestration (for generating Rust projects to interact with Simplicity contracts, dependency management, and associated test suites). | | [hal-simplicity](https://github.com/BlockstreamResearch/hal-simplicity) | CLI to build, inspect, analyze programs and transactions. | | [simplicityhl-std](https://github.com/BlockstreamResearch/simplicityhl-std) | SimplicityHL standard library. | | [simplicity-demo](https://github.com/BlockstreamResearch/simplicity-demo) | Quickstart P2PK demo using Rust. | | [simplicity-codespace](https://github.com/Blockstream/simplicity-codespace) | Codespace to run examples. | | [LWK](https://github.com/Blockstream/LWK) | Liquid Wallet Kit (for developing applications to interact with Liquid Network and Simplicity contracts). | | [SimplicityHL VS Code extension](https://marketplace.visualstudio.com/items?itemName=Blockstream.simplicityhl) | Visual Studio Code extension (source code in SimplicityHL repository above). |