@veilo/sdk-core 0.3.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +289 -1262
  3. package/dist/cjs/accounts/admin.d.ts +84 -0
  4. package/dist/cjs/accounts/admin.js +208 -0
  5. package/dist/cjs/accounts/errors.d.ts +6 -0
  6. package/dist/cjs/accounts/errors.js +98 -0
  7. package/dist/cjs/accounts/index.d.ts +4 -0
  8. package/dist/cjs/accounts/index.js +20 -0
  9. package/dist/cjs/accounts/pdas.d.ts +23 -0
  10. package/dist/cjs/accounts/pdas.js +44 -0
  11. package/dist/cjs/accounts/queries.d.ts +41 -0
  12. package/dist/cjs/accounts/queries.js +95 -0
  13. package/dist/cjs/client.d.ts +2 -407
  14. package/dist/cjs/client.js +17 -912
  15. package/dist/cjs/cloak/client.d.ts +28 -0
  16. package/dist/cjs/cloak/client.js +68 -0
  17. package/dist/cjs/cloak/errors.d.ts +13 -0
  18. package/dist/cjs/cloak/errors.js +25 -0
  19. package/dist/cjs/cloak/helpers.d.ts +6 -0
  20. package/dist/cjs/cloak/helpers.js +35 -0
  21. package/dist/cjs/cloak/index.d.ts +6 -0
  22. package/dist/cjs/cloak/index.js +24 -0
  23. package/dist/cjs/cloak/polling.d.ts +7 -0
  24. package/dist/cjs/cloak/polling.js +66 -0
  25. package/dist/cjs/cloak/transport.d.ts +15 -0
  26. package/dist/cjs/cloak/transport.js +119 -0
  27. package/dist/cjs/cloak/types.d.ts +188 -0
  28. package/dist/cjs/cloak/types.js +5 -0
  29. package/dist/cjs/cloak.d.ts +1 -0
  30. package/dist/cjs/cloak.js +18 -0
  31. package/dist/cjs/compactNote.d.ts +128 -0
  32. package/dist/cjs/compactNote.js +191 -0
  33. package/dist/cjs/events.d.ts +1 -1
  34. package/dist/cjs/events.js +2 -2
  35. package/dist/cjs/idl/privacy_pool.d.ts +5 -0
  36. package/dist/cjs/idl/privacy_pool.js +15218 -0
  37. package/dist/cjs/index.d.ts +17 -13
  38. package/dist/cjs/index.js +43 -30
  39. package/dist/cjs/merkle.d.ts +13 -0
  40. package/dist/cjs/merkle.js +31 -8
  41. package/dist/cjs/notes/encryption.d.ts +41 -0
  42. package/dist/cjs/notes/encryption.js +75 -0
  43. package/dist/cjs/notes/index.d.ts +5 -0
  44. package/dist/cjs/notes/index.js +21 -0
  45. package/dist/cjs/notes/mailbox.d.ts +70 -0
  46. package/dist/cjs/notes/mailbox.js +164 -0
  47. package/dist/cjs/notes/model.d.ts +91 -0
  48. package/dist/cjs/notes/model.js +109 -0
  49. package/dist/cjs/notes/nullifier.d.ts +27 -0
  50. package/dist/cjs/notes/nullifier.js +45 -0
  51. package/dist/cjs/notes/recovery.d.ts +10 -0
  52. package/dist/cjs/notes/recovery.js +31 -0
  53. package/dist/cjs/program.d.ts +11 -0
  54. package/dist/cjs/program.js +26 -3
  55. package/dist/cjs/proof.d.ts +1 -183
  56. package/dist/cjs/proof.js +16 -290
  57. package/dist/cjs/proofs/encoding.d.ts +17 -0
  58. package/dist/cjs/proofs/encoding.js +72 -0
  59. package/dist/cjs/proofs/formatting.d.ts +6 -0
  60. package/dist/cjs/proofs/formatting.js +34 -0
  61. package/dist/cjs/proofs/index.d.ts +5 -0
  62. package/dist/cjs/proofs/index.js +21 -0
  63. package/dist/cjs/proofs/swap.d.ts +87 -0
  64. package/dist/cjs/proofs/swap.js +144 -0
  65. package/dist/cjs/proofs/transaction.d.ts +28 -0
  66. package/dist/cjs/proofs/transaction.js +110 -0
  67. package/dist/cjs/proofs/types.d.ts +70 -0
  68. package/dist/cjs/proofs/types.js +2 -0
  69. package/dist/cjs/prover.d.ts +4 -1
  70. package/dist/cjs/prover.js +12 -2
  71. package/dist/cjs/random.d.ts +16 -0
  72. package/dist/cjs/random.js +28 -0
  73. package/dist/cjs/relayer/client.d.ts +61 -0
  74. package/dist/cjs/relayer/client.js +151 -0
  75. package/dist/cjs/relayer/crypto.d.ts +5 -0
  76. package/dist/cjs/relayer/crypto.js +26 -0
  77. package/dist/cjs/relayer/encoding.d.ts +2 -0
  78. package/dist/cjs/relayer/encoding.js +23 -0
  79. package/dist/cjs/relayer/errors.d.ts +7 -0
  80. package/dist/cjs/relayer/errors.js +17 -0
  81. package/dist/cjs/relayer/index.d.ts +3 -0
  82. package/dist/cjs/relayer/index.js +19 -0
  83. package/dist/cjs/relayer/transport.d.ts +17 -0
  84. package/dist/cjs/relayer/transport.js +63 -0
  85. package/dist/cjs/relayer/types.d.ts +276 -0
  86. package/dist/cjs/relayer/types.js +5 -0
  87. package/dist/cjs/relayer.d.ts +1 -295
  88. package/dist/cjs/relayer.js +15 -243
  89. package/dist/cjs/shield/alt.d.ts +87 -0
  90. package/dist/cjs/shield/alt.js +194 -0
  91. package/dist/cjs/shield/computeBudget.d.ts +61 -0
  92. package/dist/cjs/shield/computeBudget.js +64 -0
  93. package/dist/cjs/shield/errors.d.ts +58 -0
  94. package/dist/cjs/shield/errors.js +121 -0
  95. package/dist/cjs/shield/finalize.d.ts +45 -0
  96. package/dist/cjs/shield/finalize.js +119 -0
  97. package/dist/cjs/shield/index.d.ts +35 -0
  98. package/dist/cjs/shield/index.js +68 -0
  99. package/dist/cjs/shield/ix.d.ts +54 -0
  100. package/dist/cjs/shield/ix.js +119 -0
  101. package/dist/cjs/shield/owner.d.ts +36 -0
  102. package/dist/cjs/shield/owner.js +127 -0
  103. package/dist/cjs/shield/ports.d.ts +43 -0
  104. package/dist/cjs/shield/ports.js +153 -0
  105. package/dist/cjs/shield/preflight.d.ts +30 -0
  106. package/dist/cjs/shield/preflight.js +154 -0
  107. package/dist/cjs/shield/shield.d.ts +68 -0
  108. package/dist/cjs/shield/shield.js +500 -0
  109. package/dist/cjs/shield/types.d.ts +202 -0
  110. package/dist/cjs/shield/types.js +2 -0
  111. package/dist/cjs/transactions/deposit.d.ts +94 -0
  112. package/dist/cjs/transactions/deposit.js +234 -0
  113. package/dist/cjs/transactions/index.d.ts +7 -0
  114. package/dist/cjs/transactions/index.js +26 -0
  115. package/dist/cjs/transactions/swap.d.ts +71 -0
  116. package/dist/cjs/transactions/swap.js +184 -0
  117. package/dist/cjs/transactions/transact.d.ts +34 -0
  118. package/dist/cjs/transactions/transact.js +146 -0
  119. package/dist/cjs/transactions/transfer.d.ts +51 -0
  120. package/dist/cjs/transactions/transfer.js +106 -0
  121. package/dist/cjs/transactions/withdraw.d.ts +52 -0
  122. package/dist/cjs/transactions/withdraw.js +104 -0
  123. package/dist/cjs/utxo.d.ts +1 -215
  124. package/dist/cjs/utxo.js +15 -391
  125. package/dist/esm/accounts/admin.d.ts +84 -0
  126. package/dist/esm/accounts/admin.js +165 -0
  127. package/dist/esm/accounts/errors.d.ts +6 -0
  128. package/dist/esm/accounts/errors.js +95 -0
  129. package/dist/esm/accounts/index.d.ts +4 -0
  130. package/dist/esm/accounts/index.js +4 -0
  131. package/dist/esm/accounts/pdas.d.ts +23 -0
  132. package/dist/esm/accounts/pdas.js +38 -0
  133. package/dist/esm/accounts/queries.d.ts +41 -0
  134. package/dist/esm/accounts/queries.js +88 -0
  135. package/dist/esm/client.d.ts +2 -0
  136. package/dist/esm/client.js +3 -887
  137. package/dist/esm/cloak/client.d.ts +28 -0
  138. package/dist/esm/cloak/client.js +64 -0
  139. package/dist/esm/cloak/errors.d.ts +13 -0
  140. package/dist/esm/cloak/errors.js +21 -0
  141. package/dist/esm/cloak/helpers.d.ts +6 -0
  142. package/dist/esm/cloak/helpers.js +31 -0
  143. package/dist/esm/cloak/index.d.ts +6 -0
  144. package/dist/esm/cloak/index.js +6 -0
  145. package/dist/esm/cloak/polling.d.ts +7 -0
  146. package/dist/esm/cloak/polling.js +63 -0
  147. package/dist/esm/cloak/transport.d.ts +15 -0
  148. package/dist/esm/cloak/transport.js +115 -0
  149. package/dist/esm/cloak/types.d.ts +188 -0
  150. package/dist/esm/cloak/types.js +4 -0
  151. package/dist/esm/cloak.d.ts +1 -0
  152. package/dist/esm/cloak.js +2 -0
  153. package/dist/esm/compactNote.d.ts +128 -0
  154. package/dist/esm/compactNote.js +179 -0
  155. package/dist/esm/config.d.ts +82 -0
  156. package/dist/esm/events.d.ts +77 -0
  157. package/dist/esm/events.js +1 -1
  158. package/dist/esm/idl/privacy_pool.d.ts +5 -0
  159. package/dist/esm/idl/privacy_pool.js +15216 -0
  160. package/dist/esm/index.d.ts +17 -0
  161. package/dist/esm/index.js +20 -11
  162. package/dist/esm/merkle.d.ts +77 -0
  163. package/dist/esm/merkle.js +23 -1
  164. package/dist/esm/notes/encryption.d.ts +41 -0
  165. package/dist/esm/notes/encryption.js +67 -0
  166. package/dist/esm/notes/index.d.ts +5 -0
  167. package/dist/esm/notes/index.js +5 -0
  168. package/dist/esm/notes/mailbox.d.ts +70 -0
  169. package/dist/esm/notes/mailbox.js +154 -0
  170. package/dist/esm/notes/model.d.ts +91 -0
  171. package/dist/esm/notes/model.js +99 -0
  172. package/dist/esm/notes/nullifier.d.ts +27 -0
  173. package/dist/esm/notes/nullifier.js +40 -0
  174. package/dist/esm/notes/recovery.d.ts +10 -0
  175. package/dist/esm/notes/recovery.js +28 -0
  176. package/dist/esm/package.json +1 -0
  177. package/dist/esm/poseidon.d.ts +29 -0
  178. package/dist/esm/program.d.ts +37 -0
  179. package/dist/esm/program.js +23 -1
  180. package/dist/esm/proof.d.ts +1 -0
  181. package/dist/esm/proof.js +2 -281
  182. package/dist/esm/proofs/encoding.d.ts +17 -0
  183. package/dist/esm/proofs/encoding.js +68 -0
  184. package/dist/esm/proofs/formatting.d.ts +6 -0
  185. package/dist/esm/proofs/formatting.js +31 -0
  186. package/dist/esm/proofs/index.d.ts +5 -0
  187. package/dist/esm/proofs/index.js +5 -0
  188. package/dist/esm/proofs/swap.d.ts +87 -0
  189. package/dist/esm/proofs/swap.js +138 -0
  190. package/dist/esm/proofs/transaction.d.ts +28 -0
  191. package/dist/esm/proofs/transaction.js +105 -0
  192. package/dist/esm/proofs/types.d.ts +70 -0
  193. package/dist/esm/proofs/types.js +1 -0
  194. package/dist/esm/prover.d.ts +57 -0
  195. package/dist/esm/prover.js +10 -1
  196. package/dist/esm/random.d.ts +16 -0
  197. package/dist/esm/random.js +21 -0
  198. package/dist/esm/relayer/client.d.ts +61 -0
  199. package/dist/esm/relayer/client.js +144 -0
  200. package/dist/esm/relayer/crypto.d.ts +5 -0
  201. package/dist/esm/relayer/crypto.js +20 -0
  202. package/dist/esm/relayer/encoding.d.ts +2 -0
  203. package/dist/esm/relayer/encoding.js +19 -0
  204. package/dist/esm/relayer/errors.d.ts +7 -0
  205. package/dist/esm/relayer/errors.js +13 -0
  206. package/dist/esm/relayer/index.d.ts +3 -0
  207. package/dist/esm/relayer/index.js +3 -0
  208. package/dist/esm/relayer/transport.d.ts +17 -0
  209. package/dist/esm/relayer/transport.js +59 -0
  210. package/dist/esm/relayer/types.d.ts +276 -0
  211. package/dist/esm/relayer/types.js +4 -0
  212. package/dist/esm/relayer.d.ts +1 -0
  213. package/dist/esm/relayer.js +2 -238
  214. package/dist/esm/retry.d.ts +32 -0
  215. package/dist/esm/shield/alt.d.ts +87 -0
  216. package/dist/esm/shield/alt.js +186 -0
  217. package/dist/esm/shield/computeBudget.d.ts +61 -0
  218. package/dist/esm/shield/computeBudget.js +61 -0
  219. package/dist/esm/shield/errors.d.ts +58 -0
  220. package/dist/esm/shield/errors.js +115 -0
  221. package/dist/esm/shield/finalize.d.ts +45 -0
  222. package/dist/esm/shield/finalize.js +83 -0
  223. package/dist/esm/shield/index.d.ts +35 -0
  224. package/dist/esm/shield/index.js +32 -0
  225. package/dist/esm/shield/ix.d.ts +54 -0
  226. package/dist/esm/shield/ix.js +82 -0
  227. package/dist/esm/shield/owner.d.ts +36 -0
  228. package/dist/esm/shield/owner.js +123 -0
  229. package/dist/esm/shield/ports.d.ts +43 -0
  230. package/dist/esm/shield/ports.js +147 -0
  231. package/dist/esm/shield/preflight.d.ts +30 -0
  232. package/dist/esm/shield/preflight.js +151 -0
  233. package/dist/esm/shield/shield.d.ts +68 -0
  234. package/dist/esm/shield/shield.js +493 -0
  235. package/dist/esm/shield/types.d.ts +202 -0
  236. package/dist/esm/shield/types.js +1 -0
  237. package/dist/esm/transactions/deposit.d.ts +94 -0
  238. package/dist/esm/transactions/deposit.js +198 -0
  239. package/dist/esm/transactions/index.d.ts +7 -0
  240. package/dist/esm/transactions/index.js +7 -0
  241. package/dist/esm/transactions/swap.d.ts +71 -0
  242. package/dist/esm/transactions/swap.js +144 -0
  243. package/dist/esm/transactions/transact.d.ts +34 -0
  244. package/dist/esm/transactions/transact.js +110 -0
  245. package/dist/esm/transactions/transfer.d.ts +51 -0
  246. package/dist/esm/transactions/transfer.js +103 -0
  247. package/dist/esm/transactions/withdraw.d.ts +52 -0
  248. package/dist/esm/transactions/withdraw.js +101 -0
  249. package/dist/esm/utxo.d.ts +1 -0
  250. package/dist/esm/utxo.js +2 -372
  251. package/package.json +111 -15
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Compact note ciphers — the 80-byte payload carried inside the on-chain
3
+ * `transact` instruction (`note_ciphers: Option<NoteCiphers>`) and re-emitted in
4
+ * `CommitmentEvent`.
5
+ *
6
+ * This is what makes a note recoverable from chain data alone: a recipient who
7
+ * was never told about a deposit can still find it by scanning `view_tag` and
8
+ * decrypting the blob. Without it, note delivery depends on someone calling the
9
+ * relayer, which a third-party integrator cannot be trusted to do.
10
+ *
11
+ * ## Byte-compatibility is load-bearing
12
+ *
13
+ * Four sibling implementations already exist (relayer-server, extension ×2,
14
+ * wallet-app). This is the canonical one, and it MUST agree with them byte for
15
+ * byte in both directions:
16
+ *
17
+ * - disagree on encrypt → new notes are invisible to every existing client
18
+ * - disagree on decrypt → the entire historical note corpus becomes unreadable
19
+ *
20
+ * Three details are easy to get wrong and are individually fatal:
21
+ *
22
+ * 1. The KDF is `nacl.hash(x).slice(0, 32)` — **truncated SHA-512**, despite the
23
+ * sibling helper being named `sha256ForNotes`. See `kdfSHA512_256` in utxo.ts.
24
+ * 2. The ephemeral key published on-chain is **Ed25519**; it is converted to
25
+ * X25519 only for the Diffie-Hellman. Scanners convert it back.
26
+ * 3. `viewTag` is the first byte of the hash of the **raw shared secret**, not of
27
+ * the derived key. A wrong view tag makes every note invisible to the scan
28
+ * loop, which rejects ~255/256 candidates on this byte alone.
29
+ *
30
+ * The ECDH primitives are imported from `utxo.ts` rather than reimplemented — a
31
+ * fifth independent Edwards→Montgomery implementation is exactly how the current
32
+ * drift risk between the four siblings arose.
33
+ *
34
+ * Verified against the siblings by `tests/compact-note.test.ts` (golden vectors)
35
+ * and `scripts/verify-compact-cipher-parity.mjs` (differential).
36
+ */
37
+ /** Size of the on-chain compact blob: nonce[24] || secretbox(40-byte plaintext)[56]. */
38
+ export declare const COMPACT_BLOB_LENGTH = 80;
39
+ export type CompactNoteCipher = {
40
+ /** Ed25519 ephemeral public key, published on-chain. */
41
+ ephemeralPublicKey: Uint8Array;
42
+ /** 80 bytes: nonce[24] || ciphertext[56]. */
43
+ compactBlob: Uint8Array;
44
+ /** 1-byte scan filter. */
45
+ viewTag: number;
46
+ };
47
+ /** The optional pair of compact note ciphers carried by `transact` instructions. */
48
+ export type NoteCiphers = {
49
+ note0EphemeralKey: Uint8Array;
50
+ note0Encrypted: Uint8Array;
51
+ note0ViewTag: number;
52
+ note1EphemeralKey: Uint8Array;
53
+ note1Encrypted: Uint8Array;
54
+ note1ViewTag: number;
55
+ };
56
+ /** Plaintext note material needed to create one compact on-chain cipher. */
57
+ export type NoteCipherInput = {
58
+ recipientWalletPubkey: Uint8Array;
59
+ blinding: Uint8Array;
60
+ amount: bigint;
61
+ };
62
+ /**
63
+ * Test-only determinism hook. NOT part of the public contract — it exists so
64
+ * golden vectors can pin exact bytes against the sibling implementations, which
65
+ * is the only way to prove byte-compatibility rather than assume it.
66
+ *
67
+ * @internal
68
+ */
69
+ export type CompactNoteDeterminism = {
70
+ ephemeralSeed: Uint8Array;
71
+ nonce: Uint8Array;
72
+ };
73
+ /**
74
+ * Encrypt a note's secret material to a recipient's Solana wallet key.
75
+ *
76
+ * The recipient does not need to be the signer of the transaction, or to have
77
+ * any prior relationship with the sender — this is what makes shielding to an
78
+ * arbitrary owner possible.
79
+ *
80
+ * @param recipientWalletPubkey 32-byte Ed25519 Solana wallet public key
81
+ * @param blinding 32-byte big-endian blinding factor
82
+ * @param amount note amount in base units
83
+ */
84
+ export declare function createCompactNoteCipher(recipientWalletPubkey: Uint8Array, blinding: Uint8Array, amount: bigint,
85
+ /** @internal */ __det?: CompactNoteDeterminism): CompactNoteCipher;
86
+ /**
87
+ * Recover a note's blinding and amount from an on-chain compact cipher.
88
+ *
89
+ * Returns `null` when the blob was not encrypted to this key — the expected
90
+ * outcome for the overwhelming majority of candidates during a scan, so callers
91
+ * should filter on `viewTag` first (see {@link matchesViewTag}) and only attempt
92
+ * decryption on the ~1/256 that survive.
93
+ *
94
+ * @param walletSecretKey 32-byte Ed25519 seed or 64-byte secret key
95
+ */
96
+ export declare function decryptCompactNoteCipher(walletSecretKey: Uint8Array, ephemeralPublicKey: Uint8Array, compactBlob: Uint8Array, precomputed?: {
97
+ sharedSecret?: Uint8Array;
98
+ x25519Private?: Uint8Array;
99
+ }): {
100
+ blinding: Uint8Array;
101
+ amount: bigint;
102
+ } | null;
103
+ /**
104
+ * Cheap scan filter: does this on-chain cipher plausibly belong to us?
105
+ *
106
+ * Rejects ~255/256 candidates for the cost of one scalar multiplication and one
107
+ * hash, versus a full secretbox open. Hoist `x25519Private` across a scan loop
108
+ * with {@link toX25519Private}.
109
+ */
110
+ export declare function matchesViewTag(walletSecretKey: Uint8Array, ephemeralPublicKey: Uint8Array, viewTag: number, precomputed?: {
111
+ x25519Private?: Uint8Array;
112
+ }): {
113
+ matches: boolean;
114
+ sharedSecret: Uint8Array;
115
+ };
116
+ /**
117
+ * Convert an Ed25519 wallet secret key to its X25519 scalar once, for reuse
118
+ * across a scan loop.
119
+ */
120
+ export declare function toX25519Private(walletSecretKey: Uint8Array): Uint8Array;
121
+ /** The all-zero cipher the program treats as "no note here". */
122
+ export declare function emptyNoteCipher(): CompactNoteCipher;
123
+ /**
124
+ * Build the two-note payload accepted by `transact` and `transact_swap`.
125
+ * A missing side is encoded with the program's all-zero sentinel. If both
126
+ * sides are missing, `null` is returned so Anchor encodes `Option::None`.
127
+ */
128
+ export declare function createNoteCiphers(note0: NoteCipherInput | null, note1: NoteCipherInput | null): NoteCiphers | null;
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.COMPACT_BLOB_LENGTH = void 0;
7
+ exports.createCompactNoteCipher = createCompactNoteCipher;
8
+ exports.decryptCompactNoteCipher = decryptCompactNoteCipher;
9
+ exports.matchesViewTag = matchesViewTag;
10
+ exports.toX25519Private = toX25519Private;
11
+ exports.emptyNoteCipher = emptyNoteCipher;
12
+ exports.createNoteCiphers = createNoteCiphers;
13
+ const tweetnacl_1 = __importDefault(require("tweetnacl"));
14
+ const mailbox_js_1 = require("./notes/mailbox.js");
15
+ const random_js_1 = require("./random.js");
16
+ /**
17
+ * Compact note ciphers — the 80-byte payload carried inside the on-chain
18
+ * `transact` instruction (`note_ciphers: Option<NoteCiphers>`) and re-emitted in
19
+ * `CommitmentEvent`.
20
+ *
21
+ * This is what makes a note recoverable from chain data alone: a recipient who
22
+ * was never told about a deposit can still find it by scanning `view_tag` and
23
+ * decrypting the blob. Without it, note delivery depends on someone calling the
24
+ * relayer, which a third-party integrator cannot be trusted to do.
25
+ *
26
+ * ## Byte-compatibility is load-bearing
27
+ *
28
+ * Four sibling implementations already exist (relayer-server, extension ×2,
29
+ * wallet-app). This is the canonical one, and it MUST agree with them byte for
30
+ * byte in both directions:
31
+ *
32
+ * - disagree on encrypt → new notes are invisible to every existing client
33
+ * - disagree on decrypt → the entire historical note corpus becomes unreadable
34
+ *
35
+ * Three details are easy to get wrong and are individually fatal:
36
+ *
37
+ * 1. The KDF is `nacl.hash(x).slice(0, 32)` — **truncated SHA-512**, despite the
38
+ * sibling helper being named `sha256ForNotes`. See `kdfSHA512_256` in utxo.ts.
39
+ * 2. The ephemeral key published on-chain is **Ed25519**; it is converted to
40
+ * X25519 only for the Diffie-Hellman. Scanners convert it back.
41
+ * 3. `viewTag` is the first byte of the hash of the **raw shared secret**, not of
42
+ * the derived key. A wrong view tag makes every note invisible to the scan
43
+ * loop, which rejects ~255/256 candidates on this byte alone.
44
+ *
45
+ * The ECDH primitives are imported from `utxo.ts` rather than reimplemented — a
46
+ * fifth independent Edwards→Montgomery implementation is exactly how the current
47
+ * drift risk between the four siblings arose.
48
+ *
49
+ * Verified against the siblings by `tests/compact-note.test.ts` (golden vectors)
50
+ * and `scripts/verify-compact-cipher-parity.mjs` (differential).
51
+ */
52
+ /** Size of the on-chain compact blob: nonce[24] || secretbox(40-byte plaintext)[56]. */
53
+ exports.COMPACT_BLOB_LENGTH = 80;
54
+ /** Plaintext layout: blinding[32] || amount as little-endian u64[8]. */
55
+ const COMPACT_PLAINTEXT_LENGTH = 40;
56
+ const NONCE_LENGTH = 24;
57
+ /**
58
+ * Encrypt a note's secret material to a recipient's Solana wallet key.
59
+ *
60
+ * The recipient does not need to be the signer of the transaction, or to have
61
+ * any prior relationship with the sender — this is what makes shielding to an
62
+ * arbitrary owner possible.
63
+ *
64
+ * @param recipientWalletPubkey 32-byte Ed25519 Solana wallet public key
65
+ * @param blinding 32-byte big-endian blinding factor
66
+ * @param amount note amount in base units
67
+ */
68
+ function createCompactNoteCipher(recipientWalletPubkey, blinding, amount,
69
+ /** @internal */ __det) {
70
+ if (recipientWalletPubkey.length !== 32) {
71
+ throw new Error(`recipientWalletPubkey must be 32 bytes, got ${recipientWalletPubkey.length}`);
72
+ }
73
+ if (blinding.length < 32) {
74
+ throw new Error(`blinding must be at least 32 bytes, got ${blinding.length}`);
75
+ }
76
+ if (amount < 0n || amount > 0xffffffffffffffffn) {
77
+ throw new Error(`amount ${amount} does not fit in a u64`);
78
+ }
79
+ // Ed25519 ephemeral keypair. `nacl.sign.keyPair.fromSeed(seed)` produces the
80
+ // same public key as web3.js `Keypair.generate()` for the same seed, which is
81
+ // what the sibling implementations use — but without pulling @solana/web3.js
82
+ // into the crypto path.
83
+ const ephemeralSeed = __det?.ephemeralSeed ?? (0, random_js_1.randomBytes)(32);
84
+ const ephemeralPublicKey = tweetnacl_1.default.sign.keyPair.fromSeed(ephemeralSeed).publicKey;
85
+ const sharedSecret = deriveSharedSecret(ephemeralSeed, recipientWalletPubkey);
86
+ const key = (0, mailbox_js_1.kdfSHA512_256)(sharedSecret);
87
+ const plaintext = new Uint8Array(COMPACT_PLAINTEXT_LENGTH);
88
+ plaintext.set(blinding.slice(0, 32), 0);
89
+ let amt = amount;
90
+ for (let i = 0; i < 8; i++) {
91
+ plaintext[32 + i] = Number(amt & 0xffn);
92
+ amt >>= 8n;
93
+ }
94
+ const nonce = __det?.nonce ?? tweetnacl_1.default.randomBytes(NONCE_LENGTH);
95
+ const encrypted = tweetnacl_1.default.secretbox(plaintext, nonce, key);
96
+ const compactBlob = new Uint8Array(exports.COMPACT_BLOB_LENGTH);
97
+ compactBlob.set(nonce, 0);
98
+ compactBlob.set(encrypted, NONCE_LENGTH);
99
+ // First byte of the hash of the RAW shared secret — not of `key`.
100
+ const viewTag = tweetnacl_1.default.hash(sharedSecret)[0];
101
+ return { ephemeralPublicKey, compactBlob, viewTag };
102
+ }
103
+ /**
104
+ * Recover a note's blinding and amount from an on-chain compact cipher.
105
+ *
106
+ * Returns `null` when the blob was not encrypted to this key — the expected
107
+ * outcome for the overwhelming majority of candidates during a scan, so callers
108
+ * should filter on `viewTag` first (see {@link matchesViewTag}) and only attempt
109
+ * decryption on the ~1/256 that survive.
110
+ *
111
+ * @param walletSecretKey 32-byte Ed25519 seed or 64-byte secret key
112
+ */
113
+ function decryptCompactNoteCipher(walletSecretKey, ephemeralPublicKey, compactBlob, precomputed) {
114
+ if (compactBlob.length !== exports.COMPACT_BLOB_LENGTH)
115
+ return null;
116
+ const sharedSecret = precomputed?.sharedSecret ??
117
+ (precomputed?.x25519Private
118
+ ? tweetnacl_1.default.scalarMult(precomputed.x25519Private, (0, mailbox_js_1.ed25519PublicKeyToX25519)(ephemeralPublicKey))
119
+ : deriveSharedSecret(walletSecretKey, ephemeralPublicKey));
120
+ const key = (0, mailbox_js_1.kdfSHA512_256)(sharedSecret);
121
+ const nonce = compactBlob.slice(0, NONCE_LENGTH);
122
+ const encrypted = compactBlob.slice(NONCE_LENGTH);
123
+ const plaintext = tweetnacl_1.default.secretbox.open(encrypted, nonce, key);
124
+ if (!plaintext)
125
+ return null;
126
+ const blinding = new Uint8Array(plaintext.slice(0, 32));
127
+ let amount = 0n;
128
+ for (let i = 7; i >= 0; i--) {
129
+ amount = (amount << 8n) | BigInt(plaintext[32 + i]);
130
+ }
131
+ return { blinding, amount };
132
+ }
133
+ /**
134
+ * Cheap scan filter: does this on-chain cipher plausibly belong to us?
135
+ *
136
+ * Rejects ~255/256 candidates for the cost of one scalar multiplication and one
137
+ * hash, versus a full secretbox open. Hoist `x25519Private` across a scan loop
138
+ * with {@link toX25519Private}.
139
+ */
140
+ function matchesViewTag(walletSecretKey, ephemeralPublicKey, viewTag, precomputed) {
141
+ const sharedSecret = precomputed?.x25519Private
142
+ ? tweetnacl_1.default.scalarMult(precomputed.x25519Private, (0, mailbox_js_1.ed25519PublicKeyToX25519)(ephemeralPublicKey))
143
+ : deriveSharedSecret(walletSecretKey, ephemeralPublicKey);
144
+ return { matches: tweetnacl_1.default.hash(sharedSecret)[0] === viewTag, sharedSecret };
145
+ }
146
+ /**
147
+ * Convert an Ed25519 wallet secret key to its X25519 scalar once, for reuse
148
+ * across a scan loop.
149
+ */
150
+ function toX25519Private(walletSecretKey) {
151
+ return (0, mailbox_js_1.ed25519SeedToX25519Private)(seedOf(walletSecretKey));
152
+ }
153
+ /** The all-zero cipher the program treats as "no note here". */
154
+ function emptyNoteCipher() {
155
+ return {
156
+ ephemeralPublicKey: new Uint8Array(32),
157
+ compactBlob: new Uint8Array(exports.COMPACT_BLOB_LENGTH),
158
+ viewTag: 0,
159
+ };
160
+ }
161
+ /**
162
+ * Build the two-note payload accepted by `transact` and `transact_swap`.
163
+ * A missing side is encoded with the program's all-zero sentinel. If both
164
+ * sides are missing, `null` is returned so Anchor encodes `Option::None`.
165
+ */
166
+ function createNoteCiphers(note0, note1) {
167
+ if (!note0 && !note1)
168
+ return null;
169
+ const cipher0 = note0
170
+ ? createCompactNoteCipher(note0.recipientWalletPubkey, note0.blinding, note0.amount)
171
+ : emptyNoteCipher();
172
+ const cipher1 = note1
173
+ ? createCompactNoteCipher(note1.recipientWalletPubkey, note1.blinding, note1.amount)
174
+ : emptyNoteCipher();
175
+ return {
176
+ note0EphemeralKey: cipher0.ephemeralPublicKey,
177
+ note0Encrypted: cipher0.compactBlob,
178
+ note0ViewTag: cipher0.viewTag,
179
+ note1EphemeralKey: cipher1.ephemeralPublicKey,
180
+ note1Encrypted: cipher1.compactBlob,
181
+ note1ViewTag: cipher1.viewTag,
182
+ };
183
+ }
184
+ // -- internal -----------------------------------------------------------------
185
+ /** Accept either a 32-byte Ed25519 seed or a 64-byte secret key, as the siblings do. */
186
+ function seedOf(secretKey) {
187
+ return secretKey.length === 64 ? secretKey.slice(0, 32) : secretKey;
188
+ }
189
+ function deriveSharedSecret(mySecretKey, theirEd25519PublicKey) {
190
+ return tweetnacl_1.default.scalarMult((0, mailbox_js_1.ed25519SeedToX25519Private)(seedOf(mySecretKey)), (0, mailbox_js_1.ed25519PublicKeyToX25519)(theirEd25519PublicKey));
191
+ }
@@ -1,6 +1,6 @@
1
1
  import { PublicKey } from "@solana/web3.js";
