Pactbound
Back to blog

How to Verify a Handoff Without Trusting Us

A walkthrough of checking a .pactbound bundle end to end using only the file and a public Hedera mirror node. No account, no API key, no Pactbound server.

Pactbound Team6 min read
VerificationTechnicalEvidence
A hand holding a magnifying glass over a printed financial document on a desk.
Verification is something you do to a file, not something you ask a vendor to confirm on your behalf. Photo: RDNE Stock project via Pexels. Pexels License.

You can verify a Pactbound bundle with the file, a hashing tool, and a public mirror node URL. No account, no API key, and nothing of ours in the loop. That is the whole design: a proof you have to ask us to confirm is a proof that dies with us.

There are three separate questions, and a verifier answers them separately rather than collapsing them into one result.

The three questions

QuestionChecked byFails when
Did the content change?Recomputing SHA-256 and the Merkle rootA file was edited, added, or removed
When did it exist?Fetching the anchor from a mirror nodeThe committed hash does not match, or the time is wrong
Who agreed?Reading the acknowledgment recordsNobody signed, or the record is incomplete

A bundle can pass the first and fail the third. That is a real and useful state: it means the content is intact and nobody signed for it. Reporting a single combined verdict would hide exactly the thing you needed to know.

Step one, the container

Confirm the first entry in the archive is mimetype, stored uncompressed, containing application/vnd.pactbound+zip with no trailing newline. Then check the manifest's major version is one your verifier implements. A verifier must reject a major version above its own rather than guess at it.

Step two, the content hashes

For each entry in manifest.json, recompute SHA-256 over items/<fileName> and compare it to the sha256Hash recorded there.

sha256sum items/network-diagram.pdf
A terminal window filled with monospaced output on a dark screen.

Every check in this walkthrough runs from a shell and a public URL. Nothing in the chain belongs to us. Photo: Pixabay via Pexels. Pexels License.

On a fileless receipt this step is skipped, because the bytes are not in the bundle. The hashes still are, so the rest of the chain holds.

Step three, disclosures and acknowledgments

Every disclosure and every acknowledgment is hashed after canonicalization, not as raw text. Canonicalize the object per RFC 8785, hash the result, compare.

This is the step people get wrong when writing their own checker. Hashing JSON.stringify(obj) gives you a hash of one particular serialization. Reorder two keys, or serialize from a different language, and the digest changes while the data does not. RFC 8785 fixes key order, number formatting, and string escaping so that any two implementations agree on the bytes before they hash them.

Step four, the Merkle root

Rebuild the tree and compare to merkleRoot. The leaves are, in order, each item hash tagged 0x01, then each disclosure hash tagged 0x02.

Each leaf is SHA-256(0x00 || typeTag || rawDigest) over the raw 32 bytes, and each internal node is SHA-256(0x01 || left || right). Those 0x00 and 0x01 prefixes come from RFC 6962, and skipping them is not a shortcut. Without domain separation an internal node can be replayed as a leaf, which is the CVE-2012-2459 class of bug. When a level has an odd count the last node is promoted unchanged rather than duplicated, because duplicating it is what made that CVE exploitable.

Step five, the bundle hash

bundleHash = SHA-256(JCS(manifest)). Note it is a hash of the manifest, which is why it is not a field inside the manifest. A file containing its own hash would be impossible to produce.

Step six, the anchor

This is where the timestamp stops depending on anyone's word.

Two people at a desk comparing printed documents side by side.

Inclusion, integrity and timeliness are reported separately, because an anchor that exists but commits to the wrong hash is a different finding from one that is missing. Photo: https://kaboompics.com/ via Pexels. Pexels License.

Take the topicId and sequenceNumber from a record in proofs/hedera.json and fetch it from a public mirror node:

https://mainnet-public.mirrornode.hedera.com/api/v1/topics/{topicId}/messages/{sequenceNumber}

Decode the returned message, then compare the hash it commits to against the value you recomputed in step four or five. Check that consensusTimestamp is consistent with the times recorded in the bundle.

The mirror node is operated by the network, not by us. We cannot alter what it returns, and neither can the sender. That is the entire reason the anchor is worth having: the timestamp is assigned by consensus rather than asserted by a party with an interest in the outcome.

Report inclusion, integrity, and timeliness as distinct outcomes. An anchor that exists but commits to a different hash is a very different finding from one that is simply missing.

Step seven, the signature if present

signature.json is an optional detached JWS over the canonical manifest. The algorithm is bound inside the protected header, so it falls under the signature itself. That closes the algorithm-substitution attack where someone swaps alg to none and a careless verifier agrees.

The issuer key is Ed25519, held in AWS KMS, and the public half is published at /.well-known/jwks.json.

What you cannot check from the file alone

Party identity. Confirming that the person who acknowledged is who the record says requires an off-chain key that is deliberately not shipped inside the bundle.

A wartime poster advertising a neighbourhood fingerprint station run by the War Identification Bureau.

Identity has always needed something outside the document. This wartime campaign asked people to register their fingerprints for exactly that reason. The bundle proves the record is intact, not who stood behind it. Photo: federal art project via Library of Congress. Library of Congress, no known restrictions (verify per item).

That is a privacy decision. Putting identity material in a file designed to be handed to opposing counsel and filed as an exhibit would leak personal data by construction. So the bundle proves what existed, when it existed, and that an acknowledgment record with a given hash is intact. The strength of the identity binding rests on the one-time code, the recorded consent, the IP address, and the timestamp.

We would rather write that limit down here than have someone discover it during a deposition.

The short version

Drop the file on pactbound.com/verify. It runs all seven steps in your browser and shows the three results separately. If you would rather not trust a page we wrote either, the checks above are the whole algorithm, and the specification is normative enough to implement from scratch.

A close-up of the ridge detail on a human fingertip.

Strong evidence of who acted, from the one-time code and the recorded consent, is not the same claim as cryptographic attribution. We do not make the second one. Photo: Frettie via Wikimedia Commons. CC BY 3.0.

Pactbound seals client handoffs and sign-offs into a tamper-evident record that anyone can verify without an account. See how it works.