Storing Documents on the Blockchain
Storing documents or document hashes on a blockchain provides functionality such as validation and tamper resistance. This article shows how to store document hashes on a blockchain and how to validate those blocks.

Using blockchain technology to store documents or document hashes on a blockchain provides many advantages including easy validation and tamper resistance. In an older blog article, we explained how to store signed documents on a separate blockchain for each document.
The ASP.NET Core (.NET 6) sample project in this article is based on the same concept, but uses only one blockchain to store all transactions and to validate document hashes based on the integrity of previous blocks.
Tamper Resistance
Each block in a blockchain is cryptographically linked to the previous block by hashing the previous block hash into the current block hash. The highly effective tamper resistance of a blockchain prevents document fraud and enables easy validation processes. Storing a complete document on a blockchain is technically possible, but file size limitations might prevent you from utilizing this strategy. Hashes are smaller in size and therefore a more efficient option to store documents on a blockchain. Every time a document is changed, the cryptographic hash will change. As the block hash contains the document data, the blockchain can be used to verify the document by validating the integrity of the blockchain.
In this sample, the hash of each block is created by hashing the following components:
- Timestamp
- Previous block hash
- Data (our document hash)
- Nonce
A nonce ("number only used once") is a number added to the block hash that, when rehashed, meets the difficulty level restrictions.
internal string GenerateBlockHash()
{
SHA256 sha256 = SHA256.Create();
byte[] bInput =
Encoding.ASCII.GetBytes($"{TimeStamp}-{PreviousBlockHash ?? ""}-{Data}-{Nonce}");
byte[] bOutput = sha256.ComputeHash(bInput);
return Base64UrlEncoder.Encode(bOutput);
}
If any component in the blockchain is tampered, the complete blockchain is invalid. In this sample, the Data that is stored is an MD5 hash of the signed document and additional information:
public class SignedDocument
{
public string Hash { get; set; }
public string Signer { get; set; }
public string DocumentId { get; set; }
}
A simplified workflow is shown in the illustration below:
Storing the Document
This sample uses the TX Text Control DocumentViewer to sign a document. The signed PDF is stored as a hash on the blockchain and can be downloaded locally. After a document is stored, the resulting blockchain JSON looks similar this:
{
"Filename":"App_Data/Blockchains/documents.bc",
"Chain":[
{
"Index":0,
"TimeStamp":"2022-01-21T16:21:19.201057+01:00",
"PreviousBlockHash":null,
"BlockHash":"7CFt0qfPXVqFHi93Cbc2bYj2u5FmspegLSrKLA9grbE",
"Data":"{}",
"Nonce":0
},
{
"Index":1,
"TimeStamp":"2022-01-21T16:21:19.2232914+01:00",
"PreviousBlockHash":"7CFt0qfPXVqFHi93Cbc2bYj2u5FmspegLSrKLA9grbE",
"BlockHash":"003lDVZDCCyy08DME81QuWqtbpiSYCC2Hq--dUUvbhk",
"Data":"{\"Hash\":\"c20ed3409dbcd3b89af77c9d47e5d9d2\",\"Signer\":\"Tim Typer\",\"DocumentId\":\"6003f9e8-3340-4231-82ad-2dd7b721142f\"}",
"Nonce":15421
},
{
"Index":2,
"TimeStamp":"2022-01-21T16:22:43.6496569+01:00",
"PreviousBlockHash":"003lDVZDCCyy08DME81QuWqtbpiSYCC2Hq--dUUvbhk",
"BlockHash":"00HamdzSwZBB5Em1gUlFGu8B3hwrhFXH3uBR2FOwFGs",
"Data":"{\"Hash\":\"4cd18f7b2e0200de1d4715ef2c92492e\",\"Signer\":\"Tim Typer\",\"DocumentId\":\"1d0394b7-660e-4408-a447-0d77f5bc39b9\"}",
"Nonce":241
}
],
"Difficulty":2
}
You can see that each block contains the PreviousBlockHash and the Data. The BlockHash itself is generated based on the PreviousBlockHash, the timestamp and the Data itself.
Document Validation
After signing the document, the created PDF can be validated by uploading it:
The method ValidateDocument creates an MD5 hash of the uploaded document and compares that value with the stored value in the block specified by the blockHash:
[HttpPost]
[Route("Document/ValidateDocument")]
public bool ValidateDocument(string document, string blockHash)
{
if (document == null || blockHash == null)
return false;
// calculate the MD5 of the uploaded document
string sChecksum = Checksum.CalculateMD5(Convert.FromBase64String(document));
// load the associated blockchain
Blockchain bcDocument = new Blockchain(sBlockchainPath);
Block blockDocument = bcDocument.GetBlock(blockHash);
if (blockDocument == null)
return false;
if (bcDocument.IsValid(blockDocument.BlockHash)) {
// get the SignedDocument object from the block
SignedDocument signedDocument =
JsonConvert.DeserializeObject<SignedDocument>(blockDocument.Data);
// compare the checksum in the stored block
// with the checksum of the uploaded document
return (signedDocument.Hash == sChecksum ? true : false);
}
else return false;
}
Before the hashes are compared, the integrity of the blockchain is checked by calling the IsValid method:
// checks, if the blockchain is consistent by
// re-generating and comparing the hashes in each block
public bool IsValid(string blockHash = "") {
// check all blocks
int iNumberBlocks = Chain.Count;
// check all blocks until the given, optional hash
if (blockHash != "") {
Block block = Chain.FirstOrDefault(h => h.BlockHash == blockHash);
if (block != null) {
iNumberBlocks = block.Index + 1;
}
}
// loop through all blocks, generate their hashes and compare
for (int i = 1; i < iNumberBlocks; i++) {
Block currentBlock = Chain[i];
Block previousBlock = Chain[i - 1];
if (currentBlock.BlockHash != currentBlock.GenerateBlockHash())
return false;
if (currentBlock.PreviousBlockHash != previousBlock.BlockHash)
return false;
}
return true;
}
This method is literally looping through all blocks (until the current one), re-generates and compares the hashes for each block.
The sample also includes an overview of the current blockchain by displaying all entries and the stored data:
Conclusion
A blockchain can be used to store document hashes to validate the integrity of a document. A blockchain always contains information about the most current version of an uploaded or signed document.
You can download this sample application from our GitHub repository.
Download and Fork This Sample on GitHub
We proudly host our sample code on github.com/TextControl.
Please fork and contribute.
Requirements for this sample
- TX Text Control .NET Server 30.0
- Visual Studio 2022
ASP.NET
Integrate document processing into your applications to create documents such as PDFs and MS Word documents, including client-side document editing, viewing, and electronic signatures.
- Angular
- Blazor
- React
- JavaScript
- ASP.NET MVC, ASP.NET Core, and WebForms
Related Posts
ASP.NETASP.NET CoreDocumentViewer
DocumentViewer Mobile-Friendly View Released
We just released a new NuGet package version of the DocumentViewer for ASP.NET (Core) that provides a new mobile view for document viewing, signing and annotations.
E-Sign Comes to Blazor: Document Viewer 33.0.1 Released
We are excited to announce the release of TX Text Control Document Viewer 33.0.1 for Blazor! This version comes packed with one of the most requested features: Electronic signature support.
Adoption of Electronic vs. Paper Signatures in 2025
The move to electronic signatures has accelerated in recent years. However, many organizations still rely on paper signatures for core processes. This article examines the current state of…
Sign Documents with a Self-Signed Digital ID From Adobe Acrobat Reader in…
This article shows how to create a self-signed digital ID using Adobe Acrobat Reader and how to use it to sign documents in .NET C#. The article also shows how to create a PDF document with a…
ASP.NETASP.NET CoreCertificate
How to Sign Signature Fields with Custom Signer Information and Time Stamps…
This article shows how to sign signature fields in a PDF document with custom signer information and timestamps using the TX Text Control .NET Server component in ASP.NET Core C#. The signature is…