shield-bridge-sdk
Version:
66 lines • 3.59 kB
TypeScript
/**
* Incremental shielded-balance cache (the DECRYPT layer — "v2").
*
* v1 (saplingDiffCache) made the FETCH incremental but the viewer still trial-decrypts EVERY
* commitment and re-checks EVERY matched note's spent status on each read (O(pool size)). This
* layer makes the DECRYPT incremental too: it caches the account's matched notes and only
* decrypts commitments added since the last scan, then re-checks spent status using octez.js's
* own `isSpent` (no nullifier re-implementation — the audited logic is reused verbatim).
*
* Safety model (this is a money path, so it is deliberately conservative):
* - PER-ACCOUNT + private: the cache holds DECRYPTED notes, so it is keyed by a non-reversible
* fingerprint of the viewing key. Evict on account forget (clearForViewingKey).
* - Reorg-safe: decrypted notes are persisted ONLY for the finalized prefix (head~2, immutable
* under Tenderbake). The unconfirmed tail is decrypted fresh every scan and never persisted;
* a spent flag is persisted only when caused by a FINALIZED nullifier. So a reorg of a recent
* block self-heals, and a just-received note shows immediately.
* - Self-checking: every Nth scan (and on every cold build) the incremental result is compared
* against a full stock `getBalance()`. Any mismatch invalidates the cache, returns the stock
* value, and warns — so a bug here can never silently surface a wrong balance for long.
* - Foolproof: any thrown error falls back to the full stock `getBalance()`.
*/
import BigNumber from 'bignumber.js';
import { type SaplingDiffStore, type DiffTarget } from './saplingDiffCache.js';
/** A matched, decrypted note in the FINALIZED prefix (the only notes we persist). */
interface CachedNote {
position: number;
valueStr: string;
addressHex: string;
rcmHex: string;
spent: boolean;
}
interface CachedAccountBalance {
decryptCursor: number;
notes: CachedNote[];
}
/** The subset of octez.js's SaplingTransactionViewer this layer drives (runtime-accessible). */
export interface BalanceViewer {
decryptCiphertextAsReceiver(commitmentAndCiphertext: unknown): Promise<{
value: unknown;
paymentAddress: Uint8Array;
randomCommitmentTrapdoor: unknown;
} | undefined>;
isSpent(address: unknown, value: string, randomCommitmentTrapdoor: unknown, position: number, nullifiers: unknown[]): Promise<boolean>;
getBalance(): Promise<BigNumber>;
}
/** Stable fingerprint of a full viewing key — non-reversible, so it never exposes the key. */
export declare function viewingKeyFingerprint(fvkHex: string): string;
/** Decrypt a stored envelope, or return null (→ rebuild) on a wrong key, tamper, or bad shape.
* Exported for inspection/tests; the SDK package does not re-export it. */
export declare function decryptAccount(stored: unknown, fvkHex: string): CachedAccountBalance | null;
/**
* Incremental shielded balance (base units). Falls back to a full stock `getBalance()` on any
* error. The same `store` backs v1's diff cache (namespaced keys never collide).
*/
export declare function incrementalBalance(opts: {
store: SaplingDiffStore;
rpcUrl: string;
target: DiffTarget;
viewer: BalanceViewer;
fvkHex: string;
selfCheckEvery?: number;
}): Promise<BigNumber>;
/** Evict every cached balance for a viewing key (call on account forget — removes decrypted data). */
export declare function clearForViewingKey(store: SaplingDiffStore, fvkHex: string): Promise<void>;
export {};
//# sourceMappingURL=saplingBalanceCache.d.ts.map