Skip to content

llama-risk/save

Repository files navigation

SAVE - Structured Attestation & Verification Engine

A TypeScript library for creating cryptographically signed attestations with verifiable claims organized in a DAG structure.

Overview

SAVE enables organizations to create transparent, verifiable proof-of-reserves attestations. It supports:

  • Three types of claims: Inline (predefined structure + proof), source-backed (pointers to object claims), and aggregated (computed from other claims)
  • Object claims: Proven data containers that other claims can reference via JSON Pointers
  • Multi-party attestations: Aggregate claims from diverse sources (on-chain balances, custodians, auditors, CEXs, etc.)
  • Flexible proof system: Support for signatures, ZK proofs, TEE attestations, workflow proofs, and more
  • Onchain asset references: Link claims to specific token balances on specific chains

📖 Read the detailed Claims, Aggregations, and Proofs guide →

Installation

npm install @save/core

Requires Node.js >= 18.0.0

Quick Start

import {
  createNumericClaim,
  NumericClaim,
  ObjectClaim,
  signClaim,
  sum,
  subtract,
  AttestationBuilder,
  generateKeyPair,
} from '@save/core';

// Generate keys (in production, use existing keys)
const issuerKeys = generateKeyPair();
const custodianKeys = generateKeyPair();

// 1. External party creates and signs a claim (inline)
const balanceClaim = createNumericClaim({
  id: 'eth_balance',
  value: 1_000_000,
  unit: 'USDC',
  asset: {
    address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
    chainId: 1, // Ethereum mainnet
  },
  description: 'Holdings on Ethereum',
});

const balanceProof = signClaim(balanceClaim, custodianKeys.privateKey, {
  signerIdentity: 'Custodian Inc',
});

const wrappedClaim = NumericClaim.inline({
  claim: balanceClaim,
  proof: balanceProof,
});

// 2. Create an object claim with proven data (many claims can reference this)
const cexObjectClaim = new ObjectClaim({
  id: 'cex_snapshot',
  data: {
    accounts: [{ balance: { value: 500000, unit: 'USDC' } }]
  },
  description: 'CEX account snapshot',
  proof: signClaim(
    { id: 'cex_snapshot', claimType: 'inline', dataType: 'object', data: { /* ... */ } },
    custodianKeys.privateKey,
    { signerIdentity: 'CEX Custodian' }
  )
});

// 3. Create a source-backed claim (pointer to object claim)
const cexClaim = NumericClaim.sourceBacked({
  id: 'cex_balance',
  dataPointer: 'cex_snapshot#/accounts/0/balance',
  description: 'CEX holdings'
});

// 4. Create an aggregated claim (combines inline and source-backed claims)
const totalClaim = NumericClaim.aggregated({
  id: 'total_reserves',
  data: { value: 1500000, unit: 'USDC' },
  aggregation: sum('eth_balance', 'cex_balance')
});

// 5. Build and sign the attestation
const attestation = new AttestationBuilder({
  issuer: {
    identity: '0x1234...5678',
    name: 'Protocol X',
  },
  createdAt: new Date().toISOString(),
  expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
})
  .addClaim(cexObjectClaim)
  .addClaim(wrappedClaim)
  .addClaim(cexClaim)
  .addClaim(totalClaim)
  .sign(issuerKeys.privateKey);

// Export
console.log(attestation.toJSON());

Core Concepts

📖 Full documentation on Claims, Aggregations, and Proofs →

Claims

Claims are standardized assertions about verifiable facts. SAVE supports three types of claims:

  1. Inline: Predefined structure (e.g., numeric claims) with both data and proof
  2. Source-backed: Pointer to data in an object claim that has a proof
  3. Aggregated: Computed from other claims using aggregation functions (can combine inline and source-backed claims)

Numeric claims are currently supported (quantitative assertions with value + unit):

// Inline claim (predefined structure with proof)
const claim = createNumericClaim({
  id: 'treasury_balance',
  value: 500_000,
  unit: 'USDC',
  asset: {
    address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
    chainId: 1,
  },
  description: 'Treasury holdings',
});

// Source-backed claim (pointer to object claim)
const sourceClaim = NumericClaim.sourceBacked({
  id: 'cex_balance',
  dataPointer: 'dataSourceId#/path/to/value'
});

// Aggregated claim (combines other claims)
const totalClaim = NumericClaim.aggregated({
  id: 'total',
  data: { value: 500000, unit: 'USDC' },
  aggregation: sum('claim1', 'claim2')
});

