Skip to main content
Protocol support, not a custody feature. Nothing on this page is reachable through the API. No custody endpoint is mounted, no custody table exists, no provider is called and no hold, registration or decision is ever made by this code. GET /v1/platforms/me/capabilities still advertises escrow alone, and every settlement read still reports execution.mechanism: "escrow". What exists is the hashing, validation and rule logic a later custody mechanism will be built on, proven against the protocol’s own vectors.

What the consumer is

Custody protocol v1 is owned by the parent custody-protocol package in the KeyStone-OS/keystone repository: its normative specification, EIP-712 type strings, closed JSON schemas, constants and golden vectors. This API consumes one accepted revision of it and recomputes what that revision pins. It never defines a format of its own, and a vector is never edited to make this consumer pass; a disagreement is a defect to return to the parent. The runtime pin is ACCEPTED_PROTOCOL in keystone/settlement/custody/constants.py; the artifact tests fail if the fixture copy, the provenance file or this page cite a different commit or digest, and if any artifact byte differs from the manifest.

Where it lives

Function names are Python snake_case of the parent’s names (validateBundle is validate_bundle, applyFact is apply_fact). Inputs are custody models or closed raw wire values; outputs are lowercase hex strings, frozen result records or a CustodyProtocolError carrying the pinned code. No function reads a clock, a setting, a database or a key, and none performs I/O. Wire models keep the parent’s camelCase keys as attribute names, so a model dump is the exact object the protocol digests. The one keyword, the EVM artifact’s from, carries an alias; every dump goes through .wire(), which applies aliases and omits absent optional fields without inserting nulls.

What it enforces

  • Commitments. manifestHash = hashStruct(CustodyHeader) = bundleId under the KeystoneCustodyRegistry domain pinned by the cash group’s chain and Registry. Hashing goes through eth-account and eth-abi; the typehashes, struct hashes, domain separators and signing digests of both profiles’ vectors recompute, and the fixture’s public test keys reproduce every committed signature byte for byte through the same library.
  • Signatures. Exactly one encoding: 65 lowercase hex bytes, r and s in [1, n-1], low s, v in . The high-s twin and any out-of-range scalar are refused before the recovery library runs; an unrecoverable point is refused as SIGNATURE_INVALID with the safe identity and no library text. Hash-only types never get a signing digest.
  • Closed JSON. RFC 8785 over strings, booleans, null, lists and string-keyed objects. Numbers, Decimal, non-string keys, arbitrary objects and lone surrogates are refused; keys sort by UTF-16 code units, not code points. The API request digest custody-api-request-jcs-keccak256/v1 and the deployment, package, artifact and readiness digests all use it. The escrow sha256-canonical-json/v1 request digest is a different function and is not an RFC 8785 implementation.
  • Strict schemas. A JSON number, a coercible boolean or a Decimal never becomes a decimal-string field, and numeric zero never becomes the false-only trust label; width overflow, zero commitments, zero trade amounts, checksum casing, whitespace and Unicode normalization are refused, while a zero balance stays representable. A malformed child is a sanitized SCHEMA_INVALID naming paths only; the dependent refinements run only on validated children.
  • Admission order. validate_bundle refuses in the frozen order (profile, deployment digest, admitted keys, group domains, timing, topology, commitments, addressed identity); verify_recovery_package adds recipient, own-term inclusion and the source signature; verify_package_admission binds mandate and acknowledgement to the exact digest sent and the admitted payer key.
  • Evidence. Exactly five expected facts per header; monotonic versions per fact key; identical replay is a no-op, an equal version with a changed payload conflicts, a lower version is stale, nothing changes after SETTLE or ABORT; expiry equality is invalid; registration is exactly the three non-readiness facts, each current, strictly before cutoff.
  • Projections. A custodian view carries only its own private terms, the source view both and no account-wide books; the groups are the fixed layout with recomputed ids; completion.satisfied must equal the canonical predicate; events pages, exports and provenance are refused when their correlations do not hold rather than repaired.

Conformance executed here

Every family of the parent’s section 16 matrix for the Python API runs in tests/settlement/custody/ and tests/schemas/test_custody.py, each case through the entrypoint its check names; an unmapped check fails the run. The parent’s draft 2020-12 JSON Schemas are also loaded with an independent validator: every positive sample passes both validators, every negative is refused by the models, and the exact set of negatives only the code refuses (numeric upper bounds and the executable refinements) is pinned, so a refinement silently moving into or out of JSON Schema is noticed.

What is explicitly not claimed

  • No route, query or body is served; the route tables in constants.py describe the contract a later ticket mounts and are not registered with FastAPI. The custody API error codes are not emitted by this API and do not appear in the error codes guide.
  • No authorization storage: select_view and closeout_authorized evaluate grants handed to them; no grant is persisted or looked up.
  • No durable command, replay cache or revision counter: admit_command and admit_intake decide over a supplied stored record and resource state. The ticket that mounts the routes owns authentication, the storage lock, replay before revision under it, transactional acceptance and post-commit dispatch.
  • No Registry, Canton or provider effect: eligibility and registration are computations over supplied facts and an explicit time, never an observation that a fact or a native effect occurred.
  • No signer and no spending key: the fixture keys are public test material under tests/fixtures/custody/ and are never copied into runtime modules or configuration.
  • No Solidity parity, no live Canton or provider qualification and no native transaction atomicity: those belong to their own tickets.