Application layer · marker drops-pact
Drop Pacts: agreements that keep their receipts
A Pact records an agreement's identity and the fingerprints of its rules on Bitcoin, so that any party can later show which rules were agreed and which outcome followed from them. It is deliberately honest about how much of that Bitcoin itself enforces.
No funds or live Pact state are held by the reference service. Do not sign and do not send funds. Keep the full blueprint and the plan hash together, verify any Bitcoin transaction independently in a wallet or explorer you trust, and contact support with the plan hash. No Pacts interface ever needs a seed phrase or a private key.
Everything below the capability contract describes the record formats and the validation rules that are implemented and testable today. Where a structure belongs to the future execution model rather than to what the reference service will act on, this page says so in the section itself.
1. The Pact Seed
A Pact Seed is a Drop. It uses the ordinary Drops leaf, with marker drops-pact and content type application/vnd.drops.pact-seed. Its body is a fixed 184-byte binary record. Nothing about it is free-form, which is why two implementations cannot disagree about what an agreement's identity is.
1.1 Seed fields
| Field | Size | Meaning |
|---|---|---|
network | 1 byte tag | The Bitcoin network this Pact belongs to. A Pact does not exist on another network. |
enforcementFloor | 1 byte tag | How much of the agreement Bitcoin enforces by itself. See enforcement floors. |
engineId | 16 bytes | Which rule engine interprets the ruleset. Identifies the interpreter, not the agreement. |
rulesetHash | 32 bytes | The agreement's rules. Change one clause and this hash changes. |
abiHash | 32 bytes | The shape of the actions the ruleset accepts. |
genesisStateRoot | 32 bytes | The agreement's opening state, before any transition. |
policyRoot | 32 bytes | The policy in force at genesis. Policy can move; the ruleset cannot. |
dataAvailabilityPolicyHash | 32 bytes | Where the parties agreed the off-chain material will be kept, and on what terms. |
1.2 Pact identity
A Pact's identity is derived, not chosen. It is a tagged hash over the Seed's own Drop identity plus the three hashes that cannot change during the Pact's life.
pactId = taggedHash("Drops/Pact",
networkTag (1 byte)
|| txid (32 bytes, internal byte order)
|| seedInputIndex (4 bytes, big-endian)
|| rulesetHash (32 bytes)
|| abiHash (32 bytes)
|| policyRoot (32 bytes))
The Seed's Drop identity supplies the transaction id and input index. Because genesisStateRoot and dataAvailabilityPolicyHash are not in the preimage, a Pact keeps its identity across state changes, while a different ruleset, a different action shape, or a different genesis policy produces a different Pact.
2. Enforcement floors
This is the field that stops a Pact from pretending. It states the weakest guarantee a reader may assume, in one word.
| Floor | Tag | What Bitcoin guarantees | What still depends on people |
|---|---|---|---|
recorded | 0 | That these exact terms were committed at this height, and nobody edited them afterwards. | Everything else. Performance, delivery, payment, and any consequence of breach. |
co-signed | 1 | The above, plus that a spend needs the signatures the script requires. | Whether the signers were entitled to sign, and whether the off-chain obligation was met. |
template-enforced | 2 | The above, plus the script constraints of a known template: timelocks, required keys, and which outputs a spend may create. | Anything the template does not encode. A template constrains spends; it does not read the agreement. |
A recorded Pact is a notarised document, not an escrow. Bitcoin can enforce signatures, timelocks, and whether a particular output has already been spent. It cannot enforce that a shipment arrived. An interface that blurs those two things is the failure this field exists to prevent.
3. Pact lifecycle
A Pact has three structural pieces. The Seed is recorded today. The Cell and the transition belong to the execution model, and the reference service validates them locally without acting on them.
3.1 The Cell descriptor
| Offset | Size | Field | Notes |
|---|---|---|---|
| 0 | 4 | magic DPCL | ASCII |
| 4 | 1 | reserved | Must be zero |
| 5 | 1 | flags | One byte |
| 6 | 2 | reserved | Both bytes must be zero |
| 8 | 32 | pactId | Ties the cell to its Pact |
| 40 | 8 | sequence | Unsigned 64-bit, big-endian |
| 48 | 32 | stateRoot | The state this cell asserts |
| 80 | 32 | policyRoot | The policy in force at this cell |
| 112 | 32 | stateAttachmentHash | Binds the off-chain state material |
| 144 | 32 | transitionCommitment | The transition that produced this cell |
The Cell leaf that carries the descriptor is deliberately tiny: a push of the marker drops-cell, OP_DROP, a push of the 176-byte descriptor, OP_DROP, then OP_0. It is a commitment carrier, not a spending condition, and it is not a Drops leaf.
3.2 The transition preimage
A transition is committed by hashing a fixed 245-byte preimage. The commitment appears in two places, and both must agree.
transitionCommitment = taggedHash("Drops/PactTransition", preimage)
preimage (245 bytes, big-endian integers)
0 1 networkTag
1 32 pactId
33 32 parent txid, internal byte order
65 4 parent output index
69 4 parent input index
73 4 successor output index
77 8 parent sequence
85 32 parentStateRoot
117 32 nextStateRoot
149 32 nextPolicyRoot
181 32 proofPackHash
213 32 opDropEffectHash
If a transition has no OP_DROP token effect, opDropEffectHash is 32 zero bytes. Otherwise it is the SHA-256 of the effect payload. That single field is how a Pact and a token movement are tied together without either protocol reaching into the other.
4. Outcomes and how they are checked
An outcome is a claim that the Pact moved from one state to the next. Checking it means checking that the recorded successor is exactly the successor the stated terms produce. Nine conditions must all hold.
- P-1
The parent descriptor, the successor descriptor and the transition must all carry the same
pactId.PACTS_INVALID_TRANSITION: Pact IDs do not match - P-2
parent.sequencemust equal the transition'sparentSequence, andsuccessor.sequencemust equalparent.sequence + 1. No gaps, no rewinds.PACTS_INVALID_TRANSITION: cell sequence is invalid - P-3
parent.stateRootmust equalparentStateRoot,successor.stateRootmust equalnextStateRoot, andsuccessor.policyRootmust equalnextPolicyRoot.PACTS_INVALID_TRANSITION: cell roots do not match the transition - P-4
The ruleset result recorded in the proof pack must equal
nextStateRoot. The outcome must be the outcome the rules produced.PACTS_INVALID_TRANSITION: ruleset result does not match next state root - P-5
The OP_DROP effect hash must equal
SHA256(effect)when an effect is present, and 32 zero bytes when it is not.PACTS_INVALID_TRANSITION: op-drop effect hash does not match - P-6
proofPackHashmust equal the hash of the proof pack payload actually supplied.PACTS_INVALID_TRANSITION: proof pack payload hash does not match - P-7
successor.transitionCommitmentmust equaltaggedHash("Drops/PactTransition", preimage).PACTS_INVALID_TRANSITION: successor descriptor does not commit to the transition - P-8
The successor Cell leaf must prove its own Taproot commitment against the successor output script, using the same control-block rules as a Drop.
PACTS_INVALID_SUCCESSOR_CELL - P-9
The
DPC1anchor must decode to that same transition commitment. The anchor script is exactly 38 bytes:6a 24 "DPC1" <32-byte commitment>.PACTS_INVALID_DPC1: DPC1 commitment does not match the proof pack transition
The pact verifier on this site runs rules P-1, P-2, P-3, P-7 and P-9 in your browser from descriptor and preimage hex, and explains exactly which one failed.
4.1 The proof pack
The proof pack is a deterministic CBOR structure holding the transition, both descriptors, the successor control block and output script, and the ruleset material: rulesetInput, stateWitness, stateDelta, rulesetResult, an optional opDropEffect, and a list of availability hashes. Its encoding is deterministic on purpose, so that a proof pack has one serialization and therefore one hash.
5. The Pacts reference profile
Not every Pact needs the full execution model. The lightest useful record is a Pacts reference: a Drop that ties a template to the two hashes a party keeps locally, so they can prove later which plan and which blueprint they agreed to.
| Content type | application/vnd.drops.pacts-reference+json |
|---|---|
| Body | UTF-8 JSON, exactly four keys, serialized in sorted key order with no whitespace |
| Keys | bh blueprint hash, p always pacts, ph plan hash, t template id |
| Hash format | 64 lowercase hex characters each |
| Template grammar | ^[a-z][a-z0-9-]{0,40}$ |
| Size | Always well under the 256-byte body limit |
{"bh":"<64 hex>","p":"pacts","ph":"<64 hex>","t":"escrow-basic"}
A body that decodes to the right values but was serialized differently is rejected. Re-encoding the parsed reference must reproduce the body byte for byte, exactly as it must for the leaf itself. Unknown keys, missing keys, reordered keys, added whitespace and non-string values are all rejections.
The plan hash and the blueprint hash reveal nothing on their own. They let a party show that the document they kept is the document the Pact committed to.
6. Agreement shapes
These are the agreement structures the Pact record format is designed to describe. They are descriptions of the model, not services offered by the reference deployment.
- Shared custody
- Controllers, recovery expectations, and each approved change to the agreement state. A
co-signedortemplate-enforcedfloor is what makes the signing rules real rather than described. - Escrow
- Release and refund paths recorded before value moves, so both parties can point at the same terms afterwards.
- Vesting
- A release schedule tied to block height. Timelocks are one of the few things Bitcoin enforces directly, which makes this shape a good fit for
template-enforced. - Asset policy
- An OP_DROP token policy pointed at a durable public agreement record. The
opDropEffectHashfield is the join. - Community treasury
- Shared policy, current state, and the history of approved changes, all readable from chain data rather than from an application's database.
Check an outcome against its terms or decode a Pact Seed body.