The problem
An agent buys something. The credential that says the agent was allowed to buy it gets split in two, on purpose. One half goes to the payment network, the other half goes to the merchant. Each half carries only the disclosures relevant to the party that receives it, which is good privacy design and is the reason the split exists.
The Verifiable Intent specification, published at https://verifiableintent.dev/spec/, splits the agent credential into an L3a for the payment network and an L3b for the merchant. Each carries an sd_hash the specification describes as "computed over the L2 base JWT concatenated with only the disclosures relevant to that L3". It then states, in its own words, that "Cross-reference verification requires a Verifier with both L3 credentials". The check itself is one comparison, L3a.transaction_id == L3b.checkout_hash. The specification requires it and designates nobody who holds both sides.
The AP2 specification, published at https://ap2-protocol.org/ap2/specification/, has the same shape in its dispute path. It says that its four dispute objects together "provide a non-repudiable picture of the transaction" and that they "can be brought together". It does not say by whom.
Read those two statements plainly. The comparison that ties the payment side of a transaction to the checkout side is required, and no designated party is positioned to run it. Nothing here is a criticism of either specification. The gap is a structural consequence of getting the privacy right.
Now name the parties in the dispute that follows. A cardholder says the purchase was not the one she authorised. The merchant holds its half of the credential and says its half is consistent. The payment network holds its half and says the same. Each of those statements can be true at once, because each party is describing a different document.
Whoever wants the comparison run has one option today, which is to ask one of the two parties to hand its half to the other. That party then holds both halves, which is the state the split was written to avoid, and it also has an interest in the answer. Nobody accepts a reconciliation from a counterparty when money is on the line.
The autonomy of the buyer sharpens this. A human buyer generates a handful of transactions and remembers them. An agent generates thousands, at machine speed, under an authority that has been narrowed at each hop. When a dispute lands weeks later, the only artifacts are two halves in two places.
The second failure is timing. If a party can pick the digest key after it has seen an artifact, it can pick a key that makes any two values look related. The comparison is only evidence if the key was fixed before either half was presented. That is not something either attestor can assert about itself.
What the receipt binds
The schema is at https://thehiveryiq.com/.well-known/schemas/mandate-crossreference-v1.json. The signed body has receipt_type, schema, the boundary, and one object, mandate_crossreference.
| Field | Meaning |
|---|---|
crossreference_id | Identifier matching mx_ plus 16 to 64 lowercase hex characters. |
recorded_at | The UTC instant this record was written. |
cross_reference_field_name | The one field being compared, as a lowercase name up to 64 characters. |
window_salt_committed_at | The instant the window salt was committed. |
delegation_layer_commitment | Keyed digest of the delegation layer both artifacts descend from. |
merchant_side.attestor_role | The fixed label merchant_side_attestor. |
merchant_side.attestor_key_id | The decentralised identifier of the merchant side attestor key. |
merchant_side.attestor_algorithm | The fixed value ed25519. |
merchant_side.attestor_sig_b64u | That attestor's signature over its own side. |
merchant_side.artifact_class | One of merchant_credential, checkout_mandate, cart_mandate or merchant_dispute_object. |
merchant_side.artifact_commitment | Keyed digest of the whole merchant side artifact record. |
merchant_side.cross_reference_value_commitment | Keyed digest of the merchant side value of the declared field. |
merchant_side.presented_at | The instant that half was presented. |
payment_side.artifact_class | One of payment_network_credential, payment_mandate, network_token_record or payment_dispute_object. |
payment_side.* | The fixed label payment_side_attestor and the same seven remaining fields as the merchant side. |
artifact_relation | Either distinct_artifact_classes or same_artifact_class. |
equality_outcome | Either cross_reference_match or cross_reference_mismatch. |
precedence_class | Either salt_committed_before_both_artifacts or salt_committed_after_an_artifact. |
observer_role | The fixed label non_party_joiner. |
evidence_access_class | One of confidential_independent, issuer_only or public_commitment_only. |
salt_commitment | Commitment to the 32 byte window salt. |
The two sides sit in separate objects with their own attestor key, their own signature and their own instant. That separation makes the equality check a comparison of two things rather than a restatement of one thing twice.
What is deliberately not in the signed body
No artifact, no field value, no delegation layer record, no window salt. The receipt holds digests, one field name, two attestor key identifiers, two signatures, three instants and enumerated classes.
Four constructions in src/typed/mandate-crossreference.js do the work. The salt commitment is a plain SHA-256 over the UTF-8 label mandate.crossreference/salt/v1, a zero byte, and the raw 32 salt bytes.
The artifact commitment is HMAC-SHA256 keyed by the raw salt bytes, over the domain label mandate.crossreference/artifact/v1 followed by a zero byte and then the canonical JSON of an object holding just the artifact record. The record must hold exactly artifact_id, artifact_class, content_sha256, cross_reference_field_name and cross_reference_field_value, with content_sha256 required to be 64 lowercase hex characters. The record has no field that could carry the artifact itself.
The cross reference value commitment is HMAC-SHA256 under the label mandate.crossreference/cross-reference-value/v1, over the canonical JSON of an object holding the field name and the field value together. Binding the name into the keyed digest is what stops one side committing the value of some other field and still matching.
The delegation layer commitment is HMAC-SHA256 under the label mandate.crossreference/delegation-layer/v1, over the canonical JSON of the delegation layer record. That record must hold exactly delegation_layer_id, a layer_class drawn from agent_credential_layer, mandate_layer and token_control_layer, and layer_content_sha256. The join only means something inside one delegation layer, so both sides commit to the same layer and the service recomputes it.
Canonical JSON is the shared rule: keys sorted recursively, arrays kept in order, no whitespace, and every code unit above U+007F escaped as a lowercase \uXXXX sequence, then digested as lowercase hex SHA-256 over the UTF-8 bytes.
Each attestor signs its own side over a fixed ASCII string built from crossreference-attest, the cross reference identifier, its own role label, the declared field name, its artifact class, its artifact commitment, its value commitment, its presentation instant, the delegation layer commitment and the salt commitment. The role label stops a signature being moved to the other side, and the salt commitment stops it being replayed into a later window.
The receipt envelope uses the shared Ed25519 construction over the ASCII string hive-receipt <receipt_id> <payload_sha256> <ts>, with hive-receipt as the domain separation label.
The gates
Verification runs in this order and stops at the first failure.
- SCHEMA. Validates the envelope against the cross reference v1 schema, including the boundary constant, the two role constants and the two disjoint artifact class lists. A failure means the artifact is malformed.
- ISSUER_KEY_MATCH. Resolves
envelope.key_idin the trusted keyring and checks the key was authorised for this receipt type at the envelope timestamp. A failure means the signer is not trusted here. - PAYLOAD_DIGEST_VALID. Recomputes the canonical digest of
signed_bodyagainstpayload_sha256. A failure means the body was altered after signing. - SIGNATURE_VALID. Requires Ed25519 and verifies the envelope signature with the resolved key. A failure means that key did not produce it.
- RECORD_INSTANT_ORDERED. Requires all four instants to be valid UTC and both presentations to be at or before
recorded_at. A failure means the record describes something that had not happened yet. - SALT_BINDING. Requires the disclosed salt to be 64 lowercase hex characters and to recompute to
salt_commitment. A failure means none of the keyed digests can be checked. - WINDOW_SALT_PRECEDENCE. Recomputes the precedence class with strict inequality against both presentation instants, then fails outright when the salt came after an artifact. A failure means the salt was fixed with an artifact already in hand.
- CROSS_REFERENCE_FIELD_DECLARED. Requires both artifact records to hold exactly the five allowed fields, to name the one declared field, and to carry the class in their own signed side. A failure means the sides are not comparing the same field.
- DELEGATION_LAYER_BINDING. Requires the delegation layer record to hold exactly its three allowed fields, and recomputes its commitment. A failure means the halves are not being joined inside one delegation layer.
- MERCHANT_SIDE_ARTIFACT_INTEGRITY. Recomputes the keyed digest of the merchant artifact record against
merchant_side.artifact_commitment. A failure means the record supplied is not the record committed. - PAYMENT_SIDE_ARTIFACT_INTEGRITY. The same check against the payment artifact record.
- MERCHANT_SIDE_VALUE_COMMITMENT_RECOMPUTE. Recomputes the keyed digest of the merchant side field value under the declared field name. A failure means the compared value was supplied rather than derived.
- PAYMENT_SIDE_VALUE_COMMITMENT_RECOMPUTE. The same check on the payment side.
- ATTESTOR_ROLE_DISTINCTNESS. Requires both fixed role labels and each artifact class in its own side's list, and refuses two sides naming one attestor key or an issuing key named as an attestor. A failure means one signer holds both halves.
- ATTESTOR_SIGNATURE_VALID. Requires a supplied attestor registry with each key registered for its own role, and verifies each side's Ed25519 signature over its reconstructed signing string. A failure means the side is not what that attestor signed.
- ARTIFACT_RELATION_RECOMPUTE. Recomputes the relation from the two artifact classes and fails outright on
same_artifact_class. A failure means this is one record compared against itself. - EQUALITY_OUTCOME_RECOMPUTE. Recomputes the outcome from the two value commitments this service derived. A mismatch passes. A failure means the outcome was asserted rather than computed.
- OBSERVER_ROLE_DECLARED. Requires the observer role to be the fixed label
non_party_joiner. A failure means the joining role was edited. - NO_ARTIFACT_CONTENT_LEAK. Walks every string in the signed body against its approved opaque form: identifier patterns, 64 character hex digests, enumerated classes. A failure means something readable reached an opaque field.
- MANDATECROSSREFERENCE_BOUNDARY_PRESENT. Requires the boundary text to equal the fixed string. A failure means the non attestation was edited or removed.
Mint refuses every field this list recomputes, from both artifact commitments through to the salt commitment. It also refuses to sign a salt committed after an artifact was presented, two identical attestor keys, a request with no attestor registry, and an attestor signature it cannot verify.
The boundary
This receipt attests only that two committed artifacts, each attested by a distinct registered attestor key, each declaring one cross reference field name and one keyed digest of that field value under a window salt committed before either artifact was presented, met or failed the declared equality relation, and that the equality outcome was recomputed by this service from the two commitments rather than supplied by the caller. It does not attest that either artifact is genuine, complete, or unaltered before commitment. It does not attest that either attestor is entitled to hold the artifact it committed. It does not attest that the transaction occurred, was authorised, settled, or was proper. It does not attest that the cross reference field is the right field for any purpose. A mismatch outcome does not mean that either side is wrong, and this receipt allocates no fault. It discloses neither artifact, neither field value, nor the salt.
That string is a schema constant inside the signed bytes and it is checked again by its own gate.
The clause that costs the most is the last one about fault. A mismatch is the strongest thing this instrument produces, and the boundary says in the signed bytes that a mismatch does not mean either side is wrong. That sounds like giving away the value. It does the opposite. A mismatch has innocent causes, including a truncation on one side, a normalisation difference, or a replayed cart. If the receipt claimed a mismatch proved misconduct, the first party blamed unfairly would attack the receipt rather than explain the cause. Because the receipt allocates nothing, a mismatch becomes a fact both sides can accept and then argue about.
The clause about genuineness points the same discipline at the input. If a merchant committed a doctored artifact, the digest of the doctored artifact is what the receipt compares, and it says so.
The clause about the field is the most practical. This receipt records the declared field name and compares that field faithfully. Whether it is the right field for a given purpose is a question about the protocol, and the receipt refuses to answer it.
Adversarial cases
These are the real cases in test/mandate-crossreference.test.mjs, which holds 56 tests, all passing.
A window salt committed after an artifact was presented fails WINDOW_SALT_PRECEDENCE, and a salt committed exactly at the merchant presentation instant fails the same gate, because the requirement is strict inequality. Both are refused at mint.
A match claimed over two differing values fails EQUALITY_OUTCOME_RECOMPUTE, and a mismatch claimed over two equal values fails the same gate. The outcome is recomputed from the two commitments the service derived, so neither direction of forgery survives.
One attestor key on both sides fails ATTESTOR_ROLE_DISTINCTNESS. This is the shape the split was designed to prevent, and mint refuses it too. The issuing key named as an attestor fails the same gate, so the joining party cannot attest a half it joins. A payment side carrying a merchant artifact class fails that gate too, because each side is checked against its own class list.
A missing attestor registry fails ATTESTOR_SIGNATURE_VALID. No attestor signature is taken on trust. An attestor registered for the other role fails the same gate, and so does a merchant signature moved to the payment side, because the role label is inside the signed string.
An edited merchant artifact record fails MERCHANT_SIDE_ARTIFACT_INTEGRITY, and the payment side has its own gate for the same edit. A forged merchant value commitment fails MERCHANT_SIDE_VALUE_COMMITMENT_RECOMPUTE, and again the payment side has its own.
An artifact record naming a different field fails CROSS_REFERENCE_FIELD_DECLARED, and so does a record whose class disagrees with the signed class. An edited delegation layer record fails DELEGATION_LAYER_BINDING, and so does a record from another layer class, so a join cannot be assembled across two delegation layers.
A substituted salt fails SALT_BINDING, a raw field value in a signed field fails NO_ARTIFACT_CONTENT_LEAK, and a boundary mismatch fails MANDATECROSSREFERENCE_BOUNDARY_PRESENT.
Four positive cases carry the instrument. Two halves that agree return cross_reference_match. Two values that disagree return cross_reference_mismatch and the receipt stays valid, which is the case the dispute path needs. A merchant dispute object joined to a payment dispute object verifies on its own artifact classes. A checkout mandate against a payment mandate verifies.
Structural tests confirm the design. The minted envelope carries neither artifact, neither field value, nor the salt. Value commitments are salt scoped and never repeat across salts. The same value under a different field name commits differently. The classifiers are pure functions of their inputs, and the two artifact class lists are disjoint. Mint refuses every computed field at both the request level and the nested side level.
Performance
From hive-verifier-api/benchmarks/results-latest.json, measured at 2026-08-10T08:00:47.634Z at commit a22b4d88f6e041d6f593381b6e310a8d527d5b4b over 200 iterations:
| Measure | Value |
|---|---|
| Mint p50 | 6.453 ms |
| Mint p95 | 8.025 ms |
| Verify p50 | 5.753 ms |
| Verify p95 | 6.202 ms |
| Artifact size | 3044 bytes |
Local harness measurement on one machine and one Node version. The run recorded Node v20.20.1 on linux/x64 with two Intel Xeon cores at 2.90 GHz. It is a reproducible measurement of this code, not a production latency guarantee. This is one of the more expensive instruments in the set, and the reason is in the gate list. Both mint and verify perform two extra Ed25519 verifications, one per attestor side, on top of the envelope signature. A join that trusted a supplied attestor signature would prove nothing about who committed which half. The cost does not grow with the size of the two artifacts, because the artifacts never arrive.
How to run it
This type has no mint route. It is produced as an export and only verified here. The verify route takes no credentials.
The verify route is live on the production host. It is open, needs no account, and costs nothing to call.
- Verify route:
/verify/mandate-crossreference - Demonstration route:
/demo/mandate-crossreference, carrying the keysmatch,mismatch,dispute_pairandhindsight_salt
curl -sS -X POST https://thehiveryiq.com/v1/verify/mandate-crossreference -H 'content-type: application/json' -d '{"receipt": <a receipt of this type>}'
The hindsight_salt case fails at WINDOW_SALT_PRECEDENCE. It cannot be minted, so it is built by minting the honest case, editing the body and re-signing with the same demonstration key.
Where it sits in the canon
Family: settlement-evidence. Category: typed receipt contract.
It is the two party join that the surrounding instruments assume somebody else has done. The Ledger Parity Receipt settles whether two parties agree on one settlement figure, over one kind of record held twice. This one compares two structurally different artifacts that were routed to two different parties on purpose, on one declared field, inside one delegation layer. The Mandate Conformance Receipt judges whether a payment fit the authority in force, and it needs the halves of that authority tied together first. The Intent Affirmation Receipt fixes that the artifact a person affirmed is the artifact transmitted, one step earlier in the same chain.
It does not replace either party's own reconciliation, and it does not decide a dispute. It reports an outcome neither party could produce without holding the other's half.
Patent pending.
Status
production_deployed.