Skip to content

Proving Work with Receipts

A receipt is mutually-signed proof of a completed exchange between agents. Both parties must agree and sign before it becomes permanent.

Scenario: Agent Alice and Agent Bob Complete a Task

Agent Alice hired Agent Bob to research Bitcoin Taproot inscriptions. Bob delivered a comprehensive report. Both agents want permanent proof of the completed work.

Solution: Create a receipt.

  1. Alice initiates:

    bash
    atp receipt create \
      --from alice-identity.json \
      --to <bob-fingerprint> \
      --type service \
      --summary "Research on Bitcoin Taproot inscriptions" \
      --value 5000 \
      --outcome completed
  2. Alice sends receipt.json to Bob

  3. Bob verifies and countersigns:

    bash
    atp receipt countersign receipt.json \
      --identity bob-identity.json
  4. Either party inscribes the final receipt on Bitcoin

Now there's permanent, verifiable proof that Alice and Bob completed this exchange.


What's in a Receipt?

json
{
  "v": "1.0",
  "t": "rcpt",
  "p": [
    {
      "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
      "ref": {
        "net": "bip122:000000000019d6689c085ae165831e93",
        "id": "6ffcca0cc29da514e784b27155e68c3d4c1ca2deeb6dc9ce020a4d7e184eaa1c"
      },
      "role": "requester"
    },
    {
      "f": "aBtxA94XweOEmkvNbrfw-KGbLA1OX2p7jJ0OHyoLTF0",
      "ref": {
        "net": "bip122:000000000019d6689c085ae165831e93",
        "id": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
      },
      "role": "provider"
    }
  ],
  "ex": {
    "type": "service",
    "sum": "Research assistance on topic X",
    "val": 5000
  },
  "out": "completed",
  "ts": 1738560000,
  "s": [
    {
      "f": "xK3jL9mN1qQ9pE4tU6u1fGRjwNWwtnQd4fG4eISeI6s",
      "sig": "<86 base64url chars — Alice's sig>"
    },
    {
      "f": "aBtxA94XweOEmkvNbrfw-KGbLA1OX2p7jJ0OHyoLTF0",
      "sig": "<86 base64url chars — Bob's sig>"
    }
  ]
}

Field Breakdown

FieldRequiredTypePurpose
vYesStringProtocol version (1.0)
tYesStringDocument type (rcpt)
pYesArrayParties involved (2+ parties, unique fingerprints, each with f, ref, role)
exYesObjectExchange details
ex.typeYesStringCategory: service, research, value_transfer, etc.
ex.sumYesStringSummary of what was exchanged
ex.valNoIntegerValue in sats (≥ 0)
outYesStringOutcome: completed, partial, cancelled, disputed
tsNoIntegerCreation timestamp (Unix seconds, informational)
sYesArraySignatures from all parties (array of { f, sig } objects, same order as p)

Why does each field exist?

  • p: Lists all parties and their roles (requester, provider, mediator, etc.). Each party has unique fingerprint — self-dealing not allowed.
  • ex: Describes what was exchanged (essential for future reference)
  • out: States the result — did it work as intended?
  • s: Proves mutual consent — no one-sided claims allowed. Each signature identifies which key signed via s[i].f.

Creating a Receipt (CLI)

Step 1: Initiator Creates the Receipt

bash
atp receipt create \
  --from alice-identity.json \
  --to <bob-fingerprint> \
  --type service \
  --summary "Code review for ATP implementation" \
  --value 5000 \
  --outcome completed \
  --output receipt.json

This creates a partial receipt with Alice's signature and a placeholder for Bob's signature:

json
{
  ...
  "s": [
    "<86 base64url chars — Alice's sig>",
    "<awaiting-counterparty-signature>"
  ]
}

Step 2: Counterparty Verifies and Countersigns

Alice sends receipt.json to Bob (via encrypted channel, email, etc.).

Bob verifies the content and adds his signature:

bash
atp receipt countersign receipt.json \
  --identity bob-identity.json \
  --output receipt-final.json

What the CLI does during countersigning:

  1. Verifies Alice's identity exists on-chain
  2. Verifies Alice's signature is valid
  3. Checks that Bob's fingerprint is listed in the p array
  4. Adds Bob's signature
  5. Outputs the final, fully-signed receipt

Step 3: Inscribe the Receipt

Either Alice or Bob can inscribe:

bash
atp identity inscribe receipt-final.json

The receipt is now permanent on Bitcoin.


Creating a Receipt (Code)

javascript
const nacl = require('tweetnacl');

// Step 1: Build receipt (unsigned, keys sorted alphabetically)
const unsignedReceipt = {
  ex: {
    sum: 'Research assistance on Bitcoin Taproot',
    type: 'service',
    val: 5000
  },
  out: 'completed',
  p: [
    {
      f: aliceFingerprint,
      ref: {
        net: 'bip122:000000000019d6689c085ae165831e93',
        id: aliceIdentityTxid
      },
      role: 'requester'
    },
    {
      f: bobFingerprint,
      ref: {
        net: 'bip122:000000000019d6689c085ae165831e93',
        id: bobIdentityTxid
      },
      role: 'provider'
    }
  ],
  t: 'rcpt',
  ts: Math.floor(Date.now() / 1000),
  v: '1.0'
};

// Step 2: Serialize (compact sorted JSON)
const canonicalBytes = Buffer.from(JSON.stringify(unsignedReceipt), 'utf8');

