Off-chain signed messages

Draft protocolSource at 169cbd2 ↗Source SHA-256 7439710…c62

This document is not normative. It describes the message format and the service obligations that make links, reactions, review discussion, profile data, external-address verifications, and availability-layer announcements attributable to on-chain Makechain identities, without any of them becoming a command. It supports specification chapter 8 (section 8.7, which fixes the part the protocol itself fixes), chapter 7 (key classes, generations, and the delegation epoch), and chapter 17 with the availability profile (which owns the layers these announcements advertise). Nothing here affects block validity: a validator that has never heard of this service agrees on every block.

1. Why these messages are outside consensus

The command catalog holds only what benefits from being ordered by every validator forever. A link, a reaction, a review comment, a profile field, or an announcement of where a project's objects can be fetched does not need a total order and does not change what the chain attests. What such a message does need is an unforgeable connection to an account that exists on chain, so that a reader can tell who said it and under which key. That connection is what this profile supplies.

The one consensus-relevant step of a review is the reference move that accepts it, and that step is an ordinary PUSH.

2. Envelope

A message is an mkit attestation: a DSSE envelope over an in-toto v1 statement, signed by a delegated key from the signing account's keychain.

Part Content
Subjects the digests the message is about: a commitment, a prior message id
Predicate type a Makechain-registered URI, one per message kind: link, reaction, review comment, profile, external-address verification, availability announcement (section 11), deletion
Predicate chain_id, owner_account, key_generation, issued_delegation_epoch, anchor_height, timestamp_ms, and the kind's own payload
Message id the BLAKE3 digest of the envelope

anchor_height names the finalized height whose keychain state the signer claims. The service checks that the key was active at that height.

A submitted anchor_height must lie within the service's declared checkpoint lag of the head, the same bound section 9 item 6 puts on acceptance, and a message anchored further back is rejected rather than researched. That bound is what makes the check performable: specification R-15.6 offers no retroactive proof of a past-height value, only a proof captured while that height was the head, so a service that accepted an arbitrary old anchor would have nothing to verify it against. Inside the bound, a current-state proof at the service's checkpoint suffices, because no authority transition can have been finalized outside the declared lag without the checkpoint showing it.

mkit already ships this format with Ed25519, P256, and secp256k1 signers, so a code store and the social layer share one attestation pipeline. The mkit verifier yields cryptographic results only. The authorization rules below are what the Makechain profile adds on top of it.

3. Signer rules

The envelope is canonical.

  1. It carries exactly the required signers, in a fixed order.
  2. Each key id is bound to the signed owner_account, key_generation, and issued_delegation_epoch.
  3. It carries no duplicate signature and no extra signature. A DSSE signature array is not covered by the payload signature, so an extra copy would otherwise mint a second message id and could resurrect a deleted message.
  4. The verifying key must have held the permitted capability at anchor_height. An unrelated valid countersignature never suffices.
  5. A WebAuthn signer uses the signer wrapper of specification chapter 7, with its pinned origin policy, and never a bare P256 verify.

4. Subject profile

Subjects are only real artifacts: commitments and prior message ids. A project identifier or an account identifier lives in the predicate instead, because mkit's commit subject profile requires genuine digests of the same bytes.

5. Ingress bounds

A service enforces a byte cap on an incoming envelope before decoding it, and bounds on signature count, subject count, and nesting depth. It rejects duplicate fields.

6. Scope of a project-scoped key

A project-scoped key may sign only project-scoped message kinds whose predicate binds its exact project id. It cannot sign account profiles and cannot sign account-wide external-address verifications.

The signing account must hold a current writing role for that project both at the declared anchor height and at the moment the service accepts the message. Both readings are taken from the service's own checkpoint, which the section 2 bound on anchor_height keeps within the declared lag, so neither needs a past-height proof that R-15.6 does not offer. A deletion is additionally bound to the same project as the message it targets.

A service verifies key scope, time validity, and delegation epoch against the authenticated keychain and project state. Merely including a project id inside an unrelated message kind does not authorize that message.

7. The SOCIAL key class

The SOCIAL capability class nests below WRITE and authorizes no command. A key of that class exists only to sign off-chain messages, so a leaked social key cannot spend credits and cannot move a ref. Dedicated social keys are preferred over reuse of a spending key.

8. Ordering, deduplication, and deletion

Messages are ordered and deduplicated by (timestamp_ms, digest) with a bounded clock skew.

Deletion is a second signed message naming the target digest. Cooperating services honor it. Consensus never enforces it.

9. Revocation by receipt

Revocation is forward-only, and only as a service guarantee.

  1. A service records an acceptance receipt when it first accepts a message.
  2. It keeps messages whose receipt predates the revocation of the signing key.
  3. It rejects a new submission under a revoked, expired, or delegation-epoch-invalidated generation, regardless of the timestamp the submission claims, because a timestamp proves nothing about creation time.
  4. Receipts are service attestations under a declared service trust assumption, not consensus facts. Cross-service acceptance of old messages requires trusting those receipts, or a later on-chain batch anchor.
  5. Bulk revocation, which advances the account's delegation epoch, is the epoch-change event for this purpose. Receipts predating it remain valid, and acceptance after it requires a key issued under the current epoch.
  6. Acceptance uses an independently maintained finalized checkpoint with a declared maximum lag, and fails closed beyond that bound. It never treats an old anchor as proof that a key is still authorized.

10. External-address verification

An external-address verification is a claim signed by both the account and the external address.

11. Availability announcements

There is no single availability layer. Any team may operate a layer for its projects, a project may have several, and each layer discloses content fully, to selected parties, or not at all, under its own retrieval policy and authentication.

A project's layers are announced off chain, by messages of this profile signed with the project's keys. The chain records no locator. An on-chain locator is a possible later release. The obligations a layer takes on once it serves a project are in the availability profile.

12. Relationship to chain facts

An off-chain message, or an mkit attestation attached separately to a commit, is not included in that commit's content commitment. It gains no chain inclusion and no ordering guarantee merely by naming a commitment, a project, or a prior message.

A later published commit can bind such content under the native scheme. Doing so does not retroactively prove when the content was created, and it does not replace this service's acceptance and revocation checks.

Content that requires chain-backed publication and order is represented by a commit and published through PUSH. Makechain adds no separate attestation command and no content-proof subsystem; mkit handles the relationship between a commit and its files.