# 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 →](CLAIMS.md)** ## Installation ```bash npm install @save/core ``` Requires Node.js >= 18.0.0 ## Quick Start ```typescript 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.md)** ### 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): ```typescript // 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: ```typescript // 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: ```typescript 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: ```typescript 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: ```typescript // 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: ```typescript 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 ```typescript // 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: ```typescript 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:** ```bash # 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