Normative
Witness Circles specification
Version 1.0.0. Protocol magic WITC, protocol version byte 0x01, operation CIRCLE (0x01). This page mirrors SPECIFICATION.md in the owning repository, which is the source of truth if the two ever differ.
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, NOT RECOMMENDED, MAY and OPTIONAL are interpreted as described by RFC 2119 and RFC 8174 when written in uppercase.
1. Claim boundary
A valid Circle establishes only that distinct native P2TR output keys authorized the exact transaction under Bitcoin's Taproot key-path signature rules.
A compliant application MUST NOT present a Circle as proof of human identity or personhood, attendance or physical presence, friendship, membership, endorsement or legal agreement, one key per person, or ownership before or after any period not established by the indexed UTXO history.
2. Layers
- Bitcoin consensus determines whether the transaction and its inputs are valid in the best chain.
- Relay and miner policy determine whether an unconfirmed transaction propagates or is mined.
- This specification identifies and validates WITC events from Bitcoin data.
- Optional context manifests describe a Circle but never affect its Bitcoin validity after their hash is committed.
- Profiles, aliases, renderers, invitations, notifications, moderation and analytics are application services, not protocol truth.
3. Network identifiers
| Byte | Network | Creation enabled |
|---|---|---|
0x00 | Bitcoin mainnet | No |
0x01 | Bitcoin testnet3 | No |
0x02 | Bitcoin Signet | Yes |
0x03 | Bitcoin regtest | Yes |
Other values are invalid for the protocol. The marker network MUST match the chain being indexed. All four identifiers are parseable so that independent tools can classify data without enabling creation or broadcast; the reference planner and marker encoder reject mainnet and testnet3 with INVALID_NETWORK.
4. Marker
Output index 0 MUST have value zero and this exact script:
OP_RETURN PUSH40 <40-byte payload>
The byte serialization is 6a 28 <payload>, so the complete scriptPubKey is exactly 42 bytes.
| Offset | Size | Field | Value |
|---|---|---|---|
| 0 | 4 | Magic | ASCII WITC (57495443) |
| 4 | 1 | Protocol version | 0x01 |
| 5 | 1 | Network identifier | 0x00 to 0x03 |
| 6 | 1 | Operation | 0x01 CIRCLE |
| 7 | 1 | Participant count N | 2 to 16 inclusive |
| 8 | 32 | Context hash | SHA-256 of the serialized context manifest, never all zero |
PUSHDATA1, larger pushes, extra opcodes, trailing bytes, alternate magic casing and zero-valued context hashes are invalid. Only operation 0x01 CIRCLE is valid in the protocol. Reserved values, including potential refuel or rekey operations, MUST be rejected rather than guessed.
Worked example. The marker of the published Signet vector is 6a285749544301020103ad2608134839e732280cb93bdd5b8682626dce5748a65c437a39cdcb680c2a82: 6a OP_RETURN, 28 push 40 bytes, 57495443 WITC, 01 version, 02 Signet, 01 CIRCLE, 03 three participants, then the 32 byte context hash. You can take it apart yourself in the marker decoder.
5. Context manifest
The context manifest is a JSON object serialized with RFC 8785 (JCS) and committed as:
context_hash = SHA256(UTF8(JCS(manifest)))
Required fields are protocol, version, kind, nonce, title, created and expires. Optional fields are orbit, host, media, description, renderer and aliases. The context manifest schema defines their structure.
expiresapplies to the invitation only. It does not expire a confirmed Circle.- Unknown manifest fields are rejected by the reference validator.
- A manifest MAY remain private or disappear. Its absence MUST NOT invalidate a matching on-chain Circle.
- Aliases are untrusted labels. Applications MUST escape them and MUST NOT interpret them as identities.
Schema string limits count Unicode code points, matching JSON Schema 2020-12. Every string MUST contain well-formed Unicode without unpaired surrogates, and MUST be unchanged by the ECMAScript TrimString operation used by String.prototype.trim, so leading or trailing ECMAScript whitespace is invalid. RFC 8785 preserves string code points without Unicode normalization. created and expires MUST be real RFC 3339 UTC timestamps ending in Z, and expires MUST be later than created. Alias keys MUST be 32 bytes of lowercase hex and MUST be unique within the manifest. These semantic rules are enforced by the reference validator in addition to the published structural schema.
6. Transaction grammar
A valid Circle MUST satisfy every rule below. The reference validator in src/validator.ts raises the named error code on the first rule that fails.
nVersionis 2.nLockTimeis 0.- Every input
nSequenceis0xfffffffd. - There are exactly
Ninputs andN + 1outputs, whereNis the participant count in the marker. - Every input prevout is confirmed in a lower block height than the Circle transaction.
- Every input prevout is a native P2TR output with script
OP_1 PUSH32 <output-key>. - Every input outpoint is unique.
- Every input scriptPubKey is unique within the Circle.
- Inputs are strictly ordered by their 32-byte lowercase display txid bytes, then by unsigned numeric
vout. - Output 0 is the exact marker described in section 4.
- Output
i + 1has the exact scriptPubKey of inputi. - Output
i + 1is at least 1,000 sats. - No other output exists.
- Every input uses native Taproot key-path witness data with exactly one stack item.
- The witness item is either a 64-byte BIP340 signature using
SIGHASH_DEFAULT, or a 65-byte signature whose final byte is exactlySIGHASH_ALL(0x01). - Script-path witnesses, annexes,
ANYONECANPAY,NONE,SINGLE, empty witnesses and other sighash bytes are invalid for a confirmed Circle. - Every signature MUST verify against the P2TR output key and the BIP341 key-path message for its input.
The same script successor preserves key continuity. It does not create a covenant: the controller can spend the successor in any consensus-valid transaction.
7. Fees and size
The miner fee is split as evenly as integer arithmetic allows, with the remainder assigned to the lowest slots. Let F be the total fee and N the participant count:
F = sum(input_values) - sum(outputs_1_through_N)
q = floor(F / N)
r = F mod N
fee_share(i) = q + 1 when i < r, otherwise q
successor_value(i) = input_value(i) - fee_share(i)
The output values MUST match this calculation exactly; a mismatch is OUTPUT_MAPPING. The protocol defines no maximum fee. Wallets MUST enforce signer-selected absolute and fee-rate caps before signing.
For 64-byte default signatures:
weight = 246 + 402N
vbytes = ceil((246 + 402N) / 4)
Explicit SIGHASH_ALL adds one witness byte per such input. Sizes for every legal participant count are tabulated in the reference.
8. Lineages and shards
For a valid Circle input that is not the current shard of an existing lineage:
lineage_id = SHA256(
UTF8("WITC/lineage/v1") || wire_serialized_genesis_outpoint
)
The outpoint uses Bitcoin wire form: reversed display txid bytes followed by the four-byte little-endian vout.
Each Circle creates one successor shard for each lineage. A valid later Circle can spend a current shard and create its next same-script successor. Multiple lineages participate together but never merge ownership or value.
A consensus-valid transaction that spends an active shard without satisfying every Circle rule closes that lineage. The Bitcoin is not burned by the protocol. Historical Circles remain part of the best-chain record.
The protocol has no transfer, split, merge, rekey, refuel, mint, market, burn, reward, governance or administrative operation.
9. Confirmation and mempool
Mempool observations are provisional and node-local. An indexer MAY expose pending valid candidates, conflicts, replacements and eviction, but MUST keep these projections separate from confirmed authoritative state.
A Circle becomes authoritative at one confirmation in the best chain. Applications MAY label six confirmations as settled display state, but this is not an additional protocol transition.
The transaction signals opt-in RBF. Any replacement needs new signatures from every participant, because signatures commit to all inputs and outputs. A compliant product MUST NOT promise CPFP, cancellation after broadcast, or successful relay.
10. Reorganizations
An indexer MUST store block hashes and sufficient undo information. When the indexed tip no longer matches Bitcoin Core, it MUST reverse confirmed transitions in reverse transaction order until the common ancestor, then ingest the new branch in the order transactions appear in each block.
Invalid candidate fields MUST NOT apply a Circle transition. A confirmed transaction that spends an active shard but is not a valid Circle still closes that lineage as an ordinary spend. This includes a transaction that ceases to be a valid Circle after parser correction under the parser version selected for replay.
11. Determinism and state
CompactSize integers MUST be minimally encoded. Numeric calculations MUST use checked integer arithmetic. Transaction values MUST NOT exceed Bitcoin MAX_MONEY.
Independent implementations MUST reproduce the committed test vectors. Error strings may differ, but the published error codes and the valid or invalid result must agree.
The authoritative confirmed-state snapshot is the exact object defined by the state snapshot schema. revision starts at zero and increments once for each confirmed transaction that either applies a valid Circle or closes at least one active lineage through an ordinary spend. It does not increment for unrelated transactions, or for invalid candidates that close no active lineage. Rollback restores the prior revision.
Every JSON numeric field in the snapshot MUST be a nonnegative ECMAScript safe integer, except circleCount, which MUST also be at least one. Every display outpoint MUST use lowercase txid hex and an exact decimal vout from 0 through 4294967295. Every satoshi string MUST be an exact unsigned decimal from zero through Bitcoin MAX_MONEY, with no leading zero.
Before hashing, implementations MUST order lowercase ASCII fields as follows:
lineagesbylineageId.shardsby the complete display outpoint string, using ordinal byte order.circlesbyblockHeight, thentransactionIndex, thentxid.- Each Circle's
membersby numericslot. edgesbytoCircle, thenlineageId.
No implementation-specific database fields may enter this snapshot. The authoritative state hash is SHA256(UTF8(RFC8785(snapshot))), with no prefix and no trailing newline. The empty-state hash is 90e749b7720fac379610d979e29998c7d650150548622f0a47d9d3e181f1be52. The golden transaction vector commits the first-transition state hash and block envelope. The state-lifecycle vector then commits a continuation, an ordinary-spend closure and each reverse rollback root, so independent indexers can reproduce the complete lifecycle exactly.
12. Upgrade policy
New protocol behaviour requires a new version byte, a specification, a threat model, schemas, cross-language vectors, an activation policy and independent implementation review. Implementations MUST NOT reinterpret finalized protocol history under a later version.
Unknown versions and operations MAY be retained as uninterpreted candidates, but MUST NOT apply their version-specific transitions to authoritative state. If such a transaction spends an active shard, the lineage still closes under the ordinary-spend rule in section 8.
13. Wallet requirements
Before signing, a wallet MUST independently verify every rule in section 6 plus:
- The owned outpoint appears exactly once.
- The displayed context serializes under RFC 8785 to the marker hash.
- The owned successor and its exact fee share are visible.
- Total fee and fee rate are within user-approved caps.
- No global xpub, peer derivation path, or unnecessary proprietary PSBT field is disclosed.
- The frozen unsigned transaction matches the invitation fingerprint.
The signer-specific PSBT SHOULD contain witness_utxo for every input. Only the owner's input SHOULD include its Taproot key origin data. Contributions SHOULD return a non-finalized PSBT_IN_TAP_KEY_SIG, and the coordinator MUST verify each contribution before merge.
14. Error codes
These are the stable codes exported by src/errors.ts. Implementations may use different messages, but a conforming implementation must reach the same valid or invalid verdict and should report the same code.
| Code | Raised when |
|---|---|
AMBIGUOUS_ENCODING | A non-minimal encoding was used: PUSHDATA1 for the marker, a non-minimal CompactSize, or a superfluous SegWit marker and flag. |
CONTEXT_HASH_ZERO | The 32-byte context hash in the marker payload is all zero. |
DUPLICATE_INPUT | The same outpoint appears twice as an input, or twice in the validation context. |
DUPLICATE_LINEAGE | One lineage would appear twice in the same Circle. |
DUPLICATE_SCRIPT | Two participants presented the same P2TR output key. |
FEE_CAP_EXCEEDED | A signer-selected fee share, total fee or fee rate cap would be exceeded. |
FEE_NEGATIVE | Outputs exceed inputs, or a negative fee was supplied to the allocator. |
INPUT_COUNT | The participant count is outside 2 to 16, or does not equal the input count. |
INPUT_ORDER | Inputs are not strictly sorted by display txid then vout. |
INPUT_PREVOUT_MISSING | The validation context does not contain exactly one prevout per input. |
INPUT_UNCONFIRMED | An input prevout is not confirmed in an earlier block. |
INTEGER_RANGE | A value is outside its declared range, including Bitcoin MAX_MONEY and CompactSize limits. |
INVALID_CONTEXT_MANIFEST | The context manifest failed structural or semantic validation. |
INVALID_HEX | Hex input is malformed or has the wrong length. |
INVALID_MARKER | The marker script is not exactly 42 bytes, or its magic is not WITC. |
INVALID_NETWORK | The network identifier is unknown, does not match the indexed chain, or creation was attempted outside Signet and regtest. |
INVALID_OPCODE | The operation byte is not 0x01 CIRCLE. |
INVALID_OUTPOINT | An outpoint is malformed or its vout is outside uint32. |
INVALID_P2TR | An input or successor script is not native P2TR, or a scriptSig is not empty. |
INVALID_SIGNATURE | A BIP340 key-path signature failed verification. |
INVALID_STATE | A state snapshot, transition or rollback violated a structural state invariant. |
INVALID_TRANSACTION | The transaction is truncated, exceeds decoder limits, or is otherwise unparseable. |
INVALID_VERSION | The protocol version byte is not 0x01, or a vector schema version is unsupported. |
LOCK_TIME | nLockTime is not zero. |
MARKER_VALUE | The marker output value is not zero. |
NON_CANONICAL_JSON | JSON text is not in RFC 8785 form. |
OUTPUT_COUNT | The output count is not N + 1, or the marker output is missing. |
OUTPUT_MAPPING | A successor script or value does not match the deterministic mapping for its slot. |
PREVOUT_HEIGHT | A prevout block height or the current block height is invalid. |
SEQUENCE | An input sequence is not 0xfffffffd. |
SUCCESSOR_DUST | A successor output is below 1,000 sats. |
SIGHASH_UNSAFE | A sighash byte other than SIGHASH_DEFAULT or explicit SIGHASH_ALL was used. |
TRANSACTION_VERSION | nVersion is not 2. |
TRAILING_DATA | The raw transaction contains bytes after the locktime. |
UNSUPPORTED_OPERATION | A reserved or future operation was encountered where only CIRCLE is valid. |
WITNESS_SHAPE | The witness does not carry exactly one item of 64 or 65 bytes, or an unsigned candidate carried witness data. |
15. Reference status
This repository is a reference implementation, not Bitcoin consensus software. Bitcoin Core remains authoritative for Bitcoin validity and local policy acceptance. Applications SHOULD call testmempoolaccept immediately before broadcast.