Chio/Docs
LOGIN · JOIN

PlatformJoint Authorization

Swarm

Bilateral Co-Sign

Both organization kernels evaluate and sign the same canonical body for a joint action. The signed receipt records that action.

Two predicate types, one strict

Every strict emitter writes chio.bilateral-cosign-invocation.v1 into predicateType, and every strict verifier compares against that same constant before it reads a field. A second, deliberately distinct id, chio.bilateral-signature-slice.v1, names a compatibility profile that omits tool_args_hash and carries the receipt’s canonical JSON inline. Both live in chio-federation (::bilateral, ::bilateral_dsse, ::bilateral_verifier), and FROST quorum authorization ships beside them in chio-federation::frost and chio-federation-authority. A payload whose predicateType is neither is refused as predicate.type_unrecognised.

Bilateral co-signing and BFT

Tendermint-style BFT is one way to reach a joint decision. This design uses bilateral co-signing trees instead, with FROST quorum for action classes that require it. A third party can replay a recorded case from its receipts. Routine actions have no membership roster or quorum threshold.

What a co-signed invocation commits to

Six of the predicate’s members are declared Option in the Rust type and three of those six are still refusals when absent, because the strict validator asks for them by name. Reading the type alone will mislead you here; the validator is the contract.

  • Required by the strict validator. tool_args_hash, the SHA-256 over the canonical tool arguments, which the compatibility profile deliberately omits; capability_lease_ref, naming the lease, its issuing kernel, an absolute expiry, and optionally a scope digest the registry record must match; and policy_evaluation_summary, two independent verdicts plus a joint disposition, where the verifier cross-checks the two verdict strings directly rather than trusting the summary. Each absence is predicate.schema_invalid.
  • Required conditionally. governance_receipt_ref, for an action class the local ladder manifest declares receipt-backed, refused as governance.receipt_required_missing; and consistency_anchor, non-empty whenever the consistency model is totally-ordered or quorum-required.
  • Genuinely optional. treaty_binding_ref, the widest member: treaty id, scope hash, ladder-intersection hash, admission report, continuation, lineage bundle, request and outcome hashes, both receipt hashes, and the lease, governance and signer lists. The n-of-m path makes it mandatory.
  • Forbidden. schema and receipt_canonical_json. Both belong to the compatibility profile, and a strict predicate carrying either is refused outright rather than tolerated.

Two shape rules ride alongside. Each tool_server_*.alg must be the literal ed25519 and nothing else, and each passport_key_fingerprint must be 64 lowercase hex characters. A wrong alg surfaces as a signature error rather than a schema one, which is worth knowing before you debug it.


The envelope, and the Statement inside it

A co-signed invocation travels as a standard DSSE v1 envelope. The envelope holds three members and no more: the fixed payload type application/vnd.in-toto+json, a base64 payload, and the signatures. The Statement is not a sibling of signatures; it is the decoded payload, and a verifier that reads a field before decoding is reading unsigned bytes.

crates/trust/chio-federation/src/bilateral_dsse/types.rs308-316rust
/// DSSE v1 envelope carrying the bilateral signature-slice artifact.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct DsseEnvelope {
    pub payload_type: String,
    /// Base64 (standard alphabet) of canonical-JSON of [`DsseStatement`].
    pub payload: String,
    pub signatures: Vec<DsseSignature>,
}

Two checks run before the payload is decoded at all: the payload type must be the literal above, and the envelope must carry exactly two signatures. The count is fixed in code rather than negotiated, and both entry points into the verifier repeat it, so an envelope with one signature and an envelope with three are refused the same way, with dsse.malformed. Both kernels then sign the same DSSE PAE bytes ("DSSEv1" SP LEN(type) SP type SP LEN(body) SP body), and the decoded payload must re-canonicalise to the byte string that was signed, or the envelope fails as statement.malformed.

crates/trust/chio-federation/src/bilateral_dsse/types.rs274-283rust
pub struct DsseStatement {
    /// `_type` per in-toto v1: `"https://in-toto.io/Statement/v1"`.
    #[serde(rename = "_type")]
    pub statement_type: String,
    pub subject: Vec<StatementSubject>,
    /// `predicateType` distinguishing chio bilateral envelopes from other
    /// in-toto attestations.
    pub predicate_type: String,
    pub predicate: BilateralPredicate,
}

subject[0].name carries the chio-receipt: prefix and the receipt id; subject[0].digest.sha256 is the SHA-256 of the canonical JSON of the receipt body, not of the signed wrapper, so a verifier that resolves the receipt from any store exposing the body re-derives the same digest. Decoded, the payload of a strict envelope reads:

