The problem
An authority is granted once and then used in many places. A card mandate presented at fifty merchants. An agent credential accepted by twenty services. The per action rule holds at every one of them, and the aggregate picture is invisible to all of them.
This is the structural blind spot of distributed acceptance. Each acceptor sees its own slice, evaluates its own transaction against its own view of the constraint, and approves. No acceptor sees the sum. The principal who granted the authority sees whatever its own systems happened to capture, which is usually its own slice too.
Name the parties. The principal asserts it granted a bounded authority. Each acceptor asserts it honoured a valid presentation. The actor asserts it stayed within what it was given. Somebody downstream discovers that the aggregate was far beyond the bound, and there is no party who was in a position to notice.
Three specific failures.
The first is that per action conformance does not compose. A hundred conforming actions can exceed any cumulative intent. The Mandate Conformance Receipt says so in its own boundary. Aggregating across a single acceptor is straightforward. Aggregating across acceptors that do not talk to each other is the hard case.
The second is that acceptors will not identify each other. Two competing merchants have no interest in a report that tells either of them who else the customer transacts with. An aggregation scheme that requires acceptor identities to be disclosed to a central party, or to each other, is a scheme nobody joins.
The third is that partial reporting is invisible. If eighteen of twenty acceptors report, the aggregate looks like a complete picture unless the report says otherwise. Silence from an acceptor is indistinguishable from that acceptor having no activity.
The confidentiality problem extends to the numbers. The cumulative ceiling is a commercial term between the principal and the actor. The accumulated position is sensitive to both. A report that discloses either in order to prove a relation between them is a report neither party will authorise.
When the actor is autonomous, breadth becomes the dominant risk. A human customer transacts with a handful of merchants. An agent can present the same authority at hundreds of acceptors inside an hour, and dispersion becomes a signal worth measuring in its own right.
What the receipt binds
The schema is at https://thehiveryiq.com/.well-known/schemas/cross-acceptor-v1.json. The signed body has three top level fields: receipt_type fixed to mandate.crossacceptor, schema fixed to r1.0.0, and cross_acceptor_window, plus the boundary. Everything of substance lives in the window object.
| Field | Meaning |
|---|---|
window_id | Identifier matching cw_ plus 16 to 64 hex characters. |
window_start and window_end | The accounting window bounds as UTC instants. |
window_index | The position of this window in its chain. |
aggregation_key | The 64 hex value grouping actions into one accumulation without naming a party. |
currency | A three letter code. |
pseudonym_algorithm | Fixed to hmac_sha256_v1. |
pseudonym_scope | Fixed to per_authority_per_window. |
limit_commitment | Commitment to the cumulative ceiling. |
aggregated_notional_commitment | Commitment to the accumulated amount. |
checked_action_count_commitment | Commitment to the number of actions checked. |
salt_commitment | Commitment to the window salt. |
contribution_set_digest | Canonical digest over the contribution multiset. |
coverage_set_digest | Canonical digest over the coverage report set. |
distinct_acceptor_count | How many distinct acceptors contributed, up to 100000. |
reporting_acceptor_count | How many acceptors reported. |
expected_acceptor_count | How many were expected, at least one. |
dispersion_class | One of no_checked_actions, concentrated, moderately_distributed, distributed, highly_distributed or indeterminate. |
coverage_class | One of complete, declared_subset or undeclared_subset. |
evidence_access_class | One of confidential_independent, issuer_only or public_commitment_only. |
cumulative_relation | One of within_constraint, exceeds_constraint or indeterminate. |
prior_window | The predecessor window link, or null. |
closed_by | Either schedule or constraint_reached. |
Note the three counts. Distinct contributing acceptors, reporting acceptors and expected acceptors are separate numbers, and the difference between the last two is exactly what coverage_class reports.
What is deliberately not in the signed body
No acceptor identity, no window salt, no contribution multiset, no checked action count, no exact dispersion ratio, no cumulative constraint and no accumulated amount. The receipt discloses counts, classes and a relation.
Five constructions in src/typed/cross-acceptor.js do the work. Four are HMAC-SHA256 keyed by the raw window salt bytes, over a message built from a domain label followed by each part preceded by a zero byte. The domain labels are distinct and versioned:
mandate.crossacceptor/acceptor/v1over the aggregation key and the acceptor identifier gives an acceptor pseudonym. Because the aggregation key and the salt are both per authority and per window, the same acceptor produces one stable pseudonym inside a window and an unrelated one in the next.mandate.crossacceptor/checked-action-count/v1over the decimal action count and the aggregation key giveschecked_action_count_commitment.mandate.crossacceptor/limit/v1over the decimal limit in minor units, the currency and the aggregation key giveslimit_commitment.mandate.crossacceptor/aggregated-notional/v1over the decimal accumulated amount, the currency and the aggregation key givesaggregated_notional_commitment.
The fifth is the salt commitment, a plain SHA-256 over the UTF-8 label mandate.crossacceptor/salt/v1, a zero byte, and the raw salt bytes.
Set digests use a different rule. A canonical set digest is SHA-256 over the canonical JSON body of an object holding a domain label and a sorted array of values, serialised under the shared rule of recursively sorted keys, no whitespace and non ASCII escaped as lowercase \uXXXX. Sorting gives one canonical digest per set regardless of the order evidence arrived in.
Dispersion is reported as a bucket derived by integer cross multiplication, with no division and therefore no rounding. Let d be the distinct acceptor count and n the checked action count. With n zero, the class is no_checked_actions when d is zero and indeterminate otherwise. With d zero, or d greater than n, the class is indeterminate. Otherwise: four times d at or below n gives concentrated; two times d at or below n gives moderately_distributed; four times d at or below three times n gives distributed; anything else gives highly_distributed. The exact ratio is never disclosed.
The receipt envelope uses the shared Ed25519 construction over the ASCII string hive-receipt <receipt_id> <payload_sha256> <ts>.
The gates
Verification runs in this order and stops at the first failure.
- SCHEMA. Validates the envelope against the cross acceptor v1 schema, including the fixed pseudonym algorithm and scope constants. A failure means the artifact is malformed.
- ISSUER_KEY_MATCH. Resolves the envelope key through the trusted resolver. A failure means the signer is not trusted here.
- PAYLOAD_DIGEST_VALID. Recomputes the canonical digest of the signed body against
payload_sha256. A failure means the body was altered after signing. - SIGNATURE_VALID. Verifies the Ed25519 signature over the signing string. A failure means no available key produced it.
- WINDOW_BOUNDS_ORDERED. Requires the window bounds to run forwards and requires every supplied contribution to fall inside them. A failure means a contribution from outside the window was folded in.
- WINDOW_LINK. Requires the predecessor reference to be well formed, including its ordinal. A failure means the chain reference is malformed.
- WINDOW_PREDECESSOR_INTEGRITY. Requires the named predecessor window to be supplied and to verify. A failure means the previous window is missing or broken.
- WINDOW_CONTINUITY. Requires the window chain to be continuous, including agreement on currency across windows. A failure means the chain changed terms between windows.
- SALT_BINDING. Requires the disclosed salt to recompute to
salt_commitment. A failure means the salt does not open the published commitment. - CONTRIBUTION_SET_INTEGRITY. Recomputes the canonical contribution set digest from the supplied evidence and compares. A failure means the contribution multiset is not the one committed.
- CONTRIBUTION_WINDOW_MEMBERSHIP. Recomputes the coverage set digest and checks membership of the contributions in the window. A failure means the coverage evidence does not match.
- DISTINCT_ACCEPTOR_COUNT_RECOMPUTE. Recomputes the number of distinct acceptor pseudonyms from the evidence and compares to
distinct_acceptor_count. A failure means the count was inflated or understated. - DISPERSION_CLASS_RECOMPUTE. Recomputes the checked action count commitment, then derives the dispersion class from the distinct acceptor count and the checked action count and compares. It fails when the two counts do not produce a determinate class as well as when the declared class differs.
- COVERAGE_COHERENCE. Derives the coverage class from the reporting count, the expected count and whether a subset was declared, and compares. A reporting count equal to the expected count gives
complete; a positive reporting count below it givesdeclared_subsetwhen the subset was declared andundeclared_subsetwhen it was not. - CUMULATIVE_COMMITMENT_RECOMPUTE. Recomputes the limit commitment and the aggregated notional commitment from the disclosed evidence and compares both. A failure means the confidential numbers behind the relation are not the numbers committed.
- CUMULATIVE_RELATION_RECOMPUTE. Derives the relation from the exact confidential integers,
within_constraintwhen the accumulated amount is at or below the limit andexceeds_constraintotherwise, and compares to the declared relation. - NO_ACCEPTOR_IDENTITY_LEAK. Rejects any acceptor identifier reaching the signed body. A failure means an identity escaped into a field that must carry only pseudonyms and commitments.
- CROSSACCEPTOR_BOUNDARY_PRESENT. Requires the boundary text to be present and to match. A failure means the non attestation was edited or removed.
The mint path refuses every caller supplied field the service computes, at both the request level and inside the window object. It requires window_salt_hex to be 64 lowercase hex characters, requires contributions and coverage reports to be arrays, requires a cumulative constraint object, and requires each contribution to contain exactly amount_minor, acceptor_id and authorized_at and nothing else. The acceptor pseudonym is derived at mint from the raw identifier, which never enters the receipt.
The boundary
This receipt attests only that the issuing service, for the declared authority grouping and accounting window, receives the contribution and coverage evidence it describes, verifies the stated keyed pseudonym derivations under a committed window salt, recomputes the stated distinct acceptor count, dispersion class, commitments, cumulative relation, and coverage class, and signs that limited result. It does not attest that every action under the authority reaches the issuing service, that every acceptor reports, that a nonreporting acceptor is inactive, that the declared expected acceptor population is complete, that any acceptors are legally or institutionally unrelated, or that any pseudonym reveals an acceptor identity. It does not disclose the window salt, acceptor identities, contribution multiset, checked action count, exact dispersion ratio, cumulative constraint, or accumulated amount. It is not an authorization control and does not prevent, block, reverse, delay, ratify, or validate an underlying action. It does not decide whether an action, authority, constraint, report, acceptor, or actor is valid, authorized, proper, compliant, enforceable, or lawful. It does not deny, resolve, adjudicate, or affect any dispute or right.
The clause about a nonreporting acceptor not being inactive is the one that earns the instrument its credibility. Silence is not evidence of nothing happening. The receipt refuses to convert an absence of reports into an absence of activity, and instead exposes the gap through coverage_class so a reader can see exactly how much of the expected population is represented.
The clause about acceptors not being legally or institutionally unrelated is subtler and just as important. A dispersion class of highly_distributed counts distinct pseudonyms. It does not know that eight of those acceptors are subsidiaries of one group. Somebody reading a dispersion class as evidence of genuine market breadth would be over reading it, and the receipt says so.
Enumerating these makes the artifact more useful in a negotiation. A principal reading a within_constraint relation over complete coverage has something firm. The same relation over undeclared_subset coverage is a much weaker statement, and the receipt makes the difference visible rather than burying it. That is what lets both readings coexist in one instrument without the weak one masquerading as the strong one.
Adversarial cases
From test/cross-acceptor.test.mjs.
An adversarial schema field fails SCHEMA. There is nowhere to add anything.
An untrusted issuer fails ISSUER_KEY_MATCH. A payload digest tamper fails PAYLOAD_DIGEST_VALID. A signature tamper fails SIGNATURE_VALID.
An outside contribution fails WINDOW_BOUNDS_ORDERED. Pulling activity in from a neighbouring window, which is how a total gets shaped, is caught against the declared bounds.
A malformed predecessor ordinal fails WINDOW_LINK, and a missing predecessor fails WINDOW_PREDECESSOR_INTEGRITY. A window chain with a hole in it cannot be presented as continuous.
A changed continuity currency fails WINDOW_CONTINUITY. Switching currency between windows would make the chained accumulation meaningless.
A mismatched salt commitment fails SALT_BINDING. Salt shopping is the first attack on any pseudonym scheme. A different salt gives different pseudonyms and therefore a different distinct count, and the commitment blocks it.
A changed contribution digest fails CONTRIBUTION_SET_INTEGRITY, and a changed coverage digest fails CONTRIBUTION_WINDOW_MEMBERSHIP. Both evidence sets are digest bound, so the evidence cannot be swapped after the counts are computed.
An inflated contributing count fails DISTINCT_ACCEPTOR_COUNT_RECOMPUTE. This is the direct attack on dispersion. Report more distinct acceptors than the pseudonyms support and the picture looks broader than it was. The count is recomputed from the pseudonym set.
A changed action count commitment fails DISPERSION_CLASS_RECOMPUTE. The other half of the dispersion attack is to change the denominator. The action count is committed, so it cannot be adjusted to move the bucket.
An incomplete class at complete coverage fails COVERAGE_COHERENCE. Understating coverage would be an odd attack, but the gate is symmetric: the class has to be the class the counts produce, in either direction.
A changed limit commitment fails CUMULATIVE_COMMITMENT_RECOMPUTE. Raising the ceiling to turn an exceedance into compliance requires changing a commitment that is recomputed from the disclosed evidence.
A changed cumulative relation fails CUMULATIVE_RELATION_RECOMPUTE. The relation is derived from the exact confidential integers, never read from the body.
A defense in depth identifier leak fails NO_ACCEPTOR_IDENTITY_LEAK, and a defense in depth boundary mismatch fails CROSSACCEPTOR_BOUNDARY_PRESENT. Both of these are named as defense in depth in the tests, which is accurate: the pseudonym derivation should already prevent identifiers appearing, and the gate catches the case where something went wrong upstream.
Two structural tests confirm the design. The receipt contains neither the raw salt nor a raw acceptor identifier, and the mint path refuses every caller supplied computed field.
Performance
From hive-verifier-api/benchmarks/results-latest.json, measured at 2026-08-09T22:28:01.099Z at commit e167719f7d815809c6f55c5dde4495edf1c5615f over 200 iterations:
| Measure | Value |
|---|---|
| Mint p50 | 3.129 ms |
| Mint p95 | 6.394 ms |
| Verify p50 | 2.152 ms |
| Verify p95 | 2.461 ms |
| Artifact size | 2841 bytes |
Local harness measurement on one machine and one Node version, recorded on Node v20.20.1, linux/x64, two Intel Xeon cores at 2.90 GHz. It is a reproducible measurement of this code, not a production latency guarantee.
How to run it
This type has no mint route. It is produced as an export by another route and only verified here, so there is nothing to authenticate against. The verify route takes no credentials.
- Host:
https://thehiveryiq.com/v1 - Verify route:
/verify/cross-acceptor
curl -sS -X POST https://thehiveryiq.com/v1/verify/cross-acceptor -H 'content-type: application/json' -d '{"receipt": <a receipt of this type>}'
Where it sits in the canon
Family: network authority. Category: typed receipt contract.
It answers the question the Mandate Conformance Receipt explicitly disclaims. That receipt evaluates one transaction against a per transaction constraint and states in its boundary that a series of individually conforming transactions may still exceed a spending intent it cannot see. The Cumulative Mandate Receipt sees that intent within one supplied set of conformance receipts. This receipt sees it across acceptors who never share a set, and never share an identity.
It chains to itself through prior_window, so a sequence of accounting windows under one authority is verifiable end to end with continuity checks between them.
It does not replace an authorization control. It observes and reports, and the boundary says so in the signed bytes.
Status
production_deployed.