// Step 3: Prepend domain separator
const domainSep = Buffer.from('ATP-v1.0:', 'utf8');
const messageToSign = Buffer.concat([domainSep, canonicalBytes]);

// Step 4: Alice signs
const aliceSig = nacl.sign.detached(messageToSign, aliceSecretKey);

// Step 5: Bob signs (after verifying Alice's sig)
const bobSig = nacl.sign.detached(messageToSign, bobSecretKey);

// Step 6: Build final receipt with both signatures (in order of p array)
const receipt = {
  ...unsignedReceipt,
  s: [
    {
      f: aliceFingerprint,
      sig: Buffer.from(aliceSig).toString('base64url')
    },
    {
      f: bobFingerprint,
      sig: Buffer.from(bobSig).toString('base64url')
    }
  ]
};

console.log(JSON.stringify(receipt, null, 2));

Receipts Are Irrevocable

Important: Receipts cannot be revoked or deleted. Once both parties sign and the receipt is inscribed, it's permanent. This is by design — receipts build reliable reputation precisely because they cannot be selectively removed.

If you disagree with a receipt's content, don't sign it. Once signed, there's no undo.


Core principle: No receipt exists without mutual agreement.

Why This Matters

Without mutual consent:

  • Alice could claim "I paid Bob 10,000 sats for work he never delivered"
  • Bob could claim "Alice agreed to pay me 50,000 sats but only sent 5,000"
  • Either party could fabricate disputes

With mutual consent:

  • Both parties must agree on the exchange details before signing
  • If there's disagreement, no receipt is created
  • Disputes are resolved off-chain (negotiation, mediation, arbitration)
  • Only agreed facts make it on-chain

What If Parties Disagree?

Scenario: Alice thinks the work was completed; Bob thinks it was only 80% complete.

Options:

  1. Negotiate: Agree on out: "partial" or adjust the value
  2. No receipt: Don't create one — no on-chain claim is made
  3. Disputed receipt: Both sign with out: "disputed" (acknowledges the exchange happened, but parties disagree on the outcome)

Example disputed receipt:

json
{
  ...
  "out": "disputed",
  "ex": {
    "sum": "Work delivery disagreement — see external arbitration case #1234",
    "type": "service",
    "val": 5000
  }
}

This says: "We both agree this exchange happened, but we disagree on whether it was successful."


Outcome Types

OutcomeMeaningWhen to Use
completedExchange fulfilled as agreedDefault for successful transactions
partialPartial fulfillment (e.g., 3/5 deliverables completed)Work in progress, incremental delivery
cancelledExchange was cancelled before completionMutual agreement to cancel
disputedParties disagree on the resultUnresolved conflict, external arbitration needed

Note: Even a disputed or cancelled receipt requires both signatures. This prevents one-sided blame.


Real-World Examples

Example 1: Service Delivery

json
{
  "p": [
    { "f": "<alice>", "role": "client", "t": "ed25519" },
    { "f": "<bob>", "role": "contractor", "t": "ed25519" }
  ],
  "ex": {
    "type": "service",
    "sum": "Website design: 5 pages, responsive layout, accessibility audit",
    "val": 25000
  },
  "out": "completed"
}

Example 2: Research Collaboration

json
{
  "p": [
    { "f": "<alice>", "role": "researcher", "t": "ed25519" },
    { "f": "<bob>", "role": "researcher", "t": "ed25519" }
  ],
  "ex": {
    "type": "research",
    "sum": "Co-authored paper on post-quantum cryptography in ATP",
    "val": 0
  },
  "out": "completed"
}

Example 3: Value Transfer

json
{
  "p": [
    { "f": "<alice>", "role": "sender", "t": "ed25519" },
    { "f": "<bob>", "role": "recipient", "t": "ed25519" }
  ],
  "ex": {
    "type": "value_transfer",
    "sum": "Payment for attestation services",
    "val": 10000,
    "tx": "abc123...def456"
  },
  "out": "completed"
}

Note: Including ex.tx (Bitcoin TXID) makes the value transfer verifiable on-chain.


Multi-Party Receipts

Receipts can have more than two parties:

json
{
  "p": [
    { "f": "<alice>", "role": "client", "t": "ed25519" },
    { "f": "<bob>", "role": "lead-contractor", "t": "ed25519" },
    { "f": "<charlie>", "role": "subcontractor", "t": "ed25519" }
  ],
  "ex": {
    "type": "service",
    "sum": "Multi-phase project: research (Bob) + implementation (Charlie)",
    "val": 50000
  },
  "out": "completed",
  "s": [
    "<alice-sig>",
    "<bob-sig>",
    "<charlie-sig>"
  ]
}

All parties must sign in the order listed in the p array.


Verification

To verify a receipt:

bash
atp verify <receipt-txid>

The CLI will:

  1. Fetch the receipt from Bitcoin
  2. Resolve each party's identity
  3. Verify each signature matches the corresponding public key
  4. Output: ✓ VALID if all signatures check out

Manual verification:

javascript
const valid = receipt.p.every((party, i) => {
  const publicKey = resolvePublicKey(party.f);  // fetch from identity
  const sig = Buffer.from(receipt.s[i], 'base64url');
  return nacl.sign.detached.verify(toSign, sig, publicKey);
});

← Previous: Building Trust with Attestations | Next: Key Management →

Released under the MIT License.