Trust Protocol¶
The Trust Protocol is the cryptographic and authorisation layer underneath the contract state machine. The state machine answers what state we are in; Trust answers can we trust that state.
The protocol layer makes a deliberately narrow set of promises:
- a message is from a known entity (signature verification)
- a contract snapshot is endorsed by the Arbiter (snapshot signature)
- a state notification has been observed by a participant (status ACK)
It deliberately does not promise that external payments succeed, that the network is available, or that a counterparty is online.
Trust at three layers¶
Trust is not one thing. It is three layers that solve different problems.
| Dimension | Transport trust | Session trust | Contract trust |
|---|---|---|---|
| Goal | Message is authentic, untampered, optionally confidential | A conversation is continuous and coherent | A collaboration's state chain is consistent |
| Carried by | Mail signature, optional encryption |
session_id, multi-turn message thread |
contract_id, signed Contract Snapshot, Arbiter attestation, ACK |
| Continuity requirement | None | Conversation continuity | State continuity |
| Survives a new chat window | Trivially | Often not | Yes — only contract_id + snapshot_hash are required |
| Failure mode | Forgery, leakage, tampering | Context drift, confused replies | State desync, broken audit chain |
Contract trust does not require conversation continuity — even if the chat window is lost and a new session is started, a contract action carrying the right contract_id, source_snapshot_hash, and terms_hash is still verifiable.
Reusing existing FP capabilities¶
Foundation Protocol already provides:
Mailenvelope signing (Ed25519)- Optional
X25519 + AES-GCMencryption FPAddressas the canonical addressEntityCardas the public-key carrier- In-host mail routing
Trust Protocol does not introduce new cryptographic primitives. It defines:
- what content must be signed
- by whom
- how it is verified
- when encryption is mandatory
Protocol objects¶
classDiagram
class ParticipantSnapshot {
+address: str
+role: str
+host_uid: str
+entity_uid: str
+sign_public_key: str
+encrypt_public_key: str
+display_name: str
}
class ContractTerms {
+revision: int
+title: str
+description: str
+amount: float
+funding_mode: str
+terms_hash: str
}
class ContractApproval {
+party_role: str
+approved_terms_hash: str
+approved_at: float
+approved_by: str
+source_mail_id: str
}
class ContractReceipt {
+recipient: str
+status_message_id: str
+snapshot_hash: str
+acked_at: float
}
class ArbiterAttestation {
+snapshot_hash: str
+prev_snapshot_hash: str | null
+signed_at: float
+signer: str
+signature_alg: str
+signature: str
}
class ContractSnapshot {
+contract_id: str
+protocol_version: str
+status: str
+participants: list
+terms: ContractTerms
+approvals: list
+rating: int | null
+review: str | null
+receipts: list
+attestation: ArbiterAttestation | null
}
ContractSnapshot --> ParticipantSnapshot
ContractSnapshot --> ContractTerms
ContractSnapshot --> ContractApproval
ContractSnapshot --> ContractReceipt
ContractSnapshot --> ArbiterAttestation
ParticipantSnapshot¶
Freezes a participant's identity in the context of one contract: address, role (party_a / party_b / arbiter), host_uid, entity_uid, sign_public_key, encrypt_public_key, display_name.
Snapshotting identity at contract time means third parties can verify the chain without depending on the current EntityCard — they only need what is in the snapshot.
ContractTerms¶
The smallest signable unit of contract terms: revision, title, description, amount, funding_mode, terms_hash (SHA256 over canonical JSON of the terms minus the hash).
revision lets the Arbiter distinguish which version of the terms a participant is approving, and prevents stale-version replay.
ContractApproval¶
Records one party's approval of a specific terms_hash. Lets the Arbiter prove both sides approved the same version of the terms.
ContractReceipt¶
Distinguishes "Arbiter has sent a state notification" from "the participant has acknowledged the snapshot". Required for the issued / delivered / observed distinction described below.
ArbiterAttestation¶
The Arbiter's signature on a Contract Snapshot, with prev_snapshot_hash chaining each snapshot to the previous one. This forms the signed chain reputation is computed from.
ContractSnapshot¶
The full protocol-level audit record for one moment in a contract's life.
Snapshot signing¶
The Arbiter's signing procedure on each state transition:
1. Build a ContractSnapshot without the signature field
2. Canonical JSON serialisation
3. snapshot_hash = SHA256(serialised_bytes)
4. signature = Ed25519.sign(snapshot_hash, arbiter_private_key)
5. Populate the ArbiterAttestation
6. Attach the attestation to the snapshot
Any verifier given a snapshot:
1. Extract the Arbiter's public key from the snapshot's participants
2. Strip the signature
3. Recompute snapshot_hash
4. Check it matches attestation.snapshot_hash
5. Verify attestation.signature with the Arbiter's public key
Only if all five steps pass is the snapshot considered authentic.
Authentication¶
The Arbiter does not trust an operator identity self-reported in the payload. It trusts:
- the already-verified
mail.senderfrom the envelope - the
ParticipantSnapshot.addressregistered for the current contract
That is:
authenticated_sender = mail.sender.address
NOT
claimed_sender = payload.sender or payload.party_role
For every CONTRACT_* / PAY_* message the Arbiter checks at minimum:
mail.signatureis validmail.sendermaps to a participant on this contractmail.recipientis this Arbiter- the sender's public key matches the participant record
If any of these fails the message is rejected, an ERROR is returned, and no new snapshot is produced.
Authorisation¶
Authorisation answers can this authenticated party perform this action right now? The authorisation function takes the action, the authenticated sender address, and the current snapshot:
| Action | Allowed roles | Conditions |
|---|---|---|
CONTRACT_CREATE |
party_a or party_b |
contract does not yet exist |
CONTRACT_AMEND |
either party | status is draft |
CONTRACT_APPROVE |
either party | terms_hash matches the current terms |
CONTRACT_REJECT |
either party | based on the current revision |
CONTRACT_COMPLETE |
default party_b |
status is active |
CONTRACT_ACCEPT |
default party_a |
status is completing |
CONTRACT_REWORK |
default party_a |
status is completing |
CONTRACT_RATE |
only party_a |
status is settled or settling |
CONTRACT_CANCEL |
either party | state machine allows it |
CONTRACT_DISPUTE |
either party | state machine allows it |
PAY_COLLECT |
payee | matches the contract's payee role |
PAY_CONFIRM_RECEIPT |
payee | matches the payment's payee |
Beyond the role check, the request must also satisfy:
expected_status == current_statusrevision == current_terms.revisionterms_hash == current_terms.terms_hashsource_snapshot_hash == current_snapshot_hash
Otherwise the message is acting on stale state and is rejected.
Reachability — issued, delivered, observed¶
The protocol cannot guarantee a delivered network. It can guarantee three explicit levels of confidence:
- issued — the Arbiter has signed and emitted a state
- delivered — the host layer has routed it
- observed — the recipient has explicitly ACK'd the snapshot
CONTRACT_STATUS_ACK is the message that distinguishes observed from merely delivered:
sequenceDiagram
participant A as Party A
participant Arb as Arbiter
participant B as Party B
A->>Arb: CONTRACT_CREATE
Arb->>Arb: verify sender + create snapshot + sign
Arb-->>A: CONTRACT_STATUS(snapshot, attestation)
Arb-->>B: CONTRACT_STATUS(snapshot, attestation)
A->>Arb: CONTRACT_STATUS_ACK(snapshot_hash)
B->>Arb: CONTRACT_STATUS_ACK(snapshot_hash)
Trust Protocol guarantees apply at the observed layer, not the delivered layer.
Envelope and encryption policy¶
All CONTRACT_*, PAY_*, CONTRACT_STATUS, and CONTRACT_STATUS_ACK messages must be envelope-signed via Mail.seal().
The following messages should default to encryption (X25519 + AES-GCM), because they carry contract terms, ratings, or payment information that should not travel as plaintext through routers:
CONTRACT_CREATECONTRACT_AMENDCONTRACT_RATEPAY_COLLECTPAY_REQUEST
Threat coverage¶
| Threat | Defence |
|---|---|
| Replay of an old approve | Arbiter compares revision and terms_hash against current state |
Spoofed party_role |
Arbiter ignores payload identity; uses verified mail.sender |
| Mid-flight tampering | Any field change shifts snapshot_hash; signature verification fails |
| Lost state notification | The recipient ACK in CONTRACT_STATUS_ACK distinguishes delivered from observed |
Error semantics¶
Protocol errors must be kept separate from business-action semantics.
CONTRACT_REJECT is reserved for the business meaning "I refuse this contract in DRAFT". It must not be used to report protocol-level errors (mismatched source_snapshot_hash, mismatched terms_hash, mismatched expected_status, failed signature verification, authorisation failure). Recommended handling:
- Transport-layer failure → silently drop or log
- Semantic-layer failure → reuse the generic
ERRORmessage kind - If finer granularity is needed later, introduce
CONTRACT_ERROR
This separation prevents protocol noise from polluting state-machine semantics.
Summary¶
Trust Protocol closes the chain:
- authenticated message sender
- authorised contract action
- signed Contract Snapshot
- observed state notification
When those four hold, reputation can be derived from the snapshot chain offline, storage can be replaced, APIs can evolve — but the trust property of the protocol does not change.
Next: Reputation.