diff --git a/elip-xxx.mediawiki b/elip-xxx.mediawiki new file mode 100644 index 0000000..de0838c --- /dev/null +++ b/elip-xxx.mediawiki @@ -0,0 +1,552 @@ +
+ ELIP: TBD + Layer: Wallet + Title: Transaction Manifests for Simplicity Contracts + Author: stringhandler+ +==Introduction== + +===Abstract=== + +This ELIP defines the '''transaction manifest''', a JSON document that describes the transactions of a multi-step protocol on Elements: which UTXOs each transaction consumes, which outputs it creates, and which witnesses are needed to satisfy each input. A wallet that understands the format can execute any protocol described by a manifest, without hard-coded knowledge of that protocol. + +The format was designed for driving [https://github.com/BlockstreamResearch/simplicity Simplicity] contracts, where a spend must often recreate covenant outputs with particular amounts, assets and scripts, and where existing descriptor and PSET tooling gives no help with transaction construction. It is not limited to Simplicity, or to covenants, but they are the main use case. + +A manifest declares the covenant scripts (''UTXO types'') a contract uses and the ''actions'' that can be executed against it. A companion ''state file'' tracks the contract's live on-chain UTXO set. + +This document specifies the core format. Optional capabilities (contract classes and instance files, pre-flight validations, hooks, and lifecycle documentation) are specified separately as ''extensions''. + +===Copyright=== + +This document is licensed under the 3-clause BSD license. + +===Motivation=== + +Simplicity covenants can express rich spending conditions on Elements, but a covenant program says nothing about ''how'' a wallet should drive it: which UTXOs to select, what each output must contain, which signatures and typed witness values to supply, and how the contract moves from one state to the next. Today every Simplicity protocol needs bespoke wallet integration, so a protocol only works in the wallets whose authors wrote support for it by hand. + +Existing tooling does not fill this gap. Output descriptors describe how a wallet spends outputs it owns; they cannot express a multi-step contract in which a spend must recreate outputs with particular amounts, assets, indices and scripts. PSET standardises signing and finalising a transaction that has already been constructed, but not the construction itself. Construction is the part that differs from ordinary wallet behaviour, and it is the part this format describes. + +A transaction manifest captures the wallet-facing half of a protocol in one declarative document, in the same way that output descriptors captured the description of ordinary spendable outputs. The format is independent of any particular wallet implementation, and hands off to standard PSET at the signing boundary, so existing Elements wallet machinery can be reused. + +==Design== + +===Overview=== + +A live contract deployment is described by two companion files: + +{| class="wikitable" +! File !! Naming convention !! Purpose +|- +| Manifest ||+ Comments-Summary: No comments yet. + Comments-URI: TBD + Status: Draft + Type: Standards Track + Created: 2026-07-02 + License: BSD-3-Clause +
txmanifest.json || Protocol definition (static; shared across all deployments)
+|-
+| State file || <name>.state.json || Live on-chain UTXO set for this deployment
+|}
+
+Tools should auto-derive the state file path from the manifest's location. (The Classes & Instances extension adds a third companion file, <name>.instance.json, holding per-deployment compile parameters.)
+
+The manifest declares:
+
+* '''UTXO types''': named covenant scripts (SimplicityHL .simf programs) that outputs lock to and inputs spend, together with how each program's compile-time parameters are supplied.
+* '''Actions''': transaction recipes. Each action declares typed runtime parameters, an ordered list of input descriptors, an ordered list of output descriptors, and per-input witness specifications.
+* '''Formulas''': small build-time expressions (integer arithmetic, comparisons, boolean logic and a few functions) used for amounts, asset references and computed witness values.
+
+To execute an action, a tool resolves the parameters, selects inputs from the wallet or the state file, constructs the outputs, builds a PSET, satisfies the covenant witnesses, signs, broadcasts, and updates the state file. The PSET is the boundary between manifest-level reasoning and standard Elements wallet machinery.
+
+===Rationale and Drawbacks===
+
+JSON was chosen for ubiquity and ease of review. The format is not yet content-addressed; compact verifiable transmission (including via QR) is planned as future work.
+
+The format is not tied to Simplicity, or to covenants at all. The script.type field is extensible, and Simplicity is currently the only defined program type. The same model (typed params, UTXO selection, output recipes, per-input witnesses) works for any protocol whose transactions must follow a set shape, whether that shape is enforced by a covenant, by counterparties, or only by convention.
+
+The formula language is intentionally small: no loops, no user-defined functions, integer-only arithmetic. This keeps independent implementations small and evaluation deterministic.
+
+A tool that does not implement an extension must reject a manifest that uses it. Silently skipping a validation or a hook would change the meaning of a transaction. The cost is possible fragmentation between tools that support different extension sets; keeping the core small is intended to make full core support cheap.
+
+The wallet keeps a state file instead of finding contract UTXOs by scanning the chain. This keeps wallets light and works for confidential protocols, at the cost of a resync step when the file goes stale. Because its format is standardised, the state file also serves as an interchange artifact: one party can produce and distribute it — a DEX publishing a contract's live state via a website, say — and any conforming wallet can read it. Persisting one is not mandatory, however: a wallet may implement the manifest format without maintaining a state file, so long as it has another mechanism (chain scanning, an external indexer) for locating covenant UTXOs.
+
+===Specification===
+
+The key words "MUST", "MUST NOT", "SHOULD", and "MAY" are to be interpreted as described in RFC 2119.
+
+====Top-Level Structure====
+
+manifest_version || string || YES || Semver of the manifest file format. Current value: "0.1.0".
+|-
+| protocol || string || YES || Kebab-case protocol identifier (e.g. "simplicity-lending")
+|-
+| description || string || YES || Human-readable protocol description
+|-
+| chain || string || NO || "bitcoin", "elements", "liquid", or "cross-chain". "liquid" and "elements" denote the same network. Default: "elements"
+|-
+| utxo_types || object || NO || Map of UTXO type name → UTXO type definition. Each entry carries its own script.source.
+|-
+| actions || object || NO || Map of action ID → action definition
+|}
+
+The top-level fields classes and lifecycle are reserved for extensions.
+
+====UTXO Types====
+
+A UTXO type names a covenant script that outputs lock to and inputs spend from. Inputs reference a UTXO type via utxo_source; outputs lock to one via destination; the state file tracks live UTXOs by their type name.
+
+{| class="wikitable"
+! Field !! Type !! Required !! Description
+|-
+| description || string || NO || Human-readable purpose
+|-
+| script || object || YES || The covenant program (see below)
+|-
+| asset || string || NO || Default asset locked in this UTXO type; a selection/validation hint. Same syntax as the input asset field.
+|}
+
+'''Script definition:'''
+
+{| class="wikitable"
+! Field !! Type !! Required !! Description
+|-
+| type || string || YES || Covenant program type. Currently only "simplicity".
+|-
+| source || string || YES || Relative path to the .simf source file
+|-
+| compile_params || object || NO || Wires the program's own .simf parameters to manifest values (see below)
+|}
+
+'''Compile-param wiring.''' A .simf program declares its own compile-time parameters, and the covenant's address cannot be computed until every one is given a value. The compile_params map supplies them: each '''key''' is a parameter name from the .simf, and each '''value''' is what to bind it to.
+
+script''', for values fixed across every use of the covenant. A value is a string literal (e.g. "1", "true") or a bare name (e.g. BORROWER_PUB_KEY) resolved from the compile-parameter context. The core format leaves that context empty; bare names require the Classes & Instances extension, where the name resolves to a field's value from the instance file.
+* '''Inline at a reference site''', on an input's utxo_source or an output's destination, for values known only per-action. Inline values are formula expressions in the action's context (e.g. params.pubkey).
+
+compile_params supplement, and override, any bound on the UTXO type's own script.
+
+====Actions====
+
+An action is a single transaction recipe.
+
+{| class="wikitable"
+! Field !! Type !! Required !! Description
+|-
+| description || string || NO || Human-readable action purpose
+|-
+| params || object || NO || Runtime action parameters; map of name → ParamDef
+|-
+| inputs || array || NO || Ordered list of input descriptors
+|-
+| outputs || array || NO || Ordered list of output descriptors
+|}
+
+The action fields is_constructor, create_instance, validations, on_pre_broadcast, and on_post_broadcast are reserved for extensions.
+
+'''ParamDef:'''
+
+{| class="wikitable"
+! Field !! Type !! Required !! Description
+|-
+| type || string || YES || A type string from the table below
+|-
+| description || string || NO || Human-readable purpose
+|-
+| default || string || NO || Default value, pre-filled when the user is prompted
+|-
+| source || object || NO || Auto-fill the param from a wallet-derived value instead of prompting (see below)
+|}
+
+If no source is given, the tool prompts the user for the value (pre-filled with default if present). A source object tells the tool how to derive the value automatically:
+
+type !! Resolves to !! Derivation path
+|-
+| "wallet_key" || 32-byte x-only BIP340 Schnorr pubkey (hex) || m/86h/<coin>h/0h/0/0
+|}
+
+coin = 0 for mainnet, 1 for testnet.
+
+'''Types.''' Each type string compiles to a SimplicityHL primitive type:
+
+{| class="wikitable"
+! Type string !! SimplicityHL type !! Description
+|-
+| u8 / u16 / u32 / u64 || u8/u16/u32/u64 || Unsigned integers
+|-
+| bool || bool || Boolean ("true" / "false")
+|-
+| bytes32 || u256 || 32-byte raw value
+|-
+| pubkey || u256 || 32-byte x-only BIP340 Schnorr public key
+|-
+| liquid.asset_id || u256 || Liquid asset ID (32 bytes)
+|}
+
+'''Param-only type.''' ParamDefs MAY additionally use address, a Liquid/Elements address supplied at build time. Unlike the types above it is resolved wallet-side and is '''not''' compiled into a covenant; its typical use is a params.NAME reference as an output destination, letting the caller choose where funds go:
+
+id || string || YES || Unique identifier within this action
+|-
+| description || string || NO || Human-readable purpose
+|-
+| utxo_source || string or object || YES || How to locate this UTXO (see below)
+|-
+| asset || string || NO || Required asset ID (see below)
+|-
+| amount_sat || number or object || NO || Required amount or constraint (see below)
+|-
+| required_index || integer || NO || Required transaction input index. Positive = absolute (0-based); negative = relative from end (−1 = last).
+|-
+| optional || boolean || NO || If true, the transaction proceeds even if this UTXO is not found. Default: false.
+|-
+| issuance || object || NO || Create or reissue a Liquid asset on this input (see below)
+|-
+| sequence || number or object || NO || Sets nSequence for BIP68 relative timelocks (see below)
+|-
+| witnesses || object || NO || Per-input witness map (see Witnesses)
+|}
+
+The input field on_resolved is reserved for extensions.
+
+'''utxo_source''' takes one of these forms:
+
+{| class="wikitable"
+! Value !! Type !! Description
+|-
+| "wallet" || string || Any wallet-controlled UTXO, selected by asset + amount
+|-
+| {"utxo_type": "name"} || object || Covenant UTXO looked up by type name, from the state file or an equivalent discovery mechanism
+|-
+| {"utxo_type": "name", "compile_params": { ... }} || object || As above, with inline compile_params supplying values known only per-action (see UTXO Types)
+|}
+
+When utxo_source is a covenant type, the input's script is derived from the named UTXO type's script definition; inline compile_params supplement and override those declared on the type's own script.
+
+asset:''' "lbtc" or "bitcoin" (the network policy asset), a 64-char hex asset ID, or a reference ("instance.NAME" / "params.NAME").
+
+'''amount_sat:''' an exact satoshi value, a formula string evaluated to a u64, or {"min_amount": "expression"} (auto-select a UTXO meeting the threshold).
+
+'''issuance.''' Creates or reissues a Liquid asset as part of the input. Fields: kind (required; "new" for a first issuance or "reissue"), asset_amount_sat (required; asset tokens to issue in satoshi denomination, formula or literal), and inflation_amount_sat (optional; inflation token amount, 0 disables reissuance). After resolution the computed asset ID is accessible as input_id.asset in subsequent formulas.
+
+sequence.''' Sets the input's nSequence for BIP68 relative timelocks (the check_lock_distance / check_lock_duration Simplicity jets); omitted, the input stays at Sequence::MAX (relative locktime disabled). Forms: {"relative_blocks": "<expr>"} (spendable only after N blocks since confirmation, ≤ 65535), {"relative_seconds": "<expr>"} (rounded up to 512-second units), or a raw nSequence value (literal or formula).
+
+id || string || YES || Unique identifier within this action
+|-
+| description || string || NO || Human-readable purpose
+|-
+| destination || string or object || YES || Where funds are sent (see below)
+|-
+| amount_sat || number or string || YES || Output amount in satoshis; formula or literal
+|-
+| asset || string || YES || Asset ID; same syntax as the input asset field
+|-
+| required_index || integer || NO || Required transaction output index (same semantics as input required_index)
+|-
+| optional || boolean || NO || If true, output may be omitted. Default: false.
+|-
+| confidential || boolean || NO || Whether this output is blinded (Liquid/Elements only). Overrides the chain default (see Blinding below).
+|-
+| data || string || NO || OP_RETURN payload; formula using concat(...). Only valid when destination is {"type": "op_return"}.
+|}
+
+'''destination:'''
+
+{| class="wikitable"
+! Value !! Description
+|-
+| "wallet" || Primary wallet receive address
+|-
+| "change" || Wallet change address
+|-
+| "params.NAME" || Address resolved from an action parameter (e.g. a type: "address" param)
+|-
+| {"utxo_type": "name"} || Locks to the named covenant's P2TR address; optionally with inline compile_params
+|-
+| {"script_hash": "instance.NAME"} || Raw 32-byte script hash from an instance field
+|-
+| {"type": "op_return"} or {"type": "burn"} || OP_RETURN output (asset destroyed); the two forms are synonyms
+|}
+
+'''Blinding.''' Output blinding follows the chain default — unblinded on Bitcoin, blinded on Liquid (Elements) — unless the output's confidential field overrides it. Covenant outputs (utxo_type destination) and OP_RETURN outputs are always unblinded, regardless of any confidential setting (see Behavioral Rules). On chain: "bitcoin" there is no blinding; confidential: true is invalid there and tools SHOULD reject it.
+
+====Witnesses====
+
+Witnesses appear in the witnesses field of an input descriptor. Each key is the SimplicityHL witness name as it appears in the .simf source; each value is a witness spec with a type field (and optional description):
+
+'''"simplicityhl" (static SimplicityHL value).''' Fields: value (required; a SimplicityHL value expression: "0x<hex>" for byte arrays, "Left(())" / "Right(())" for Either types) and simplicity_type
+
+'''"Signature" (BIP340 Schnorr signature).''' Fields: sig_type (required; currently only "simplicity.sig_all_hash") and source (required; {"type": "wallet", "key": <reference>} where the reference resolves to a 64-hex-char x-only public key). The tool computes the sighash, signs with the wallet key matching the given pubkey, and injects the 64-byte signature as a "simplicityhl" witness. The value "simplicity.sig_all_hash" is namespaced to make clear it is Simplicity's own sig_all_hash jet commitment over the full transaction (computed via the Elements transaction environment), '''not''' the standard Elements SIGHASH_ALL.
+
+'''"formula" (computed witness value).''' Fields: expr (required; a formula expression, e.g. "index_of(collateral_out)").
+
+====Formula Language====
+
+Formulas are string expressions evaluated by the tool at transaction build time. In the core format they appear in input and output amount_sat and witness expr; extensions add further contexts.
+
+'''Operators:''' integer arithmetic + - * / (division truncates); comparisons == != < <= > >=; boolean logic && || !; grouping ( ).
+
+'''References''' (written bare, no $):
+
+{| class="wikitable"
+! Syntax !! Description
+|-
+| instance.NAME || Instance field by name (populated by the Classes & Instances extension)
+|-
+| params.NAME || Action parameter by name
+|-
+| input_id.amount_sat || Satoshi amount of a resolved input
+|-
+| input_id.asset || Asset ID of a resolved input (hex string)
+|-
+| input_id.present || True if an optional input was found
+|-
+| output_id.amount_sat || Satoshi amount of a constructed output (post-construction only)
+|-
+| fee || Estimated network fee (sats); resolves to 0 until fee estimation runs
+|}
+
+'''Functions:''' index_of(id) (transaction index of a named input or output), concat(a, b, ...) (byte concatenation; OP_RETURN data only).
+
+====State File====
+
+The state file tracks the live on-chain UTXO set for a contract deployment and is updated after every successfully broadcast action.
+
+The state file is close to a standard of its own. Its fixed format makes it an interchange artifact: one tool can produce it, a website can distribute it, and another tool can read it to learn the contract's live UTXO set. A tool is not required to persist one: the manifest format requires only the ability to resolve the covenant UTXOs referenced by utxo_source. Tracking them in a state file is the standard mechanism, but a tool MAY substitute another (chain scanning, an external indexer). Note that chain scanning does not work for confidential protocols — one motivation for the state file.
+
+{| class="wikitable"
+! Field !! Type !! Required !! Description
+|-
+| instance || string || NO || Relative path to the companion instance file (Classes & Instances extension)
+|-
+| last_action || string || YES || Name of the last action that produced this state
+|-
+| utxos || array || YES || All live covenant UTXOs for this deployment
+|}
+
+Each entry in utxos:
+
+{| class="wikitable"
+! Field !! Type !! Required !! Description
+|-
+| utxo_type || string || YES || The UTXO type name (matches utxo_source.utxo_type in action inputs)
+|-
+| utxo_id || string || NO || The id of the output that produced this UTXO; disambiguates multiple UTXOs of the same type
+|-
+| txid || string || YES || Transaction ID (64 hex chars)
+|-
+| vout || integer || YES || Output index
+|-
+| amount_sat || integer || YES || Satoshi amount
+|-
+| asset || string || YES || Asset ID (64 hex chars)
+|}
+
+====Behavioral Rules and Invariants====
+
+'''Ordering.''' Input resolution precedes output construction: outputs MAY reference resolved input amounts and assets; inputs MUST NOT forward-reference outputs. Compile params MUST be fully materialized before outputs are evaluated.
+
+'''Covenant address determinism.''' A Simplicity covenant address is fully determined by its .simf source and compile parameters:
+
++address = P2TR(internal_key=NUMS, merkle_root=tapbranch(cmr_leaf, extra_leaves...)) ++ +The internal key is the NUMS point
0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0; extra leaves are appended in declaration order via TapBranch. The same source and compile parameters MUST always produce the same address.
+
+'''Confidentiality.''' Covenant outputs MUST be explicit (unblinded). Simplicity programs use jets such as current_amount and current_asset that operate on explicit values; they cannot introspect confidential commitments.
+
+'''Witness stack format.''' The witness stack for a Simplicity tapscript input MUST be exactly 4 items, in this order:
+
++[witness_bits_bytes, pruned_program_bytes, cmr_script_bytes, control_block_bytes] ++ +where
cmr_script_bytes is the CMR (32 bytes) as a bare script (no script opcodes) and control_block_bytes is a standard BIP341 control block with the Simplicity leaf version.
+
+====Extensions====
+
+Extensions add optional capabilities on top of the core format; a manifest MAY use any combination. A tool that does not implement an extension MUST reject a manifest that uses that extension's fields, rather than silently ignoring them.
+
+{| class="wikitable"
+! Extension !! Adds
+|-
+| Classes & Instances || classes, constructor methods (is_constructor, create_instance), tapleaf compute specs, and the per-deployment instance file
+|-
+| Validations || Per-action validations: pre-flight checks with structured errors
+|-
+| Hooks || on_pre_broadcast, on_post_broadcast, on_resolved, and the args.NAME reference
+|-
+| Lifecycle || Documentation-only lifecycle block (states and transitions)
+|}
+
+Extension specifications are maintained alongside the format specification at [https://github.com/stringhandler/tx_manifest_spec the transaction manifest repository] under extensions/.
+
+==Backwards Compatibility==
+
+The transaction manifest is a new, standalone document format. It requires no consensus changes and no changes to existing wallet formats. At the signing boundary the format hands off to a standard PSET, so a wallet that does not implement manifests can still receive, sign, and broadcast the resulting transaction.
+
+The manifest_version field carries the format version under semver; tools MUST reject manifests whose major version they do not support. Unknown fields belonging to unimplemented extensions MUST cause rejection as described above.
+
+==Worked Example==
+
+The smallest complete manifest is a Simplicity pay-to-public-key: a Pay action that locks funds into a covenant output, and a Receive action that spends it back to the wallet with a BIP340 signature. Abridged:
+
+examples/. Each pairs a txmanifest.json with the .simf covenant source it references.
+
+==Reference Implementation==
+
+An example wallet that consumes transaction manifests, instance files, and state files is available at [https://github.com/stringhandler/txmanifest-wallet txmanifest-wallet]. The format specification, extension specifications, and a wallet integration guide describing the full execution lifecycle are maintained at [https://github.com/stringhandler/tx_manifest_spec tx_manifest_spec].