Decentralized document timestamping on Starknet - Inspired by OpenTimestamps
Starknet Timestamps allows you to prove when a document existed by anchoring Merkle roots on the Starknet blockchain. Similar to OpenTimestamps for Bitcoin, but leveraging Starknet's scalability.
- Submit - Hash your document and submit to the aggregator
- Batch - Aggregator collects hashes for 1 hour and builds a Merkle tree
- Anchor - Merkle root is anchored on-chain via smart contract
- Verify - Use your
.stsproof file to verify the timestamp anytime
┌──────────────────────────────┐
┌───────▶ │ User │
│ └──────────────┬───────────────┘
│ ▼
│ ┌──────────────────────────────┐
│ │ CLI Tool │
Return .sts │ - starknet-timestamp file │
│ │ → Hash + Submit │
│ │ - starknet-timestamp verify │
│ │ → Verify on-chain │
│ └─────┬────────────────┬───────┘
│ Timestamp │ │ Verify
│ │ ▼
│ ▼ ┌──────────────────────────┐
┌─────────────────────┐ │ Smart Contract │
│ Aggregator │ │ - Store root → ts │
│ - Collect hashes │ │ - Query timestamp │
│ - Every 1 hour: │ └──────────────────────────┘
│ • Build tree │ ▲
│ • Anchor ────────┼─────────────┘
│ • Generate .sts │ contract.anchor(root)
└─────────────────────┘
Timestamping:
- User runs
starknet-timestamp file.pdf - CLI hashes document (SHA256) and submits to aggregator
- Aggregator collects hashes for 1 hour
- Aggregator builds Merkle tree (Poseidon) and calls
contract.anchor(root) - Aggregator generates
.stsfile - User receives
file.pdf.sts
Verification:
- User runs
starknet-timestamp verify file.pdf - CLI loads
file.pdf.sts - CLI queries smart contract:
get_timestamp(merkle_root) - CLI verifies proof and displays timestamp
The aggregator uses Poseidon hash (native to Cairo/Starknet) for tree construction:
Example with 4 documents:
ROOT = Poseidon(H12, H34)
/ \
/ \
H12 = Poseidon(H1, H2) H34 = Poseidon(H3, H4)
/ \ / \
/ \ / \
H1=0x111 H2=0x222 H3=0x333 H4=0x444
| | | |
doc1.pdf doc2.pdf doc3.pdf doc4.pdf
For doc1.pdf, the proof contains:
merkle_proof = [0x222, H34]- To verify:
Poseidon(Poseidon(0x111, 0x222), H34) = ROOT
Contract Address:
0x0484d5a27e18a26cb19ddfcc46956b6061632e8bb0fa9156f7513ac2bd4a790c
#[starknet::interface]
pub trait ITimestampRegistry<TContractState> {
fn anchor(ref self: TContractState, merkle_root: felt252);
fn get_timestamp(self: @TContractState, merkle_root: felt252) -> u64;
}anchor(merkle_root)- Stores a Merkle root with block timestampget_timestamp(merkle_root)- Returns the timestamp for a root
Binary proof file (similar to OpenTimestamps .ots):
Your files:
contract.pdf (your document)
contract.pdf.sts (proof file, ~500 bytes)
Contains:
- Document hash
- Merkle proof (siblings)
- Starknet attestation (contract, root, timestamp, tx)
$ starkstamp document.pdf
Hashing document.pdf...
Submitting to aggregator...
Waiting for proof...
✓ Proof saved: document.pdf.sts
✓ Timestamp: 2025-12-30 15:00:00 UTC$ starkstamp verify document.pdf
✓ Document timestamped at: 2025-12-30 15:00:00 UTC
✓ Transaction: 0xdef456...No aggregator needed for verification - just the .sts file and a Starknet node.
1. Re-hash the document
SHA256(document.pdf) → 0x123abc...
2. Follow Merkle path from .sts file:
current = 0x123abc... # Your document hash
current = Poseidon(current, 0x222...) # Apply sibling 1
current = Poseidon(current, 0x789...) # Apply sibling 2
current = Poseidon(current, 0xbbb...) # Apply sibling 3
→ Merkle root: 0xabc123...
3. Query Starknet contract:
contract.get_timestamp(0xabc123...)
→ Returns: 1735484400
4. Convert to human-readable:
1735484400 → 2025-12-30 15:00:00 UTC
✓ Verification complete!- No aggregator needed - Only your .sts file and a Starknet RPC
- Fully decentralized - Anyone can verify with just blockchain access
- Permanent - As long as Starknet exists, timestamp is provable
- Fast - Verification takes <1 second
starknet-timestamps/
├── contracts/
│ └── timestamp_registry.cairo
├── aggregator/
│ ├── server.py
│ └── scheduler.py
├── cli/
│ ├── main.py
│ └── proof.py
└── README.md
MIT License
Inspired by OpenTimestamps by Peter Todd.