decoded DSSE payloadjson
{
  "_type": "https://in-toto.io/Statement/v1",
  "subject": [
    {
      "name": "chio-receipt:555c44ff5f7df4330aef70a65ab3fa6816cc9a1394f469f23f7e8ecbf6c6506a",
      "digest": {
        "sha256": "5b41362bc82b7f3d56edc5a306db22105707d01ff4819e26faef9724a2d406c9"
      }
    }
  ],
  "predicateType": "chio.bilateral-cosign-invocation.v1",
  "predicate": {
    "invocation_id": "inv_8f3a2c1d-7e4b-4a92-b1d5-2f9a8c6e1b04",
    "tool_server_a": {
      "kernel_id": "did:chio:blueteam-soc",
      "passport_key_fingerprint": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
      "alg": "ed25519"
    },
    "tool_server_b": {
      "kernel_id": "did:chio:treasury-cfo",
      "passport_key_fingerprint": "4d5a92e0b1c8f7a392d4e6f8b2a1c5d7e9f0a3b4c5d6e7f8a9b0c1d2e3f4a5b6",
      "alg": "ed25519"
    },
    "tool_name": "credentials.revoke_passport",
    "tool_args_hash": {
      "alg": "sha256",
      "value": "7e2b1f3a4d8c9e0b2f1d5a8c6e3b9f7a2d4c1e0b8f3a5d7c9e1b2f4a6d8c0e2b"
    },
    "capability_lease_ref": {
      "lease_id": "lease_4d2a1b8e",
      "issuer": "did:chio:blueteam-soc",
      "expires_at_unix_ms": 1746717600000,
      "scope_digest": {
        "alg": "sha256",
        "value": "b2c58e91a0d4c7f3e6a9b1d8c2f5a0e3d7b4c1f8a5e2d9c6b3a0f7e4d1c8b5a2"
      }
    },
    "policy_evaluation_summary": {
      "server_a_verdict": {
        "verdict": "allow",
        "policy_id": "blueteam.policy.revoke",
        "policy_version": "2.4.1",
        "rationale_code": "passport_compromise_confirmed"
      },
      "server_b_verdict": {
        "verdict": "allow",
        "policy_id": "treasury.policy.revoke",
        "policy_version": "1.7.0",
        "rationale_code": "settlement_freeze_aligned"
      },
      "joint_disposition": "allow"
    },
    "governance_receipt_ref": {
      "receipt_id": "a00c38719c742afb7952efb16bb68b9a3385646c3a7833eba3f9487f02dddbb7",
      "kernel_id": "did:chio:blueteam-soc",
      "digest": {
        "alg": "sha256",
        "value": "c104a8b2d5e9f1c3a7b0e4d8f2a5c9e1b4d7f0a3c6e9b2d5f8a1c4e7b0d3f6a9"
      }
    },
    "consistency_model": "totally-ordered",
    "consistency_anchor": "chio-anchor",
    "cross_org_visibility": "federated",
    "co_sign": "bilateral_required",
    "timestamp_unix_ms": 1746710400000
  }
}

Four of the predicate’s enumerations are closed, and a value outside them is a schema refusal rather than a verification failure. consistency_model accepts crdt-commutative, totally-ordered, single-kernel, and quorum-required, and the last two require a non-empty consistency_anchor. cross_org_visibility accepts private, treaty_only, federated, and public. Omitting the optional consistency_anchor under totally-ordered is the commonest way to trip this.

The keyid of each signature is the SHA-256 of that kernel’s passport public key, and it must equal the passport_key_fingerprint declared inside the predicate. That binding is what distinguishes a bilateral-cosign-invocation envelope from two independent signers that happen to sign the same Statement: the predicate body itself names which two keys must appear in the envelope, and sig is base64 of the Ed25519 signature over the PAE bytes, not over the Statement.

More than two parties

The shipped primitive is two-party and the shipped tests are two-party. CoSigningBody, the structure both kernels sign, is {schema, receiptCanonicalJson, orgAKernelId, orgBKernelId} with deny_unknown_fields: there is no third-party slot and no parent-hash field. All nine tests in chio-federation/tests/bilateral_signing.rs drive one pair. DualSignedReceipt, ExpectedBilateralPeers, and InProcessCoSigner all live in chio-federation::bilateral, and all three are pairwise.

Composition over more than two parties is a question the spec answers by stacking envelopes, not by widening one. Each step of a workflow may produce its own bilateral-cosign-invocation Statement; the workflow receipt’s body lists per-step records, each carrying the SHA-256 of that step’s Statement payload, and a verifier walks the plan bottom-up, checking each joint commit at the leaves before the roll-up signature at the root. The composite assertion “every cross-org step jointly committed” holds exactly when every step-level predicate verifies; the workflow receipt’s own signature certifies only its roll-up. The subject of that roll-up is the canonical-JSON SHA-256 of WorkflowReceiptBody (crates/platform/chio-workflow/src/receipt.rs), and its own predicate type is the part the spec leaves to a sibling document rather than fixing here.

