Skip to content
Oris Docs

L4 Compliance Bundle

L4 v1 LIVE (Ed25519 proof type 0x01)

L4 is the bundle. It is the single payload that travels with every payment. Identity from L1, policy verdict from L2, compliance attestation from L3, revocation witness from L5, all bound into one signed object. v1 signs with Ed25519. v2 will add ZK proofs without changing the wire format.

What it does

The Bundle Assembler is an orchestrator. Given a tx_intent, it:

  1. Fetches the policy verdict from L2 (with the Merkle inclusion proof).
  2. Calls L3 for the Veris attestation (BLS signed).
  3. Reads the L5 revocation root and produces a non-membership witness.
  4. Reads L1 for the agent DID and tenant node.
  5. Encodes a canonical 196-byte public input.
  6. Signs the bundle with an Ed25519 recursive signature (96 bytes).
  7. Hashes the bundle twice (Keccak256 + SHA-256) for cross-chain ID parity.
  8. Returns the bundle plus a sealed envelope copy for L7 audit.

Wire format

The schema is locked. Every field has a fixed offset in the public input. Adding a field requires a protocol-level version bump.

ComplianceBundle v2 (1.5 KB total, 196-byte public input)
version: "v2"
agent_did: "did:ethr:<chain_id>:<agent_addr>" // L1
tenant_node: bytes32 // L1, namehash
policy_root: bytes32 // L2
policy_proof: bytes // L2 inclusion proof
veris_attestation: { // L3, BLS-signed
subject: did_or_address
sanctions_clean: bool
risk_tier: enum
drift_score_bp: u16
evaluated_at: u64
expires_at: u64
signer_pubkey: bytes
signature: bytes
}
tx_intent: {
counterparty: address
amount_usd_e6: u64
stablecoin: bytes4
chain_id: u64
category: bytes32
nonce: bytes32 // Redis SETNX 30 s
expires_at: u64
}
revocation_witness: { // L5
tier: u8 // 1 immediate, 2 behavioural
root: bytes32
not_present_proof: bytes
tree_size: u64
}
public_verdict: {
kya_level: u8
kya_status: bytes32
not_revoked: bool
sanctions_clean: bool
counterparty_allowed:bool
amount_under_cap: bool
}
proof_type: u8 // 0x01 Ed25519, 0x02 SP1, 0x03 Halo2
proof: bytes // 96 bytes for Ed25519

The 196 bytes of public_verdict plus the canonical hash of every other field is what the proof signs. Verifiers read the public input from the bundle, recompute the canonical hash, and check the proof.

Bundle assembly latency

StepBudget
L1 identity fetch2 ms
L2 policy verdict4 ms
L3 Veris attestation (network)6 ms
L5 revocation witness2 ms
Canonical bytes encode1 ms
Ed25519 sign1 ms
Dual-hash bundle id1 ms
AGE seal for audit4 ms
Audit log write (async)0 ms (fire and forget)
p95 total< 50 ms

Bundle id

Every bundle has a deterministic id used as a primary key across all layers:

bundle_id_keccak = keccak256(canonical_bytes)
bundle_id_sha256 = sha256(canonical_bytes)

Two hashes because the L1 chains expect Keccak256 but some non-EVM rails verify SHA-256 by default. Both ids are recorded. Either resolves to the same bundle.

Sealed envelope

L4 also produces a sealed envelope for the audit log. The envelope is AGE-encrypted with the regulator quorum’s public keys (typically 3-of-5 threshold). Inside the envelope:

  • the full unredacted bundle,
  • the KYC artifacts that back the agent identity,
  • the operator signing identity that approved the policy,
  • any escalation approvals that fired.

The envelope is unsealable only with the regulator quorum’s signatures. Oris cannot read it. See sealed envelope for the disclosure flow.

SDK example

from oris import OrisClient
client = OrisClient(...)
# Build a bundle without sending it (e.g. to verify offline first)
bundle = client.bundles.assemble(
agent_id=agent.id,
to_address="0xA1b2...",
amount=12.50,
chain="base-sepolia",
category="api_consumption",
)
print(bundle.id_keccak) # 0x...
print(bundle.id_sha256) # 0x...
print(bundle.proof_type) # 1 (Ed25519)
print(bundle.canonical_bytes_hex)
print(bundle.signature_hex) # 96 bytes

Proof types and the ZK boundary

proof_type = 0x01 → Ed25519 recursive sig (LIVE)
proof_type = 0x02 → SP1 zkVM (v2 candidate)
proof_type = 0x03 → Halo2 recursive (v2 candidate)

The wire format does not change between v1 and v2. Only the proof field flips from 96 bytes of Ed25519 to whatever the ZK system produces. The L6 verifier dispatches on proof_type and runs the right verification path.

v2 is partner-led. SDK code does not change.

Where to go next