Skip to content
SignChain

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.

SignChain · v1 · chainId 11155111 · 0x5c9b1e0a…
documentHash
0x5fc1d7e707db3f710ebf2bd323356513b95e4c5ac1e5ff9c997d5f811bdd39b6
title
Master Services Agreement — Halden Freight
signer
0xfb250521274230730f6e7ad41fc50974805ae30c
role
signer
decision
approve
nonce
7
What a signer's wallet displays

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
}
Registry interface (sketch)

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 file
const 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 }))
Verify a file in a few lines

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.