| Internet-Draft | MUSUBI Settlement | October 2026 |
| Oga | Expires 20 April 2027 | [Page] |
MUSUBI settlement is a deterministic function that takes a contract signed by two parties, a set of signed records of what an agent did under that contract, and a view of Bitcoin block headers, and computes whether the recorded acts stayed within the grant the contract gives. Anyone holding the same bytes can recompute the result without trusting the party that produced it. This document specifies one well defined part of that function, settle core v0, precisely enough that a second implementation can be written from the text and the published test vectors alone. Settlement is not runtime enforcement and is not a judgement of liability.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 20 April 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
A principal and a contractor sign a contract. The contract carries a grant: the actions the contractor is authorized to take, the actions it is prohibited from taking, the actions that need a separate approval from the principal, the parties to whom work may be delegated, an expiry height and a revocation policy. While the contractor (or a witness named in the grant) works, it signs records of what it did, and each record is committed to the Bitcoin block chain [BITCOIN] through an anchor. Later, anyone who holds the contract, the records and a view of the relevant block headers can settle: walk the records in the order the block chain gives them, compare each act with the grant, and produce a verdict, a status and a bond outcome.¶
The record schema names carry the prefix "a2a" because the format was designed for agents that interact over the Agent2Agent (A2A) protocol [A2A]. Settlement reads no A2A message and does not depend on A2A.¶
Until this document, the MUSUBI settle function was defined only by Python code (settle_v1.py to settle_v1_16.py, each layer importing the ones below it) [MUSUBI-REF]. A function that only one program can compute is a tool, not a protocol. This document states one well defined part of it, settle core v0, in plain language. A stranger should be able to write a second implementation from this text and the vectors alone, without reading the reference.¶
The text was written from the reference code, not from intent. Writing it against settle_v1_10.py found places where the code accepted input a careful second implementation would read differently, and one place where a record nobody signed could change the verdict (items N1 to N11 of [MUSUBI-NOTES]). settle_v1_16.py was written to close them, and this document describes it. Section 14 lists what changed. Where the reference still does something surprising, this document says so and marks it WRITTEN BEHAVIOUR. Written behaviour is still normative for core v0: a second implementation that differs on it will disagree with the vectors.¶
This document is an adaptation of [MUSUBI-SETTLE-CORE] into the Internet-Draft format. Where the two differ in meaning, that is an error in this document.¶
Settlement is not runtime enforcement. It never stops an agent from acting; it reads records after the fact and says whether the recorded acts were within the grant.¶
Settlement is not a judgement of liability or fault, and it does not make the contract a legal contract. The verdict is a function anyone can recompute from the same bytes. Section 17.5 lists what the verdict does not establish.¶
Settle core v0 is exactly what the reference computes when the contract carries none of the following:¶
requirements.spine (the spine gate, settle v1.8),¶
requirements.convergence (drawn measurers, settle v1.9),¶
grant.approval_policy.approvers (pinned approvers, settle v1.10),¶
grant.limits (which forces requirements.admission),¶
requirements.admission (settle v1.11 to v1.14 read it; the admissions input is passed through unchanged).¶
It is computed in binding mode "strict" over exactly one header view.¶
Out of scope, and not defined here:¶
If a contract carries any of the keys above, this document does not define the result. An implementation MUST NOT claim core v0 conformance for such an input.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
Most rules in this document are stated in the indicative ("is", "gives", "adds"). They are normative all the same: they determine the core fields (Section 10.1) that a second implementation MUST reproduce exactly.¶
a2a‑contract‑v0, signed by a principal and a contractor, that carries the grant.¶
role and holding the Ed25519 [RFC8032] keys pinned in the contract.¶
grant.witnesses that may sign execution records.¶
a2a‑execution‑v0), a revocation (a2a‑revocation‑v0) or a revocation acknowledgement (a2a‑revocation‑ack‑v0).¶
nenrin_ref.¶
true and false are never integers.¶
The following notation is used for byte strings. A string in double quotes, such as "a2a‑contract‑v0\n", denotes the ASCII bytes between the quotes, where \n stands for one line feed (0x0A). When A and B are byte strings, A + B is their concatenation. sha256(x) is SHA-256 [FIPS180-4] of the bytes x. Byte ranges such as "bytes 4 to 36" are zero-based and exclude the upper bound, so they name the 32 bytes at offsets 4 through 35. "Lowercase hex" means the characters 0 to 9 and a to f, two per byte.¶
contract: one JSON object (schema a2a‑contract‑v0).¶
events: a list of JSON values. Each is meant to be a record: an execution (a2a‑execution‑v0), a revocation (a2a‑revocation‑v0) or a revocation acknowledgement (a2a‑revocation‑ack‑v0). Anything else is ignored. Order carries no meaning.¶
view: one JSON object {"headers": [{"height": H, "hex": "<160 lowercase hex>"}, ...]}, raw 80 byte Bitcoin block headers. Other keys are ignored.¶
nenrin_records: optional list of JSON values (walk records cited by executions). Absent and empty mean the same thing. A value that is not a list is treated as absent.¶
parent: optional, the parent contract, needed only when contract.parent_contract is present (Section 5.3).¶
If contract is not an object, the settlement is underspecified with the single reason contract_not_verified. If events is not a list, it is underspecified with input_not_settleable. Nothing else is computed in either case.¶
Keys are not a separate input. The principal's and contractor's Ed25519 public keys are parties[i].public_key_ed25519_b64 inside the contract, and witness keys are grant.witnesses[i].public_key_ed25519_b64. key_url is never fetched. Checking that the key served at key_url equals the pinned key is outside settle core.¶
Inputs are parsed as JSON [RFC8259] with one extra rule: an object with a duplicate key is an error and no settlement is produced. Text nested deeper than 512 levels of [ or { (counted outside strings) is also an error.¶
Numbers MUST be integers within plus or minus (2^53 - 1). The contract is checked for this in Section 5.1, records in Section 7.1, the view in Section 6.1 and walks in Section 12. A JSON value with a non-integer number is not "strict" in the sense of Section 7.1.¶
A JSON value is an "integer" in this document only if it is written as an integer token: no fraction, no exponent. true and false are never integers.¶
These rules are close to, but not the same as, I-JSON [RFC7493].¶
Compute contract_sha256 (Section 4.3) and bind records by it (Section 7.2). Triage every bound record (Section 7.1): records not strictly signed by a party are rejected or set aside here, and no later step sees them. Check the contract's terms (Section 5) and verify the header view (Section 6). Every problem found so far is collected; none of them stops the other checks in this paragraph. If a problem was found in Section 5.1, Section 5.2 or rules 1 to 8 of Section 6.1, no record is examined further. Otherwise: collapse copies of one signed body (Section 7.5), check conformance (Section 7.7), check anchors (Section 8). Records that pass are the event set, ordered by anchor height then record sha256 (Section 9). Walk the event set against the grant (Section 11), add the NENRIN checks (Section 12), and decide the verdict (Section 13), status (Section 9.5) and bond outcome (Section 10.3). The checks of Section 5.3, rule 9 of Section 6.1, Section 7.1 (a record that fails closed), Section 8.4 (below the lower bound) and Section 12 (a cited walk) are collected in the same way and make the settlement underspecified; they never stop the computation of the event set.¶
The output is one JSON object. Section 10 lists the core fields a second implementation MUST reproduce exactly. Everything else the reference writes is informative.¶
canonical(v) is the UTF-8 [RFC3629] encoding of this text:¶
{"k":v,...} with no whitespace.¶
[a,b,...] with no whitespace.¶
". Escape only " as \", \ as \\, and U+0000 to U+001F. Of those, U+0008 is \b, U+000C is \f, U+000A is \n, U+000D is \r, U+0009 is \t, and the rest are \u00xx with lowercase hex. Every other character is written as itself, including U+007F, U+2028, U+2029 and all non-ASCII. A lone surrogate code unit (U+D800 to U+DFFF not part of a valid pair) is written \udxxx with lowercase hex.¶
‑ only for negatives, no leading zeros, no fraction, no exponent.¶
true, false, null as literals.¶
The reference ships fixed inputs for this rule (musubi-v0/canonical_vectors.json) and a Node twin (canonical_v0.mjs) [MUSUBI-REF].¶
This form is not the JSON Canonicalization Scheme [RFC8785]. Among other differences, RFC 8785 sorts keys by UTF-16 code units, which gives a different order for keys with characters above U+FFFF, and serializes non-integer numbers, which this form never writes.¶
Base64 is standard base64 (Section 4 of [RFC4648]) with padding, and MUST be canonical: decoding then re-encoding gives the same text. A public key is 32 bytes after decoding, a signature 64 bytes. Any other length, any non-canonical text, or any non-string is "unusable".¶
A public key is also unusable if it is not the canonical encoding of a point on edwards25519, if it is the identity point, or if the point is not in the prime order subgroup (small order or mixed order points).¶
| name | definition |
|---|---|
| contract signing bytes |
"a2a‑contract‑v0\n" + canonical(contract without its "signatures" key)
|
| contract_sha256 | lowercase hex sha256 of the contract signing bytes |
| record_sha256 | lowercase hex sha256 of canonical(record), the whole record including anchor and signatures
|
| body digest | sha256 of canonical(record without its "anchor" key). It includes signatures. Anchor proofs start from these 32 bytes |
| record signing bytes | context + canonical(record without "signatures" and without "anchor"). Context is "a2a‑execution‑v0\n", "a2a‑revocation‑v0\n" or "a2a‑revocation‑ack‑v0\n" by the record's schema |
contract_sha256 is the identity of the terms. It does not change when a signature is added. A record is bound to a contract only by it (Section 7.2).¶
verify(pub_b64, sig_b64, message) returns one of: true, false, unusable. It returns unusable when the key or signature fails Section 4.2. Otherwise it is Ed25519 verification ([RFC8032]) of the 64 byte signature over the message bytes. The reference uses the Python cryptography package (OpenSSL). See item N9 of [MUSUBI-NOTES] on libraries that differ for adversarial signatures.¶
A 64 byte signature is "strict" when its last 32 bytes, read as a little-endian integer S, are below the group order L = 2^252 + 27742317777372353535851937790883648493, and its first 32 bytes R pass the public key test of Section 4.2 (a canonical encoding of a point of prime order, not the identity). "Strictly verifies" means the signature is strict and verify returned true. Libraries that disagree on lax signatures all agree on strict ones.¶
Everywhere below, "verifies" means verify returned true. False and unusable are both "does not verify", except in the contract door (Section 5.1), which names them differently.¶
The contract is accepted only if every check below passes. Any failure gives the single underspecified reason contract_not_verified (Section 5.2 is then skipped). The reference reports a list of refusal codes alongside; those codes are informative. Only contract_not_verified is core.¶
schema is "a2a‑contract‑v0".¶
contract_id and nonce are each 32 lowercase hex characters.¶
parties is a list of exactly two objects whose role values are exactly one "principal" and one "contractor".¶
domain normalizes to a bare hostname: lowercase, outer spaces and one or more trailing dots removed, no /, @, : or space, at least two labels, each label matching ^[a‑z0‑9]([a‑z0‑9‑]*[a‑z0‑9])?$. The two normalized domains differ.¶
key_url is an https:// URL. (Whether its host is under the domain is a finding, not a refusal.)¶
public_key_ed25519_b64 decodes as canonical base64 to 32 bytes. (The point check of Section 4.2 is applied later, when signatures are verified.)¶
task is an object with a non-empty string purpose. task.payload_digest, when present and not null, is 64 lowercase hex.¶
grant is an object. Its keys are only: authorized_actions, prohibited_actions, conditional, delegation, data_access, max_hops, privacy, revocation, finality, witnesses, expiry_height, ordering, approval_policy, limits. Any other key refuses the contract.¶
grant.authorized_actions and grant.prohibited_actions are both lists of non-empty strings, and no action is in both.¶
Typed grant keys, each checked only when present and not null:¶
data_access: list of non-empty strings.¶
max_hops, expiry_height: integer >= 0.¶
delegation: object whose only key is allowed, a list of non-empty strings.¶
conditional: list of objects, each with a non-empty string action; actions unique; requires, when present, a string.¶
finality: object with keys only from depth, max_target_bits. depth, when present, integer >= 1. max_target_bits, when present, 8 lowercase hex that decodes (Section 6.2) to a positive target.¶
witnesses: list of objects, each with a non-empty string name and a public_key_ed25519_b64 that decodes to 32 bytes.¶
privacy: non-empty string.¶
ordering: object whose only key is same_height, a string or null.¶
approval_policy: object with keys only from allow_unscoped (boolean) and approvers (out of scope, Section 1.4).¶
revocation: an object whose effective_at is "anchor" or "delivery_ack", with keys only from effective_at, ack_window; ack_window, when present, integer >= 1.¶
establishes is a non-empty list. No string in it matches, case-insensitively, any of the patterns listed after this list.¶
does_not_establish is a non-empty list. Lowercased, its strings together contain: one of runtime or enforce; one of judge, liability or fault; deviat; and legal. Each of the four groups must be hit by at least one string.¶
signatures is a list. For each object in it: its domain normalizes (rule 4) to a party's normalized domain, or the contract is refused. Its signature is verified (Section 4.4) with that party's key over the contract signing bytes. False or unusable refuses the contract, even if the same party also has a good signature (WRITTEN BEHAVIOUR). alg is not read.¶
The patterns of rule 13, one per line, are:¶
\bperformed\b \bdeliver(ed|y)\b \bmoney (moved|was sent)\b \bfunds? (moved|were sent|were transferred)\b \bpaid\b(?!\s+for\s+(this|the)\s+record) \bpayment (was|has been) (made|completed|settled|received)\b \bcontract\b \bbinding\b \bguarantee \bescrow\b \bcustody\b \bsolvent\b \blawful\b \bfair\b \bcertified\b
Not read by the door, and not read anywhere in core v0: expiry (a wall clock), agreed_at, requirements.recovery, selection_provenance, liability_boundary, settlement. lower_bound is not checked here; Section 6 checks it. requirements.evidence is read in Section 12, bond and parent_contract in Section 5.3.¶
Only when Section 5.1 accepts, each of these adds the underspecified reason shown:¶
| condition | reason |
|---|---|
grant.authorized_actions is not a list |
authorized_actions_missing
|
an action name in authorized_actions, prohibited_actions or conditional[].action does not match ^[a‑z][a‑z0‑9_.:‑]{0,63}$ (see Section 14 and item N1 of [MUSUBI-NOTES]) |
grant_action_name_invalid
|
| more than 32 witnesses |
too_many_witnesses
|
| a witness key does not decode to 32 bytes |
witness_key_invalid
|
| two witnesses share a name, or any two of {principal key, contractor key, each witness key} decode to the same 32 bytes |
key_conflict
|
grant.ordering.same_height is present and is neither null nor "conservative"
|
tie_policy_refused
|
grant.revocation is an object and effective_at is not "anchor" or "delivery_ack"
|
revocation_policy_not_recomputable
|
effective_at is "delivery_ack" and ack_window is not an integer >= 1 |
ack_window_missing
|
grant.finality.depth is not an integer >= 1 (including a missing finality or depth) |
finality_policy_missing
|
grant.expiry_height is present, not null, and not an integer >= 0 |
bad_expiry_height
|
allow_unscoped in grant.approval_policy is truthy |
unscoped_approvals_refused
|
An empty authorized_actions list means nothing is authorized. It is not an error.¶
Whether or not Section 5.1 accepts, each of these adds the underspecified reason shown:¶
| condition | reason |
|---|---|
a signatures[].signature decodes to 64 bytes and is not strict (Section 4.4) |
contract_signature_not_strict
|
bond is present and not null, and is not an object whose amount is an integer from 1 to 2^53 - 1 (true is not an integer) |
bond_malformed
|
parent_contract is present and not null, and no parent was supplied |
parent_not_supplied
|
parent_contract is present and not null, and the parent is not an object, or does not pass Section 5.1 on its own, or the contract does not pass Section 5.1 with the parent supplied (the reference's door then also checks that parent_contract.contract_id equals the parent's contract_id and that the child's grant is a subset of the parent's: contract_v0.grant_subset) |
parent_narrowing_failed
|
These checks do not stop the walk; the event set and dispositions are still computed (Section 3.3). The vectors show the subset rule with one widening (an added authorized action). A full statement of grant_subset is left to a later version of this document; until then, the reference's grant_subset (in contract_v0.py beside the reference [MUSUBI-REF]) is normative.¶
The view is checked under two contract terms: the difficulty floor grant.finality.max_target_bits and the checkpoint lower_bound (height, hash). Any failure below gives the underspecified reason chain_view_rejected. The view is checked even when Section 5 already failed.¶
grant.finality.max_target_bits is 8 lowercase hex and decodes (Section 6.2) to a target, the floor.¶
lower_bound is an object with an integer height and a 64 lowercase hex hash.¶
view.headers is a non-empty list.¶
height >= 0 and a hex of 160 lowercase hex characters, optionally followed by one line feed (WRITTEN BEHAVIOUR of the lower layers; rule 9 below then refuses the line feed under its own reason).¶
lower_bound.height whose hash equals lower_bound.hash.¶
The header hash is sha256(sha256(80 raw bytes)), byte-reversed, as lowercase hex (the usual display order). The merkle root used by anchors (Section 8.5) is bytes 36 to 68 of the raw header, not reversed. The tip is the height of the last header. The view may start below the checkpoint. The first header's previous-hash field is not checked. Consensus rules beyond these (retargeting, timestamps, transactions) are not checked.¶
height and a hex of exactly 160 lowercase hex characters, and every string, key and number in the view is strict (first paragraph of Section 7.1). If not, the reason view_not_strict is added. A header in upper case therefore gives both chain_view_rejected and view_not_strict; one that ends in a line feed gives only view_not_strict. Rule 9 does not stop the walk.¶
If the view verifies but holds more than 200000 headers, the reason is input_too_large instead, and no record is examined.¶
For a 32 bit value bits: exp = bits >> 24, mant = bits & 0x007fffff. If bits & 0x00800000 is set, or mant is 0, there is no target (invalid). Otherwise the target is mant << (8 * (exp ‑ 3)) when exp >= 3, else mant >> (8 * (3 ‑ exp)).¶
A JSON value is strict when, at every depth up to 512 levels: every string and every object key is free of U+0000 to U+001F and U+007F; every key is ASCII; every number is an integer within plus or minus (2^53 - 1). A value nested deeper is not strict.¶
A record's parts:¶
signatures and anchor. This is what its signers signed (Section 4.3, record signing bytes).¶
anchor. Nobody signed it.¶
signatures. Nobody signed it either.¶
The body is not strict when the body as a value is not strict, or (when present) nenrin_ref is neither null nor 64 lowercase hex, or contract_ref.contract_sha256 or contract_ref.payload_digest is present and not 64 lowercase hex, or, for an execution: performed_actions or delegated_to is present and not a list of strings; approvals is present and not a list of objects; or an approval's sig_b64 (or signature) decodes to 64 bytes and is not strict (Section 4.4).¶
The anchor is not strict when it is present and: it is not strict as a value; or its block_hash is present and not exactly 64 lowercase hex; or a proof operation's hex is present and is not exactly pairs of lowercase hex digits (the empty string is allowed).¶
A record is strictly signed when one entry of signatures has a role its schema allows (Section 7.6), a signature that strictly verifies (Section 4.4) with the matching key over the record signing bytes, and is itself a strict value.¶
First, a record of one of the three schemas whose record_sha256 cannot be computed at all (for example an integer too long to write out) is set aside with reason record_not_strict. The others are bound (Section 7.2). Each bound record is then triaged, in this order:¶
| condition | result |
|---|---|
| not strictly signed, body not strict | set aside, record_not_strict
|
| not strictly signed, but a signature verifies under Section 4.4 alone (lax) | set aside, signature_not_strict
|
| not strictly signed otherwise | rejected (disposition rejected) |
| strictly signed, body not strict | the record is a party's own statement: underspecified reason record_not_strict
|
| strictly signed, anchor not strict, and another bound record with the same record signing bytes is kept by this table and anchors validly (Section 7.5) | set aside, anchor_not_strict
|
| strictly signed, anchor not strict, otherwise | underspecified reason anchor_not_strict
|
a revocation, and grant.revocation is not an object |
set aside, revocation_not_provided_for (the contract cannot be revoked) |
| otherwise | kept |
Set aside and rejected records are never examined again: no collapse (Section 7.5), size limit (Section 7.3), conformance check, anchor check or NENRIN check sees them. They are listed with their disposition (Section 10.2). Problems in the signatures list of a strictly signed record (an extra entry that does not verify or is not strict) are ignored.¶
A third party holding copies of genuine records can change their anchors and signature lists but not their bodies. The table is arranged so that such copies are set aside or rejected whenever the genuine record is in the input.¶
Let cid = contract.contract_id, pdg = contract.task.payload_digest (it may be absent), csha = contract_sha256. Walk events in the given order. Skip anything that is not an object, or whose schema is not one of the three record schemas. Skip a record whose record_sha256 was already seen (identical bytes are one record). Then, with ref = contract_ref if it is an object, else empty:¶
| condition, in this order | class |
|---|---|
ref.contract_sha256 absent or null, and ref.contract_id == cid
|
unbound (not settled in strict mode) |
ref.contract_sha256 absent or null otherwise |
not ours: dropped silently |
ref.contract_sha256 is not 64 lowercase hex |
inconsistent |
ref.contract_sha256 != csha
|
foreign |
ref.contract_id != cid
|
inconsistent |
pdg is present and not null, and ref.payload_digest != pdg
|
inconsistent |
| otherwise | bound |
Only bound records go further. Foreign, inconsistent and unbound records are listed (Section 10) and never settled. Binding runs even when Section 5 or Section 6 failed; its classes are then still reported. Binding is by contract_sha256 alone, so a record made under earlier terms with the same contract_id is foreign and is never triaged (Section 7.1).¶
If more than 10000 records are kept by Section 7.1, the reason input_too_large is added and no record is examined further.¶
Two inputs with the same record_sha256 are one record. Supplying a record twice changes nothing.¶
The same signed body can reach the input more than once: anchored twice (two calendars, two batches), or with another signatures list. These copies differ only in their unsigned parts. Group the records kept by Section 7.1 by their record signing bytes (Section 4.3). In a group with more than one distinct record_sha256:¶
anchor.height is an integer >= 0 that the view holds; anchor.block_hash equals the view's hash at that height; the proof (Section 8.5) reaches that block's merkle root; and the batch leg rule (Section 8.6) accepts it.¶
duplicate_dropped).¶
This is done when rules 1 to 8 of Section 6.1 passed, whether or not Section 5 did. Note that "anchors validly" does not compare the height with the checkpoint (see item N7 of [MUSUBI-NOTES]).¶
Authentication is part of Section 7.1: only strictly signed records are kept. The roles and keys are:¶
| schema | signer it needs | key |
|---|---|---|
| a2a-execution-v0 | a signature with role "contractor", or with role "witness" and a witness_name
|
the contractor's party key, or the key of the grant witness with that exact name |
| a2a-revocation-v0 |
role "principal"
|
the principal's party key |
| a2a-revocation-ack-v0 |
role "contractor"
|
the contractor's party key |
signatures must be a non-empty list. Each entry is an object with role, sig_b64 and, for a witness, witness_name. An entry with another role, or one that does not strictly verify, is skipped.¶
Then, for a kept revocation: if its revoked_by field is not "principal", it is ignored (disposition ignored), even though the principal signed it.¶
Allowed top-level fields:¶
schema, contract_ref, performed_actions, approvals, delegated_to, nenrin_ref, outcomes, anchor, signatures,¶
schema, contract_ref, revoked_by, anchor, signatures,¶
schema, contract_ref, revocation_sha256, acked_by, anchor, signatures.¶
A record has a conformance problem if:¶
performed_actions is not a list of strings,¶
outcomes is present, not null, and not an object; or one of its keys is not in performed_actions; or one of its values is not "ok", "error", "timeout" or "partial",¶
action of an approval that is an object, does not match ^[a‑z][a‑z0‑9_.:‑]{0,63}$. An approval object with no action counts as not matching. (A name ending in a line feed never gets here: its body is not strict, Section 7.1.)¶
If an execution with a problem has a contractor signature that verifies, it is a charge: the deviation nonconforming_record is added, and the record continues to Section 8. Its acts are still judged. Any other record with a problem is rejected (disposition rejected). The computation is then repeated without the rejected records, until no new record is rejected, so no check reads a rejected record.¶
The types of approvals, delegated_to, nenrin_ref and contract_ref are checked in Section 7.1.¶
An anchor says where a record's body was committed to Bitcoin:¶
"anchor": {"height": H, "block_hash": "<64 hex>", "proof": [op, ...]}
¶
The anchor is not signed. Anyone holding a record can change it. The checks below decide whether the claim holds against the verified view.¶
The height of a record is anchor.height when anchor is an object and height is an integer >= 0. Otherwise the record has no height.¶
For each record that passed Section 7, apply Section 8.4, Section 8.5 and Section 8.6 in this order. The first failure decides what happens to the record. Every record that reaches this section is strictly signed by a party (Section 7.1).¶
| condition, in this order | result |
|---|---|
no height (Section 8.2), or anchor.block_hash is not 64 lowercase hex |
underspecified reason anchor_incomplete
|
height < lower_bound.height
|
underspecified reason anchor_below_lower_bound. The body names this contract, and the contract names the checkpoint's block hash, so no honest anchor can be below it |
| height > tip, or the view holds no header at that height | underspecified reason anchor_beyond_observed_chain
|
anchor.block_hash != the view's hash at that height |
disposition orphaned: excluded, no reason added, but its height counts for finality (Section 9.5) |
anchor.proof must be a non-empty list of at most 512 operations. Start with the 32 byte body digest (Section 4.3) as the current bytes, and apply each operation:¶
| op | effect |
|---|---|
{"op": "sha256"}
|
current = sha256(current) |
{"op": "hexlify"}
|
current = the ASCII bytes of the lowercase hex of current |
{"op": "append", "hex": X}
|
current = current + bytes(X) |
{"op": "prepend", "hex": X}
|
current = bytes(X) + current |
X is a string of pairs of lowercase hex digits (the empty string is allowed) of at most 4096 bytes, and bytes(X) is the byte string that X encodes. (An operand with anything else, a line feed included, never gets here: the anchor is not strict, Section 7.1.) Any other op, a missing or malformed operand, a non-object entry, or current bytes longer than 65536 at any step, fails the proof. The proof holds if the final bytes equal the merkle root of the header at the record's height (Section 6.1). If it fails: underspecified reason anchor_proof_invalid.¶
The operation set resembles a subset of the operations used by OpenTimestamps [OPENTIMESTAMPS]; only the operations above are defined here.¶
A ledger lists records by their hex sha inside a batch, and stamps the batch. A proof can pass through such a batch with hexlify. A hex digest that merely appears somewhere in stamped bytes is not a listing. So:¶
hexlify, the rule accepts.¶
hexlify is not the first op, or there is more than one, refuse (hexlify_not_leading).¶
sha256. Each must be append or prepend, else refuse (batch_leg_malformed). If there is no sha256, refuse (batch_leg_malformed). The batch is (all prepend operands, the later one in front) + the ASCII lowercase hex of the body digest + (all append operands, in order).¶
schema is one of nenrin‑agreement‑batch‑v1 (typed), nenrin‑witness‑batch‑v1, nenrin‑trace‑pin‑batch‑v0. Else refuse (batch_not_json or batch_schema_unknown).¶
records is a list of objects (else batch_schema_unknown). If count is present it equals the length of records (else batch_count_mismatch).¶
records[i] has sha equal to the hex digest. None: record_not_listed_in_batch. More: record_listed_twice.¶
a2a‑execution‑v0 to execution; revocations and acknowledgements have no kind, so they are refused with record_kind_unknown, WRITTEN BEHAVIOUR). records[i].kind equals that kind (else record_kind_mismatch). If records[i].schema is present it equals the record's schema (else record_schema_mismatch).¶
records[i].sha must be written in the raw text as the 64 hex characters themselves, with no escapes (else refuse, code internal), and its first character must sit at byte offset equal to the total length of the prepend operands (else refuse, batch_leg_splice_not_at_listing).¶
If the rule refuses: underspecified reason anchor_batch_leg_refused. The refusal code is informative detail.¶
If all of Section 8.4 to Section 8.6 pass, the record is accepted (disposition accepted).¶
The event set is the accepted records sorted by (height, record_sha256 as a lowercase hex string). Arrival order plays no part.¶
Two records in the same block are not ordered by their bytes for any rule that matters. Section 11 says how same-block cases are decided. The order of Section 9.1 only fixes the listing and the order in which single use approvals are consumed (Section 11.4).¶
underspecified: undetermined.¶
final if every accepted record's height and every orphaned record's height is at or below the horizon; else provisional.¶
With no accepted and no orphaned record, the status is final. Foreign, rejected, ignored and set aside records do not affect status.¶
A second implementation MUST reproduce these exactly. They are what the vectors' expected.json files hold.¶
| field | value |
|---|---|
contract_sha256
|
Section 4.3, always computed, even for a contract the door refuses |
verdict
|
within_grant, deviation, nothing_to_settle or underspecified (Section 13) |
status
|
final, provisional or undetermined (Section 9.5) |
bond_outcome
|
Section 10.3 |
deviation_codes
|
the sorted set of deviation codes (Appendix A). Empty when the verdict is not deviation
|
underspecified_reasons
|
the sorted set of underspecified reasons (Appendix A). Empty unless the verdict is underspecified
|
event_set
|
the list of Section 9.1, each item {"kind", "sha256", "height"} with sha256 the record_sha256. Empty when the walk did not run. A reason from Section 5.3, rule 9 of Section 6.1, Section 7.1, Section 8.4 (below the lower bound) or Section 12 (a cited walk) does not empty it |
dispositions
|
an object mapping record_sha256 to one code, for every record the computation classified (Section 10.2) |
deviation_codes and underspecified_reasons are sets. The reference may list a code more than once (one per action); that multiplicity is informative.¶
| code | meaning |
|---|---|
foreign, inconsistent, unbound
|
Section 7.2 |
record_not_strict, signature_not_strict, anchor_not_strict, revocation_not_provided_for
|
Section 7.1, set aside, or (for record_not_strict and anchor_not_strict) failing closed with the same reason added to the settlement |
duplicate_dropped
|
Section 7.5 |
rejected
|
Section 7.1 or Section 7.7 |
ignored
|
Section 7.6, a revocation whose revoked_by is not "principal"
|
anchor_incomplete, anchor_beyond_observed_chain, anchor_proof_invalid, anchor_batch_leg_refused
|
Section 8.4 to Section 8.6, with the same reason added to the settlement |
anchor_below_lower_bound
|
Section 8.4, with the same reason added |
orphaned
|
Section 8.4 |
accepted
|
in the event set |
A bound record that the computation never examined (because Section 5, Section 6 or Section 7.3 failed) has no disposition. A record that is not ours (Section 7.2, dropped silently) has no disposition. A record set aside because it cannot be digested has no record_sha256 and so no disposition; it is counted in the informative v1_16.refused. Every record has at most one disposition.¶
If contract.bond is absent or null: n/a. A bond of any other form than an object with a positive integer amount makes the settlement underspecified (Section 5.3) and the outcome is then undetermined when the value is one the lower layers read as a bond (an object with a truthy amount), else n/a.¶
For a well formed bond: undetermined if the verdict is underspecified; else pending_finality if the status is provisional; else held for within_grant and nothing_to_settle, and forfeited for deviation.¶
Everything else the reference writes is informative: schema, settled_under, v1_16, signed_body_collapse, contract_id, binding_mode, bound_by_label_only, ordering_rule, approval_rule, anchor_rule, view_rules, chain_tip, finality_horizon, pinned_blocks, revocations, approvals, binding, orphaned, predates_contract, ignored, rejected, duplicate_anchorings, establishes, does_not_establish, spine_gate, convergence_gate, approval_gate, signatures, the full underspecified and deviations objects (heights, why texts, details), and their order. The informative fields are useful when comparing implementations. They are not part of conformance.¶
The walk runs over the event set (Section 9.1) only. Let g = contract.grant.¶
For each accepted revocation at height h, its effective height E is:¶
effective_at "anchor": E = h.¶
effective_at "delivery_ack": E = the smallest of h + ack_window and the heights of every accepted acknowledgement whose revocation_sha256 names this revocation.¶
An acknowledgement names a revocation if its revocation_sha256 equals the lowercase hex body digest of the revocation, or the record_sha256 of any bound copy of that revocation (any anchoring, counted before Section 7.5 drops copies). An acknowledgement anchored before its revocation makes E earlier than h (WRITTEN BEHAVIOUR, see item N5 of [MUSUBI-NOTES]).¶
Sort the revocations by (E, record_sha256). An execution at height h is revoked if some revocation has E <= h. Authority ends at block E inclusive: an act in the same block as an anchor-mode revocation is revoked. For a revoked execution, every string in performed_actions gives the deviation revoked, and nothing else in that record is judged (no expiry, prohibited, conditional, unauthorized or delegation check, and no approval nonce is consumed).¶
If the grant has no revocation object, a revocation never gets here: it is set aside in Section 7.1 (revocation_not_provided_for).¶
If g.expiry_height is present and not null, every string action of an execution with height h > expiry_height gives after_expiry. This does not stop the other checks for that action: an expired prohibited action gives both after_expiry and prohibited. An execution at exactly expiry_height is not expired.¶
For each accepted, unrevoked execution, for each string a in performed_actions, in order, after Section 11.2:¶
a is in g.prohibited_actions: deviation prohibited. Next action.¶
a is the action of some g.conditional entry: apply Section 11.4. Next action. (A conditional action does not need to be in authorized_actions.)¶
a is not in g.authorized_actions: deviation unauthorized.¶
Every entry of performed_actions and delegated_to is a string here (Section 7.1). Comparison is exact string equality. There is no case folding or Unicode normalization.¶
Then, for each entry d of delegated_to not in g.delegation.allowed (empty when absent): deviation delegation. An outcome (outcomes) never excuses an act. An attempt that errored is judged like one that succeeded.¶
Every approval carried by an accepted execution (revoked or not) is classified. With pk the principal's key and csha this contract's contract_sha256:¶
forged (Section 11.5).¶
valid_until_height is an integer >= 0, nonce is a string of 32 lowercase hex characters (a nonce with a line feed never gets here, Section 7.1), single_use is a boolean, and contract_sha256 is 64 lowercase hex: compute the v2 approval bytes shown after this list. If sig_b64 verifies over them with pk: counted when its contract_sha256 equals csha, else other_terms.¶
Otherwise, if sig_b64 verifies with pk over either older form, it is label_bound:¶
forged.¶
The three forms of approval bytes are:¶
v2: "a2a-approval-v2\n" + canonical({
"contract_sha256": <its contract_sha256>,
"action": <its action>,
"valid_until_height": ..,
"nonce": ..,
"single_use": ..})
v1: "a2a-approval-v1\n" + canonical({
"contract_id", "payload_digest", "action",
"valid_until_height", "nonce", "single_use"})
v0: "a2a-approval-v0\n" + canonical({
"contract_id", "payload_digest", "action"})
A missing field is canonicalized as null. The approval's by field is not read. label_bound and other_terms approvals are listed (informative) and never count. They are not deviations.¶
A conditional action a in an execution at height h with record sha s is approved if some counted approval, taken in event set order (records by Section 9.1, approvals in list order within a record), satisfies all of:¶
action equals a,¶
valid_until_height,¶
single_use is true, its nonce has not been consumed yet.¶
The first approval that satisfies all four is used. If it is single use, its nonce is consumed. The set of consumed nonces is shared across all actions and all executions (see item N5 of [MUSUBI-NOTES]). If no approval satisfies all four: deviation conditional. The value of requires in the conditional entry is not read: every conditional action needs a counted approval.¶
Every approval classified forged adds the deviation forged_approval, whatever its action, whether or not the action was needed.¶
Only when nenrin_records is a non-empty list. A walk that is not strict (first paragraph of Section 7.1) is left out. Index every other object in it by its record_sha256 (Section 4.3). For each execution kept by Section 7.1 and not rejected by Section 7.7, left after Section 7.5 (once per record_sha256), look up the walk whose record_sha256 equals the execution's nenrin_ref. If found, and the walk's context is an object whose contract_sha256 is present, not null, and not equal to csha: deviation evidence_names_other_contract. If an execution's nenrin_ref names a walk that was left out for not being strict: underspecified reason walk_not_strict. The contractor chose what to cite, and leaving the walk out silently could hide a deviation.¶
Records that were rejected or set aside are never checked here (item N2 of [MUSUBI-NOTES]).¶
When requirements.evidence is "nenrin_required", each accepted execution whose nenrin_ref is not 64 lowercase hex gives the deviation evidence_missing. This is applied after Section 13 found within_grant or deviation.¶
underspecified if any underspecified reason was found (Section 5, Section 6, Section 7.1, Section 7.3, Section 8, Section 12). Then deviations is empty and status is undetermined.¶
deviation if any deviation was found (Section 7.7, Section 11, Section 12).¶
nothing_to_settle if the event set is empty: no record was read, so nothing was found within the grant or outside it. Status is as Section 9.5 gives it (final with no orphaned record).¶
within_grant.¶
within_grant covers only the accepted records. One strictly signed record whose anchor fails Section 8.4 (first, second or third row), Section 8.5 or Section 8.6 makes the whole settlement underspecified. A record that is not strictly signed never does.¶
These are what the reference still does. They are normative for core v0 because the vectors pin them, or would.¶
| item | vector |
|---|---|
| An extra bad signature from a party refuses the contract even when that party also signed correctly | none |
| Revocations and acknowledgements cannot pass a typed batch leg (no kind) | none |
| An acknowledgement anchored before its revocation ends authority at the acknowledgement's height | none |
| An orphaned record is excluded without making the settlement underspecified, yet its claimed height still counts for status |
real_p31_anchor_names_other_height
|
A header ending in a line feed passes rules 1 to 8 of Section 6.1 and is refused only by rule 9 (view_not_strict); one in upper case is refused by both |
synth_view_header_trailing_newline
|
| A party-signed record that names this contract_sha256 with another contract_id or payload_digest is inconsistent (Section 7.2) and is never triaged, so a deviation it shows is not read | none |
Each item below was written behaviour of settle v1.10 to v1.15; [MUSUBI-NOTES] has the details under the item numbers given.¶
| item | before | now |
|---|---|---|
| N2 | The NENRIN cross check read every bound execution, also rejected and unsigned ones: anyone could add a charge and forfeit a bond | only records strictly signed and not rejected are read by any check |
| N1 | A trailing line feed passed in block_hash, proof operands, headers, action names and approval nonces | strict forms (Section 7.1, rule 9 of Section 6.1) |
| N3, N6 |
delegated_to and approvals were not type checked; an object in delegated_to stopped the reference |
Section 7.1 |
| N4 | Floats and unsafe integers in records, views and walks were accepted | Section 7.1, rule 9 of Section 6.1, Section 12.1 |
| N6 | A revocation against a grant with no revocation block stopped the reference | set aside |
| N8 | The bond test used truthiness, so [] was no bond in Python and a bond in JavaScript |
bond_malformed
|
| N9 | Signatures were checked by OpenSSL alone | strict signatures (Section 4.4) |
| N10 | A copy with a junk signatures entry was a second body that blocked the verdict (fixed in v1.15) | copies of one signed body collapse (Section 7.5) |
| N11 | An anchor below the checkpoint was dropped without a word; parent_contract and requirements.evidence were never read; an empty event set was within_grant
|
anchor_below_lower_bound; Section 5.3; Section 12.2; nothing_to_settle
|
The vectors that pin each change are:¶
| item | vectors |
|---|---|
| N2 |
synth_nenrin_check_reads_rejected_record, synth_unsigned_copies_anchored_first
|
| N1 |
real_p31_proof_operand_trailing_newline, real_p31_proof_operand_trailing_newline_beside_good, real_p31_block_hash_trailing_newline, synth_action_name_trailing_newline, synth_approval_nonce_trailing_newline, synth_view_header_trailing_newline
|
| N3, N6 |
synth_delegated_to_object
|
| N4 |
synth_float_in_signed_body, synth_unsafe_integer_in_signed_body, synth_unsigned_float_beside_good, synth_cited_walk_not_strict
|
| N6 |
synth_revocation_irrevocable_grant
|
| N8 |
synth_bond_empty_list, synth_bond_amount_string
|
| N9 |
synth_signature_s_plus_l, synth_strict_signature_beside_malleated, real_p31_contract_bad_principal_signature
|
| N10 |
real_p31_copy_with_junk_signature_entry
|
| N11 |
real_p31_anchor_below_checkpoint, synth_parent_not_supplied, synth_parent_supplied_narrows, synth_parent_widened, synth_nenrin_required_without_ref, synth_nothing_to_settle
|
The vectors are published beside the source text of this document at commit 4100d52a [MUSUBI-VECTORS], in the folder settle-core-v0/vectors/ under https://github.com/ogasurfproject-jpg/horizon-shield/tree/4100d52a/workers/hs-ledger/nenrin/musubi-v0/settle-core-v0. They are not reproduced here.¶
There are 76 vectors: 22 real and 54 synthetic. Their expected verdicts are within_grant for 23, deviation for 19, nothing_to_settle for 8 and underspecified for 26.¶
The folder holds one subfolder per vector:¶
contract.json, view.json, events/eNN.json (the events, in any order), nenrin/nNN.json when the vector supplies walks, parent.json when it supplies a parent contract,¶
expected.json: the core fields (Section 10.1).¶
The index vectors/vectors.json lists, for each vector, its id, its family (real or synthetic), the section of the source text it tests, a description, the expected verdict, status and code sets, and the sha256 of each of its files. It also lists the four synthetic public keys.¶
Real vectors use the real records, contracts and Bitcoin headers of two settled engagements, with one change each (the record bodies cannot change without breaking the real Bitcoin anchors). real_p31_baseline is the first MUSUBI contract settled with no party from the project that publishes the reference (agent-passport.org and kuangmi-bit.github.io, Bitcoin block 970480), with its 359 header view.¶
Synthetic vectors use keys named synthetic-principal, synthetic-contractor, synthetic-witness and synthetic-stranger, derived from fixed seeds, and a synthetic header chain at regtest difficulty (max_target_bits 207fffff, checkpoint height 800001). The public keys are:¶
| name | public_key_ed25519_b64 |
|---|---|
| synthetic-principal |
rrXBDf5mF249t6wdmMvKtkcBXZmVVfcfYFBBjIpSttE=
|
| synthetic-contractor |
hl3xmxz86HFYXTczn/5HZw4Jkb3i0nL7iig/2GXZrcs=
|
| synthetic-witness |
gMKIYPPIjG6r9mt8ybr3dIGGm6LYxkngSym9ltvHNtY=
|
| synthetic-stranger |
YmzNu/v6K9wUOtcmnssHABVLBDuLBqMcZnM0NL+32rU=
|
gen_vectors.py regenerates every vector from the two real inputs and the reference; it is deterministic. run_reference.py runs the reference over every vector and checks every file hash and every core field.¶
expected.json holds exactly the eight core fields of Section 10.1 and nothing else. The reference's full settlement object is projected onto them as follows:¶
contract_sha256, verdict, status and bond_outcome are copied.¶
deviation_codes and underspecified_reasons are the sorted sets of the codes in the reference's deviations and underspecified lists. Repetitions are removed.¶
event_set keeps, for each accepted record in the order of Section 9.1, only kind, sha256 and height.¶
dispositions is one object from record_sha256 to one disposition code (Section 10.2), with keys sorted. A record that would receive two different codes is an error of the projection.¶
An implementation conforms on a vector when its eight core fields are equal to expected.json as JSON values. The informative fields (Section 10.4) are not compared.¶
A disagreement is valuable. It means either the text, the reference or the implementation is wrong, and only a comparison finds out which.¶
expected.json.¶
python3 run_reference.py from the folder holding the source text, to confirm the reference still gives the expected values on the same machine.¶
settle core v0 disagreement: <vector id>, with the above.¶
An implementer should not change an implementation to match a vector believed to be wrong without saying so. A silent match hides a defect. A reported disagreement is resolved by changing this text, the reference, or the vector, and the change is recorded with a new version of this document.¶
Note to the RFC Editor: please remove this section before publication.¶
This section records the status of known implementations of the protocol defined by this specification at the time of posting of this Internet-Draft, and is based on a proposal described in [RFC7942]. The description of implementations in this section is intended to assist the IETF in its decision processes in progressing drafts to RFCs. Please note that the listing of any individual implementation here does not imply endorsement by the IETF. Furthermore, no effort has been spent to verify the information presented here that was supplied by IETF contributors. This is not intended as, and must not be construed to be, a catalog of available implementations or their features. Readers are advised to note that other implementations may exist.¶
According to [RFC7942], "this will allow reviewers and working groups to assign due consideration to documents that have the benefit of running code, which may serve as evidence of valuable experimentation and feedback that have made the implemented protocols more mature. It is up to the individual working groups to use this information as they see fit".¶
settle_v1_16.settle_v1_16(contract, events, view, parent=..., nenrin_records=...). Its command line (‑‑settle, ‑‑event, ‑‑view, ‑‑nenrin, ‑‑parent) first checks each view's strict form and runs fork choice, and for a view that does not pass prints why and writes no settlement. The vectors use the library.¶
The reference, as run for the vectors, consists of these files (first 16 hex digits of each file's sha256):¶
| file | sha256 prefix |
|---|---|
| settle_v1.py |
0e51350331c8931c
|
| settle_v1_1.py |
a2391a5db199bcda
|
| settle_v1_2.py |
6c674fd54a1a5c89
|
| settle_v1_3.py |
17119bbc7802647f
|
| settle_v1_4.py |
ef3779d05fc98e40
|
| settle_v1_5.py |
f9b313566713cdc4
|
| settle_v1_6.py |
6752a748ed7b6e3b
|
| settle_v1_7.py |
878a3a3697775d3a
|
| settle_v1_8.py |
fe61f67c1593c776
|
| settle_v1_9.py |
7a84302be1ec38a1
|
| settle_v1_10.py |
34beeebaa1397059
|
| settle_v1_11.py |
fb03687cbacd2713
|
| settle_v1_12.py |
40a8fbb82a8e7680
|
| settle_v1_13.py |
eb02b3a6544b1bf1
|
| settle_v1_14.py |
7d0704fa060adafd
|
| settle_v1_15.py |
a2be5de8ba60656e
|
| settle_v1_16.py |
ec332ecb387e9b56
|
| admission_verify_v0.py |
aa889e542ff40e25
|
| agreement_verify.py |
72990326ac4a0a65
|
| anchor_compose.py |
a661f673c05dbe6e
|
| approval_v3.py |
e8dde985dbe6fbf4
|
| clause_eval_v0.py |
fee48c3a66c28fe1
|
| contract_v0.py |
211a568783705e11
|
| convergence_v0.py |
65a267380fa623f5
|
| corroboration_v0.py |
62ae1d6c27a92228
|
| independence_v0.py |
50964883c27896de
|
| recovery_verify.py |
e9bc9c11dd7d9aef
|
| spine_verify.py |
ad2076d49be6763e
|
| terms_v0.py |
6532d5c6160a3974
|
No independent implementation of settle core v0 exists at the time of writing. An open call for one, with the procedure of Section 15.3, is at [ISSUE-49].¶
A record has three parts (Section 7.1): the body, which its signer signed, and the anchor and the signatures list, which nobody signed. A third party that holds copies of genuine records, the contract and the walks can therefore change anchors and signature lists at will, but cannot change a body without the signer's key. The rules are arranged around this:¶
signatures has the same record signing bytes as the genuine record. Junk entries are ignored, and copies of one signed body are collapsed (Section 7.5), keeping the copy that anchors validly at the smallest (height, record_sha256).¶
What remains is a liveness property, not a safety one. When the genuine copy is not in the input, a copy with an incomplete or wrong anchor from a strictly signed record makes the whole settlement underspecified (Section 13); a party can delay a verdict in the same way by handing the settler such a record. A copy whose block_hash was edited to disagree with the view at a held height is orphaned and excluded without an underspecified reason (Section 14.1); a settler that has only such a copy does not see the act. Settlers that collect records from more than one source reduce both risks. underspecified must not be read as a statement that the terms are bad.¶
The record_sha256 covers the unsigned parts, so it identifies a copy, not an act. The record signing bytes identify the act.¶
Ed25519 implementations differ on adversarial signatures: a non-canonical S (for example S + L), a small order or mixed order R or public key, and cofactored versus cofactorless verification equations [EDDSA-TAMING]. Honest signatures verify everywhere. Settle core v0 counts a signature only when it is strict (Section 4.4): S, read as a little-endian integer, is below L, and R and the public key are canonical encodings of points of prime order that are not the identity. With all points in the prime order subgroup and S below L, the libraries that disagree on lax signatures agree.¶
A lax signature that a library accepts is not ignored silently: the record is set aside as signature_not_strict, and a contract signature that is not strict makes the settlement underspecified (contract_signature_not_strict). Anyone can turn a genuine signature into a lax one (S + L) without a key; that changes the record_sha256 but not the body, and the genuine copy, if present, still settles (vectors synth_signature_s_plus_l and synth_strict_signature_beside_malleated).¶
Item N9 of [MUSUBI-NOTES] records that the behaviour of the reference's OpenSSL on published Ed25519 edge cases was not tested beyond the strictness rule. The strictness rule is what conformance depends on.¶
Settlement depends on byte-exact digests computed by different runtimes. JSON readers differ: some accept the tokens NaN and Infinity, some read 970480.0 as a float and others as the integer 970480, integers above 2^53 are exact in some and rounded in others, duplicate keys are resolved differently, and key order by code point differs from order by UTF-16 code unit above U+FFFF. A pattern ending in $ in some regular expression engines also accepts a trailing line feed, and some hex decoders skip whitespace.¶
The rules of Section 3.2, Section 4.1 and Section 7.1 remove these differences for the values that matter: only integers within plus or minus (2^53 - 1) are strict, keys in the contract and in strict values are ASCII, fixed forms such as hex digests are matched whole, and a value that is not strict is set aside or makes the settlement underspecified instead of being read two ways. An implementer has to parse numbers to an exact integer representation, match fixed forms against the whole string, and refuse whitespace in hex.¶
Some divergence remains possible outside strict values. The record_sha256 of a record that is set aside, foreign or unbound is still reported in dispositions, and its canonical form can depend on the runtime for values that are not strict. A record whose record_sha256 "cannot be computed at all" is set aside without a disposition; in the reference this happens, for example, for an integer too long for the Python runtime to write out, and another runtime may reach that limit at a different length. Item N4 of [MUSUBI-NOTES] also records that JSON batches reached through a proof (Section 8.6) are parsed as the lower layers of the reference parse them.¶
Settle core v0 takes one header view as given. It checks linkage, proof of work against each header's own nBits, the grant's difficulty floor and the signed checkpoint, and nothing else of Bitcoin consensus. It does not establish that the view is the heaviest chain. A status of final means that every accepted and orphaned record is at least grant.finality.depth blocks below the tip of the view; a reorganization deeper than that depth can still change a final settlement. Choosing between several views (fork choice) is out of scope (Section 1.4). The difficulty floor and the checkpoint limit what a party can forge cheaply, but a view supplied by an adversary with enough work is accepted.¶
within_grant covers the accepted records only.¶
key_url is the key in the contract. Settle never fetches.¶
nenrin_ref is correct. Section 12 only compares the contract it names.¶
A contract names both parties by domain and pins their public keys, and records carry action names, delegation targets, payload digests and references to walks. Anyone who is given these bytes can settle, so anyone who is given them learns who contracted with whom and what the agent did. Anchoring commits only a digest to the Bitcoin block chain, but the commitment is public and permanent, and publishing a record later links it to a block and so to a time. Parties should treat the inputs of a settlement as disclosed to every settler they hand them to. Settlement itself fetches nothing (Section 1.4), so computing it reveals nothing to a network observer.¶
This document has no IANA actions.¶
Underspecified reasons in core v0: contract_not_verified, input_not_settleable, contract_signature_not_strict, bond_malformed, parent_not_supplied, parent_narrowing_failed, view_not_strict, record_not_strict, anchor_not_strict, anchor_below_lower_bound, walk_not_strict, authorized_actions_missing, grant_action_name_invalid, too_many_witnesses, witness_key_invalid, key_conflict, tie_policy_refused, revocation_policy_not_recomputable, ack_window_missing, finality_policy_missing, bad_expiry_height, unscoped_approvals_refused, chain_view_rejected, input_too_large, anchor_incomplete, anchor_beyond_observed_chain, anchor_proof_invalid, anchor_batch_leg_refused.¶
Deviation codes in core v0: revoked, after_expiry, prohibited, conditional, unauthorized, delegation, forged_approval, nonconforming_record, evidence_names_other_contract, evidence_missing.¶
Verdicts: within_grant, deviation, nothing_to_settle, underspecified.¶
Dispositions: Section 10.2.¶
The parties behind agent-passport.org and kuangmi-bit.github.io published the contract, the anchored record and the header view of the first MUSUBI contract settled with no party from the project that publishes the reference; the real vectors are built from them. Two independent adversarial reviews of the reference found defects that this document now describes as closed.¶