New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@fairseal/verify

Package Overview
Dependencies
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@fairseal/verify

Independent verification for FairSeal anchor receipts and VEO-2 objects — structure, integrity, Ed25519 signatures, and on-chain Merkle anchors. ESM + CJS, Node >= 18.

latest
Source
npmnpm
Version
0.5.1
Version published
Weekly downloads
253
644.12%
Maintainers
1
Weekly downloads
 
Created
Source

@fairseal/verify

Independent, fail-closed verification for FairSeal receipts (VEO objects).

Every check runs from public inputs. You do not need to trust FairSeal — or this package's authors — to re-verify a receipt: signatures verify against the keys embedded or supplied, and anchors verify against Base mainnet through any RPC you choose.

npm install @fairseal/verify

Requires Node >= 18. Both ESM (import) and CJS (require) are supported on all Node versions from 18 onward. @noble v2 cryptography is bundled into the dist so CJS consumers do not need --experimental-require-module.

Quick start (VEO-2)

import { verifyVEO } from '@fairseal/verify';

// v0.5.0+: Pass trustedKeys to verify signer identity.
// Without trustedKeys, signed VEOs return valid:false (fail-closed).
// trustedKeys[0] = Ed25519 public key hex (VEO-2) or Ethereum address (VEO-1).
const result = await verifyVEO(receipt, {
  trustedKeys: ['<your-provider-public-key-hex>']
});

if (result.valid) {
  // ALL checks passed: structure + integrity + trusted-signer
  // + on-chain anchor (if present).
} else {
  console.log(result.checks);   // structure / integrity / signature / anchor
  console.log(result.errors);   // machine-readable failure reasons
  console.log(result.checks.signature?.detail); // e.g. "no_trusted_keys"
}

Migration: 0.4.x → 0.5.0 (security fix)

Breaking change: verifyVEO(veo) without trustedKeys now returns valid: false with reason: "no_trusted_keys" for signed objects.

In 0.4.x, a signature was verified against the public key embedded in the object itself — so an attacker could self-sign a forged object and get valid: true. 0.5.0 closes this: signer identity must come from the caller.

- const result = await verifyVEO(receipt);
+ const result = await verifyVEO(receipt, { trustedKeys: ['<pubkey-hex>'] });

If you need 0.4.x behavior temporarily during migration, use the explicit opt-in:

const result = await verifyVEO(receipt, { allowUnverifiedSigner: true });
// ⚠️ WARNING: this accepts any self-signed VEO — only use when you verify
//             signer identity through an out-of-band channel.

Result semantics (fail-closed)

FieldMeaning
validtrue only when all applicable checks pass and, if an anchor is present, it verified on-chain. Never true for an anchored object whose chain check was skipped or failed.
structuralStructure + integrity + signature only. Can be true while valid is false (anchor pending/failed).
anchoredtrue (verified on-chain) / false (check ran and failed) / "unknown" (chain unreachable) / "not_present" (no anchor on this object) / "not_checked" (structuralOnly mode)
checks.*Each check is pass, fail, or skipped with a detail string.

"We could not verify" and "we verified it is wrong" are different states — this package never collapses them.

Offline pre-screening

const result = await verifyVEO(receipt, { structuralOnly: true });
// result.anchored === 'not_checked'
// result.valid will be false when an anchor is present:
// structurally-valid-but-not-chain-verified is NOT called "valid".

VEO-1 receipts (EIP-191 / secp256k1)

FairSeal API receipts (VEO-1) are signed with personal_sign (EIP-191) over a canonical serialization:

import { verifyEIP191Signature, canonicalizeVEO1 } from '@fairseal/verify';

const check = verifyEIP191Signature(veo1Object);
// recovers the secp256k1 signer address and compares to the declared signer

Merkle and on-chain anchor utilities

import { verifyMerklePath, verifyAnchor, DEFAULT_CONTRACT_ADDRESSES } from '@fairseal/verify';

// Pure sha256 fold — no network needed:
const ok = verifyMerklePath(leafHash, merklePath, expectedRoot);

// Confirm the batch root on Base mainnet via any RPC you trust:
const anchor = await verifyAnchor(anchorInfo, { rpcUrl: 'https://mainnet.base.org' });

verifyMerklePath alone proves inclusion against a root offline; it does not prove the root is on-chain. Use verifyAnchor (or call MerkleAnchor.getBatchRoot on any Base RPC yourself) for chain confirmation.

Verifying a notarize receipt directly (v0.4.0+)

From v0.4.0, verifyAnchor accepts the raw response from GET /v1/notarize/:receipt_id — no manual field remapping needed:

import { verifyAnchor } from '@fairseal/verify';

// Fetch the receipt directly from the API
const receipt = await fetch('https://api.fairseal.io/v1/notarize/nr_<id>').then(r => r.json());

// Pass it straight to verifyAnchor — adapter auto-detects 'agent_decision' schema
const result = await verifyAnchor(receipt, { rpcUrl: 'https://mainnet.base.org' });

if (result.valid) {
  // Receipt is anchored and verified on Base mainnet
  console.log('✓ Anchored at block', result.onChain?.blockNumber);
} else {
  console.log('✗', result.errors);
}

The adapter maps proof.anchor_tx → tx_hash, proof.anchor_chain → chain, proof.merkle_root → merkle_root, and the other proof.* fields automatically. The three accepted schemas and their field sources:

SchemaSourceChain fieldTx fieldMerkle root field
agent_decision/v1/notarize/:idproof.anchor_chainproof.anchor_txproof.merkle_root
fairseal-anchor-receipt-v1/v2/anchor/:id/receiptchaintx_hashmerkle_root
VEO-2/v1/rng/latest etc.anchor.chainanchor.tx_hashanchor.merkle_root

Note on Merkle path (multi-leaf batches): notarize receipts encode proof.merkle_path as a pair-sorted string[]. For single-receipt batches (the common case) this is always [] and verification runs normally. For multi-leaf batches the path check is skipped (surfaced as a warnings[] entry) but the on-chain transaction and contract-state checks still apply — the anchor is confirmed via the chain, not just the path.

What verification proves — and what it does not

Proves:

  • the object's content matches its hashes (integrity),
  • the declared signer produced the signature (EIP-191 secp256k1 for VEO-1, Ed25519 for VEO-2),
  • when anchored: the receipt's hash is included in a Merkle batch whose root exists on Base mainnet.

Does not prove:

  • that the decision or output described by the receipt was correct, safe, or compliant,
  • that the issuing agent recorded all of its actions (capture completeness),
  • signer identity beyond the key itself, unless you bind keys to identities out-of-band.

A fully receipted agent can still be wrong. Audit reasoning separately.

API surface

  • verifyVEO(veo, options) / verifyVEOSync(veo, options) — top-level VEO-2 verification
  • verifyStructure, verifyIntegrity, verifySignature, isSigned — individual VEO-2 checks
  • verifyEIP191Signature, canonicalizeVEO1, eip191Hash, pubKeyToAddress — VEO-1
  • computeMerkleRoot, verifyMerklePath, normalizeHex, toBytes32Hex — Merkle
  • verifyAnchor, DEFAULT_CONTRACT_ADDRESSES, DEFAULT_RPC_URLS — on-chain anchor
  • RPC constants: TOPIC_BATCH_ANCHORED, SELECTOR_GET_BATCH_ROOT, SELECTOR_BATCH_EXISTS
  • Verify in a browser: https://verify.fairseal.io
  • API docs: https://api.fairseal.io/docs
  • Site: https://fairseal.io

License

MIT

Keywords

fairseal

FAQs

Package last updated on 12 Oct 2026

Related posts