Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
*.json text eol=lf
*.rs text eol=lf
*.yml text eol=lf
*.simf text eol=lf
15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
# Changelog

User-facing changes to the `tx-manifest-wallet` CLI and the manifest format.
User-facing changes to the `tx-manifest-wallet` CLI. Changes to the manifest format
itself are in the [format changelog](docs/format-changelog.md).
Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
[Semantic Versioning](https://semver.org/). No changelog was kept before 0.2.0.

## [Unreleased]

### Breaking

- `run` refuses an *unpinned* manifest: one where a Simplicity program has no content hash, no compiler-version requirement, or doesn't match its hash. Add the hashes with `tx-manifest-wallet pin` and set a compiler version (see the [format changelog](docs/format-changelog.md)), or pass `--allow-unpinned` (or `--debug`) while developing, which turns each of these into a warning.

### Added

- Release binaries for macOS (Apple Silicon).
- Supports manifest format [`0.3.1`](docs/format-changelog.md), which adds program pinning. `0.3.0` manifests still load.
- `pin` command: writes every program's current hash into a manifest without reformatting it; `pin --check` fails if any is missing or stale.
- `validate --strict`: unpinned programs are errors rather than warnings.
- `run --debug`: development mode. For now the same as `--allow-unpinned`; it may enable more development-only behaviour later.

### Changed

- `validate` also checks each program file: that it exists, matches its hash, meets its compiler requirement, and declares the witnesses the manifest supplies.

## [0.3.0] - 2026-10-06

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 18 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,24 +63,33 @@ A manifest declares:
- **`chain`** and **`requires`** — the ledger it targets and what the wallet must
support (e.g. `"simplicity"`).
- **`utxo_types`** — covenant output types, each pointing at a `.simf` program.
- **`actions`** — the operations a user performs, each with `params`, `inputs`,
`outputs` and `validations`.
- **`actions`** — the operations a user performs, each with `params`, `inputs` and
`outputs`.

The full format is defined by the JSON Schema in
[`schema/txmanifest.schema.json`](schema/txmanifest.schema.json); point your editor at it
with `"$schema"` for completion and inline errors. `manifest_version` must be `"0.3.0"`.
with `"$schema"` for completion and inline errors. `manifest_version` is `"0.3.1"`; `"0.3.0"` manifests still load.
[`examples/p2pk`](examples/p2pk/txmanifest.json) is the smallest complete manifest.

Check a manifest with `validate` (offline structure checks) and `capabilities` (what a
wallet needs to run it; `--supports` turns it into a CI check for other wallet
implementations).
Each program is **pinned** by a hash of its file and a SimplicityHL version requirement,
so a manifest identifies exactly the programs it runs. `tx-manifest-wallet pin` writes the
hashes after you edit a `.simf`; set the version with `"simplicity_hl": { "version":
"0.7.1" }`. Unpinned manifests, including ones whose programs changed since they were
pinned, are refused by `run` unless you pass `--allow-unpinned` or `--debug`.

Check a manifest with `validate` (structure and programs; `--strict` before publishing)
and `capabilities` (what a wallet needs to run it; `--supports` turns it into a CI check
for other wallet implementations).

Terms and design decisions are recorded in [`docs/`](docs).

## Commands

| Command | What it does |
|---------|--------------|
| `run <manifest> <action>` | Run an action: resolve inputs, build, sign, broadcast. |
| `validate <manifest>` | Check a manifest without touching the network. |
| `validate <manifest>` | Check a manifest and its programs without touching the network. |
| `pin <manifest>` | Write each program's current hash into the manifest. |
| `capabilities <manifest>` | Report what a wallet must support to run a manifest. |
| `describe <manifest>` | Browse a manifest's actions interactively. |
| `prepare <manifest> <action>` | Split wallet funds so an action has the UTXOs it needs (Liquid). |
Expand Down Expand Up @@ -124,7 +133,8 @@ The `simplicityhl` dependency is a fork that adds Bitcoin support; see
[`contrib/regtest`](contrib/regtest) builds a local Bitcoin node with Simplicity active.
CI runs `cargo fmt --check` and `cargo clippy -- -D warnings`.

Changes are listed in [CHANGELOG.md](CHANGELOG.md).
Changes to the CLI are listed in [CHANGELOG.md](CHANGELOG.md), and changes to the manifest
format in [docs/format-changelog.md](docs/format-changelog.md).

## License

Expand Down
18 changes: 18 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Docs

This repository defines the txmanifest format, and other wallets implement it, so the
format's terms and design decisions are recorded here.

- [`glossary.md`](glossary.md): the terms this repo uses, one entry each.
- [`format-changelog.md`](format-changelog.md): changes to the manifest format, by
`manifest_version`.
- [`decisions/`](decisions): one record per settled design decision, numbered in order.

## Decision records

Each record is short: the context, what was decided, and what follows from it. Once
accepted, a record isn't edited except for typos. A later decision that changes it gets
a new record, and the old one's status becomes "superseded by NNNN".

To add one, copy the shape of the latest record, take the next number, and link any new
terms from the glossary.
73 changes: 73 additions & 0 deletions docs/decisions/0001-program-pinning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# 0001: Pin Simplicity programs by hash and compiler version

- **Status:** accepted
- **Date:** 2026-10-07

## Context

A manifest references its Simplicity programs by path (`"source": "./lending.simf"`). Its
manifest id is computed from the manifest file, so the programs were not covered by it:
editing, swapping or corrupting a `.simf` file left the id unchanged while changing where
funds are locked. A wallet given a manifest and its sources separately, such as a browser
extension receiving a request from a dapp, had no way to check it was given the right
sources.

Nothing in a manifest said which compiler a program was written for either, and
different compilers could in principle produce different programs from the same source.

## Decision

1. **The manifest id stays derived from the manifest file alone.** Each program reference
carries a hash of the file it points to, so the id covers the programs.
2. **Hashes are self-describing:** `"sha256:<lowercase hex>"`, over the file's exact bytes.
`sha256` is the only algorithm defined; an unknown one is an error.
3. **Two forms, both valid, and they may be mixed:**
- on the reference: `source` / `source_hash` / `simplicity_hl_version` on a `utxo_types`
script, `simf` / `simf_hash` / `simplicity_hl_version` on a `tapleaf` or `simf_fn`
compute;
- a top-level `programs` table of `{ source, hash, simplicity_hl_version }`, named from a
reference with `"program": "<name>"`.

Every reference to one file must agree on its hash and version. A reference that
states neither inherits them; one that states a different value is an error.
4. **Compiler versions are semver requirements**, with the same syntax and meaning as
SimplicityHL's `simc "<range>";` directive (`"0.7.1"` means `^0.7.1`). Where a program's
requirement comes from, highest priority first: the reference or `programs` entry, then
`simplicity_hl.version` for the whole manifest, then the file's own `simc` directive.
The manifest can narrow the file's directive but never widen it, because the compiler
still enforces the directive.
5. **Pinned and unpinned.** A program is *pinned* when it has a hash and a compiler-version
requirement. A manifest with any unpinned program is unpinned:
- allowed while developing (`validate` warns; `run --allow-unpinned` runs it). A
program edited since it was pinned counts as unpinned here, so `run --allow-unpinned`
warns about the stale hash instead of refusing;
- refused for publishing (`validate --strict` errors) and by wallets (`run` refuses by
default, and a wallet library has no way to opt out).

Manifests written before this change load as unpinned.
6. **Introduced in manifest format `0.3.1`.** The change only adds fields, so it's a patch
release of the format: `0.3.0` manifests still load (as unpinned).
7. **The compiler version is not separately part of the manifest id.** It's a declared
requirement, covered by the id like any other field.

## Consequences

- Changing one byte of a pinned program makes loading fail, naming the file and both
hashes, unless running with `--allow-unpinned`, where it's a warning. Two manifests that differ only in a program hash have different ids.
- Every program is read once per run, by one checked loader; the covenant code receives
source text, never a path.
- Authors refresh hashes after editing a program with `tx-manifest-wallet pin`, which
edits only the hash values and leaves the rest of the file as written.
`pin --check` fails if any hash is missing or stale, for CI.
- **Assumption:** a newer SimplicityHL compiles the same source to the same program as the
version it was written for. Under that assumption a requirement is a minimum, and wallets
on different compiler versions derive the same addresses. If it stops holding,
requirements must become exact versions.
- **Single-file programs only.** A program is compiled from the one file it names; imports
from other files are not resolved, so one hash per file is enough. Supporting multi-file
programs will need the hash rule extended to their dependencies.
- **A planned compiled object file** for Simplicity programs could replace compiling from
source. The hash rule ("exact bytes of whatever `source` points at") carries over to it
unchanged.
- The protocol-defined program ids some manifests carry (e.g. simplicity-lending's
`LENDING_PROGRAM_ID`) are unrelated and unchanged.
87 changes: 87 additions & 0 deletions docs/format-changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Manifest format changelog

Changes to the txmanifest **format**: the fields a `txmanifest.json` may contain, what they
mean, and the rules a valid manifest follows. This is for manifest authors and for
anyone implementing the format in a wallet. Changes to the `tx-manifest-wallet` CLI are in
the [app changelog](../CHANGELOG.md).

A manifest declares the format it's written against in `manifest_version`. While the major
version is `0`, a different minor version is a different, incompatible format; the patch
version only adds things, so a reader of `0.3.x` accepts any `0.3` manifest. The format's
history before `0.2.0` was not recorded.

The JSON Schema for the current version is
[`schema/txmanifest.schema.json`](../schema/txmanifest.schema.json).

## [0.3.1] - unreleased

Additive: every `0.3.0` manifest is a valid `0.3.1` manifest.

### Added

- **Program pinning** ([decision 0001](decisions/0001-program-pinning.md)). A manifest can
pin each Simplicity program by a content hash and a compiler-version requirement, so its
manifest id covers the programs it runs:
- `source_hash` beside a `utxo_types` script's `source`, and `simf_hash` beside a
`tapleaf` / `simf_fn` compute's `simf`. Hashes are self-describing,
`"sha256:<lowercase hex>"`, over the file's exact bytes.
- A top-level `programs` table of `{ source, hash, simplicity_hl_version, description }`.
A script or compute names an entry with `"program": "<name>"` instead of giving
`source` / `simf`.
- `simplicity_hl_version` on a reference or a `programs` entry, and
`simplicity_hl.version` for the whole manifest: semver requirements in the syntax of
SimplicityHL's `simc` directive (`"0.7.1"` means `^0.7.1`). Priority: the reference or
entry, then the manifest-wide value, then the file's own `simc` directive. The manifest
can narrow the directive but not widen it.
- **Pinned and unpinned manifests.** A program is pinned when it has a hash and a
compiler-version requirement; a manifest is pinned when all its programs are. Wallets
must refuse unpinned manifests and programs that don't match their hash. Tools may
accept them for development.

### Rules

- A `tapleaf` / `simf_fn` compute gives exactly one of `simf` and `program`; a
`utxo_types` script gives at most one of `source` and `program`.
- Every reference to one file must agree on its hash and compiler requirement. A
reference that gives neither inherits them from the others.
- A `program` must name an entry in `programs`.

## [0.3.0] - 2026-10-06

### Breaking

- `manifest_version` must be `"0.3.0"`.
- A manifest with covenant `utxo_types` must declare `"requires": ["simplicity"]`.
- `chain` accepts only `elements`, `bitcoin`, `liquid` or `btc`; `cross-chain` is refused.

### Added

- `"chain": "bitcoin"` (or `"btc"`): manifests for Bitcoin mainnet, testnet, signet and
regtest. Fields that only make sense on Elements (non-policy assets, issuance, blinded
outputs) are invalid on a Bitcoin manifest.
- `requires`: the capabilities a wallet must support to run the manifest, including
namespaced capabilities (`ns::name`) defined by third parties.

## [0.2.0] - 2026-08-20

### Breaking

- `manifest_version` must be `"0.2.0"`, and readers enforce it.
- `utxo_type.confidential` is removed; set `confidential` on each output instead.

### Added

- `blinding` (`asset_bf` / `value_bf`) on outputs and inputs, to pin or declare blinding
factors.
- `confidential: true` on covenant outputs, and confidential covenant inputs.
- `allow_change` on actions: `none`, `lbtc_only` or `any`.
- `params` on `utxo_type`, and `args` at each place it's used.
- `script_hash` param compute: `sha256(scriptPubKey)` of an address.
- `simplicity_hl.unstable_features`, for SimplicityHL `imports` and `enums`.

### Changed

- A reissuance uses the spent token's asset blinding factor as its
`issuance_blinding_nonce`.
- Pinning an output's `asset_bf` to the factor an input of the same asset already carries
is invalid.
53 changes: 53 additions & 0 deletions docs/glossary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Glossary

Terms as this repo uses them. Where a term is defined in more detail, the entry links there.

**Action.** One operation a manifest defines: its parameters, the inputs it spends, the
outputs it creates. Declared under `actions`, or under a contract template.

**Capability.** Something a wallet must support to run a manifest, listed in the
manifest's `requires` (e.g. `"simplicity"`). Third parties may define their own under a
namespace (`ns::name`).

**Compiler-version requirement.** The SimplicityHL versions a program is written for, as a
semver requirement in the syntax of SimplicityHL's `simc` directive: `"0.7.1"` means
`^0.7.1`. See [decision 0001](decisions/0001-program-pinning.md).

**Content hash.** A self-describing hash of a file's exact bytes: `"sha256:<lowercase
hex>"`. See [decision 0001](decisions/0001-program-pinning.md).

**Contract template.** A reusable contract definition under `contract_templates`: typed
fields plus the actions that operate on one instance of it.

**Instance.** One deployed contract made from a template: the values its fields were given
when it was created.

**Manifest.** A `txmanifest.json` file describing a protocol's transactions. The format is
defined by [`schema/txmanifest.schema.json`](../schema/txmanifest.schema.json).

**Manifest id.** The hash identifying a manifest, computed from its canonical form. It is
derived from the manifest file alone; programs are covered through their content hashes.

**Pin / pinned / unpinned.** A program is *pinned* when the manifest gives it a content
hash and it has a compiler-version requirement (from the manifest or its own `simc`
directive). A manifest is pinned when all its programs are. Unpinned manifests are for
development only: wallets refuse them. `tx-manifest-wallet pin` writes the hashes. See
[decision 0001](decisions/0001-program-pinning.md).

**Program.** A Simplicity program, written in SimplicityHL (a `.simf` file), that a
covenant or a computed parameter uses.

**Program reference.** A place in a manifest that names a program: a `utxo_types` script's
`source` or `program`, or a `tapleaf` / `simf_fn` compute's `simf` or `program`.

**Programs table.** The optional top-level `programs` object of named programs, each with
its `source`, `hash` and `simplicity_hl_version`, which program references can name.

**State.** The on-chain UTXOs a contract instance currently holds, as recorded after each
broadcast.

**SWEL.** The expression language used inside manifests for amounts, references and
conditions (`params.amount_sat`, `vault_in.amount_sat - fee`). Not yet formally specified.

**UTXO type.** A kind of output a manifest's actions create or spend, under `utxo_types`;
for a covenant, the program that locks it.
6 changes: 3 additions & 3 deletions examples/bitcoin_covenant/txmanifest.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"$schema": "https://raw.githubusercontent.com/stringhandler/txmanifest-wallet/main/schema/txmanifest.schema.json",
"manifest_version": "0.3.0",
"manifest_version": "0.3.1",
"protocol": "bitcoin-covenant",
"description": "A Simplicity covenant on Bitcoin. Locks coins under a program the node itself executes, then spends them by satisfying it — the same p2pk.simf that runs on Liquid, unchanged.",
"chain": "bitcoin",
"requires": ["simplicity"],
"simplicity_hl": { "debug_symbols": false },
"simplicity_hl": { "version": "0.7.1", "debug_symbols": false },
"utxo_types": {
"p2pk_vault": {
"description": "Coins spendable only by a BIP340 signature over the whole transaction, checked on-chain by Simplicity rather than by Bitcoin script.",
Expand All @@ -15,7 +15,7 @@
"description": "x-only key the covenant checks against. Take it from `info` (Wallet Signing Key), so this wallet can satisfy it."
}
},
"script": { "type": "simplicity", "source": "./p2pk.simf" }
"script": { "type": "simplicity", "source": "./p2pk.simf", "source_hash": "sha256:658bf994eac2f8dcbf5486787910513a1e4a79bd56f6e3c6c2a74dac25c0e2bf" }
}
},
"actions": {
Expand Down
2 changes: 1 addition & 1 deletion examples/bitcoin_pay/txmanifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/stringhandler/txmanifest-wallet/main/schema/txmanifest.schema.json",
"manifest_version": "0.3.0",
"manifest_version": "0.3.1",
"protocol": "bitcoin-pay",
"description": "A plain Bitcoin payment. The smallest manifest that runs on Bitcoin, and the one that shows what `requires: []` buys you: no covenant, no Simplicity, nothing a stock node lacks.",
"chain": "bitcoin",
Expand Down
Loading
Loading