Skip to content

Multi-party (co-attestation)

The multi-party trust layer (Layer 2 in the trust spectrum) lets N independent parties co-sign the same content claim. The SDK collects each party’s EIP-712 signature off-chain, then a coordinator submits one on-chain attestation containing all of them. A separate resolver (FidemarkMultiResolver) atomically recovers each signature on-chain and validates it matches the declared attesters[] array.

The submitter pays gas, but the trust guarantee is in the signatures. Anyone can be the coordinator.

  1. Define the claim. All co-signers must sign the same { contentHash, contentType, createdAt } triple.
  2. Each co-signer signs. They produce a slip ({ signer, signature }) and forward it to the coordinator over any transport.
  3. The coordinator submits. One transaction, one UID, all signatures embedded.
  4. Anyone verifies. The verify response includes multi.attesters[] and multi.signatures[]. The chain has already validated them; verifiers can re-check off-chain if they want.

Every co-signer must derive the claim from the same inputs.

import { buildMultiPartyClaim } from "@fidemark/sdk";
const claim = buildMultiPartyClaim({
content: theArticle,
contentType: "text/article",
createdAt: 1735689600, // optional, defaults to now
});

The returned claim is { contentHash, contentType, createdAt }. Share it with every signer verbatim.

import { signMultiPartyClaim, getNetwork } from "@fidemark/sdk";
const network = getNetwork("base-sepolia");
const slip = await signMultiPartyClaim(myWallet, claim, network);

The slip is a plain { signer, signature } pair: send it however suits your pipeline (HTTP, file, queue).

The coordinator (anyone with a funded wallet) collects every co-signer’s slip, constructs a Fidemark client signed in their name, and submits the bundle. See Configuration for every constructor option.

import { Fidemark, getNetwork } from "@fidemark/sdk";
const coordinator = new Fidemark({
network: getNetwork("base-sepolia"),
privateKey: process.env.COORDINATOR_KEY,
});
const result = await coordinator.attestMultiParty({
claim,
slips: [aliceSlip, bobSlip, carolSlip],
});

The resolver enforces:

  • 2 <= slips.length <= 16
  • signatures[i] recovers to attesters[i]
  • No duplicate addresses in attesters[]
  • proofMethod is on the resolver’s allowlist (multi-party is registered at deploy)
  • createdAt is not too far in the future (1 day drift)

If any check fails, the EAS attest call reverts and no attestation is created.

const att = await fidemark.verify(result.uid);
// att.type === "multi"
// att.contentHash === claim.contentHash
// att.attester === coordinator address (the submitter)
// att.multi.attesters === [alice, bob, carol]
// att.multi.signatures === [...EIP-712 sigs]
// att.multi.proofMethod === "multi-party"
// att.multi.contentType === claim.contentType

To re-verify a slip off-chain (e.g. for a custom auditor UI), reconstruct the digest and recover the signer.

import { multiPartyClaimDigest } from "@fidemark/sdk";
import { recoverAddress } from "ethers";
const digest = multiPartyClaimDigest(claim, network);
const recovered = recoverAddress(digest, slip.signature);
// recovered === slip.signer

The on-chain resolver also exposes a claimDigest(...) view returning the same value, so you can cross-check from a block explorer.

  • Co-authored content: newsroom, research lab, joint statement. Each author signs once.
  • Witness verification: a creator signs, then a third-party witness counter-signs. Auditors check both.
  • Compliance flows: provenance where multiple stakeholders must approve.
  • Threshold attestations: verifiers reading the chain can require N-of-M (e.g. “at least 3 of these 5 attesters must be present”).

ECDSA recovery is ~3 000 gas per signature. A 5-party attestation costs roughly the same as a single Human Proof plus 15 000 gas for the recovery loop. At Base mainnet gas prices, that’s well under one cent.

You can approximate multi-party by chaining Human attestations with refUID. Multi-party has three concrete advantages:

  1. Atomic: all signatures land or none do.
  2. Cheaper: one tx, one storage cost.
  3. Verifier-friendly: one UID with a list of co-signers, not a chain to walk.

Use chained Human attestations when the additional signers come in over time (review -> revision -> approval). Use multi-party when the signers are simultaneous.