Guide
How a circle is built, signed and read back
Every number on this page is taken from the vectors committed in test-vectors/v1/. Nothing here is illustrative.
The idea
Imagine four people who want a durable record that they were all party to the same moment. They do not want a token, a website account, or a database row that someone can edit. Each of them puts one confirmed Taproot coin into a single Bitcoin transaction, signs the whole transaction, and gets that coin back to the same key minus a quarter of the miner fee. Once it confirms, anyone with a Bitcoin node can check the record, and nobody can revise it.
That is all Witness Circles does. The circle is the transaction. The membership record is the set of input keys. The proof is the four signatures, each of which commits to every input and every output.
Session lifecycle
The protocol has nothing to say about how participants find each other. That is application territory. The reference implementation ships a planner that produces the frozen unsigned transaction, and a signing-intent inspector that a wallet runs before it signs.
- Context. Someone writes a small JSON manifest (title, nonce, created, expires) and hashes it with RFC 8785. That hash goes in the marker.
- Plan.
buildCirclePlansorts the participant inputs, estimates the virtual size, multiplies by the chosen fee rate, splits the fee, and emits an unsigned transaction plus its fingerprint. It refuses any participant whose fee share would exceed their declared cap (FEE_CAP_EXCEEDED) or whose successor would fall under 1,000 sats (SUCCESSOR_DUST). - Freeze. The unsigned transaction is now immutable. Its SHA-256 fingerprint is what each wallet compares against the invitation.
- Inspect. Each wallet calls
inspectUnsignedSigningIntent, which re-validates the whole grammar inunsignedmode and returns the signer's own outpoint, value, successor, fee share, total fee, fee rate, context hash, and two fixed warnings about public linkage and the claim boundary. - Sign. Each participant returns a non-finalized
PSBT_IN_TAP_KEY_SIG. The coordinator verifies each contribution before merging. A coordinator never holds a key. - Broadcast and confirm. At one confirmation in the best chain the Circle is authoritative and the state transition applies.
Because every signature commits to every input and output, a replacement transaction needs a new signature from every participant. There is no way for one member to change the fee or drop out after signing without the whole group signing again.
Example A: a genesis circle for three participants
This is test-vectors/v1/golden-circle.json in full. Signet, block height 200, three participants who have never been in a Circle before.
The context manifest
{"created":"2026-08-01T18:00:00Z","expires":"2026-08-01T19:00:00Z","kind":"circle",
"nonce":"90e8f5cf27f04c89a6657bc9c60e3021","orbit":"signet-builders","protocol":"witc",
"title":"Reference genesis circle","version":1}
That is the RFC 8785 serialization exactly: object keys sorted, no whitespace. Its SHA-256 is ad2608134839e732280cb93bdd5b8682626dce5748a65c437a39cdcb680c2a82, which is the last 32 bytes of the marker.
The inputs
| Slot | Outpoint | Value | scriptPubKey | Height |
|---|---|---|---|---|
| 0 | 1111…1111:0 | 30,000 | 5120 79be667e…16f81798 | 199 |
| 1 | 2222…2222:1 | 40,000 | 5120 c6047f94…5c709ee5 | 199 |
| 2 | 3333…3333:2 | 50,000 | 5120 f9308a01…bce036f9 | 199 |
The slot order is not a choice. Rule 6.9 sorts inputs by display txid bytes then vout, so any two implementations independently derive the same slots and therefore the same fee assignment.
The arithmetic
N = 3
weight = 246 + 402 * 3 = 1452
vbytes = ceil(1452 / 4) = 363
fee = 363 vB * 10 sat/vB = 3630 sats
q = floor(3630 / 3) = 1210
r = 3630 mod 3 = 0
successor(0) = 30000 - 1210 = 28790
successor(1) = 40000 - 1210 = 38790
successor(2) = 50000 - 1210 = 48790
Because the remainder is zero every share is identical. When the fee does not divide evenly, the first r slots each pay one extra satoshi. That rule is what makes the allocation reproducible rather than negotiated.
The result
| Field | Value |
|---|---|
| txid | 3703d5b332ebf2871a0a24066f7ea4b84dd3e9ebca1b1a879140e844c64b0f65 |
| wtxid | 772b6d1d179e6f5ba1c4699486a754ef7681b6299ee0e023fc2a5dd0c11245ab |
| Virtual size | 363 vB |
| Fee | 3,630 sats |
| Lineages created | 4bc0f5c8…, 63c22eae…, ee745631… |
| State hash after transition | 07b97f0c2cbaa172e66a2137fefbee64c5a05f03e03967d46936cd14070523f1 |
The lineage identifiers are not derived from the Circle. Each one is SHA256("WITC/lineage/v1" || wire_outpoint) of the participant's own genesis outpoint, so a participant can compute their lineage identifier before the Circle exists.
Rebuild this exact circle in the simulator. The default state of the builder is this vector, so you can watch what happens when you change one value.
Example B: two of the three come back
At height 201 the holders of slots 0 and 1 form a second Circle by spending their successor shards from example A. This is the continuation section of state-lifecycle.json.
| Field | Value |
|---|---|
| Inputs | 3703…0f65:1 (28,790) and 3703…0f65:2 (38,790) |
| Participants | 2, so 263 vB |
| Fee | 1,315 sats, q = 657, r = 1 |
| Fee shares | slot 0 pays 658, slot 1 pays 657 |
| txid | f7c7ecb3be4ee1925fc97111afde797633de0fae797d0a12383aecb6868328ef |
| State hash | b29662d78f73f3f8940b2dccaf935dc2a7f1ceba21ef6b1c5b11a7d7f3f5a6fc |
This is the odd-remainder case: 1315 is not divisible by 2, so slot 0 pays the extra satoshi. Note what did not happen. No new lineage was created. The two existing lineages advanced, their circleCount went from 1 to 2, and the indexer recorded two continuation edges pointing from the genesis Circle to this one. The third lineage was untouched and stays active.
Example C: an ordinary spend ends a lineage
At height 202 a transaction that is not a Circle spends f7c7…28ef:1, one of the active shards. It might be the owner moving funds, paying someone, or consolidating. The protocol does not care what it was.
- That lineage becomes
closed, itscurrentOutpointbecomes null, andclosedByTxidrecords the spending transaction. - The revision still increments, because a closure is a state transition.
- No Bitcoin is burned or forfeited. The coin simply left the protocol.
- Every historical Circle that lineage took part in remains in the record.
The state hash after that closure is faf7f730b9b48b39c4509dc3ccb23c94cac6979d334a57fbd1eda20dab7af84c. Roll the three transitions back in reverse and you land on the continuation hash, then the genesis hash, then the empty-state hash. That round trip is exactly what an indexer performs during a chain reorganization, and it is committed in the vector so two implementations cannot silently diverge.
What actually supports Witness Circles today
Only claims that can be checked in Bitcoin Universe source code appear here.
| Component | Status | Evidence |
|---|---|---|
| Reference SDK and CLI | Available | src/sdk.ts, src/cli.ts in this repository, package @bitcoinuniverse/witness-circles 0.1.0. No reviewed registry release yet. |
| Reference state engine | Available | src/state.ts, with rollback and a committed state hash. |
| Dedicated indexer | Available as source | bitcoinuniverseio/index-witness-circles. |
| Wallet integration | Not currently supported in Bitcoin Universe products | No wallet in the organization implements the section 13 signing flow. The requirements are specified so that any wallet can. |
| Marketplace or trade path | Not applicable and not implemented | Witness Circles has no entry in the marketplace protocol registry. The protocol defines no transferable asset, so there is nothing a marketplace could list. |
| Mainnet creation | Disabled | encodeMarkerPayload and buildCirclePlan reject any network other than Signet and regtest with INVALID_NETWORK. |
Common mistakes
- Using a wallet's change output as a participant input
- A Circle publicly links every participating input forever. Use a dedicated UTXO whose history you are willing to publish. Dedicated inputs reduce unrelated exposure; they do not create privacy.
- Letting a wallet auto-select an active shard
- An active shard is an ordinary UTXO to a wallet. If coin selection spends it in an unrelated payment, the lineage closes permanently. Wallets that support the protocol should protect active shards from automatic selection.
- Reusing one key for two slots
- Rule 6.8 rejects it with
DUPLICATE_SCRIPT. Two slots controlled by one person is not detectable, but two slots with one key is, and the protocol refuses it. - Treating the manifest as truth
- The manifest is an untrusted label committed by hash. It can be withheld or lost without affecting validity, and its titles and aliases must be escaped before display.
- Expecting a fee bump
- There is no CPFP workflow and no single-party RBF. If the fee rate was too low, every participant signs again or the transaction waits.