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 statePain
Each compliance engine produces a bespoke attestation objectBanks must integrate N APIs and parse N shapes
No canonical hashCannot cross-reference across engines
No shared signature authorityEach regulator trusts its own source
Attestations not composableCannot prove “this payment is compliant everywhere”
Audit trail fragmented across enginesRegulatory 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.

FieldTypeDescription
payment_idUUIDWarpCore internal identifier
uetrstring (36)ISO 20022 Unique End-to-End Transaction Reference
message_typestringe.g. pacs.008.001.08
amountdecimal stringInterbank settlement amount; never a float
currencyISO 4217AED, USD, etc.
debtor_bicISO 9362Debtor agent BIC
creditor_bicISO 9362Creditor agent BIC
origin_countryISO 3166-1 alpha-2Derived from debtor BIC chars 5-6
destination_countryISO 3166-1 alpha-2Derived from creditor BIC chars 5-6

4.2 Attestation slot

One per engine that contributed to the UCA.

FieldTypeDescription
engineenumCORE, MENA, SGHK, BRICS, ASEAN, AFRICA
scopestringRegion or sub-jurisdiction (e.g. Uae, Singapore)
approvedboolThis engine’s decision
predicatesSlotPredicatesAll predicates this engine evaluated
auditorstringEngine version / identifier (e.g. CBUAE_ENGINE_v1.2)
origin_hashhexOriginal engine attestation hash for traceability
attested_atRFC3339When the engine produced the attestation

4.3 Slot predicates

All optional. None means the engine did not evaluate that predicate.

FieldTypeMeaning when Some(true)
sanctions_clearedboolOFAC/UN/EU sanctions screening passed
local_sanctions_clearedboolRegional sanctions list cleared
kyc_passedboolKYC/CDD valid for both parties
travel_rule_satisfiedboolFATF Recommendation 16 obligation met
below_ctr_thresholdboolNo CTR filing required
sharia_compliantboolSharia compliance confirmed
risk_scoref64Aggregate 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:

  1. approved is true iff every slot’s approved is true.
  2. sanctions_cleared is true unless any slot has sanctions_cleared == Some(false) or local_sanctions_cleared == Some(false).
  3. kyc_passed is true unless any slot has kyc_passed == Some(false).
  4. travel_rule_satisfied is true unless any slot has travel_rule_satisfied == Some(false).
  5. below_ctr_threshold is true unless any slot has below_ctr_threshold == Some(false).
  6. sharia_compliant is None if no slot evaluated it; otherwise it is the conjunction of every non-None slot value.
  7. risk_score is 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:

  1. Object keys are emitted in lexicographic (Unicode codepoint) order.
  2. No whitespace between tokens.
  3. Strings are UTF-8, with standard JSON escaping for control chars and quotes.
  4. Numbers use their shortest lossless decimal representation.
  5. 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).

FieldTypeDescription
key_idstringHSM key identifier, stable across rotations
algorithmstringEd25519 (MUST), others optional
signature_b64stringBase64-encoded signature bytes
signed_atRFC3339When the signature was produced

A verifier:

  1. Fetches the public key associated with key_id.
  2. Decodes signature_b64.
  3. Decodes body_hash as hex.
  4. Verifies the signature against the body hash bytes.
  5. 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, verification
    • tests/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_version changes 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 SlotPredicates MUST 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

  1. 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/keys returns a JWK Set with all active and recently-rotated keys.
  2. 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 signatures array.
  3. 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

VersionPlanned
v1 (this doc)5 engines, Ed25519, JCS-style canonicalisation
v1.1JWK Set key directory, revocation registry
v2Multi-signature, ZK-proof attachment, 15+ engines
v3Full ISO 20022 TR submission

Authors: WarpCore engineering