1. What is being cosigned, and by whom
Org A runs a Helm. It produces a stream of head-commits: each one a small signed object naming a stream, a seq, and a head_hash over its own content, per SPEC.md §HEAD-1.1. Org B is a counterparty org A already has a relationship with, a partner, a customer, an auditor. Org A wants a specific head-commit to carry evidence that org B has seen it and agrees it is the head org A claims it is, at that point in time.
That evidence is a ocg-head-bilateral-cosign@1 entry in the head's anchor_bindings[] array, one more member of the same backing-ladder list §HEAD-1.3 already defines alongside ocg-head-file@1 and ocg-head-tlog@1. Nothing about how org A's Helm stores or serves its own heads changes. The binding is additive, computed after the head's own hash, and never enters the hash preimage.
Two Helms are involved, and nothing else is. There is no third Helm, no shared server, no lookup a verifier performs at check time beyond the JSON it already has in hand.
2. Org A's Helm produces the note
Org A's Helm builds the C2SP signed-note header (origin line plus the head's own head_hash) with buildNoteText(), then hands that text to org B out of band. The transport is unspecified by design: email, an API call between the two Helms, a file dropped in a shared folder. What matters is that the bytes reaching org B are exactly this header, unmodified.
import { buildNoteText } from './_bilateral-cosign.mjs';
import { headHash } from './_head.mjs';
const anchoredHash = await headHash(orgAHead); // "sha256:..."; org A's own head
const noteText = buildNoteText('orgA<->orgB/settlement-stream', anchoredHash);
// noteText is two lines: the origin, then the head_hash. Send exactly this to org B.
Org A does not sign anything yet. The note is a claim about a specific hash, nothing more, until a counterparty countersigns it.
3. Org B's Helm cosigns, independently
Org B receives the note text, decides for itself whether to cosign it (this is a business decision made entirely on org B's side, off-spec), and if it agrees, signs a cosignature line with its own key using signCosignLine(). Org B never touches org A's Helm, database, or state to do this.
import { signCosignLine } from './_bilateral-cosign.mjs';
const cosignLine = await signCosignLine(noteText, {
didKey: 'did:key:z6Mk...orgB',
privateKey: orgBPrivateKey, // org B's own Ed25519 key, never shared
timestampMs: 1755000000000, // caller-supplied, never Date.now() inside the kernel
});
// cosignLine: "— did:key:z6Mk...orgB <base64 cosignature/v1 blob>"
// Sent back to org A, out of band, the same way the note arrived.
If org A wants an n-of-n binding across several counterparties, each one repeats this step independently against the same noteText. None of them need to see the others' lines to produce their own.
4. Org A assembles the binding
Once org A has the cosignature line(s) back, its Helm assembles the full anchor_bindings[] entry with buildBilateralCosignBinding() and attaches it to the head. This is the only step that touches org A's own head object, and it recomputes head_hash from the head itself rather than trusting a caller-supplied value.
import { buildBilateralCosignBinding } from './_bilateral-cosign.mjs';
const binding = await buildBilateralCosignBinding(
orgAHead,
[{ didKey: 'did:key:z6Mk...orgB', privateKey: null }], // keys only, on the verify side
{ logOrigin: 'orgA<->orgB/settlement-stream', timestampMs: 1755000000000 }
);
orgAHead.anchor_bindings ??= [];
orgAHead.anchor_bindings.push(binding);
type, the anchored_hash it covers, the proof text (the note plus every cosignature line collected), and cosigner_keys: the specific counterparty keys org A agreed to be cosigned by for this relationship. There is no shared registry mapping org identity to key; cosigner_keys travels with the binding precisely so a verifier never needs one.5. Anyone verifies, offline
A third party, an auditor, org B checking its own cosignature landed correctly, or org A itself before publishing, calls verifyBilateralCosignBinding() with only the head and its binding. No network call, no live counterparty, no service anywhere in the loop.
import { verifyBilateralCosignBinding } from './_bilateral-cosign.mjs';
const result = await verifyBilateralCosignBinding(binding, orgAHead);
// { valid: true, anchored_hash_match: true, origin_match: true,
// valid_witness_count: 1, threshold: 1, cosignatures: [...], errors: [] }
Three things this check actually proves: the note's anchored_hash matches the head's own recomputed head_hash (so the note cannot be reattached to a mutated head), every claimed cosignature line verifies against the specific key named in cosigner_keys (a line signed by an unlisted key is never counted, even if it is a valid signature from somewhere), and the count of valid cosignatures meets the stated threshold. A verifier that has never heard of ocg-head-bilateral-cosign@1 simply skips the binding ({ skipped: true }) without failing the rest of the head chain.
6. What this evidence is, and is not
A cosigned head-commit is evidence that org B, using its own key, agreed a specific head_hash existed at the time it signed. That is what valid: true means, and it is all it means. Per SPEC.md's own equivocation-corollary text, a cosignature attests: it does not settle a dispute between org A and org B, and it does not pick a winner if org A later publishes a conflicting head at the same stream and sequence.
If that happens, detectEquivocation() in _head.mjs flags the conflict, unmodified by anything in this profile, and if the SAME counterparty key cosigned both conflicting heads, that is portable evidence a verifier can hand to a third party. Deciding which head is the real one, or what happens next, is a question this profile deliberately refuses to answer; that decision belongs to org A and org B's own business process, never to a piece of software either of them runs.
7. What never appears in this picture
- No AINumbers-operated Helm anywhere in the signing path. Every role, signer, cosigner, verifier, is filled by org A's own Helm, org B's own Helm, or a third party's own tooling.
- No registry.
cosigner_keystravels inside the binding. How org A learned org B'sdid:keyin the first place is the same out-of-band question any DID trust relationship already answers, and this profile makes no claim about it. - No ordering service. Nothing here sequences, arbitrates, or picks a winner between conflicting heads.
If nobody touches either Helm again, the binding already produced keeps verifying exactly the same way, forever, against the same offline function. That is the whole point of shipping a format and a reference verifier instead of a service.