Cert Seal on Canton
We ran it live across two Canton participants: closed on one rail, sealed, reopened under a fresh identity, with five attacks each rejected at its own check on the ledger. The kit carries those rejections as recorded evidence, and the cryptography you recompute yourself, offline, in minutes.
Move a position between venues without re-disclosing the counterparty, and without re-running the compliance checks it already passed. Built for overnight and intraday repo and collateral mobility, where the same position must be recognized in more than one place, fast, without leaking who holds it.
Built, per our patent, so each party sees only its own leg of a transfer while the full chain stays auditable to authorized parties.
Banking-grade compliance attestations, verifiable today and decades from now.
ML-DSA signatures on the validator node, per NIST FIPS 204.
The attestation format, the Passport portability protocol, and the composite signature design are shared under NDA. What you see below is one run, fully open, so you can judge the engineering before we go further.
New to this? See how the compliance gate works →
The verifier executing its seven checks against the evidence bundle.
Press Run the verifier to begin.
One signed archive, one standalone verifier, fully offline. No Canton, no Docker, no account. You establish authenticity with your own tools before running anything we shipped.
openssl.openssl.| Archive | zerovaultid-certseal-rung6.zip (4510686 bytes, reproducible) |
| Archive SHA-256 | fe876ce644321c2f22b3cd2c565b25ea6c5452912dfdc905a100f31b49e3efac |
| Verifier | node verify.js (offline, 7 checks) |
234374010ba7c648e59f26003b7915ce2c70899cd101c770cb046f5d6e3b5286
github.com/ZeroVaultID-Inc/evidence-signing-keys
Verify the file you received before trusting it. Its SHA-256 is
fe876ce644321c2f22b3cd2c565b25ea6c5452912dfdc905a100f31b49e3efac
On your machine, run sha256sum zerovaultid-certseal-rung6.zip (or shasum -a 256 on macOS) and confirm it matches.
Prefer to talk first? Contact ZeroVaultID and we will send the kit and walk your engineers through it.
Confirm the archive you received is the archive ZeroVaultID published, before you open it. Everything on this page was executed against the archive described here.
| File | zerovaultid-certseal-rung6.zip |
| Size | 4510686 bytes |
| Contents | 430 files, all under a single bundle/ directory |
| SHA-256 | fe876ce644321c2f22b3cd2c565b25ea6c5452912dfdc905a100f31b49e3efac |
Confirm the digest. Either command, run in the directory holding the download.
sha256sum zerovaultid-certseal-rung6.zip shasum -a 256 zerovaultid-certseal-rung6.zip
Both print the same line:
fe876ce644321c2f22b3cd2c565b25ea6c5452912dfdc905a100f31b49e3efac zerovaultid-certseal-rung6.zip
If that digest does not match, stop. You do not have the archive published here, and nothing further on this page applies to the file you are holding.
Extract it. Any standard zip tool will extract this archive. The form below is the one we ran end to end for this page.
python3 -m zipfile -e zerovaultid-certseal-rung6.zip .
That creates bundle/ beside the archive. Every command in the next
three sections runs from inside bundle/.
manifest.json and manifest.sig, which are the
manifest and its signature. Both numbers are correct; they count different things.
These five steps are reproduced unchanged from the bundle's own README. The order is load-bearing: steps 1 through 4 use only your tools, and none of them executes our code.
manifest-pubkey.pem for their public key, alters the artifacts freely, regenerates
manifest.json, and signs it with their private key produces a bundle that passes every
check and exits 0. Worse, because verify.js runs inside the bundle, the forger can also edit it to
print the authentic fingerprint and "7/7 PASS". Nothing the bundle prints can be trusted to expose
the swap; the verifier is not, and cannot be, its own authenticity backstop.
So the backstop is not anything verify.js prints. It is computing the
fingerprint of manifest-pubkey.pem with your own tooling and matching it against the
anchor, then verifying the signature with your own tooling, before you run any code from this bundle.
Those steps, set out under "Establishing authenticity" below, are the entire authenticity claim. The
public key ships inside this bundle and proves nothing on its own; do not trust any fingerprint quoted
inside this bundle, including any value verify.js prints.
Tools assumed. OpenSSL 3.x (Ed25519 raw verification needs the 3.0+
-rawin form), a shell with process substitution (bash or zsh) plus perl for the
hex-to-binary step in step 3, a SHA-256 command (sha256sum on Linux, or
shasum -a 256 on macOS), and grep. The node:20 clean room ships bash, perl,
and openssl but not xxd, which is why perl is the primary decoder; xxd is a host fallback only.
openssl pkey -pubin -in manifest-pubkey.pem -pubout -outform DER | openssl dgst -sha256
The hex after the "= " is the fingerprint. On the published archive it prints:
SHA2-256(stdin)= 234374010ba7c648e59f26003b7915ce2c70899cd101c770cb046f5d6e3b5286
https://github.com/ZeroVaultID-Inc/evidence-signing-keys/blob/9a3ef7eed30481a4010441829af5a0a85526fca5/README.md
If they do not match, stop. Do not run the bundle; the key is not ZeroVaultID's.
The link above pins one immutable commit and will never change, which is what makes it trustworthy and also what makes it blind: a key revoked after that commit was written cannot appear in it. Before relying on a match, open the repository's default branch and read the current key status and the revoked-key list there.
This form writes nothing into the bundle, so it works when the bundle is mounted read only. It needs a shell with process substitution (bash or zsh, not POSIX sh) and perl:
openssl pkeyutl -verify -pubin -inkey manifest-pubkey.pem -rawin -in manifest.json -sigfile <(perl -ne 'chomp; print pack("H*",$_)' manifest.sig)
Expect "Signature Verified Successfully". If it fails, stop.
Signature Verified Successfully
Host fallback, if you would rather use xxd. It writes a temporary file to /tmp (never into a read-only bundle) and removes it afterward:
xxd -r -p manifest.sig > /tmp/manifest.sig.bin openssl pkeyutl -verify -pubin -inkey manifest-pubkey.pem -rawin -in manifest.json -sigfile /tmp/manifest.sig.bin rm -f /tmp/manifest.sig.bin
manifest.json is now trusted.
grep '"verify.js":' manifest.json sha256sum verify.js
The hex in the manifest line must equal the sha256sum output. On the published
archive both are e9fa708bf48ae88354d60ebc379dacb277d2f425b64895aeb6b5206c6114cdb4.
node verify.js
Its output, including any fingerprint it prints, is meaningful only after steps 1 through 4. It is a convenience for a reviewer who has already established trust independently, never the source of that trust.
Step 5 in full. Node 20, no network, no Canton, no account. This is the complete output of the published archive, byte for byte, with nothing trimmed.
node verify.js; echo "exit $?"
=== ZeroVaultID Rung 6 bundle verifier ===
[0] [SIGNATURE] manifest signature ... PASS
signature valid over manifest.json; bundled key fp=234374010ba7c648e59f26003b7915ce2c70899cd101c770cb046f5d6e3b5286. This printed value is NOT the authenticity backstop: a re-signing forger controls it. Establish authenticity independently first (openssl fingerprint of manifest-pubkey.pem matched against the anchor, then openssl signature verify); see README section "Establishing authenticity". Anchor: https://github.com/ZeroVaultID-Inc/evidence-signing-keys/blob/9a3ef7eed30481a4010441829af5a0a85526fca5/README.md
[1] [RECOMPUTATION] manifest hash integrity ... PASS
recomputed SHA-256 of 428 files; mismatches=0, missing=0, extra=0
[2] [CONSISTENCY] destination-context ... PASS
components join "canton-1|0xDestAuthority|v1|E1" == sealed "canton-1|0xDestAuthority|v1|E1" (internal consistency only)
[3] [RECOMPUTATION] Close_C Pedersen recompute (bb.js 0x4343) ... PASS
recomputed 149a673f840dcb9233f8… == stored 149a673f840dcb9233f8… (6 inputs, no obligation term)
[4] [RECOMPUTATION] openA == SHA3-256(Sigma) ... PASS
recomputed 110bd8d2740dc7dadff8… == stored 110bd8d2740dc7dadff8…
[5] [CONSISTENCY] cross-artifact and metadata consistency ... PASS
nullifier bound at both sites; sigma.closeC==close-c.point; sigma.kScalar==close-c.scalars.k; close-c and sigma self-describing metadata equal the verifier constants
[6] [STRUCTURAL] negative-controls attestation ... PASS
5/5 rows name their own check and no other
RESULT: 7/7 PASS (verified against key fp=234374010ba7c648e59f26003b7915ce2c70899cd101c770cb046f5d6e3b5286)
exit 0
Seven checks, exit code 0. The check tags are not decoration. RECOMPUTATION means a value was re-derived from published inputs and compared, and only checks 1, 3 and 4 do that. CONSISTENCY, checks 2 and 5, compares stored values against each other or against the verifier's own hardcoded constants, with no derivation. STRUCTURAL, check 6, inspects the shape of a stored value. SIGNATURE, check 0, is the manifest signature and it fails closed: if it does not verify, the verifier aborts and checks 1 through 6 never run.
A verifier that only ever prints PASS tells you nothing. Damage the evidence deliberately and watch named checks fail. Every result below was produced by running the exact command shown.
break/, mutates that copy, runs the verifier, and leaves your archive and any
other extraction untouched. Delete break/ when you are done.
The wrapper. Identical for every recipe, run from the directory holding the archive. Substitute one mutation line from the recipes below.
rm -rf break && python3 -m zipfile -e zerovaultid-certseal-rung6.zip break cd break/bundle # apply one mutation from a recipe below node verify.js; echo "exit $?" cd ../..
artifacts/close-c.json
perl -pi -e 's/"k": "vk-cr05\+cr09"/"k": "vk-TAMPERED"/' artifacts/close-c.json
Fails check 1 (the file no longer matches its hash in the signed manifest), check 3 (the Pedersen commitment recomputes to a different point), and check 5 (sigma.kScalar no longer equals close-c.scalars.k). RESULT: 4/7 PASS, exit 1.
Run inside a fresh extracted copy, using the wrapper above.
artifacts/sigma.json
perl -pi -e 's/"customer": "Alice-BaseIdentity/"customer": "Mallory-BaseIdentity/' artifacts/sigma.json
Fails check 1 and check 4: openA is the SHA3-256 of the canonical Sigma serialization, so changing any covered field changes the digest. RESULT: 5/7 PASS, exit 1.
Run inside a fresh extracted copy, using the wrapper above.
artifacts/destination-context.json
perl -pi -e 's/"destEpoch": "E1"/"destEpoch": "E2"/' artifacts/destination-context.json
Fails check 1 and check 2: the four components no longer join to the sealed string. RESULT: 5/7 PASS, exit 1.
Run inside a fresh extracted copy, using the wrapper above.
artifacts/nullifier.json
perl -pi -e 's/"nullifier": "nu:A:rung6r3b-542e60f5"/"nullifier": "nu:A:REPLAYED"/' artifacts/nullifier.json
Fails check 1 and check 5: the nullifier is recorded at two sites and they must agree. RESULT: 5/7 PASS, exit 1.
Run inside a fresh extracted copy, using the wrapper above.
artifacts/negative-controls.json
perl -pi -e 's/Check 3 failed: verifying key not admissible/Check 4 failed: verifying key not admissible/' artifacts/negative-controls.json
Fails check 1 and check 6, which reports 4/5 rows naming their own check and no other. RESULT: 5/7 PASS, exit 1.
Run inside a fresh extracted copy, using the wrapper above.
artifacts/run.json
perl -pi -e 's/"epoch": "E1"/"epoch": "E9"/' artifacts/run.json
Fails check 1 only. RESULT: 6/7 PASS, exit 1. Nothing recomputes this file. Its integrity rests on the manifest hash and the signature over that manifest, and on nothing else. That is worth seeing directly.
Run inside a fresh extracted copy, using the wrapper above.
artifacts/ledger-refs.json
perl -pi -e 's/"offset": 2601/"offset": 9999/' artifacts/ledger-refs.json
Fails check 1 only. RESULT: 6/7 PASS, exit 1. Same limit as recipe 6: the offsets, update id and contract ids carry the run's provenance, and the verifier does not check them against a live ledger.
Run inside a fresh extracted copy, using the wrapper above.
manifest.json
perl -pi -e 's/"fileCount": 428/"fileCount": 429/' manifest.json
Fails check 0. exit 1, and the verifier prints
RESULT: FAIL at check 0 (signature). Aborting; remaining checks not run. Checks 1
through 6 never execute. This is the fail-closed path.
Run inside a fresh extracted copy, using the wrapper above.
manifest.sig
perl -pi -e 's/^(.)/sprintf("%x",(hex($1)+1)%16)/e' manifest.sig
Advances the first hex digit by one. Fails check 0, aborts, exit 1.
Run inside a fresh extracted copy, using the wrapper above.
manifest-pubkey.pem
openssl genpkey -algorithm ed25519 -out attacker-key.pem && openssl pkey -in attacker-key.pem -pubout -out manifest-pubkey.pem && rm attacker-key.pem
Fails check 0, aborts, exit 1, and prints a key fingerprint that is not
23437401.... This is the halfway point of the one forgery the signature does not stop.
A forger who also regenerates and re-signs manifest.json with that private key produces
a bundle that passes all seven checks. The only thing that catches it is step 1 of the section
above: your own openssl computes the attacker's fingerprint and it does not match the anchor.
Run inside a fresh extracted copy, using the wrapper above.
Reproduced from the bundle's own README, unchanged. Two of the Cert Seal Rail checks at the ledger gate are Check 1, Merkle inclusion, and Check 2, signature and proof of possession.
The README inside the signed bundle contains one sentence that is wrong. You should read it here rather than discover it yourself.
Under "Claim limits" the README says that two of the Cert Seal Rail checks,
Check 1 Merkle inclusion and Check 2 signature and proof of possession, are "verified off-ledger and
asserted at the gate". The second half is accurate. The first half is not. Nothing verified those two
checks in this run, at either layer. The ledger gate receives a boolean from its caller and asserts
that the boolean is true; it does not itself validate an inclusion proof or a signature. The caller
supplied true unconditionally, and the four corresponding fields recorded in the evidence,
piClose, sigSrc, sigCust and sigDst, are the
literal placeholder strings you can read for yourself in artifacts/sigma.json. The
accurate position is that Check 1 and Check 2 were asserted in and were verified nowhere.
No published value on this page changes because of this, and none of the seven checks depends on that sentence. We cannot correct it in place: the README is hashed by the manifest and the manifest is covered by the signature, so editing it would invalidate the archive you are checking. The bundle will be re-sealed with corrected text, and this note will name the replacement when it ships.
The verifier proves the artifacts it recomputes and the manifest signature. It does not prove Canton's enforcement. The ledger references point at that evidence, they do not reproduce it.
bundle/README.md, "Claim limits"This bundle does not reproduce Checks 1 and 2; there are no Merkle roots or signatures in this run to verify, and it fabricates none.
bundle/README.md, "Claim limits"The piClose, sigSrc, sigCust, and sigDst fields
inside Sigma are the run's asserted-in gate tokens, not real inclusion proofs or signatures.
Exactly what the evidence supports, and where the edges are.