2
2
  import { Program, Idl } from "@coral-xyz/anchor";
3
- import { MerkleTree } from "./merkle";
3
+ import { MerkleTree } from "./merkle.js";
4
4
  /**
5
5
  * On-chain CommitmentEvent emitted by `transact` and `transact_swap`.
6
6
  * Mirrors the IDL CommitmentEvent struct.
@@ -37,7 +37,7 @@ exports.scanCommitmentEvents = scanCommitmentEvents;
37
37
  exports.scanNullifierEvents = scanNullifierEvents;
38
38
  exports.buildTreeFromEvents = buildTreeFromEvents;
39
39
  const anchor = __importStar(require("@coral-xyz/anchor"));
40
- const merkle_1 = require("./merkle");
40
+ const merkle_js_1 = require("./merkle.js");
41
41
  // -----------------------------------------------------------------------------
42
42
  // Event Scanning
43
43
  // -----------------------------------------------------------------------------
@@ -156,7 +156,7 @@ async function buildTreeFromEvents(params) {
156
156
  const treeEvents = allEvents
157
157
  .filter((e) => e.treeId === treeId)
158
158
  .sort((a, b) => a.leafIndex - b.leafIndex);
159
- const tree = new merkle_1.MerkleTree(depth);
159
+ const tree = new merkle_js_1.MerkleTree(depth);
160
160
  for (const ev of treeEvents) {
161
161
  tree.insert(ev.commitment);
162
162
  }
@@ -0,0 +1,5 @@
1
+ import type { Idl } from "@coral-xyz/anchor";
2
+ declare const IDL_JSON: Idl & {
3
+ address: string;
4
+ };
5
+ export default IDL_JSON;