FROST quorum opt-in

FROST-aggregated Ed25519 over a canonical body is the opt-in for action classes declared quorum-required in the ladder manifest. The opt-in is per-class precisely so the operational overhead (signing-key custody and the distributed key-generation ceremony) is paid only where needed: cross-issuer credential revocation, multi-party settlement, treaty-wide sanctions. FROST Quorum is the counterpart page. It owns that ceremony and the operator objects a quorum authorization is bound to: roster, epoch checkpoint, authorization slot, and rotation. This page owns the co-sign invocation.

The action registry is closed. Seven classes carry a FROST authorization domain: settle.commitment, clearing.round_finalize, channel.close, pouncer.revoke_credential, and credentials.passport_revoke at 2-of-3; governance.case_enforce_sanction and governance.roster_rotate at 3-of-5. All seven are treaty-scoped. Each registration canonicalizes its own ladder entry, and the SHA-256 of that entry is the ladderContractDigest every authorization commits to, so an authorization issued against a drifted ladder row fails verification.

The implementation splits across two crates. chio-federation::frost holds the action registry, the roster and epoch-checkpoint artifacts, the authorization body and envelope types, the one-shot authorization slot lifecycle, epoch rotation, and verification. chio-federation-authority holds the distributed key-generation ceremony, the coordinator (signing-package construction, per-share validation, aggregation), and the signer. frost-ed25519 is a non-optional dependency of chio-federation; the suite id on the wire is FROST-ED25519-SHA512-v1.

The DSSE envelope carries exactly the two passport signatures, and the verification contract requires that every signature in the envelope verify; DSSE threshold rejection falls back to the ladder, not to DSSE’s permissive default. The aggregate does not ride inside the envelope. It travels as its own signed artifact, chio.frost.authorization.v1, which the strict verifier takes as a separate input. A predicate declaring co_sign: "n_of_m" is rejected unless a verified FROST authorization accompanies it, the predicate declares consistency_model: "quorum-required" anchored by consistency_anchor: "frost-quorum", and the authorization’s action class, scope, and resource match the treaty binding and the invocation id:

frost-authorization.jsonjson
{
  "schema": "chio.frost.authorization.v1",
  "body": {
    "schema": "chio.frost.authorization-body.v1",
    "authorizationId": "6b1d0f47a29c8e35d704b3f1a68c25de90f4b7c2e51a8d63049fb2c7e18a3d50",
    "domain": "chio.frost.credentials-passport-revoke.v1",
    "ladderActionClass": "credentials.passport_revoke",
    "ladderContractDigest": "d3a71f0c9b48e625af1c07d3b924e85f60ca3d7192bf4e08c561a7d2934bf0e6",
    "quorumN": 2,
    "quorumM": 3,
    "quorumScope": "treaty",
    "scopeId": "treaty.atlantic.v1",
    "resourceId": "inv_8f3a2c1d-7e4b-4a92-b1d5-2f9a8c6e1b04",
    "resourceVersion": 7,
    "resourceFence": 11,
    "actionDigest": "0f92c4b7e1a35d86027cf4b1938ae56d20b7c9f3418e6a05d739c2b84f10e6a7",
    "rosterDigest": "8c5b21e07af943d6b1032c8ef754a9d0361be27c4f80a95d2e7361cb4a08f2d9",
    "keyEpoch": 5,
    "issuedAt": 1752000000,
    "expiresAt": 1752000300
  },
  "suiteId": "FROST-ED25519-SHA512-v1",
  "groupSignature": "<128 lowercase hex characters>"
}

The envelope validates before anything reads it: the schema must be the literal above, the suite id must be FROST-ED25519-SHA512-v1, and groupSignature must be exactly 128 lowercase hex characters. Field names are camelCase on the wire and deny_unknown_fields is set, so a snake_case key or an extra member is a parse failure rather than a silently ignored field.

The one-shot property is enforced by the authorization slot, not by convention. A slot id derives from the domain, scope id, resource id, resource version, and resource fence; the slot binds, then either completes or burns, and neither terminal state can be reused. Burn is the abort path: a burned checkpoint carries no aggregate signature and no authorization blob, and both terminal states are the same slot-version-2 successor of one bound checkpoint. Roster rotation advances the key epoch and refuses to advance while any old-epoch session is live; the burned-session set is committed by its burn root inside the FROST-signed rotation authorization. At most one quorum-aggregated signature therefore succeeds per execution slot, so there is no partition-divergent co-sign window for these classes. The Partition-contingency mode cannot apply to quorum-required classes, since a FROST quorum cannot be assembled under partition by definition.

Verifying offline

