The transparency log
Every completed agreement is entered into a public, append-only log at the moment it is sealed. Anyone holding the Certificate of Completion can prove the record is in that log, and that the log has not been rewritten since — without our cooperation.
Why a log
An audit trail is only as good as the party keeping it. Ours is a hash chain sealed with a keyed MAC, which makes tampering detectable — by us. Anyone who could verify that seal could also forge it, because it takes the same secret either way. So the seal cannot travel, and a counterparty examining the record has, in the end, only our word.
That is the ceiling every e-signature platform sits under, and it is rarely stated. The usual answer is a certificate authority: buy a trusted signing certificate and the signature becomes checkable by anyone with the right root store. It works, and we intend to do it, but it moves the trust rather than removing it.
A transparency log removes a different piece. The log signs each published state of itself with a key anyone can fetch, and it can prove that any earlier state is a prefix of the current one. To rewrite history it would have to sign two statements that cannot both be true — and those signatures would be ours, permanently, in the hands of whoever kept them.
The construction is RFC 6962, the Merkle tree behind Certificate Transparency, restated in RFC 9162. We use it unmodified and deliberately so: conforming verifiers already exist in most languages, and a log that needs its operator’s own software to check is not much of a log.
What is published
Each entry commits to exactly the facts the seal covers:
| envelopeId | Which agreement |
| bundleSha256 | SHA-256 of the sealed PDF bundle |
| auditChainHead | Head of the audit hash chain at sealing |
| sealedAt | When it completed |
| recipientCount | How many people signed |
| salt | 32 random bytes, printed on the certificate |
Those fields are serialised as canonical JSON — keys sorted, no whitespace — and hashed as an RFC 6962 leaf. The log stores the hash. It never stores the fields.
Privacy
A public log of agreements would be a catastrophe if it were readable. It is not. What is published is a list of 32-byte hashes, and the salt is what keeps them opaque: without it, someone who correctly guessed the envelope, the date and the document hash still cannot produce the leaf, so they cannot confirm the guess.
The effect is that the record is verifiable by whoever you show the certificate to, and invisible to everyone else. The log reveals its own size and nothing more — not who signed, not what, not for whom.
Entries also outlive the agreements they describe. Delete an envelope and its log entry stays, with the link to it removed. A log that could be pruned would not be append-only, and a party holding a certificate can still verify a document long after the sender has cleaned up.
Verifying
The verification page takes the values off a certificate and shows the whole Merkle path from your document to the signed root. If the document is confidential, the file button hashes it in your browser and sends only the digest.
That page runs on our servers, which makes it a convenience rather than a proof. For the real thing, download the verifier — no dependencies, no secrets, and it shares no code with anything that produced your document:
curl -O https://cursiv.co/verify-transparency.mjs
node verify-transparency.mjs \
--envelope env_... --bundle signed-bundle.pdf \
--chain-head <hex> --sealed-at <iso> \
--recipients <n> --salt <hex> --leaf <index> \
--log https://cursiv.co/api/transparencyAdd --since <treeSize> to also prove the log has only appended since a head you recorded earlier, and --pin <keyId> to fail if the log is signing with anything but the key you expect.
Endpoints
All three are unauthenticated and cross-origin readable. A log only its operator can read proves nothing.
| GET /api/transparency/log | Current signed tree head, and the public key |
| GET /api/transparency/proof?leaf=&size= | Inclusion proof for one entry against a tree of that size |
| GET /api/transparency/consistency?first=&second= | Proof that the smaller tree is a prefix of the larger |
The proof endpoint takes a position, never an envelope id. An endpoint that resolved ids to positions would turn the log into an oracle for whether a given agreement exists, which is precisely what the blinding prevents. The position is on the certificate, so whoever is entitled to verify already has it.
Passing an older size is the interesting case: a verifier who kept a head from months ago can prove the entry was in that tree, independent of anything published since.
Monitoring
The strongest use of the log is not verifying your own document — it is watching. Fetch a head, keep it, and check each later head against it with a consistency proof. Anyone can do this, it costs almost nothing, and it is what makes a split view impossible to sustain rather than merely difficult.
# record a head today
curl -s https://cursiv.co/api/transparency/log > head.json
# tomorrow, prove nothing was rewritten
node verify-transparency.mjs --certificate record.json \
--log https://cursiv.co/api/transparency \
--since $(jq .head.treeSize head.json)What it does not prove
The log proves that a specific sealed document, with a specific audit chain, existed at a specific position by a specific time, and that the record has not been altered since. That is a narrow claim, and stating it precisely matters more than making it sound larger.
It does not prove who the signers were — that is what the certificate, the authentication events and the audit trail are for. It is not a qualified electronic signature under eIDAS, and it is not a substitute for a trusted timestamp from an accredited authority: the times in the log are ours, and a log operator can backdate an entry it has not yet published. What it cannot do is change one afterwards.
Fetching the public key from the log you are auditing is also circular. Record the key id once, out of band, and pin it. A log that quietly changes keys is a log worth asking about.