/** * Compact note ciphers — the 80-byte payload carried inside the on-chain * `transact` instruction (`note_ciphers: Option`) and re-emitted in * `CommitmentEvent`. * * This is what makes a note recoverable from chain data alone: a recipient who * was never told about a deposit can still find it by scanning `view_tag` and * decrypting the blob. Without it, note delivery depends on someone calling the * relayer, which a third-party integrator cannot be trusted to do. * * ## Byte-compatibility is load-bearing * * Four sibling implementations already exist (relayer-server, extension ×2, * wallet-app). This is the canonical one, and it MUST agree with them byte for * byte in both directions: * * - disagree on encrypt → new notes are invisible to every existing client * - disagree on decrypt → the entire historical note corpus becomes unreadable * * Three details are easy to get wrong and are individually fatal: * * 1. The KDF is `nacl.hash(x).slice(0, 32)` — **truncated SHA-512**, despite the * sibling helper being named `sha256ForNotes`. See `kdfSHA512_256` in utxo.ts. * 2. The ephemeral key published on-chain is **Ed25519**; it is converted to * X25519 only for the Diffie-Hellman. Scanners convert it back. * 3. `viewTag` is the first byte of the hash of the **raw shared secret**, not of * the derived key. A wrong view tag makes every note invisible to the scan * loop, which rejects ~255/256 candidates on this byte alone. * * The ECDH primitives are imported from `utxo.ts` rather than reimplemented — a * fifth independent Edwards→Montgomery implementation is exactly how the current * drift risk between the four siblings arose. * * Verified against the siblings by `tests/compact-note.test.ts` (golden vectors) * and `scripts/verify-compact-cipher-parity.mjs` (differential). */ /** Size of the on-chain compact blob: nonce[24] || secretbox(40-byte plaintext)[56]. */ export declare const COMPACT_BLOB_LENGTH = 80; export type CompactNoteCipher = { /** Ed25519 ephemeral public key, published on-chain. */ ephemeralPublicKey: Uint8Array; /** 80 bytes: nonce[24] || ciphertext[56]. */ compactBlob: Uint8Array; /** 1-byte scan filter. */ viewTag: number; }; /** The optional pair of compact note ciphers carried by `transact` instructions. */ export type NoteCiphers = { note0EphemeralKey: Uint8Array; note0Encrypted: Uint8Array; note0ViewTag: number; note1EphemeralKey: Uint8Array; note1Encrypted: Uint8Array; note1ViewTag: number; }; /** Plaintext note material needed to create one compact on-chain cipher. */ export type NoteCipherInput = { /** Legacy Ed25519 wallet target. Omit when recipientViewPublicKey is set. */ recipientWalletPubkey?: Uint8Array; /** Direct X25519 view-v2 target. Takes precedence when present. */ recipientViewPublicKey?: Uint8Array; blinding: Uint8Array; amount: bigint; }; /** * Test-only determinism hook. NOT part of the public contract — it exists so * golden vectors can pin exact bytes against the sibling implementations, which * is the only way to prove byte-compatibility rather than assume it. * * @internal */ export type CompactNoteDeterminism = { ephemeralSeed: Uint8Array; nonce: Uint8Array; }; /** * Encrypt a note's secret material to a recipient's Solana wallet key. * * The recipient does not need to be the signer of the transaction, or to have * any prior relationship with the sender — this is what makes shielding to an * arbitrary owner possible. * * @param recipientWalletPubkey 32-byte Ed25519 Solana wallet public key * @param blinding 32-byte big-endian blinding factor * @param amount note amount in base units */ export declare function createCompactNoteCipher(recipientWalletPubkey: Uint8Array, blinding: Uint8Array, amount: bigint, /** @internal */ __det?: CompactNoteDeterminism): CompactNoteCipher; /** * Encrypt a v2 note to a published X25519 view public key. * * The byte shape is deliberately identical to v1: ephemeral Ed25519 key[32], * nonce+ciphertext[80], viewTag u8. The Solana program treats those fixed-size * fields as opaque, so changing only the ECDH recipient requires no redeploy * and consumes no additional transaction bytes. */ export declare function createCompactNoteCipherToX25519(recipientViewPublicKey: Uint8Array, blinding: Uint8Array, amount: bigint, /** @internal */ __det?: CompactNoteDeterminism): CompactNoteCipher; /** * Recover a note's blinding and amount from an on-chain compact cipher. * * Returns `null` when the blob was not encrypted to this key — the expected * outcome for the overwhelming majority of candidates during a scan, so callers * should filter on `viewTag` first (see {@link matchesViewTag}) and only attempt * decryption on the ~1/256 that survive. * * @param walletSecretKey 32-byte Ed25519 seed or 64-byte secret key */ export declare function decryptCompactNoteCipher(walletSecretKey: Uint8Array, ephemeralPublicKey: Uint8Array, compactBlob: Uint8Array, precomputed?: { sharedSecret?: Uint8Array; x25519Private?: Uint8Array; }): { blinding: Uint8Array; amount: bigint; } | null; /** * Cheap scan filter: does this on-chain cipher plausibly belong to us? * * Rejects ~255/256 candidates for the cost of one scalar multiplication and one * hash, versus a full secretbox open. Hoist `x25519Private` across a scan loop * with {@link toX25519Private}. */ export declare function matchesViewTag(walletSecretKey: Uint8Array, ephemeralPublicKey: Uint8Array, viewTag: number, precomputed?: { x25519Private?: Uint8Array; }): { matches: boolean; sharedSecret: Uint8Array; }; /** * Convert an Ed25519 wallet secret key to its X25519 scalar once, for reuse * across a scan loop. */ export declare function toX25519Private(walletSecretKey: Uint8Array): Uint8Array; /** The all-zero cipher the program treats as "no note here". */ export declare function emptyNoteCipher(): CompactNoteCipher; /** * Build the two-note payload accepted by `transact` and `transact_swap`. * A missing side is encoded with the program's all-zero sentinel. If both * sides are missing, `null` is returned so Anchor encodes `Option::None`. */ export declare function createNoteCiphers(note0: NoteCipherInput | null, note1: NoteCipherInput | null): NoteCiphers | null;