WarpCore / Documentation
Unified Compliance Attestation
SOURCE · docs/specs/uca-v1.md · synced from the engineering repo
Unified Compliance Attestation — Specification v1
Status: Draft for ISO/TC 68 submission
Schema identifier: uca/v1
Document version: 2026-04-11
Reference implementation: crates/warpcore-uca
Reference endpoint: POST /v1/compliance/uca
1. Abstract
A cross-border ISO 20022 payment routinely touches five or more independent compliance regimes: the originator’s AML authority, the beneficiary’s AML authority, FATF Recommendation 16 (travel rule), sanctions screening against multiple lists, and jurisdiction-specific reporting thresholds (CTR / STR / Sharia). Today each regime produces its own attestation, its own audit artifact, and its own failure mode.
The Unified Compliance Attestation (UCA) is a single cryptographically-signed JSON object that composes all of these into one artifact. A single regulator — or counterparty bank, or auditor — can verify the complete compliance position of a payment with one hash check and one signature verification. The UCA is designed to be filed as an ISO 20022 message extension and is intentionally minimal so that adoption does not require a central committee process to change its meaning.
2. Problem
| Current state | Pain |
|---|---|
| Each compliance engine produces a bespoke attestation object | Banks must integrate N APIs and parse N shapes |
| No canonical hash | Cannot cross-reference across engines |
| No shared signature authority | Each regulator trusts its own source |
| Attestations not composable | Cannot prove “this payment is compliant everywhere” |
| Audit trail fragmented across engines | Regulatory review takes days, not seconds |
A UCA solves all five.
3. Non-goals
- UCA does not replace the underlying compliance engines. It composes their outputs.
- UCA does not encode the reasoning for a decision. That lives in each
engine’s native audit log, referenced by
origin_hash. - UCA does not encode PII. The underlying engines do, but the UCA surfaces only predicate outcomes.
- UCA does not encode proof material (ZK proofs, Merkle paths). Those
live in a separate envelope (see
docs/specs/zk-envelope-v1.md).
4. Structure
A UCA is a JSON object with the following top-level fields:
{
"schema_version": "uca/v1",
"uca_id": <UUID v4>,
"subject": <UcaSubject>,
"slots": [ <AttestationSlot>, ... ],
"summary": <UcaPredicateSummary>,
"body_hash": <64-char hex SHA-256>,
"assembled_at": <RFC3339 UTC>,
"signature": <UcaSignature | null>
}
4.1 Subject
Binds the attestation to a specific payment. Every field here is carried verbatim into the body hash; mutating any of them after signing invalidates the signature.
| Field | Type | Description |
|---|---|---|
payment_id | UUID | WarpCore internal identifier |
uetr | string (36) | ISO 20022 Unique End-to-End Transaction Reference |
message_type | string | e.g. pacs.008.001.08 |
amount | decimal string | Interbank settlement amount; never a float |
currency | ISO 4217 | AED, USD, etc. |
debtor_bic | ISO 9362 | Debtor agent BIC |
creditor_bic | ISO 9362 | Creditor agent BIC |
origin_country | ISO 3166-1 alpha-2 | Derived from debtor BIC chars 5-6 |
destination_country | ISO 3166-1 alpha-2 | Derived from creditor BIC chars 5-6 |
4.2 Attestation slot
One per engine that contributed to the UCA.
| Field | Type | Description |
|---|---|---|
engine | enum | CORE, MENA, SGHK, BRICS, ASEAN, AFRICA |
scope | string | Region or sub-jurisdiction (e.g. Uae, Singapore) |
approved | bool | This engine’s decision |
predicates | SlotPredicates | All predicates this engine evaluated |
auditor | string | Engine version / identifier (e.g. CBUAE_ENGINE_v1.2) |
origin_hash | hex | Original engine attestation hash for traceability |
attested_at | RFC3339 | When the engine produced the attestation |
4.3 Slot predicates
All optional. None means the engine did not evaluate that predicate.
| Field | Type | Meaning when Some(true) |
|---|---|---|
sanctions_cleared | bool | OFAC/UN/EU sanctions screening passed |
local_sanctions_cleared | bool | Regional sanctions list cleared |
kyc_passed | bool | KYC/CDD valid for both parties |
travel_rule_satisfied | bool | FATF Recommendation 16 obligation met |
below_ctr_threshold | bool | No CTR filing required |
sharia_compliant | bool | Sharia compliance confirmed |
risk_score | f64 | Aggregate risk [0.0, 1.0] |
4.4 Predicate summary
The conjunction of every slot’s predicates. Any single engine’s false
flips the summary’s approved to false.
Rules:
approvedistrueiff every slot’sapprovedistrue.sanctions_clearedistrueunless any slot hassanctions_cleared == Some(false)orlocal_sanctions_cleared == Some(false).kyc_passedistrueunless any slot haskyc_passed == Some(false).travel_rule_satisfiedistrueunless any slot hastravel_rule_satisfied == Some(false).below_ctr_thresholdistrueunless any slot hasbelow_ctr_threshold == Some(false).sharia_compliantisNoneif no slot evaluated it; otherwise it is the conjunction of every non-None slot value.risk_scoreis the maximum across all slots.
5. Canonicalisation
The body_hash is SHA-256 over the canonical JSON encoding of a
body view — the UCA minus uca_id, minus body_hash, minus
signature. The assembled_at timestamp IS included.
Canonical JSON follows these rules:
- Object keys are emitted in lexicographic (Unicode codepoint) order.
- No whitespace between tokens.
- Strings are UTF-8, with standard JSON escaping for control chars and quotes.
- Numbers use their shortest lossless decimal representation.
- Arrays preserve insertion order — but see §6 on slot ordering.
This is compatible with RFC 8785 (JCS) with one deviation: we emit timestamps as pre-formatted RFC3339 strings rather than attempting numeric canonicalisation for them.
6. Slot ordering
To produce a stable hash regardless of the order in which engines were invoked, slots are sorted before hashing. The canonical order is:
CORE, AFRICA, ASEAN, BRICS, MENA, SGHK
(i.e., CORE first, then regional engines alphabetically by label).
Inserting the same set of slots in any order must produce the same
body_hash.
7. Signature
The signature covers the body_hash bytes (raw, not hex-encoded).
| Field | Type | Description |
|---|---|---|
key_id | string | HSM key identifier, stable across rotations |
algorithm | string | Ed25519 (MUST), others optional |
signature_b64 | string | Base64-encoded signature bytes |
signed_at | RFC3339 | When the signature was produced |
A verifier:
- Fetches the public key associated with
key_id. - Decodes
signature_b64. - Decodes
body_hashas hex. - Verifies the signature against the body hash bytes.
- Independently recomputes the body hash from the rest of the UCA
and compares it byte-for-byte to the stored
body_hash. This catches any post-signing mutation of fields that would otherwise go undetected.
Only if all five checks pass is the UCA considered valid.
8. Security properties
- Tamper evidence. Any mutation of the subject, slots, summary, or assembled_at changes the recomputed body hash, breaking verification.
- Signature binding. The signature covers the body hash, so a signature from one UCA cannot be moved to another.
- Slot integrity. Each slot carries the originating engine’s own attestation hash, so a verifier can cross-check the UCA against the engine’s native log.
- No PII surface. Predicate outcomes only. No names, no accounts.
- Deterministic hash. Equal logical content yields equal bytes, which is required for audit replay.
9. HTTP API
POST /v1/compliance/uca
Authorization: Bearer <JWT>
Content-Type: application/json
{
"xml": "<Document xmlns=\"urn:iso:std:iso:20022:tech:xsd:pacs.008.001.08\">...</Document>",
"regions": ["mena", "sghk", "brics"]
}
Required role: Compliance or Admin.
Response (approved):
200 OK
Content-Type: application/json
{
"uca": { ...full UCA... },
"approved": true,
"rejecting_engines": []
}
Response (any engine rejects):
403 Forbidden
Content-Type: application/json
{
"uca": { ...full UCA with summary.approved = false... },
"approved": false,
"rejecting_engines": ["MENA", "CORE"]
}
The UCA is always returned — even on rejection — so that the caller has an auditable artifact of the denial decision.
10. ISO 20022 mapping path
The UCA is designed to be filed as an ISO 20022 message extension on any payment message (pacs.008, pacs.009, pain.001, etc.). The proposed embedding is:
<SplmtryData>
<PlcAndNm>WarpCore/UCA</PlcAndNm>
<Envlp>
<UnifiedComplianceAttestation>
... JSON body verbatim ...
</UnifiedComplianceAttestation>
</Envlp>
</SplmtryData>
Alternatively, a UCA can accompany a payment out-of-band via a simple
reference hash embedded in the message’s RgltryRptg field, with the
full UCA stored in a parallel regulator-accessible store.
Path to ISO/TC 68 adoption: file as a Technical Report (TR) first, targeting 2027 publication. In parallel, seek endorsement from:
- SWIFT CBPR+ working group (Brussels)
- MAS ISO 20022 Industry Advisory Group (Singapore)
- ECB TIPS Consultative Group (Frankfurt)
- FATF Virtual Assets Contact Group (Paris)
11. Reference implementation
- Crate:
warpcore-uca - Key files:
src/lib.rs— types, builder, signing, verificationtests/roundtrip.rs— 9 tests (build, sign, verify, tamper, conflict)
- Dependencies:
warpcore-hsm(HSM Ed25519),warpcore-models(ComplianceResult),warpcore-{mena,sghk,brics,asean,africa}(regional attestation structs) - Test count: 9 passing
- Test evidence:
cargo test -p warpcore-uca
12. Compatibility and versioning
schema_versionchanges on any breaking field removal or semantic change.- New optional predicates MAY be added in a minor revision. A verifier
encountering an unknown predicate in
SlotPredicatesMUST ignore it. - New engine labels MAY be added. A verifier encountering an unknown
engine label MUST treat the slot as opaque but still include it in the
conjunction if it declares
approved: true.
13. Open questions
- Key rotation across UCAs. If the HSM key rotates, historical UCAs
remain valid under the old key but new UCAs use the new key. A
verifier needs a key directory. Propose: HTTP
GET /v1/compliance/uca/keysreturns a JWK Set with all active and recently-rotated keys. - Multi-signature UCAs. Should a UCA support multiple signatures
(e.g., one from WarpCore, one from a bank’s own HSM)? v1 says no. v2
may add a
signaturesarray. - Revocation. If a UCA is later found to be incorrect (engine bug, missed sanctions match), how does a regulator revoke it? Propose: a parallel revocation registry with reference hashes, reviewed at verification time.
14. Test vectors
See crates/warpcore-uca/tests/roundtrip.rs for machine-checkable
vectors. Human-readable form:
Vector A — core only, approved
{
"schema_version": "uca/v1",
"subject": {
"payment_id": "00000000-0000-0000-0000-000000000000",
"uetr": "11111111-2222-3333-4444-555555555555",
"message_type": "pacs.008.001.08",
"amount": "50000.00",
"currency": "AED",
"debtor_bic": "NRKEAEAD",
"creditor_bic": "DBSSSGSG",
"origin_country": "AE",
"destination_country": "SG"
},
"slots": [ { "engine": "CORE", ... "approved": true } ],
"summary": {
"sanctions_cleared": true,
"kyc_passed": true,
"travel_rule_satisfied": true,
"below_ctr_threshold": true,
"sharia_compliant": null,
"risk_score": 0.05,
"approved": true
}
}
Vector B — core + MENA, partial rejection
Core approves, MENA rejects sanctions. Summary.approved becomes false.
See tests/roundtrip.rs::partial_rejection_flips_approved_false.
15. Roadmap
| Version | Planned |
|---|---|
| v1 (this doc) | 5 engines, Ed25519, JCS-style canonicalisation |
| v1.1 | JWK Set key directory, revocation registry |
| v2 | Multi-signature, ZK-proof attachment, 15+ engines |
| v3 | Full ISO 20022 TR submission |
Authors: WarpCore engineering