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

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:

  1. Constructs a Merkle Tree from all statementFideIds.
  2. Computes the Root Hash.
  3. 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.

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.ethereum

Verification & Key Management

Verification

Statements can be verified by anyone—applications, indexers, or other agents—at any time.

Verification Checklist:

  1. Cryptographic Integrity — Extract the signer address from the signature and verify it matches the attestation's CAIP-10 user. See verifyAttestation().
  2. Authority — Confirm the signer has the "right" to make this statement (e.g., via sec:controller).
  3. Temporal Validity (optional) — Validate timestamps if your indexer uses them.
  4. Deduplication — Use the Attestation Fide ID to detect duplicate submissions.

Managing Signers

Key Rotation

When rotating keys (e.g., periodic security updates):

  1. Create a new keypair.
  2. Delegate control: New Key → sec:controller → Entity.
  3. End old control: Set schema:validThrough with an RFC 3339 UTC timestamp (e.g., "2026-02-08T00:00:00Z").

Key Compromise

If a key is compromised:

  1. Immediately set schema:validThrough to the compromise cutoff timestamp in RFC 3339 UTC.
  2. 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:validThrough on the control relationship. To correct a mistake, use prov:wasRevisionOf.

On this page