@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.
- package/LICENSE +21 -0
- package/README.md +171 -0
- package/package.json +108 -0
- package/packages/btc-client/src/esplora-client.ts +153 -0
- package/packages/btc-client/src/index.ts +3 -0
- package/packages/btc-client/src/op-return.ts +93 -0
- package/packages/btc-client/src/types.ts +112 -0
- package/packages/sdk/README.md +277 -0
- package/packages/sdk/dist/announcement-client.d.ts +64 -0
- package/packages/sdk/dist/announcement-client.js +337 -0
- package/packages/sdk/dist/auditor-ciphertext.d.ts +72 -0
- package/packages/sdk/dist/auditor-ciphertext.js +135 -0
- package/packages/sdk/dist/auditor.d.ts +115 -0
- package/packages/sdk/dist/auditor.js +292 -0
- package/packages/sdk/dist/bitcoin/ika.d.ts +47 -0
- package/packages/sdk/dist/bitcoin/ika.js +74 -0
- package/packages/sdk/dist/bitcoin/index.d.ts +2 -0
- package/packages/sdk/dist/bitcoin/index.js +1 -0
- package/packages/sdk/dist/bound-params.d.ts +96 -0
- package/packages/sdk/dist/bound-params.js +249 -0
- package/packages/sdk/dist/chadbuffer.d.ts +124 -0
- package/packages/sdk/dist/chadbuffer.js +405 -0
- package/packages/sdk/dist/claim-link.d.ts +30 -0
- package/packages/sdk/dist/claim-link.js +50 -0
- package/packages/sdk/dist/client.d.ts +233 -0
- package/packages/sdk/dist/client.js +416 -0
- package/packages/sdk/dist/commitment-tree.d.ts +268 -0
- package/packages/sdk/dist/commitment-tree.js +564 -0
- package/packages/sdk/dist/config.d.ts +177 -0
- package/packages/sdk/dist/config.js +426 -0
- package/packages/sdk/dist/core/esplora.d.ts +110 -0
- package/packages/sdk/dist/core/esplora.js +187 -0
- package/packages/sdk/dist/core/mempool.d.ts +60 -0
- package/packages/sdk/dist/core/mempool.js +107 -0
- package/packages/sdk/dist/crypto-babyjub.d.ts +105 -0
- package/packages/sdk/dist/crypto-babyjub.js +312 -0
- package/packages/sdk/dist/crypto-ed25519.d.ts +116 -0
- package/packages/sdk/dist/crypto-ed25519.js +235 -0
- package/packages/sdk/dist/crypto.d.ts +59 -0
- package/packages/sdk/dist/crypto.js +140 -0
- package/packages/sdk/dist/event-client.d.ts +64 -0
- package/packages/sdk/dist/event-client.js +165 -0
- package/packages/sdk/dist/events.d.ts +127 -0
- package/packages/sdk/dist/events.js +305 -0
- package/packages/sdk/dist/explorer.d.ts +86 -0
- package/packages/sdk/dist/explorer.js +197 -0
- package/packages/sdk/dist/index.d.ts +61 -0
- package/packages/sdk/dist/index.js +216 -0
- package/packages/sdk/dist/instructions.d.ts +1125 -0
- package/packages/sdk/dist/instructions.js +1760 -0
- package/packages/sdk/dist/keys.d.ts +467 -0
- package/packages/sdk/dist/keys.js +799 -0
- package/packages/sdk/dist/logger.d.ts +10 -0
- package/packages/sdk/dist/logger.js +40 -0
- package/packages/sdk/dist/magicblock.d.ts +90 -0
- package/packages/sdk/dist/magicblock.js +164 -0
- package/packages/sdk/dist/merkle.d.ts +82 -0
- package/packages/sdk/dist/merkle.js +141 -0
- package/packages/sdk/dist/note.d.ts +348 -0
- package/packages/sdk/dist/note.js +483 -0
- package/packages/sdk/dist/pda.d.ts +160 -0
- package/packages/sdk/dist/pda.js +384 -0
- package/packages/sdk/dist/pool-state.d.ts +100 -0
- package/packages/sdk/dist/pool-state.js +126 -0
- package/packages/sdk/dist/poseidon.d.ts +53 -0
- package/packages/sdk/dist/poseidon.js +136 -0
- package/packages/sdk/dist/prover/index.d.ts +10 -0
- package/packages/sdk/dist/prover/index.js +10 -0
- package/packages/sdk/dist/prover/mobile.d.ts +45 -0
- package/packages/sdk/dist/prover/mobile.js +227 -0
- package/packages/sdk/dist/prover/web.d.ts +136 -0
- package/packages/sdk/dist/prover/web.js +597 -0
- package/packages/sdk/dist/psbt.d.ts +73 -0
- package/packages/sdk/dist/psbt.js +202 -0
- package/packages/sdk/dist/selective-disclosure.d.ts +181 -0
- package/packages/sdk/dist/selective-disclosure.js +172 -0
- package/packages/sdk/dist/sender-memo.d.ts +149 -0
- package/packages/sdk/dist/sender-memo.js +250 -0
- package/packages/sdk/dist/sns-resolver.d.ts +121 -0
- package/packages/sdk/dist/sns-resolver.js +229 -0
- package/packages/sdk/dist/solana/connection.d.ts +77 -0
- package/packages/sdk/dist/solana/connection.js +133 -0
- package/packages/sdk/dist/solana/priority-fee.d.ts +78 -0
- package/packages/sdk/dist/solana/priority-fee.js +137 -0
- package/packages/sdk/dist/stealth.d.ts +429 -0
- package/packages/sdk/dist/stealth.js +703 -0
- package/packages/sdk/dist/taproot.d.ts +172 -0
- package/packages/sdk/dist/taproot.js +494 -0
- package/packages/sdk/dist/token-registry.d.ts +96 -0
- package/packages/sdk/dist/token-registry.js +122 -0
- package/packages/sdk/dist/utils/encoding.d.ts +19 -0
- package/packages/sdk/dist/utils/encoding.js +29 -0
- package/packages/sdk/dist/vk-registry.d.ts +100 -0
- 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
|
+
}
|