A third-party auditor with no live access to either kernel can verify a workflow receipt months after the fact. The walk is deterministic; the only inputs are the receipt corpus, the pinned passport keys, and the chio-anchor epoch the action committed at.

  1. 1. Resolve the workflow receipt root. Open the workflow receipt and read the per-step records. Each record carries the SHA-256 of the corresponding step’s bilateral-cosign-invocation Statement payload.
  2. 2. Walk to each step receipt. For every step, fetch the DSSE envelope whose payload SHA-256 matches the recorded digest. Reject the workflow receipt if any step envelope is missing.
  3. 3. Validate Statement and predicate schema. Decode payload base64, parse as in-toto Statement v1, validate against the schema. Confirm subject[0].digest.sha256 equals the canonical-JSON SHA-256 of the underlying chio receipt body resolved from the audit store. Validate the predicate body: every required member present, every closed enumeration in range.
  4. 4. Confirm pinned passport keys. Look up both kernel ids in the verifier’s peer set. Check that each declared passport_key_fingerprint equals the SHA-256 of the pinned passport public key, and that no passport is revoked at the predicate’s pinned epoch (consult the chio-revocation-oracle epoch root).
  5. 5. Verify both signatures over PAE. Compute pae = "DSSEv1" SP LEN(payloadType) SP payloadType SP LEN(payload) SP payload. Verify exactly one signature whose keyid equals server_a’s fingerprint under A’s passport key, and exactly one whose keyid equals server_b’s fingerprint under B’s passport key. Reject on missing or out-of-order keys.
  6. 6. Verify policy agreement, capability, anchor. Confirm server_a_verdict.verdict == server_b_verdict.verdict and that joint_disposition agrees. Resolve capability_lease_ref, confirm issuer match and non-expiry. For totally-ordered steps, reconcile the consistency anchor (parent-hash chain or chio-anchor epoch) with the auditor’s view; for quorum-required steps, resolve the accompanying chio.frost.authorization.v1 artifact, verify its group signature against the roster pinned at the declared key epoch, and confirm its action class, scope, and resource bind to this step.

Failure at any step surfaces a stable code, and the set is closed: VerifierError::code is the whole vocabulary a caller can receive, sixteen strings with no catch-all.

crates/trust/chio-federation/src/bilateral_verifier/error.rs87-106rust
pub fn code(&self) -> &'static str {
    match self {
        Self::DsseMalformed(_) => "dsse.malformed",
        Self::StatementMalformed(_) => "statement.malformed",
        Self::StatementSchemaInvalid(_) => "statement.schema_invalid",
        Self::PredicateTypeUnrecognised(_) => "predicate.type_unrecognised",
        Self::PredicateSchemaInvalid(_) => "predicate.schema_invalid",
        Self::SubjectDigestMismatch(_) => "subject.digest_mismatch",
        Self::PeerUnpinnedOrKeyidMismatch(_) => "peer.unpinned_or_keyid_mismatch",
        Self::PeerRevokedAtEpoch(_) => "peer.revoked_at_epoch",
        Self::SignatureServerAInvalid(_) => "signature.server_a_invalid",
        Self::SignatureServerBInvalid(_) => "signature.server_b_invalid",
        Self::PolicyVerdictDisagreement(_) => "policy.verdict_disagreement",
        Self::CapabilityLeaseExpiredOrUnknown(_) => "capability.lease_expired_or_unknown",
        Self::GovernanceReceiptRequiredMissing(_) => "governance.receipt_required_missing",
        Self::LadderManifestMissing(_) => "ladder.manifest_missing",
        Self::LadderManifestStale(_) => "ladder.manifest_stale",
        Self::UnknownActionClass { .. } => "governance.unknown_action_class",
    }
}

Two of those cost a reader something worth knowing. governance.unknown_action_class exists because falling back to a routine class for an unregistered tool name would be fail-open for every receipt-backed class that was misspelled, so an unregistered tool_name refuses rather than relaxes. And predicate.schema_invalid is where the closed enumerations land, so a bad consistency_model and a missing anchor arrive under the same code with different context. The composite assertion “every cross-org step jointly committed” is true exactly when every step-level predicate verifies; the workflow receipt signature itself does not certify that property, only its own roll-up.

vs. Sigstore + in-toto runtime extensions

The structural slice is durable, not temporal. Bilateral co-signed intent (both parties independently evaluated and signed the same canonical body), per-action attenuated capability scoping, workflow receipts as joint multi-party plans, and evidence-referential governance over a lineage DAG are different questions than transparency-log-anchored single-party signatures.

Co-signing on top of Rekor is possible (DSSE multi-sig plus a custom predicate), but the predicate, the verifier, the capability binding, and the dispute model are not what Sigstore ships. The two answer different questions: a transparency log says a signature was published at a time, and the bilateral predicate says two named kernels evaluated one canonical body and agreed. Neither substitutes for the other, and an envelope written to a log is still refused by this verifier if its predicate does not hold.