@utxopia/sdk 0.1.0-alpha.1

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 (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +171 -0
  3. package/package.json +108 -0
  4. package/packages/btc-client/src/esplora-client.ts +153 -0
  5. package/packages/btc-client/src/index.ts +3 -0
  6. package/packages/btc-client/src/op-return.ts +93 -0
  7. package/packages/btc-client/src/types.ts +112 -0
  8. package/packages/sdk/README.md +277 -0
  9. package/packages/sdk/dist/announcement-client.d.ts +64 -0
  10. package/packages/sdk/dist/announcement-client.js +337 -0
  11. package/packages/sdk/dist/auditor-ciphertext.d.ts +72 -0
  12. package/packages/sdk/dist/auditor-ciphertext.js +135 -0
  13. package/packages/sdk/dist/auditor.d.ts +115 -0
  14. package/packages/sdk/dist/auditor.js +292 -0
  15. package/packages/sdk/dist/bitcoin/ika.d.ts +47 -0
  16. package/packages/sdk/dist/bitcoin/ika.js +74 -0
  17. package/packages/sdk/dist/bitcoin/index.d.ts +2 -0
  18. package/packages/sdk/dist/bitcoin/index.js +1 -0
  19. package/packages/sdk/dist/bound-params.d.ts +96 -0
  20. package/packages/sdk/dist/bound-params.js +249 -0
  21. package/packages/sdk/dist/chadbuffer.d.ts +124 -0
  22. package/packages/sdk/dist/chadbuffer.js +405 -0
  23. package/packages/sdk/dist/claim-link.d.ts +30 -0
  24. package/packages/sdk/dist/claim-link.js +50 -0
  25. package/packages/sdk/dist/client.d.ts +233 -0
  26. package/packages/sdk/dist/client.js +416 -0
  27. package/packages/sdk/dist/commitment-tree.d.ts +268 -0
  28. package/packages/sdk/dist/commitment-tree.js +564 -0
  29. package/packages/sdk/dist/config.d.ts +177 -0
  30. package/packages/sdk/dist/config.js +426 -0
  31. package/packages/sdk/dist/core/esplora.d.ts +110 -0
  32. package/packages/sdk/dist/core/esplora.js +187 -0
  33. package/packages/sdk/dist/core/mempool.d.ts +60 -0
  34. package/packages/sdk/dist/core/mempool.js +107 -0
  35. package/packages/sdk/dist/crypto-babyjub.d.ts +105 -0
  36. package/packages/sdk/dist/crypto-babyjub.js +312 -0
  37. package/packages/sdk/dist/crypto-ed25519.d.ts +116 -0
  38. package/packages/sdk/dist/crypto-ed25519.js +235 -0
  39. package/packages/sdk/dist/crypto.d.ts +59 -0
  40. package/packages/sdk/dist/crypto.js +140 -0
  41. package/packages/sdk/dist/event-client.d.ts +64 -0
  42. package/packages/sdk/dist/event-client.js +165 -0
  43. package/packages/sdk/dist/events.d.ts +127 -0
  44. package/packages/sdk/dist/events.js +305 -0
  45. package/packages/sdk/dist/explorer.d.ts +86 -0
  46. package/packages/sdk/dist/explorer.js +197 -0
  47. package/packages/sdk/dist/index.d.ts +61 -0
  48. package/packages/sdk/dist/index.js +216 -0
  49. package/packages/sdk/dist/instructions.d.ts +1125 -0
  50. package/packages/sdk/dist/instructions.js +1760 -0
  51. package/packages/sdk/dist/keys.d.ts +467 -0
  52. package/packages/sdk/dist/keys.js +799 -0
  53. package/packages/sdk/dist/logger.d.ts +10 -0
  54. package/packages/sdk/dist/logger.js +40 -0
  55. package/packages/sdk/dist/magicblock.d.ts +90 -0
  56. package/packages/sdk/dist/magicblock.js +164 -0
  57. package/packages/sdk/dist/merkle.d.ts +82 -0
  58. package/packages/sdk/dist/merkle.js +141 -0
  59. package/packages/sdk/dist/note.d.ts +348 -0
  60. package/packages/sdk/dist/note.js +483 -0
  61. package/packages/sdk/dist/pda.d.ts +160 -0
  62. package/packages/sdk/dist/pda.js +384 -0
  63. package/packages/sdk/dist/pool-state.d.ts +100 -0
  64. package/packages/sdk/dist/pool-state.js +126 -0
  65. package/packages/sdk/dist/poseidon.d.ts +53 -0
  66. package/packages/sdk/dist/poseidon.js +136 -0
  67. package/packages/sdk/dist/prover/index.d.ts +10 -0
  68. package/packages/sdk/dist/prover/index.js +10 -0
  69. package/packages/sdk/dist/prover/mobile.d.ts +45 -0
  70. package/packages/sdk/dist/prover/mobile.js +227 -0
  71. package/packages/sdk/dist/prover/web.d.ts +136 -0
  72. package/packages/sdk/dist/prover/web.js +597 -0
  73. package/packages/sdk/dist/psbt.d.ts +73 -0
  74. package/packages/sdk/dist/psbt.js +202 -0
  75. package/packages/sdk/dist/selective-disclosure.d.ts +181 -0
  76. package/packages/sdk/dist/selective-disclosure.js +172 -0
  77. package/packages/sdk/dist/sender-memo.d.ts +149 -0
  78. package/packages/sdk/dist/sender-memo.js +250 -0
  79. package/packages/sdk/dist/sns-resolver.d.ts +121 -0
  80. package/packages/sdk/dist/sns-resolver.js +229 -0
  81. package/packages/sdk/dist/solana/connection.d.ts +77 -0
  82. package/packages/sdk/dist/solana/connection.js +133 -0
  83. package/packages/sdk/dist/solana/priority-fee.d.ts +78 -0
  84. package/packages/sdk/dist/solana/priority-fee.js +137 -0
  85. package/packages/sdk/dist/stealth.d.ts +429 -0
  86. package/packages/sdk/dist/stealth.js +703 -0
  87. package/packages/sdk/dist/taproot.d.ts +172 -0
  88. package/packages/sdk/dist/taproot.js +494 -0
  89. package/packages/sdk/dist/token-registry.d.ts +96 -0
  90. package/packages/sdk/dist/token-registry.js +122 -0
  91. package/packages/sdk/dist/utils/encoding.d.ts +19 -0
  92. package/packages/sdk/dist/utils/encoding.js +29 -0
  93. package/packages/sdk/dist/vk-registry.d.ts +100 -0
  94. package/packages/sdk/dist/vk-registry.js +212 -0
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Baby Jubjub curve operations for UTXOpia
3
+ *
4
+ * Twisted Edwards curve matching circomlib's BabyJubjub:
5
+ * a*x^2 + y^2 = 1 + d*x^2*y^2
6
+ * a = 168700, d = 168696
7
+ *
8
+ * Over BN254 scalar field.
9
+ * Used for spending keys and in-circuit key derivation via BabyPbk().
10
+ *
11
+ * @see https://eips.ethereum.org/EIPS/eip-2494
12
+ * @see circomlib/circuits/babyjub.circom
13
+ */
14
+ import { sha256 } from "@noble/hashes/sha2.js";
15
+ // =============================================================================
16
+ // Field Constants
17
+ // =============================================================================
18
+ /** BN254 scalar field prime (Baby Jubjub base field) */
19
+ export const BABYJUB_FIELD_PRIME = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
20
+ /** Baby Jubjub curve parameter a */
21
+ export const BABYJUB_A = 168700n;
22
+ /** Baby Jubjub curve parameter d */
23
+ export const BABYJUB_D = 168696n;
24
+ /**
25
+ * Baby Jubjub subgroup order (order of BASE8 generator)
26
+ * = field_prime / 8 (cofactor 8)
27
+ */
28
+ export const BABYJUB_ORDER = 2736030358979909402780800718157159386076813972158567259200215660948447373041n;
29
+ /**
30
+ * Baby Jubjub cofactor
31
+ */
32
+ export const BABYJUB_COFACTOR = 8n;
33
+ /**
34
+ * Generator point (BASE8) - matches circomlib's BabyPbk() generator
35
+ * This is the base point of the prime-order subgroup (cofactor-cleared).
36
+ */
37
+ export const BABYJUB_BASE8 = {
38
+ x: 5299619240641551281634865583518297030282874472190772894086521144482721001553n,
39
+ y: 16950150798460657717958625567821834550301663161624707787222815936182638968203n,
40
+ };
41
+ /** Identity element (point at infinity for twisted Edwards) */
42
+ export const BABYJUB_IDENTITY = { x: 0n, y: 1n };
43
+ // =============================================================================
44
+ // Modular Arithmetic
45
+ // =============================================================================
46
+ const P = BABYJUB_FIELD_PRIME;
47
+ function mod(n, p = P) {
48
+ const result = n % p;
49
+ return result >= 0n ? result : result + p;
50
+ }
51
+ function modInverse(a, p = P) {
52
+ let [old_r, r] = [a, p];
53
+ let [old_s, s] = [1n, 0n];
54
+ while (r !== 0n) {
55
+ const q = old_r / r;
56
+ [old_r, r] = [r, old_r - q * r];
57
+ [old_s, s] = [s, old_s - q * s];
58
+ }
59
+ return mod(old_s, p);
60
+ }
61
+ // =============================================================================
62
+ // Curve Operations
63
+ // =============================================================================
64
+ /**
65
+ * Check if a point is the identity element
66
+ */
67
+ export function isIdentity(point) {
68
+ return point.x === 0n && point.y === 1n;
69
+ }
70
+ /**
71
+ * Verify a point is on the Baby Jubjub curve
72
+ *
73
+ * a*x^2 + y^2 = 1 + d*x^2*y^2
74
+ */
75
+ export function isOnBabyJubCurve(point) {
76
+ if (isIdentity(point))
77
+ return true;
78
+ const { x, y } = point;
79
+ const x2 = mod(x * x);
80
+ const y2 = mod(y * y);
81
+ const lhs = mod(BABYJUB_A * x2 + y2);
82
+ const rhs = mod(1n + BABYJUB_D * x2 * y2);
83
+ return lhs === rhs;
84
+ }
85
+ /**
86
+ * Point addition on Baby Jubjub (twisted Edwards addition law)
87
+ *
88
+ * For twisted Edwards: a*x^2 + y^2 = 1 + d*x^2*y^2
89
+ * x3 = (x1*y2 + y1*x2) / (1 + d*x1*x2*y1*y2)
90
+ * y3 = (y1*y2 - a*x1*x2) / (1 - d*x1*x2*y1*y2)
91
+ */
92
+ export function babyJubAdd(p1, p2) {
93
+ const { x: x1, y: y1 } = p1;
94
+ const { x: x2, y: y2 } = p2;
95
+ const x1x2 = mod(x1 * x2);
96
+ const y1y2 = mod(y1 * y2);
97
+ const dx1x2y1y2 = mod(BABYJUB_D * x1x2 * y1y2);
98
+ const x3Num = mod(x1 * y2 + y1 * x2);
99
+ const x3Den = mod(1n + dx1x2y1y2);
100
+ const y3Num = mod(y1y2 - BABYJUB_A * x1x2);
101
+ const y3Den = mod(1n - dx1x2y1y2);
102
+ const x3 = mod(x3Num * modInverse(x3Den));
103
+ const y3 = mod(y3Num * modInverse(y3Den));
104
+ return { x: x3, y: y3 };
105
+ }
106
+ /**
107
+ * Point doubling on Baby Jubjub
108
+ */
109
+ export function babyJubDouble(point) {
110
+ return babyJubAdd(point, point);
111
+ }
112
+ /**
113
+ * Scalar multiplication using double-and-add (constant-time Montgomery ladder)
114
+ *
115
+ * SECURITY: Uses Montgomery ladder for constant-time execution.
116
+ */
117
+ export function babyJubMul(scalar, point) {
118
+ scalar = mod(scalar, BABYJUB_ORDER);
119
+ if (scalar === 0n)
120
+ return BABYJUB_IDENTITY;
121
+ if (isIdentity(point))
122
+ return BABYJUB_IDENTITY;
123
+ // Montgomery ladder
124
+ let r0 = BABYJUB_IDENTITY;
125
+ let r1 = point;
126
+ // Process all bits of the scalar
127
+ const bits = scalar.toString(2).length;
128
+ for (let i = bits - 1; i >= 0; i--) {
129
+ const bit = (scalar >> BigInt(i)) & 1n;
130
+ if (bit === 1n) {
131
+ r0 = babyJubAdd(r0, r1);
132
+ r1 = babyJubDouble(r1);
133
+ }
134
+ else {
135
+ r1 = babyJubAdd(r0, r1);
136
+ r0 = babyJubDouble(r0);
137
+ }
138
+ }
139
+ return r0;
140
+ }
141
+ /**
142
+ * Negate a point (flip x coordinate for twisted Edwards)
143
+ */
144
+ export function babyJubNegate(point) {
145
+ if (isIdentity(point))
146
+ return point;
147
+ return {
148
+ x: mod(-point.x),
149
+ y: point.y,
150
+ };
151
+ }
152
+ // =============================================================================
153
+ // Compression / Serialization
154
+ // =============================================================================
155
+ /**
156
+ * Compress a Baby Jubjub point to 32 bytes
157
+ *
158
+ * Format: y-coordinate with sign of x stored in MSB of last byte
159
+ * This matches circomlib's pointbits.circom compression.
160
+ */
161
+ export function babyJubCompress(point) {
162
+ const bytes = new Uint8Array(32);
163
+ let y = point.y;
164
+ // Store y in little-endian (matching circomlib convention)
165
+ let temp = y;
166
+ for (let i = 0; i < 32; i++) {
167
+ bytes[i] = Number(temp & 0xffn);
168
+ temp = temp >> 8n;
169
+ }
170
+ // Set MSB of last byte to sign of x (1 if x is odd)
171
+ if ((point.x & 1n) === 1n) {
172
+ bytes[31] |= 0x80;
173
+ }
174
+ return bytes;
175
+ }
176
+ /**
177
+ * Decompress a 32-byte representation to a Baby Jubjub point
178
+ */
179
+ export function babyJubDecompress(bytes) {
180
+ if (bytes.length !== 32) {
181
+ throw new Error("Expected 32 bytes for compressed Baby Jubjub point");
182
+ }
183
+ // Extract sign of x from MSB
184
+ const xSign = (bytes[31] & 0x80) !== 0;
185
+ // Read y (little-endian), clearing the sign bit
186
+ const cleanBytes = new Uint8Array(bytes);
187
+ cleanBytes[31] &= 0x7f;
188
+ let y = 0n;
189
+ for (let i = 31; i >= 0; i--) {
190
+ y = (y << 8n) | BigInt(cleanBytes[i]);
191
+ }
192
+ // Recover x from curve equation: a*x^2 + y^2 = 1 + d*x^2*y^2
193
+ // x^2 = (1 - y^2) / (a - d*y^2)
194
+ const y2 = mod(y * y);
195
+ const num = mod(1n - y2);
196
+ const den = mod(BABYJUB_A - BABYJUB_D * y2);
197
+ const x2 = mod(num * modInverse(den));
198
+ // Compute x via Tonelli-Shanks square root
199
+ let x = modSqrt(x2);
200
+ // Adjust sign of x
201
+ const xIsOdd = (x & 1n) === 1n;
202
+ if (xIsOdd !== xSign) {
203
+ x = mod(-x);
204
+ }
205
+ const point = { x, y };
206
+ if (!isOnBabyJubCurve(point)) {
207
+ throw new Error("Decompressed point is not on the Baby Jubjub curve");
208
+ }
209
+ return point;
210
+ }
211
+ /**
212
+ * Tonelli-Shanks modular square root
213
+ */
214
+ function modSqrt(n) {
215
+ if (n === 0n)
216
+ return 0n;
217
+ // For p ≡ 3 (mod 4), sqrt = n^((p+1)/4)
218
+ // BN254 scalar field: p ≡ 1 (mod 4), so we need full Tonelli-Shanks
219
+ const p = P;
220
+ // Check if n is a quadratic residue
221
+ const euler = modPow(n, (p - 1n) / 2n);
222
+ if (euler !== 1n) {
223
+ throw new Error("No square root exists (not a quadratic residue)");
224
+ }
225
+ // Factor out powers of 2 from p-1
226
+ let Q = p - 1n;
227
+ let S = 0n;
228
+ while ((Q & 1n) === 0n) {
229
+ Q >>= 1n;
230
+ S++;
231
+ }
232
+ // Find a non-residue z
233
+ let z = 2n;
234
+ while (modPow(z, (p - 1n) / 2n) !== p - 1n) {
235
+ z++;
236
+ }
237
+ let M = S;
238
+ let c = modPow(z, Q);
239
+ let t = modPow(n, Q);
240
+ let R = modPow(n, (Q + 1n) / 2n);
241
+ while (true) {
242
+ if (t === 1n)
243
+ return R;
244
+ let i = 1n;
245
+ let temp = mod(t * t);
246
+ while (temp !== 1n) {
247
+ temp = mod(temp * temp);
248
+ i++;
249
+ }
250
+ const b = modPow(c, 1n << (M - i - 1n));
251
+ M = i;
252
+ c = mod(b * b);
253
+ t = mod(t * c);
254
+ R = mod(R * b);
255
+ }
256
+ }
257
+ function modPow(base, exp) {
258
+ let result = 1n;
259
+ base = mod(base);
260
+ while (exp > 0n) {
261
+ if (exp & 1n) {
262
+ result = mod(result * base);
263
+ }
264
+ exp >>= 1n;
265
+ base = mod(base * base);
266
+ }
267
+ return result;
268
+ }
269
+ // =============================================================================
270
+ // Key Generation
271
+ // =============================================================================
272
+ /**
273
+ * Generate a random Baby Jubjub keypair
274
+ */
275
+ export function generateBabyJubKeyPair() {
276
+ const bytes = new Uint8Array(32);
277
+ crypto.getRandomValues(bytes);
278
+ const privKey = babyJubScalarFromBytes(bytes);
279
+ const pubKey = babyJubMul(privKey, BABYJUB_BASE8);
280
+ return { privKey, pubKey };
281
+ }
282
+ /**
283
+ * Derive a Baby Jubjub keypair from a seed (deterministic)
284
+ */
285
+ export function deriveBabyJubKeyFromSeed(seed) {
286
+ const hash = sha256(seed);
287
+ const privKey = babyJubScalarFromBytes(hash);
288
+ const pubKey = babyJubMul(privKey, BABYJUB_BASE8);
289
+ return { privKey, pubKey };
290
+ }
291
+ /**
292
+ * Derive a scalar from bytes (reduces modulo Baby Jubjub order)
293
+ */
294
+ export function babyJubScalarFromBytes(bytes) {
295
+ let result = 0n;
296
+ for (let i = 0; i < bytes.length; i++) {
297
+ result = (result << 8n) | BigInt(bytes[i]);
298
+ }
299
+ return mod(result, BABYJUB_ORDER);
300
+ }
301
+ /**
302
+ * Convert a Baby Jubjub scalar to 32 bytes (big-endian)
303
+ */
304
+ export function babyJubScalarToBytes(scalar) {
305
+ const bytes = new Uint8Array(32);
306
+ let temp = mod(scalar, BABYJUB_ORDER);
307
+ for (let i = 31; i >= 0; i--) {
308
+ bytes[i] = Number(temp & 0xffn);
309
+ temp >>= 8n;
310
+ }
311
+ return bytes;
312
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Ed25519/X25519 utilities for UTXOpia viewing keys
3
+ *
4
+ * Uses @noble/curves for Ed25519 key generation and X25519 ECDH.
5
+ * Ed25519 is used for viewing keys (off-chain only, fast and standard).
6
+ *
7
+ * Viewing key operations:
8
+ * - Key generation: Ed25519 keypair
9
+ * - ECDH: X25519 (Montgomery form of Curve25519)
10
+ * - Shared secret: 32 bytes from X25519
11
+ *
12
+ * @see https://github.com/paulmillr/noble-curves
13
+ */
14
+ /**
15
+ * Generate an Ed25519 keypair for viewing key use
16
+ *
17
+ * @returns 32-byte private key and 32-byte public key
18
+ */
19
+ export declare function ed25519GenerateKeyPair(): {
20
+ privKey: Uint8Array;
21
+ pubKey: Uint8Array;
22
+ };
23
+ /**
24
+ * Derive an Ed25519 public key from a private key
25
+ *
26
+ * @param privKey - 32-byte Ed25519 private key
27
+ * @returns 32-byte Ed25519 public key
28
+ */
29
+ export declare function ed25519GetPublicKey(privKey: Uint8Array): Uint8Array;
30
+ /**
31
+ * Derive an Ed25519 keypair from a seed (deterministic)
32
+ *
33
+ * @param seed - Arbitrary bytes to derive from (will be SHA256'd to 32 bytes)
34
+ * @returns Ed25519 keypair
35
+ */
36
+ export declare function ed25519DeriveKeyFromSeed(seed: Uint8Array): {
37
+ privKey: Uint8Array;
38
+ pubKey: Uint8Array;
39
+ };
40
+ /**
41
+ * Convert Ed25519 public key to X25519 (Montgomery form) for ECDH
42
+ *
43
+ * Uses the birational map from twisted Edwards to Montgomery form.
44
+ */
45
+ export declare function ed25519PubToX25519(edPub: Uint8Array): Uint8Array;
46
+ /**
47
+ * Perform X25519 ECDH key exchange
48
+ *
49
+ * @param privKey - 32-byte Ed25519 private key (will be converted internally)
50
+ * @param pubKey - 32-byte Ed25519 public key (will be converted to X25519)
51
+ * @returns 32-byte shared secret
52
+ */
53
+ export declare function x25519Ecdh(privKey: Uint8Array, pubKey: Uint8Array): Uint8Array;
54
+ /**
55
+ * Convert an Ed25519 private key to its X25519 (Montgomery) scalar.
56
+ *
57
+ * Uses the same clamped-scalar derivation as `x25519Ecdh` internally.
58
+ * The returned 32 bytes are a valid X25519 private key suitable for
59
+ * `x25519.getSharedSecret` or `x25519.getPublicKey`.
60
+ */
61
+ export declare function ed25519PrivToX25519(edPriv: Uint8Array): Uint8Array;
62
+ /**
63
+ * Derive an X25519 public key from a raw X25519 private scalar.
64
+ */
65
+ export declare function x25519PubFromPriv(x25519Priv: Uint8Array): Uint8Array;
66
+ /**
67
+ * Perform X25519 ECDH with raw X25519 keys (no Ed25519 conversion).
68
+ *
69
+ * @param x25519Priv - 32-byte X25519 private scalar (from `ed25519PrivToX25519` or `x25519PubFromPriv`)
70
+ * @param x25519Pub - 32-byte X25519 public key (u-coordinate)
71
+ * @returns 32-byte shared secret
72
+ */
73
+ export declare function x25519EcdhRaw(x25519Priv: Uint8Array, x25519Pub: Uint8Array): Uint8Array;
74
+ /**
75
+ * Derive an 8-byte encryption key from X25519 shared secret
76
+ *
77
+ * @param sharedSecret - 32-byte X25519 shared secret
78
+ * @returns 8-byte encryption key
79
+ */
80
+ export declare function deriveAmountKey(sharedSecret: Uint8Array): Uint8Array;
81
+ /**
82
+ * Encrypt amount with XOR using shared secret
83
+ *
84
+ * @param amount - Amount in satoshis
85
+ * @param sharedSecret - 32-byte X25519 shared secret
86
+ * @returns 8-byte encrypted amount
87
+ */
88
+ export declare function encryptAmountEd25519(amount: bigint, sharedSecret: Uint8Array): Uint8Array;
89
+ /**
90
+ * Decrypt amount with XOR using shared secret
91
+ *
92
+ * @param encryptedAmount - 8-byte encrypted amount
93
+ * @param sharedSecret - 32-byte X25519 shared secret
94
+ * @returns Decrypted amount in satoshis
95
+ */
96
+ export declare function decryptAmountEd25519(encryptedAmount: Uint8Array, sharedSecret: Uint8Array): bigint;
97
+ /**
98
+ * Encrypt note data: token_id(32 bytes, big-endian) || amount(8 bytes, LE)
99
+ *
100
+ * @param tokenId - Token identifier as bigint
101
+ * @param amount - Amount in token's native units
102
+ * @param sharedSecret - 32-byte X25519 shared secret
103
+ * @returns 40-byte encrypted blob
104
+ */
105
+ export declare function encryptNoteData(tokenId: bigint, amount: bigint, sharedSecret: Uint8Array): Uint8Array;
106
+ /**
107
+ * Decrypt note data: extracts token_id and amount from 40-byte encrypted blob.
108
+ *
109
+ * @param encryptedData - 40-byte encrypted blob
110
+ * @param sharedSecret - 32-byte X25519 shared secret
111
+ * @returns { tokenId, amount }
112
+ */
113
+ export declare function decryptNoteData(encryptedData: Uint8Array, sharedSecret: Uint8Array): {
114
+ tokenId: bigint;
115
+ amount: bigint;
116
+ };
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Ed25519/X25519 utilities for UTXOpia viewing keys
3
+ *
4
+ * Uses @noble/curves for Ed25519 key generation and X25519 ECDH.
5
+ * Ed25519 is used for viewing keys (off-chain only, fast and standard).
6
+ *
7
+ * Viewing key operations:
8
+ * - Key generation: Ed25519 keypair
9
+ * - ECDH: X25519 (Montgomery form of Curve25519)
10
+ * - Shared secret: 32 bytes from X25519
11
+ *
12
+ * @see https://github.com/paulmillr/noble-curves
13
+ */
14
+ import { ed25519 } from "@noble/curves/ed25519.js";
15
+ import { x25519 } from "@noble/curves/ed25519.js";
16
+ import { sha256 } from "@noble/hashes/sha2.js";
17
+ // =============================================================================
18
+ // Key Generation
19
+ // =============================================================================
20
+ /**
21
+ * Generate an Ed25519 keypair for viewing key use
22
+ *
23
+ * @returns 32-byte private key and 32-byte public key
24
+ */
25
+ export function ed25519GenerateKeyPair() {
26
+ const privKey = ed25519.utils.randomSecretKey();
27
+ const pubKey = ed25519.getPublicKey(privKey);
28
+ return { privKey, pubKey };
29
+ }
30
+ /**
31
+ * Derive an Ed25519 public key from a private key
32
+ *
33
+ * @param privKey - 32-byte Ed25519 private key
34
+ * @returns 32-byte Ed25519 public key
35
+ */
36
+ export function ed25519GetPublicKey(privKey) {
37
+ return ed25519.getPublicKey(privKey);
38
+ }
39
+ /**
40
+ * Derive an Ed25519 keypair from a seed (deterministic)
41
+ *
42
+ * @param seed - Arbitrary bytes to derive from (will be SHA256'd to 32 bytes)
43
+ * @returns Ed25519 keypair
44
+ */
45
+ export function ed25519DeriveKeyFromSeed(seed) {
46
+ const privKey = sha256(seed);
47
+ const pubKey = ed25519.getPublicKey(privKey);
48
+ return { privKey, pubKey };
49
+ }
50
+ // =============================================================================
51
+ // ECDH (X25519)
52
+ // =============================================================================
53
+ /**
54
+ * Convert Ed25519 public key to X25519 (Montgomery form) for ECDH
55
+ *
56
+ * Uses the birational map from twisted Edwards to Montgomery form.
57
+ */
58
+ export function ed25519PubToX25519(edPub) {
59
+ return ed25519.utils.toMontgomery(edPub);
60
+ }
61
+ /**
62
+ * Perform X25519 ECDH key exchange
63
+ *
64
+ * @param privKey - 32-byte Ed25519 private key (will be converted internally)
65
+ * @param pubKey - 32-byte Ed25519 public key (will be converted to X25519)
66
+ * @returns 32-byte shared secret
67
+ */
68
+ export function x25519Ecdh(privKey, pubKey) {
69
+ // Convert Ed25519 pub to X25519 u-coordinate
70
+ const x25519Pub = ed25519.utils.toMontgomery(pubKey);
71
+ // Convert Ed25519 private key to X25519 scalar
72
+ const x25519Priv = ed25519.utils.toMontgomerySecret(privKey);
73
+ return x25519.getSharedSecret(x25519Priv, x25519Pub);
74
+ }
75
+ /**
76
+ * Convert an Ed25519 private key to its X25519 (Montgomery) scalar.
77
+ *
78
+ * Uses the same clamped-scalar derivation as `x25519Ecdh` internally.
79
+ * The returned 32 bytes are a valid X25519 private key suitable for
80
+ * `x25519.getSharedSecret` or `x25519.getPublicKey`.
81
+ */
82
+ export function ed25519PrivToX25519(edPriv) {
83
+ return ed25519.utils.toMontgomerySecret(edPriv);
84
+ }
85
+ /**
86
+ * Derive an X25519 public key from a raw X25519 private scalar.
87
+ */
88
+ export function x25519PubFromPriv(x25519Priv) {
89
+ return x25519.getPublicKey(x25519Priv);
90
+ }
91
+ /**
92
+ * Perform X25519 ECDH with raw X25519 keys (no Ed25519 conversion).
93
+ *
94
+ * @param x25519Priv - 32-byte X25519 private scalar (from `ed25519PrivToX25519` or `x25519PubFromPriv`)
95
+ * @param x25519Pub - 32-byte X25519 public key (u-coordinate)
96
+ * @returns 32-byte shared secret
97
+ */
98
+ export function x25519EcdhRaw(x25519Priv, x25519Pub) {
99
+ return x25519.getSharedSecret(x25519Priv, x25519Pub);
100
+ }
101
+ // =============================================================================
102
+ // Amount Encryption/Decryption
103
+ // =============================================================================
104
+ /**
105
+ * Derive an 8-byte encryption key from X25519 shared secret
106
+ *
107
+ * @param sharedSecret - 32-byte X25519 shared secret
108
+ * @returns 8-byte encryption key
109
+ */
110
+ export function deriveAmountKey(sharedSecret) {
111
+ const hash = sha256(sharedSecret);
112
+ return hash.slice(0, 8);
113
+ }
114
+ /**
115
+ * Encrypt amount with XOR using shared secret
116
+ *
117
+ * @param amount - Amount in satoshis
118
+ * @param sharedSecret - 32-byte X25519 shared secret
119
+ * @returns 8-byte encrypted amount
120
+ */
121
+ export function encryptAmountEd25519(amount, sharedSecret) {
122
+ const key = deriveAmountKey(sharedSecret);
123
+ const amountBytes = new Uint8Array(8);
124
+ let temp = amount;
125
+ for (let i = 0; i < 8; i++) {
126
+ amountBytes[i] = Number(temp & 0xffn);
127
+ temp >>= 8n;
128
+ }
129
+ const encrypted = new Uint8Array(8);
130
+ for (let i = 0; i < 8; i++) {
131
+ encrypted[i] = amountBytes[i] ^ key[i];
132
+ }
133
+ return encrypted;
134
+ }
135
+ /**
136
+ * Decrypt amount with XOR using shared secret
137
+ *
138
+ * @param encryptedAmount - 8-byte encrypted amount
139
+ * @param sharedSecret - 32-byte X25519 shared secret
140
+ * @returns Decrypted amount in satoshis
141
+ */
142
+ export function decryptAmountEd25519(encryptedAmount, sharedSecret) {
143
+ const key = deriveAmountKey(sharedSecret);
144
+ const decrypted = new Uint8Array(8);
145
+ for (let i = 0; i < 8; i++) {
146
+ decrypted[i] = encryptedAmount[i] ^ key[i];
147
+ }
148
+ let amount = 0n;
149
+ for (let i = 7; i >= 0; i--) {
150
+ amount = (amount << 8n) | BigInt(decrypted[i]);
151
+ }
152
+ return amount;
153
+ }
154
+ // ============================================================================
155
+ // Combined Note Data Encryption (token_id + amount)
156
+ // ============================================================================
157
+ /**
158
+ * Derive a 40-byte keystream for encrypting token_id(32) + amount(8).
159
+ * Uses two SHA-256 hashes with domain separation to get enough key material.
160
+ *
161
+ * keystream[0..32] = sha256(sharedSecret || 0x00) — for token_id
162
+ * keystream[32..40] = sha256(sharedSecret || 0x01)[0..8] — for amount
163
+ */
164
+ function deriveNoteDataKeystream(sharedSecret) {
165
+ const buf0 = new Uint8Array(sharedSecret.length + 1);
166
+ buf0.set(sharedSecret);
167
+ buf0[sharedSecret.length] = 0x00;
168
+ const buf1 = new Uint8Array(sharedSecret.length + 1);
169
+ buf1.set(sharedSecret);
170
+ buf1[sharedSecret.length] = 0x01;
171
+ const key0 = sha256(buf0); // 32 bytes for token_id
172
+ const key1 = sha256(buf1); // 32 bytes, take first 8 for amount
173
+ const keystream = new Uint8Array(40);
174
+ keystream.set(key0, 0);
175
+ keystream.set(key1.slice(0, 8), 32);
176
+ return keystream;
177
+ }
178
+ /**
179
+ * Encrypt note data: token_id(32 bytes, big-endian) || amount(8 bytes, LE)
180
+ *
181
+ * @param tokenId - Token identifier as bigint
182
+ * @param amount - Amount in token's native units
183
+ * @param sharedSecret - 32-byte X25519 shared secret
184
+ * @returns 40-byte encrypted blob
185
+ */
186
+ export function encryptNoteData(tokenId, amount, sharedSecret) {
187
+ const keystream = deriveNoteDataKeystream(sharedSecret);
188
+ const plaintext = new Uint8Array(40);
189
+ // token_id: 32 bytes big-endian
190
+ let t = tokenId;
191
+ for (let i = 31; i >= 0; i--) {
192
+ plaintext[i] = Number(t & 0xffn);
193
+ t >>= 8n;
194
+ }
195
+ // amount: 8 bytes little-endian
196
+ let a = amount;
197
+ for (let i = 0; i < 8; i++) {
198
+ plaintext[32 + i] = Number(a & 0xffn);
199
+ a >>= 8n;
200
+ }
201
+ // XOR
202
+ const encrypted = new Uint8Array(40);
203
+ for (let i = 0; i < 40; i++) {
204
+ encrypted[i] = plaintext[i] ^ keystream[i];
205
+ }
206
+ return encrypted;
207
+ }
208
+ /**
209
+ * Decrypt note data: extracts token_id and amount from 40-byte encrypted blob.
210
+ *
211
+ * @param encryptedData - 40-byte encrypted blob
212
+ * @param sharedSecret - 32-byte X25519 shared secret
213
+ * @returns { tokenId, amount }
214
+ */
215
+ export function decryptNoteData(encryptedData, sharedSecret) {
216
+ if (encryptedData.length < 40) {
217
+ throw new Error(`Expected 40-byte encrypted note data, got ${encryptedData.length}`);
218
+ }
219
+ const keystream = deriveNoteDataKeystream(sharedSecret);
220
+ const plaintext = new Uint8Array(40);
221
+ for (let i = 0; i < 40; i++) {
222
+ plaintext[i] = encryptedData[i] ^ keystream[i];
223
+ }
224
+ // token_id: 32 bytes big-endian → bigint
225
+ let tokenId = 0n;
226
+ for (let i = 0; i < 32; i++) {
227
+ tokenId = (tokenId << 8n) | BigInt(plaintext[i]);
228
+ }
229
+ // amount: 8 bytes little-endian → bigint
230
+ let amount = 0n;
231
+ for (let i = 7; i >= 0; i--) {
232
+ amount = (amount << 8n) | BigInt(plaintext[32 + i]);
233
+ }
234
+ return { tokenId, amount };
235
+ }