Attesting & Batching
How to sign thousands of statements efficiently using Merkle Trees.
This guide demonstrates how to sign a batch of statements using the SDK.
Protocol Context
- Why Batching? Merkle Trees enable efficient signing and selective disclosure. See Attesting: Batch Pattern.
- Signing Methods: See Attesting: Methods for Ed25519 vs EIP-712 details.
Step 1: Prepare Your Batch
Collect all the Statements you want to attest.
// Let's say you have 10,000 statements from a data import
const statements: Statement[] = await processDataImport();Step 2: Create the Attestation (Merkle Tree)
Use createAttestation. This function does the heavy lifting:
- Constructs a Merkle Tree from all
statementFideIds. - Computes the Root Hash.
- Signs the Root Hash with your provided signer.
import {
createAttestation,
generateEd25519KeyPair,
signEd25519
} from '@fide.work/fcp';
// Generate or load your keypair (Ed25519 is recommended for speed/size)
const keyPair = await generateEd25519KeyPair();
const attestation = await createAttestation(
statements.map(s => s.statementFideId!), // Extract just the IDs
{
method: 'ed25519',
caip10User: `ed25519::${exported.address}`,
sign: (root) => signEd25519(root, keyPair.privateKey)
}
);The result attestation object contains the merkleRoot and the signature.
Step 3: Provenance Statements
The protocol has a special pattern for linking content to its author: Provenance.
You must create a prov:wasGeneratedBy statement for each content statement in your batch. This links the content (Subject) to the Attestation (Object).
import { createProvenanceStatements } from '@fide.work/fcp';
const provenanceBatch = await createProvenanceStatements(
statements.map(s => s.statementFideId!),
attestation // The result from Step 2
);
// provenanceBatch is an array of ~10,000 NEW statements
// Each one says: "Statement X wasGeneratedBy Attestation Y"Recursive Implementation
Currently, indexers infer provenance from the attestation object structure itself, so explicitly publishing these prov:wasGeneratedBy statements is optional but recommended for full semantic graph completeness.
Signing Methods
The SDK supports multiple signing algorithms.
Ed25519 (Recommended)
Fast, small keys, zero dependencies. Ideal for server-side bots and high-throughput indexers.
method: 'ed25519'EIP-712 (Ethereum)
Compatible with Ethereum wallets (Metamask, etc). Use this if your user is signing with their browser wallet.
import { signEip712 } from '@fide.work/fcp';
// ...
method: 'eip712',
sign: (root) => signEip712(root, privateKey) // or via window.ethereumVerification & Key Management
Verification
Statements can be verified by anyone—applications, indexers, or other agents—at any time.
Verification Checklist:
- Cryptographic Integrity — Extract the signer address from the signature and verify it matches the attestation's CAIP-10 user. See
verifyAttestation(). - Authority — Confirm the signer has the "right" to make this statement (e.g., via
sec:controller). - Temporal Validity (optional) — Validate timestamps if your indexer uses them.
- Deduplication — Use the Attestation Fide ID to detect duplicate submissions.
Managing Signers
Key Rotation
When rotating keys (e.g., periodic security updates):
- Create a new keypair.
- Delegate control:
New Key → sec:controller → Entity. - End old control: Set
schema:validThroughwith an RFC 3339 UTC timestamp (e.g.,"2026-02-08T00:00:00Z").
Key Compromise
If a key is compromised:
- Immediately set
schema:validThroughto the compromise cutoff timestamp in RFC 3339 UTC. - Statements signed after that date will be rejected by indexers who verify key validity.
Why Revocation Should Be Rejected
We explicitly reject a "Revocation" predicate.
- Immutability: In a ledger/git system, you cannot "unsign" something that has already happened.
- Solution: To revoke authority, set
schema:validThroughon the control relationship. To correct a mistake, useprov:wasRevisionOf.