Object Claims

Object claims are proven data containers that other claims can reference:

// First, create and sign the object claim content
const snapshotContent = {
  id: 'cex_snapshot_001',
  claimType: 'inline' as const,
  dataType: 'object' as const,
  data: {
    accounts: [
      { balance: { value: 500000, unit: 'USD' } }
    ]
  }
};

const snapshotProof = signClaim(snapshotContent, custodianKey, {
  signerIdentity: 'CEX Custodian',
});

// Then create the ObjectClaim with the proof
const cexSnapshot = new ObjectClaim({
  id: 'cex_snapshot_001',
  data: {
    accounts: [
      { balance: { value: 500000, unit: 'USD' } }
    ]
  },
  description: 'CEX account snapshot',
  proof: snapshotProof,
});

Claims can then reference this data using JSON Pointers:

const claim = NumericClaim.sourceBacked({
  id: 'balance',
  dataPointer: 'cex_snapshot_001#/accounts/0/balance'
});

Signing Claims

External parties (custodians, auditors) sign claims to attest to their validity:

const proof = signClaim(claim, privateKey, {
  signerIdentity: 'Custodian A',
  publicKeySource: 'https://custodian.example/.well-known/gpor-keys.json',
});

// Proof structure:
// {
//   proofType: 'signature',
//   trustModel: 'reputational',
//   mechanism: 'signature',
//   algorithm: 'ECDSA_secp256k1',
//   signerPublicKey: '0x...',
//   signerIdentity: 'Custodian A',
//   publicKeySource: 'https://...',
//   signature: '0x...',
//   metadata: { createdAt: '...' }
// }

Composite Claims

Derive values from other claims using aggregation functions:

// Sum multiple claims
const totalReserves = NumericClaim.aggregated({
  id: 'total_reserves',
  data: { value: 0, unit: 'USDC' }, // Will be computed from aggregation
  description: 'Total reserves across all chains',
  aggregation: sum('eth_balance', 'arb_balance'),
});

// Subtract claims
const netPosition = NumericClaim.aggregated({
  id: 'net_position',
  data: { value: 0, unit: 'USDC' }, // Will be computed from aggregation
  description: 'Net reserves after liabilities',
  aggregation: subtract('total_reserves', 'liabilities'),
});

// Nested aggregations
const netInline = NumericClaim.aggregated({
  id: 'net_inline',
  data: { value: 0, unit: 'USDC' }, // Will be computed from aggregation
  aggregation: subtract(
    sum('eth_balance', 'arb_balance'),
    'liabilities'
  ),
});

Attestations

Attestations bundle multiple claims into a signed document:

const attestation = new AttestationBuilder({
  issuer: {
    identity: '0x1234...5678',
    name: 'Protocol X',
  },
  createdAt: new Date().toISOString(),
  publicKeySource: 'https://protocol.example/.well-known/gpor-keys.json',
  expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
})
  .addClaim(claim1)
  .addClaim(claim2)
  .addClaim(compositeClaim)
  .sign(privateKey);

File Operations

// Export to file
await attestation.exportToFile('attestation.json');

// Load from file
const loaded = await Attestation.fromFile('attestation.json');

Verification

Verify attestations programmatically or via CLI:

import { verifyAttestation } from '@save/core';

const verification = await verifyAttestation(attestationData, {
  verifier: {
    name: 'Auditor Inc',
    publicKey: verifierPublicKey,
    identity: '0xVerifier...',
  },
  verifiedAt: new Date().toISOString(),
  signingKey: verifierPrivateKey,
});

console.log(verification.summary);
// {
//   totalClaims: 8,
//   validClaims: 8,
//   invalidClaims: 0,
//   uncertainClaims: 0,
//   overallStatus: 'Valid'
// }

CLI Tool:

# Verify an attestation
npx save-verify attestation.json

# With options
npx save-verify attestation.json \
  --output verification.json \
  --verifier-name "Auditor Inc" \
  --verbose

# Publish to IPFS and on-chain registry
npx save-verify attestation.json \
  --publish \
  --env-file .env \
  --registry 0x... \
  --rpc-url https://...

API Reference

Claim Creation

Numeric Claims:

  • createNumericClaim(options) - Create a numeric claim content
  • NumericClaim.inline({ claim, proof }) - Create an inline claim from signed content
  • NumericClaim.sourceBacked({ id, dataPointer, unit?, ... }) - Create a source-backed claim
  • NumericClaim.aggregated({ id, data, aggregation, ... }) - Create an aggregated claim

