Why a dataroom needs a proof
A dataroom is a folder of files that stood behind a decision: a credit agreement, bank statements, a borrowing base certificate, restated accounts. The files are what an LP, an auditor or a regulator asks for a year later. The question they ask is not "do you have the files?" but "are these the same files, and were they there when the decision was made?"
A database or a storage bucket cannot answer that on its own. As the Rekord Kloud FAQ puts it, an administrator can edit a record and leave no outside trace of the earlier value. The lender ends up asking the reviewer to trust the lender.
A proof moves the answer outside the lender's control. It has 4 parts, and this guide covers each in turn: a hash of every file that changes if any byte changes; a canonical form for the structured records, so 2 systems that hold the same data compute the same hash; a Merkle tree that batches all the hashes of one version into a single 32-byte root; and an anchor that writes that root to a public blockchain, outside the lender's systems. Sometimes this is called on-chain notarization. The files themselves never leave the lender.
For a direct lender, the dataroom is the evidence behind the spread, the scorecard and the borrowing base. The auditable AI credit underwriting guide covers how Rekord AI builds those figures; this guide covers how the evidence under them is made checkable.
Deterministic SHA-256 of the exact file bytes
Each file is hashed with SHA-256 exactly as it was submitted, byte for byte. Nothing is trimmed, re-encoded, re-paginated or reordered first. A PDF, a scanned image or an XLSX is a binary, and binaries are never normalized.
SHA-256 is specified in NIST FIPS 180-4, Secure Hash Standard. It takes a message of fewer than 264 bits and returns a 256-bit digest, 32 bytes. The standard states the purpose directly: the digests "are used to detect whether messages have been changed since the digests were generated". The same bytes give the same digest on any machine, with any tool. A changed byte gives a different digest.
NIST SP 800-107 Rev. 1 names the property that lets the digest stand in for the file: second preimage resistance, which means that given an input it is "computationally infeasible to find a second input" with the same hash value. For SHA-256 NIST puts the expected preimage resistance at 256 bits and the expected collision resistance at 128 bits.
The reason to hash the bytes rather than the extracted text is reproducibility. Text extraction from a PDF differs between libraries, versions and settings, so a hash of extracted text proves only what one tool saw on one day. The bytes are the same for every party that holds the file, so a hash of the bytes proves that the file an auditor received is the file that was anchored.
In Rekord AI, the dataroom inventory lists every file sorted by relative path, with its size, SHA-256, extension, MIME type, an empty flag and a file class, plus totals for file count, bytes, empty files and files per class. Every documentary figure in the spread cites its source document, and the SHA-256 of that file is recorded next to the figure. If a citation cannot be resolved against the inventory, the run stops and a person fixes it.
RFC 8785 canonical JSON for manifests
Binaries are hashed as they are. Structured records are different: the same data can be written with keys in a different order, different whitespace or a different number format, and each variant hashes to a different value. Manifests, inventories and certificates that a system generates need one byte-exact form before they are hashed.
RFC 8785, the JSON Canonicalization Scheme (JCS), defines that form. Object properties "MUST be sorted recursively", compared as arrays of UTF-16 code units (section 3.2.3); whitespace between tokens "MUST NOT be emitted" (section 3.2.1); numbers and strings follow the ECMAScript serialization rules (section 3.2.2); and the result "MUST be encoded in UTF-8" (section 3.2.4). Two systems that hold the same data and both apply RFC 8785 produce the same bytes, so they compute the same SHA-256.
Rekord AI applies this to the evidence revision manifest: when you publish a revision, the manifest is serialized as canonical JSON, hashed with SHA-256 and anchored as a proof through Rekord Kloud. Scorecards are serialized the same way before hashing. The rule is simple to state: raw bytes for files, canonical JSON for records the system writes.
One caveat for anyone reproducing a hash by hand. RFC 8785 restricts input to I-JSON (section
3.1): every number must be expressible as an IEEE 754 double, and NaN, Infinity and lone
surrogates must stop a compliant implementation with an error. A key-sorted
JSON.stringify gives the RFC 8785 bytes only when the record meets those limits and
no key is an integer-like string, because a JavaScript object lists such keys first, outside the
sorted order. Other records need a full implementation of the scheme.
Merkle batching as Rekord Kloud builds it
A dataroom version can hold hundreds of files. A Merkle tree folds all the hashes of one version into a single root, and gives each file a short inclusion proof back to that root.
Rekord Kloud builds the tree the same way every time, and frozen golden test vectors keep the procedure byte-identical across releases:
- each leaf hash is trimmed, lowercased and stripped of any
0xprefix, and empty values are dropped; - duplicate hashes are removed and the remaining leaves are sorted in ascending order;
- consecutive nodes are paired, and a trailing odd node pairs with itself;
- each pair is sorted, decoded from hex (base16 as defined in RFC 4648, section 8), concatenated into 64 bytes and hashed with SHA-256, level by level, until one root remains.
Because each pair is sorted before it is hashed, an inclusion proof carries only the sibling hashes in level order. There are no left or right position flags, because the verifier sorts each pair itself. And because the leaves are deduplicated and sorted before the first level is built, the root commits to the set of hashes in the version, not to their order or to how many times a hash was submitted.
Proof size grows with the logarithm of the file count. A version of 1,000 files gives a proof of 10 sibling hashes, 320 bytes, and a version of 1,000,000 files gives 20. The auditor who needs one bank statement does not need the other files, only the proof and the root.
How it differs from RFC 6962 (Certificate Transparency)
Readers who know Merkle trees from Certificate Transparency should note that RFC 6962 solves a different problem. It is presented here for comparison only. RFC 9162 obsoletes RFC 6962 and keeps the same leaf prefix, node prefix and split in its section 2.1.1.
RFC 6962 describes "publicly auditable, append-only, untrusted logs" of certificates. Its
leaves stay in insertion order, and its consistency proofs (section 2.1.2) "prove the
append-only property of the tree". Section 2.1 defines a leaf hash as
SHA-256(0x00 || d(0)) and an interior node as
SHA-256(0x01 || MTH(D[0:k]) || MTH(D[k:n])), where k is "the largest power
of two smaller than n". The RFC explains the 2 prefix bytes: "This domain separation is
required to give second preimage resistance." A Merkle audit path (section 2.1.1) is defined
for a leaf index m in a tree of size n, so the verifier needs both values to
know, at each level, whether the sibling sits on the left or on the right.
The Rekord Kloud tree commits to one version, not to an ordering. Leaves are sorted, pairs are sorted, a trailing odd node pairs with itself instead of a split at the largest power of two, and no domain-separation bytes are added. The only inputs to the verifier are the file hash, the sibling hashes and the root. Where RFC 6962 proves this entry was appended at position m, the Kloud tree proves this file was in this version. Version history in Kloud comes from the append-only sequence of anchored versions, each with its own root, rather than from one growing tree.
A public log of certificates needs ordering and consistency proofs; a dataroom version needs a small, position-free proof that a given file was part of a given anchored set. Know which one you are verifying, because a verifier written for one will reject proofs from the other.
On-chain anchoring: VeChain by default, Litecoin and BSV
Only the Merkle root goes on-chain. A root is a 32-byte SHA-256 value that holds none of the document content, so the credit agreement, the bank statements and the borrower's identity stay with you.
Rekord Kloud anchors to VeChain by default, and to Litecoin and BSV. Each anchor records its confirmations and the anchored Merkle hash, and the Proof Console links every anchor to a public explorer for that chain. When you verify, the version service re-checks every anchor live against the chain. A reviewer who distrusts the Proof Console can look the transaction up in the explorer and read the root from the chain directly.
Every change to the dataroom is appended as a new version with its own anchored root. Earlier versions and their anchors stay where they are, so a later edit to a record is detectable by anyone holding the original bytes. The lender cannot replace the root of an earlier version without leaving the earlier anchor in place. This is what makes the record tamper-evident rather than merely stored.
The evidence revision chain in Rekord AI
An evidence revision is a published set of dataroom files behind a credit decision. Rekord AI writes its manifest, serializes it as canonical JSON, hashes it with SHA-256 and anchors it through Rekord Kloud. Each revision points to the previous one, so an auditor can walk the full history of the dataroom.
The evidence revision manifest records the revision number, the previous revision, who
published it and when, and for each member file its logical document ID, proof ID, version ID,
filename, MIME type, fileHash and size. The fileHash is the SHA-256
from the dataroom inventory, so the manifest ties each member back to exact bytes.
Here is the shape, with the real field names and illustrative values. The 3 members are the
files from the worked example below, so each fileHash is a real value you can
recompute.
{
"kind": "underwriting_evidence_revision",
"schemaVersion": 1,
"canonicalizationVersion": 1,
"dataroomId": "example-dataroom",
"revisionId": "example-revision-2",
"revisionNumber": "2",
"previousRevisionId": "example-revision-1",
"reason": "September bank statement and borrowing base certificate added (illustrative)",
"publishedBy": "example-analyst",
"publishedAt": "2026-10-01T09:00:00Z",
"members": [
{
"logicalDocumentId": "example-doc-1",
"proofId": "example-proof-1",
"versionId": "example-version-1",
"filename": "bank-statement-2026-09.csv",
"mimeType": "text/csv",
"fileHash": "f87e0cb69600c5535196da02ea0ce2b17dbfc5818b1556e34418798cab20b09d",
"sizeBytes": "103"
},
{
"logicalDocumentId": "example-doc-2",
"proofId": "example-proof-2",
"versionId": "example-version-2",
"filename": "borrowing-base-certificate.json",
"mimeType": "application/json",
"fileHash": "370d978249f63190a543c1692ff2fee35b8c534268b6ff03194e655a6b5e2706",
"sizeBytes": "229"
},
{
"logicalDocumentId": "example-doc-3",
"proofId": "example-proof-3",
"versionId": "example-version-3",
"filename": "credit-agreement-excerpt.txt",
"mimeType": "text/plain",
"fileHash": "1fc5ece60a0b53d4dc51037b838343b2198ca86b87c162aa8affb1be03b9624e",
"sizeBytes": "321"
}
]
}
In canonical form the manifest is 1,170 bytes and hashes to
0286782a8976e6e2d9b869787922e0965c57319ae0a4dd3772c1882151952a3f. That is the value anchored for the revision. The pretty-printed
version above hashes to something else, which is why the canonical form is the only one that
counts.
When a borrower sends restated accounts, the new revision records the new file version, and the
earlier revision still shows what stood behind the earlier decision. Nothing is overwritten.
previousRevisionId is an identifier, not a hash; the tamper evidence for each
revision comes from its own anchored manifest hash, and the link lets the auditor find the one
before it.
Auditor verification procedure
The procedure needs the files, the manifest, the inclusion proofs and read access to the Proof Console or a public explorer. It does not need access to the lender's database.
- Take the anchored proof of the revision from Rekord Kloud and the evidence revision manifest the lender hands over.
- Serialize the manifest as canonical JSON, hash it with SHA-256 and compare the result with the anchored hash. A match shows the manifest is the one that was published.
- For each member file, recompute the SHA-256 of the exact bytes and compare it with its
fileHash. Any mismatch shows the file changed after publication. - For any file you want to tie to an anchored version on its own, take its inclusion proof: combine the file hash with each sibling hash in level order, sorting each pair before hashing it, and compare the final value with the Merkle root of that version.
- Open the anchoring transaction in the public explorer the Proof Console links to, and compare the root on-chain with the root you computed.
- Follow
previousRevisionIdto the earlier revision and repeat, until the chain of revisions is checked in order. - Recompute the metrics from the cited figures. As the Rekord AI FAQ states, the formulas are deterministic, so the same inputs return the same DSCR, Debt Yield or borrowing base.
Steps 3 and 4 can be done in the browser with the SHA-256 dataroom verifier: it hashes a file locally, without uploading it, and checks the hash against a Merkle root and an inclusion proof. Any standard SHA-256 tool gives the same leaf hashes.
Plain checksums, an RFC 6962 log and the Rekord Kloud tree compared
The table compares the 3 approaches on the points a reviewer asks about when deciding what a given proof shows.
| Criterion | Plain file checksums | RFC 6962 Merkle log | Rekord Kloud sorted-pair tree + anchor |
|---|---|---|---|
| Tamper evidence | Detects a changed file only if the checksum list itself was kept somewhere the editor could not reach. | Detects a changed entry against a signed tree head (RFC 6962, section 4.3: tree size, timestamp, root hash and signature); depends on the log operator's signature and consistency proofs. | Detects a changed file against a root anchored on a public chain, outside the systems of every party; earlier versions and their anchors stay in place. |
| Single-file proof | None. The reviewer needs the whole checksum list to check one file. | Audit path from the leaf to the tree head. | Inclusion proof from the leaf to the version root. |
| Proof size | The full list, linear in the number of files. | Logarithmic: about log2(n) sibling hashes, plus the leaf index and tree size. | Logarithmic: about log2(n) sibling hashes, nothing else. |
| Position data | Not applicable. | Required. Leaves stay in insertion order and the path depends on the leaf index. | None. Leaves and pairs are sorted, so the proof carries no position flags. |
| Outside record | None, unless the list is sent to a third party. | The log operator's signed tree head, which carries a timestamp. | The anchoring transaction on VeChain, Litecoin or BSV, with its confirmations, linked from the Proof Console to a public explorer. |
| What goes on-chain | Nothing. | Nothing; the log is its own record. | The 32-byte Merkle root of the version, and no document content. |
Worked example: 3 dataroom files, a root and an inclusion proof
The example below is illustrative. The borrower, the figures and the file contents are invented, but every hash is real and computed when this page is built, so you can reproduce each value with any SHA-256 tool.
Step 1: hash each file verbatim
A lender's dataroom for one facility holds 3 files: an excerpt of the credit agreement, one
line of a bank statement export and a borrowing base certificate. Here are the exact contents
of the first 2, UTF-8 encoded, with every line ending in a single line feed
(0x0A).
CREDIT AGREEMENT (EXCERPT, ILLUSTRATIVE)
Section 2.01 Revolving Commitments. Each Lender severally agrees to make Revolving Loans
to the Borrower from time to time during the Availability Period in an aggregate principal
amount that will not result in such Lender's Revolving Exposure exceeding such Lender's Commitment.
posting_date,description,debit,credit,balance
2026-09-30,Operating account closing balance,0,0,1842310
Each file is hashed exactly as submitted. A re-export that changes 1 byte, such as a CRLF line ending or a trailing space, gives a different hash. A real credit agreement would be a PDF; its bytes are hashed the same way, with no text extraction in between.
Step 2: canonicalize the borrowing base certificate with RFC 8785
The certificate is structured data that the borrower's system exported with unsorted keys and uneven spacing.
{
"certificateId": "example-bbc-2026-09",
"borrower" : "Example Holdings LLC (illustrative)",
"asOf": "2026-09-30",
"eligibleReceivables": { "value": 10000000, "unit": "USD" },
"advanceRatePct": 85,
"borrowingBase": { "value": 8500000, "unit": "USD" }
} Here is the same record in RFC 8785 canonical form.
{"advanceRatePct":85,"asOf":"2026-09-30","borrower":"Example Holdings LLC (illustrative)","borrowingBase":{"unit":"USD","value":8500000},"certificateId":"example-bbc-2026-09","eligibleReceivables":{"unit":"USD","value":10000000}}
The 265-byte "as exported" text hashes to
c2e89c341ae5c384d4ef5e7f06945e6a6f7181d221a2c60e8e1579617a743f7c, while the 229-byte
canonical form hashes to 370d978249f63190a543c1692ff2fee35b8c534268b6ff03194e655a6b5e2706. Same data, 2 hashes. The lender
canonicalizes the record before it submits it, so every party that holds the data computes
the second value.
Step 3: the dataroom inventory and the 3 leaf hashes
The dataroom inventory lists the files sorted by relative path with their size and SHA-256. Here is an illustrative excerpt with the computed values. The key names in this excerpt are placeholders for the shape; the manifest in the revision chain section above uses the real field names.
{
"files": [
{
"path": "bank-statement-2026-09.csv",
"sizeBytes": 103,
"sha256": "f87e0cb69600c5535196da02ea0ce2b17dbfc5818b1556e34418798cab20b09d",
"extension": "csv"
},
{
"path": "borrowing-base-certificate.json",
"sizeBytes": 229,
"sha256": "370d978249f63190a543c1692ff2fee35b8c534268b6ff03194e655a6b5e2706",
"extension": "json"
},
{
"path": "credit-agreement-excerpt.txt",
"sizeBytes": 321,
"sha256": "1fc5ece60a0b53d4dc51037b838343b2198ca86b87c162aa8affb1be03b9624e",
"extension": "txt"
}
],
"totals": {
"fileCount": 3,
"bytes": 653,
"emptyFiles": 0
}
} 1fc5ece60a0b53d4dc51037b838343b2198ca86b87c162aa8affb1be03b9624e credit-agreement-excerpt.txt (321 bytes)
f87e0cb69600c5535196da02ea0ce2b17dbfc5818b1556e34418798cab20b09d bank-statement-2026-09.csv (103 bytes)
370d978249f63190a543c1692ff2fee35b8c534268b6ff03194e655a6b5e2706 borrowing-base-certificate.json (229 bytes) Step 4: build the Merkle root
With 3 leaves, the first 2 sorted leaves form node N1. The third leaf is the odd node, so it pairs with itself to form N2. N1 and N2 are then sorted and hashed into the root. Which file ends up as the odd node depends only on how the hashes sort.
root = SHA-256( sort(N1, N2) )
= f39f11a5462c8d8d1c1a8168ae5bf4a4ae6579a8ed51678bfdea741b0d094256
N1 = SHA-256( 1fc5ece6… || 370d9782… )
= 518da4e10845710aeb137e09a1eba69625c00e6e25af047c3369ca00073756f2
N2 = SHA-256( f87e0cb6… || f87e0cb6… ) odd node, paired with itself
= d63044240bfd8480d70de8d9be996b36e5cb701edb4f6f75a6e4fba32e290988
leaf 1 = 1fc5ece60a0b53d4dc51037b838343b2198ca86b87c162aa8affb1be03b9624e credit-agreement-excerpt.txt
leaf 2 = 370d978249f63190a543c1692ff2fee35b8c534268b6ff03194e655a6b5e2706 borrowing-base-certificate.json
leaf 3 = f87e0cb69600c5535196da02ea0ce2b17dbfc5818b1556e34418798cab20b09d bank-statement-2026-09.csv
The || sign means byte concatenation of the 32-byte decoded values, so each
SHA-256 input is 64 bytes.
Step 5: the inclusion proof for the bank statement
An auditor who only needs the bank statement does not need the other 2 files. The inclusion proof gives the sibling hash at each level between that leaf and the root, and nothing else.
{
"file": "bank-statement-2026-09.csv",
"leaf": "f87e0cb69600c5535196da02ea0ce2b17dbfc5818b1556e34418798cab20b09d",
"siblings": [
{
"level": 0,
"hash": "f87e0cb69600c5535196da02ea0ce2b17dbfc5818b1556e34418798cab20b09d"
},
{
"level": 1,
"hash": "518da4e10845710aeb137e09a1eba69625c00e6e25af047c3369ca00073756f2"
}
],
"root": "f39f11a5462c8d8d1c1a8168ae5bf4a4ae6579a8ed51678bfdea741b0d094256"
} Step 6: verify
- Hash the file bytes you received with SHA-256 and compare the result with
leaf. - For each sibling in level order, sort the pair of your current hash and the sibling, hex-decode both, concatenate them and hash the 64 bytes with SHA-256.
- Compare the final value with
root. - Compare
rootwith the Merkle hash anchored for that version, in the Proof Console or in a public explorer for the chain.
step 1 h = SHA-256(file bytes) = f87e0cb69600c5535196da02ea0ce2b17dbfc5818b1556e34418798cab20b09d
step 2 h = SHA-256(sort(h, sib[0])) = d63044240bfd8480d70de8d9be996b36e5cb701edb4f6f75a6e4fba32e290988
step 3 h = SHA-256(sort(h, sib[1])) = f39f11a5462c8d8d1c1a8168ae5bf4a4ae6579a8ed51678bfdea741b0d094256
step 4 h == anchored root = true Step 7: what a tampered file looks like
Suppose the closing balance in the bank statement were edited after the version was anchored, changing 1 digit. The same proof no longer recomputes the root.
original …closing balance,0,0,1842310
tampered …closing balance,0,0,1842319 (1 byte changed)
SHA-256(tampered bytes) = 4f9d555b284e14ae1d366032b37e44f7acb211dd0d10a998823c8f0253668b0f
recomputed root = d4ba13ff7b77a2236f4e19f7638408bf81665716a99e4b41ad9f33f485524642
matches anchored root = false The proof and the root are unchanged; only the file differs, and the mismatch is visible to anyone who holds the proof. This check is what the SHA-256 dataroom verifier runs in the browser.
Entity separation
Two entities share the work behind a dataroom proof, and the split is deliberate.
Rekord AG, Zug, Switzerland, builds Rekord AI and Rekord Kloud, read through the Proof Console (see For Institutions). It is a software provider. It does not take deposits, manage vaults or issue financial products, and it does not hold depositor funds.
Rekord Capital Markets (RCM) is the separate vault and credit operator. It deploys capital, runs the credit strategies behind the Rekord Vaults and the digital asset credit line, and publishes vault NAV. The records behind those vaults are hashed and anchored with the procedure in this guide, so the same checks are open to depositors and auditors of the vaults as to a direct lender's LPs.
Allocators and compliance teams that want the full product context can start from For Institutions. The same hashing and anchoring procedure is applied to supply-chain evidence in the EU Digital Product Passport guide.
Sources
- FIPS 180-4, Secure Hash Standard (SHS), NIST, August 2015. Supports: SHA-256 message bound, 256-bit digest and purpose of a digest (SHA-256 of the bytes section); 32 bytes per sibling hash and per anchored root.
- SP 800-107 Rev. 1, Recommendation for Applications Using Approved Hash Algorithms, NIST, August 2012, section 4.1. Supports: second preimage resistance, 256-bit preimage and 128-bit collision strength of SHA-256. NIST announced in December 2022 that it plans to withdraw this publication once replacement guidance is published.
- RFC 8785: JSON Canonicalization Scheme (JCS), RFC Editor, Independent Submission Stream, 2020, sections 3.1 to 3.2.4. Supports: the canonical JSON section, the I-JSON caveat and the worked example step 2.
- RFC 6962: Certificate Transparency, IETF, June 2013, sections 1, 2.1, 2.1.1, 2.1.2 and 4.3. Supports: the RFC 6962 comparison section and the RFC 6962 column of the comparison table.
- RFC 9162: Certificate Transparency Version 2.0, IETF, 2021, abstract and section 2.1.1. Supports: RFC 9162 obsoletes RFC 6962 and keeps the same leaf prefix, node prefix and split.
- RFC 4648: The Base16, Base32, and Base64 Data Encodings, IETF, October 2006, section 8. Supports: hex (base16) decoding of the leaf and node hashes.
- Rekord Kloud, Rekord AG. Supports: the Merkle tree procedure and golden test vectors, the anchoring chains, confirmations and explorer links, the live re-check of anchors, append-only versions, 32-byte root only on-chain, and the database-edit statement in the first section.
- Rekord AI, Rekord AG. Supports: dataroom inventory fields, evidence revision manifest fields and anchoring, source citations with the file SHA-256, the citation kill-gate and deterministic metrics.
- For Institutions, Rekord AG. Supports: Rekord Capital Markets (RCM) operates the vaults and credit strategies; Rekord AG, Zug, provides the technology.
- EU Digital Product Passport guide, Rekord AG. Supports: the same Merkle procedure and worked-example layout used in this guide.