Exactly what is signed, stored and provable.
Every moving part, in the order it happens.
1. The fingerprint
SHA-256 turns any file into 64 characters. It can't be reversed, so the fingerprint is public and the file stays private.
halden-tessel-msa-2026.txt
SHA-256
2. The signed message
The wallet shows a readable EIP-712 message: this fingerprint, this title, this role.
- documentHash
- 0x5fc1d7e707db3f710ebf2bd323356513b95e4c5ac1e5ff9c997d5f811bdd39b6
- title
- Master Services Agreement — Halden Freight
- signer
- 0xfb250521274230730f6e7ad41fc50974805ae30c
- role
- signer
- decision
- approve
- nonce
- 7
3. The registry
Events hold addresses, fingerprints and blocks. Never names, never content.
interface ISignChainRegistry {
event EnvelopeCreated(bytes32 indexed docHash, address indexed sender,
address[] signers, bool sequential, string cid);
event DocumentSigned(bytes32 indexed docHash, address indexed signer,
uint64 timestamp);
event DocumentDeclined(bytes32 indexed docHash, address indexed signer,
bytes32 reasonHash);
event EnvelopeVoided(bytes32 indexed docHash);
function createEnvelope(bytes32 docHash, address[] calldata signers,
bool sequential, string calldata cid) external;
function sign(bytes32 docHash, bytes calldata sig) external; // EIP-712
function decline(bytes32 docHash, bytes32 reasonHash,
bytes calldata sig) external;
function voidEnvelope(bytes32 docHash) external; // sender only
}4. Where the document lives
Fingerprint only (default)
The parties keep the file; the chain keeps the proof.
Encrypted copy on IPFS
Encrypted in the browser, pinned to IPFS, its CID recorded with the envelope.
5. Verification
Re-hash, look up, check each signature. Any SHA-256 tool and block explorer will do.
Verify a fileconst hash = sha256(await file.arrayBuffer()) // 1. fingerprint
const logs = await client.getContractEvents({ // 2. look it up
address: REGISTRY, abi, eventName: "DocumentSigned",
args: { docHash: `0x${hash}` },
})
for (const s of receipt.signatures) // 3. check each one
assert(await verifyTypedData({ ...domain, message, signature: s.signature,
address: s.signer }))Legal standing
ESIGN, eIDAS and Québec's IT framework act generally accept signatures tied to a person and an unaltered document. Wills and notarised acts need more. Not legal advice.
For developers
src/lib/demo mirrors this contract one call at a time, ready to swap for wagmi and viem.
See it working.
Send a sample contract, co-sign one, then try to tamper with it.