String Claims:

  • createStringClaim(options) - Create a string claim content
  • StringClaim.inline({ claim, proof }) - Create an inline string claim
  • StringClaim.sourceBacked({ id, dataPointer, expectedValue?, ... }) - Create a source-backed string claim

Object Claims:

  • new ObjectClaim({ id, data, proof, ... }) - Create an object claim (JSON format)
  • new ObjectClaim({ id, format: 'structured-text', data, proof, ... }) - Create a structured text claim

Utilities:

  • extractFromStructuredText(text, pointer) - Extract data from plain text using pointers
  • resolveObjectPointer(data, pointer) - Resolve JSON pointers with transformations

Signing & Proofs

  • signClaim(claim, privateKey, options?) - Sign a claim with ECDSA
  • createSignatureProof(claim, privateKey, options?) - Create a signature proof
  • getClaimSigningData(claim) - Get canonical signing data for a claim
  • createVlayerProof(claimContent, vlayerProofData, options?) - Create a ZK-TLS Notary proof
  • verifyZkTlsNotaryProof(proof, options?) - Verify a ZK-TLS Notary proof

Aggregation Functions

  • sum(...operands) - Sum multiple claims or nested aggregations
  • subtract(minuend, subtrahend) - Subtract one claim from another
  • executeNumericAggregation(aggregation, resolver) - Execute an aggregation with custom resolver

Attestation Building

Builder:

  • new AttestationBuilder(options) - Create a builder
  • .addClaim(claim) - Add a claim (chainable)
  • .addClaims(claims) - Add multiple claims (chainable)
  • .sign(privateKey) - Sign and finalize the attestation

Attestation Class:

  • attestation.toJSON() - Export to JSON string
  • attestation.exportToFile(path) - Export to file
  • Attestation.fromJSON(json) - Load from JSON string
  • Attestation.fromFile(path) - Load from file

Verification

  • verifyAttestation(attestation, options) - Verify an attestation and generate verification document
  • registerProofVerifier(proofType, verifierFn) - Register custom proof verifiers

DAG

  • new ClaimDAG() - Create a new claim dependency graph
  • .addClaim(claim) - Add a claim to the DAG (resolves dependencies automatically)
  • .getClaim(id) - Get a claim by ID

Cryptographic Utilities

  • generateKeyPair() - Generate a new secp256k1 key pair
  • getPublicKey(privateKey) - Derive public key from private key
  • sign(data, privateKey) - Sign data
  • verify(signature, data, publicKey) - Verify a signature
  • hashData(data) - Hash data using SHA-256
  • hashObject(obj) - Hash a JSON object deterministically
  • deterministicId(seed) - Generate a deterministic UUID from a seed string

Proof Taxonomy

SAVE separates proof metadata into two orthogonal dimensions:

  • Trust model (trustModel): what you ultimately trust for correctness. This is informative only and open to interpretation.
  • Mechanism (mechanism): how the claim is evidenced or verified. This must match the provided proof structure.

Trust Models

Trust model What you trust
reputational A specific identity's assertion (e.g. signature)
mathematical Mathematical verification (e.g. Merkle Proof)
computation An execution environment or process (TEE or other externally sourced reproducible computations)

Mechanisms

Mechanisms are independent of trust models and describe how evidence is provided:

Mechanism Description Status
signature Cryptographic signatures — ECDSA secp256k1 supported; Ed25519 and BLS not yet implemented Supported
multisig Threshold signatures from multiple parties Planned
api Data retrieved from an API endpoint Planned
document Certified or signed documents Planned
zk_tls_notary ZK-TLS Notary proofs (cryptographic proof of web data via Vlayer) Vlayer Supported
merkle_inclusion Merkle tree inclusion proofs Planned
state_proof Blockchain state/storage proofs Planned
range_proof Range proofs for confidential values Planned
tee Trusted Execution Environment attestations (SGX, Nitro, SEV) Planned
cre_consensus Chainlink CRE DON consensus workflows Supported as runtime
on_chain Smart contract execution with blockchain consensus Planned

Trust models and mechanisms are composed together in proofs. Not all combination of trust model and mechanisms make sense. For example, an ECDSA signature mechanism with reputational trust means trusting the signer's identity, while a zk_proof mechanism with mathematical trust means trusting the math. But a computation trust model with an onchain reference mechanism makes little sense. However whether or not this makes sense is not enforced by the framework.

License

APACHE 2.0