@unicitylabs/sphere-sdk 0.9.1-dev.6 → 0.9.1-dev.7
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/dist/impl/shared/wallet-api/index.cjs +3224 -0
- package/dist/impl/shared/wallet-api/index.cjs.map +1 -0
- package/dist/impl/shared/wallet-api/index.d.cts +1770 -0
- package/dist/impl/shared/wallet-api/index.d.ts +1770 -0
- package/dist/impl/shared/wallet-api/index.js +3191 -0
- package/dist/impl/shared/wallet-api/index.js.map +1 -0
- package/package.json +11 -1
|
@@ -0,0 +1,3224 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// impl/shared/wallet-api/index.ts
|
|
31
|
+
var wallet_api_exports = {};
|
|
32
|
+
__export(wallet_api_exports, {
|
|
33
|
+
WalletApiMailboxProvider: () => WalletApiMailboxProvider,
|
|
34
|
+
WalletApiTokenStorageProvider: () => WalletApiTokenStorageProvider,
|
|
35
|
+
createOwnStorageWalletApiProviders: () => createOwnStorageWalletApiProviders,
|
|
36
|
+
createSphereProviders: () => createSphereProviders,
|
|
37
|
+
createWalletApiMailboxProvider: () => createWalletApiMailboxProvider,
|
|
38
|
+
createWalletApiProviders: () => createWalletApiProviders,
|
|
39
|
+
createWalletApiTokenStorageProvider: () => createWalletApiTokenStorageProvider
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(wallet_api_exports);
|
|
42
|
+
|
|
43
|
+
// node_modules/@noble/hashes/utils.js
|
|
44
|
+
function isBytes(a) {
|
|
45
|
+
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in a && a.BYTES_PER_ELEMENT === 1;
|
|
46
|
+
}
|
|
47
|
+
function anumber(n, title = "") {
|
|
48
|
+
if (typeof n !== "number") {
|
|
49
|
+
const prefix = title && `"${title}" `;
|
|
50
|
+
throw new TypeError(`${prefix}expected number, got ${typeof n}`);
|
|
51
|
+
}
|
|
52
|
+
if (!Number.isSafeInteger(n) || n < 0) {
|
|
53
|
+
const prefix = title && `"${title}" `;
|
|
54
|
+
throw new RangeError(`${prefix}expected integer >= 0, got ${n}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function abytes(value, length, title = "") {
|
|
58
|
+
const bytes = isBytes(value);
|
|
59
|
+
const len = value?.length;
|
|
60
|
+
const needsLen = length !== void 0;
|
|
61
|
+
if (!bytes || needsLen && len !== length) {
|
|
62
|
+
const prefix = title && `"${title}" `;
|
|
63
|
+
const ofLen = needsLen ? ` of length ${length}` : "";
|
|
64
|
+
const got = bytes ? `length=${len}` : `type=${typeof value}`;
|
|
65
|
+
const message = prefix + "expected Uint8Array" + ofLen + ", got " + got;
|
|
66
|
+
if (!bytes)
|
|
67
|
+
throw new TypeError(message);
|
|
68
|
+
throw new RangeError(message);
|
|
69
|
+
}
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
function ahash(h) {
|
|
73
|
+
if (typeof h !== "function" || typeof h.create !== "function")
|
|
74
|
+
throw new TypeError("Hash must wrapped by utils.createHasher");
|
|
75
|
+
anumber(h.outputLen);
|
|
76
|
+
anumber(h.blockLen);
|
|
77
|
+
if (h.outputLen < 1)
|
|
78
|
+
throw new Error('"outputLen" must be >= 1');
|
|
79
|
+
if (h.blockLen < 1)
|
|
80
|
+
throw new Error('"blockLen" must be >= 1');
|
|
81
|
+
}
|
|
82
|
+
function aexists(instance, checkFinished = true) {
|
|
83
|
+
if (instance.destroyed)
|
|
84
|
+
throw new Error("Hash instance has been destroyed");
|
|
85
|
+
if (checkFinished && instance.finished)
|
|
86
|
+
throw new Error("Hash#digest() has already been called");
|
|
87
|
+
}
|
|
88
|
+
function aoutput(out, instance) {
|
|
89
|
+
abytes(out, void 0, "digestInto() output");
|
|
90
|
+
const min = instance.outputLen;
|
|
91
|
+
if (out.length < min) {
|
|
92
|
+
throw new RangeError('"digestInto() output" expected to be of length >=' + min);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function clean(...arrays) {
|
|
96
|
+
for (let i = 0; i < arrays.length; i++) {
|
|
97
|
+
arrays[i].fill(0);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function createView(arr) {
|
|
101
|
+
return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
102
|
+
}
|
|
103
|
+
function rotr(word, shift) {
|
|
104
|
+
return word << 32 - shift | word >>> shift;
|
|
105
|
+
}
|
|
106
|
+
function createHasher(hashCons, info = {}) {
|
|
107
|
+
const hashC = (msg, opts) => hashCons(opts).update(msg).digest();
|
|
108
|
+
const tmp = hashCons(void 0);
|
|
109
|
+
hashC.outputLen = tmp.outputLen;
|
|
110
|
+
hashC.blockLen = tmp.blockLen;
|
|
111
|
+
hashC.canXOF = tmp.canXOF;
|
|
112
|
+
hashC.create = (opts) => hashCons(opts);
|
|
113
|
+
Object.assign(hashC, info);
|
|
114
|
+
return Object.freeze(hashC);
|
|
115
|
+
}
|
|
116
|
+
var oidNist = (suffix) => ({
|
|
117
|
+
// Current NIST hashAlgs suffixes used here fit in one DER subidentifier octet.
|
|
118
|
+
// Larger suffix values would need base-128 OID encoding and a different length byte.
|
|
119
|
+
oid: Uint8Array.from([6, 9, 96, 134, 72, 1, 101, 3, 4, 2, suffix])
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// node_modules/@noble/hashes/_md.js
|
|
123
|
+
function Chi(a, b, c) {
|
|
124
|
+
return a & b ^ ~a & c;
|
|
125
|
+
}
|
|
126
|
+
function Maj(a, b, c) {
|
|
127
|
+
return a & b ^ a & c ^ b & c;
|
|
128
|
+
}
|
|
129
|
+
var HashMD = class {
|
|
130
|
+
blockLen;
|
|
131
|
+
outputLen;
|
|
132
|
+
canXOF = false;
|
|
133
|
+
padOffset;
|
|
134
|
+
isLE;
|
|
135
|
+
// For partial updates less than block size
|
|
136
|
+
buffer;
|
|
137
|
+
view;
|
|
138
|
+
finished = false;
|
|
139
|
+
length = 0;
|
|
140
|
+
pos = 0;
|
|
141
|
+
destroyed = false;
|
|
142
|
+
constructor(blockLen, outputLen, padOffset, isLE2) {
|
|
143
|
+
this.blockLen = blockLen;
|
|
144
|
+
this.outputLen = outputLen;
|
|
145
|
+
this.padOffset = padOffset;
|
|
146
|
+
this.isLE = isLE2;
|
|
147
|
+
this.buffer = new Uint8Array(blockLen);
|
|
148
|
+
this.view = createView(this.buffer);
|
|
149
|
+
}
|
|
150
|
+
update(data) {
|
|
151
|
+
aexists(this);
|
|
152
|
+
abytes(data);
|
|
153
|
+
const { view, buffer, blockLen } = this;
|
|
154
|
+
const len = data.length;
|
|
155
|
+
for (let pos = 0; pos < len; ) {
|
|
156
|
+
const take = Math.min(blockLen - this.pos, len - pos);
|
|
157
|
+
if (take === blockLen) {
|
|
158
|
+
const dataView = createView(data);
|
|
159
|
+
for (; blockLen <= len - pos; pos += blockLen)
|
|
160
|
+
this.process(dataView, pos);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
buffer.set(data.subarray(pos, pos + take), this.pos);
|
|
164
|
+
this.pos += take;
|
|
165
|
+
pos += take;
|
|
166
|
+
if (this.pos === blockLen) {
|
|
167
|
+
this.process(view, 0);
|
|
168
|
+
this.pos = 0;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
this.length += data.length;
|
|
172
|
+
this.roundClean();
|
|
173
|
+
return this;
|
|
174
|
+
}
|
|
175
|
+
digestInto(out) {
|
|
176
|
+
aexists(this);
|
|
177
|
+
aoutput(out, this);
|
|
178
|
+
this.finished = true;
|
|
179
|
+
const { buffer, view, blockLen, isLE: isLE2 } = this;
|
|
180
|
+
let { pos } = this;
|
|
181
|
+
buffer[pos++] = 128;
|
|
182
|
+
clean(this.buffer.subarray(pos));
|
|
183
|
+
if (this.padOffset > blockLen - pos) {
|
|
184
|
+
this.process(view, 0);
|
|
185
|
+
pos = 0;
|
|
186
|
+
}
|
|
187
|
+
for (let i = pos; i < blockLen; i++)
|
|
188
|
+
buffer[i] = 0;
|
|
189
|
+
view.setBigUint64(blockLen - 8, BigInt(this.length * 8), isLE2);
|
|
190
|
+
this.process(view, 0);
|
|
191
|
+
const oview = createView(out);
|
|
192
|
+
const len = this.outputLen;
|
|
193
|
+
if (len % 4)
|
|
194
|
+
throw new Error("_sha2: outputLen must be aligned to 32bit");
|
|
195
|
+
const outLen = len / 4;
|
|
196
|
+
const state = this.get();
|
|
197
|
+
if (outLen > state.length)
|
|
198
|
+
throw new Error("_sha2: outputLen bigger than state");
|
|
199
|
+
for (let i = 0; i < outLen; i++)
|
|
200
|
+
oview.setUint32(4 * i, state[i], isLE2);
|
|
201
|
+
}
|
|
202
|
+
digest() {
|
|
203
|
+
const { buffer, outputLen } = this;
|
|
204
|
+
this.digestInto(buffer);
|
|
205
|
+
const res = buffer.slice(0, outputLen);
|
|
206
|
+
this.destroy();
|
|
207
|
+
return res;
|
|
208
|
+
}
|
|
209
|
+
_cloneInto(to) {
|
|
210
|
+
to ||= new this.constructor();
|
|
211
|
+
to.set(...this.get());
|
|
212
|
+
const { blockLen, buffer, length, finished, destroyed, pos } = this;
|
|
213
|
+
to.destroyed = destroyed;
|
|
214
|
+
to.finished = finished;
|
|
215
|
+
to.length = length;
|
|
216
|
+
to.pos = pos;
|
|
217
|
+
if (length % blockLen)
|
|
218
|
+
to.buffer.set(buffer);
|
|
219
|
+
return to;
|
|
220
|
+
}
|
|
221
|
+
clone() {
|
|
222
|
+
return this._cloneInto();
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
var SHA256_IV = /* @__PURE__ */ Uint32Array.from([
|
|
226
|
+
1779033703,
|
|
227
|
+
3144134277,
|
|
228
|
+
1013904242,
|
|
229
|
+
2773480762,
|
|
230
|
+
1359893119,
|
|
231
|
+
2600822924,
|
|
232
|
+
528734635,
|
|
233
|
+
1541459225
|
|
234
|
+
]);
|
|
235
|
+
|
|
236
|
+
// node_modules/@noble/hashes/sha2.js
|
|
237
|
+
var SHA256_K = /* @__PURE__ */ Uint32Array.from([
|
|
238
|
+
1116352408,
|
|
239
|
+
1899447441,
|
|
240
|
+
3049323471,
|
|
241
|
+
3921009573,
|
|
242
|
+
961987163,
|
|
243
|
+
1508970993,
|
|
244
|
+
2453635748,
|
|
245
|
+
2870763221,
|
|
246
|
+
3624381080,
|
|
247
|
+
310598401,
|
|
248
|
+
607225278,
|
|
249
|
+
1426881987,
|
|
250
|
+
1925078388,
|
|
251
|
+
2162078206,
|
|
252
|
+
2614888103,
|
|
253
|
+
3248222580,
|
|
254
|
+
3835390401,
|
|
255
|
+
4022224774,
|
|
256
|
+
264347078,
|
|
257
|
+
604807628,
|
|
258
|
+
770255983,
|
|
259
|
+
1249150122,
|
|
260
|
+
1555081692,
|
|
261
|
+
1996064986,
|
|
262
|
+
2554220882,
|
|
263
|
+
2821834349,
|
|
264
|
+
2952996808,
|
|
265
|
+
3210313671,
|
|
266
|
+
3336571891,
|
|
267
|
+
3584528711,
|
|
268
|
+
113926993,
|
|
269
|
+
338241895,
|
|
270
|
+
666307205,
|
|
271
|
+
773529912,
|
|
272
|
+
1294757372,
|
|
273
|
+
1396182291,
|
|
274
|
+
1695183700,
|
|
275
|
+
1986661051,
|
|
276
|
+
2177026350,
|
|
277
|
+
2456956037,
|
|
278
|
+
2730485921,
|
|
279
|
+
2820302411,
|
|
280
|
+
3259730800,
|
|
281
|
+
3345764771,
|
|
282
|
+
3516065817,
|
|
283
|
+
3600352804,
|
|
284
|
+
4094571909,
|
|
285
|
+
275423344,
|
|
286
|
+
430227734,
|
|
287
|
+
506948616,
|
|
288
|
+
659060556,
|
|
289
|
+
883997877,
|
|
290
|
+
958139571,
|
|
291
|
+
1322822218,
|
|
292
|
+
1537002063,
|
|
293
|
+
1747873779,
|
|
294
|
+
1955562222,
|
|
295
|
+
2024104815,
|
|
296
|
+
2227730452,
|
|
297
|
+
2361852424,
|
|
298
|
+
2428436474,
|
|
299
|
+
2756734187,
|
|
300
|
+
3204031479,
|
|
301
|
+
3329325298
|
|
302
|
+
]);
|
|
303
|
+
var SHA256_W = /* @__PURE__ */ new Uint32Array(64);
|
|
304
|
+
var SHA2_32B = class extends HashMD {
|
|
305
|
+
constructor(outputLen) {
|
|
306
|
+
super(64, outputLen, 8, false);
|
|
307
|
+
}
|
|
308
|
+
get() {
|
|
309
|
+
const { A, B, C, D, E, F, G, H } = this;
|
|
310
|
+
return [A, B, C, D, E, F, G, H];
|
|
311
|
+
}
|
|
312
|
+
// prettier-ignore
|
|
313
|
+
set(A, B, C, D, E, F, G, H) {
|
|
314
|
+
this.A = A | 0;
|
|
315
|
+
this.B = B | 0;
|
|
316
|
+
this.C = C | 0;
|
|
317
|
+
this.D = D | 0;
|
|
318
|
+
this.E = E | 0;
|
|
319
|
+
this.F = F | 0;
|
|
320
|
+
this.G = G | 0;
|
|
321
|
+
this.H = H | 0;
|
|
322
|
+
}
|
|
323
|
+
process(view, offset) {
|
|
324
|
+
for (let i = 0; i < 16; i++, offset += 4)
|
|
325
|
+
SHA256_W[i] = view.getUint32(offset, false);
|
|
326
|
+
for (let i = 16; i < 64; i++) {
|
|
327
|
+
const W15 = SHA256_W[i - 15];
|
|
328
|
+
const W2 = SHA256_W[i - 2];
|
|
329
|
+
const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ W15 >>> 3;
|
|
330
|
+
const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ W2 >>> 10;
|
|
331
|
+
SHA256_W[i] = s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16] | 0;
|
|
332
|
+
}
|
|
333
|
+
let { A, B, C, D, E, F, G, H } = this;
|
|
334
|
+
for (let i = 0; i < 64; i++) {
|
|
335
|
+
const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
|
|
336
|
+
const T1 = H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i] | 0;
|
|
337
|
+
const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);
|
|
338
|
+
const T2 = sigma0 + Maj(A, B, C) | 0;
|
|
339
|
+
H = G;
|
|
340
|
+
G = F;
|
|
341
|
+
F = E;
|
|
342
|
+
E = D + T1 | 0;
|
|
343
|
+
D = C;
|
|
344
|
+
C = B;
|
|
345
|
+
B = A;
|
|
346
|
+
A = T1 + T2 | 0;
|
|
347
|
+
}
|
|
348
|
+
A = A + this.A | 0;
|
|
349
|
+
B = B + this.B | 0;
|
|
350
|
+
C = C + this.C | 0;
|
|
351
|
+
D = D + this.D | 0;
|
|
352
|
+
E = E + this.E | 0;
|
|
353
|
+
F = F + this.F | 0;
|
|
354
|
+
G = G + this.G | 0;
|
|
355
|
+
H = H + this.H | 0;
|
|
356
|
+
this.set(A, B, C, D, E, F, G, H);
|
|
357
|
+
}
|
|
358
|
+
roundClean() {
|
|
359
|
+
clean(SHA256_W);
|
|
360
|
+
}
|
|
361
|
+
destroy() {
|
|
362
|
+
this.destroyed = true;
|
|
363
|
+
this.set(0, 0, 0, 0, 0, 0, 0, 0);
|
|
364
|
+
clean(this.buffer);
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
var _SHA256 = class extends SHA2_32B {
|
|
368
|
+
// We cannot use array here since array allows indexing by variable
|
|
369
|
+
// which means optimizer/compiler cannot use registers.
|
|
370
|
+
A = SHA256_IV[0] | 0;
|
|
371
|
+
B = SHA256_IV[1] | 0;
|
|
372
|
+
C = SHA256_IV[2] | 0;
|
|
373
|
+
D = SHA256_IV[3] | 0;
|
|
374
|
+
E = SHA256_IV[4] | 0;
|
|
375
|
+
F = SHA256_IV[5] | 0;
|
|
376
|
+
G = SHA256_IV[6] | 0;
|
|
377
|
+
H = SHA256_IV[7] | 0;
|
|
378
|
+
constructor() {
|
|
379
|
+
super(32);
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
var sha256 = /* @__PURE__ */ createHasher(
|
|
383
|
+
() => new _SHA256(),
|
|
384
|
+
/* @__PURE__ */ oidNist(1)
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
// token-engine/sdk.ts
|
|
388
|
+
var import_StateTransitionClient = require("@unicitylabs/state-transition-sdk/lib/StateTransitionClient.js");
|
|
389
|
+
var import_AggregatorClient = require("@unicitylabs/state-transition-sdk/lib/api/AggregatorClient.js");
|
|
390
|
+
var import_NetworkId = require("@unicitylabs/state-transition-sdk/lib/api/NetworkId.js");
|
|
391
|
+
var import_CertificationData = require("@unicitylabs/state-transition-sdk/lib/api/CertificationData.js");
|
|
392
|
+
var import_CertificationResponse = require("@unicitylabs/state-transition-sdk/lib/api/CertificationResponse.js");
|
|
393
|
+
var import_StateId = require("@unicitylabs/state-transition-sdk/lib/api/StateId.js");
|
|
394
|
+
var import_InclusionProof = require("@unicitylabs/state-transition-sdk/lib/api/InclusionProof.js");
|
|
395
|
+
var import_InclusionProofResponse = require("@unicitylabs/state-transition-sdk/lib/api/InclusionProofResponse.js");
|
|
396
|
+
var import_RootTrustBase = require("@unicitylabs/state-transition-sdk/lib/api/bft/RootTrustBase.js");
|
|
397
|
+
var import_InclusionProofUtils = require("@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils.js");
|
|
398
|
+
var import_InclusionProofVerificationRule = require("@unicitylabs/state-transition-sdk/lib/transaction/verification/rule/InclusionProofVerificationRule.js");
|
|
399
|
+
var import_Token = require("@unicitylabs/state-transition-sdk/lib/transaction/Token.js");
|
|
400
|
+
var import_MintTransaction = require("@unicitylabs/state-transition-sdk/lib/transaction/MintTransaction.js");
|
|
401
|
+
var import_TransferTransaction = require("@unicitylabs/state-transition-sdk/lib/transaction/TransferTransaction.js");
|
|
402
|
+
var import_CertifiedMintTransaction = require("@unicitylabs/state-transition-sdk/lib/transaction/CertifiedMintTransaction.js");
|
|
403
|
+
var import_CertifiedTransferTransaction = require("@unicitylabs/state-transition-sdk/lib/transaction/CertifiedTransferTransaction.js");
|
|
404
|
+
var import_TokenId = require("@unicitylabs/state-transition-sdk/lib/transaction/TokenId.js");
|
|
405
|
+
var import_TokenType = require("@unicitylabs/state-transition-sdk/lib/transaction/TokenType.js");
|
|
406
|
+
var import_TokenSalt = require("@unicitylabs/state-transition-sdk/lib/transaction/TokenSalt.js");
|
|
407
|
+
var import_MintJustificationVerifierService = require("@unicitylabs/state-transition-sdk/lib/transaction/verification/MintJustificationVerifierService.js");
|
|
408
|
+
var import_EncodedPredicate = require("@unicitylabs/state-transition-sdk/lib/predicate/EncodedPredicate.js");
|
|
409
|
+
var import_SignaturePredicate = require("@unicitylabs/state-transition-sdk/lib/predicate/builtin/SignaturePredicate.js");
|
|
410
|
+
var import_SignaturePredicateUnlockScript = require("@unicitylabs/state-transition-sdk/lib/predicate/builtin/SignaturePredicateUnlockScript.js");
|
|
411
|
+
var import_BurnPredicate = require("@unicitylabs/state-transition-sdk/lib/predicate/builtin/BurnPredicate.js");
|
|
412
|
+
var import_PredicateVerifierService = require("@unicitylabs/state-transition-sdk/lib/predicate/verification/PredicateVerifierService.js");
|
|
413
|
+
var import_SigningService = require("@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/SigningService.js");
|
|
414
|
+
var import_Signature = require("@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/Signature.js");
|
|
415
|
+
var import_MintSigningService = require("@unicitylabs/state-transition-sdk/lib/crypto/MintSigningService.js");
|
|
416
|
+
var import_HashAlgorithm = require("@unicitylabs/state-transition-sdk/lib/crypto/hash/HashAlgorithm.js");
|
|
417
|
+
var import_DataHash = require("@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHash.js");
|
|
418
|
+
var import_DataHasher = require("@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHasher.js");
|
|
419
|
+
var import_DataHasherFactory = require("@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHasherFactory.js");
|
|
420
|
+
var import_CborSerializer = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborSerializer.js");
|
|
421
|
+
var import_CborDeserializer = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborDeserializer.js");
|
|
422
|
+
var import_CborError = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborError.js");
|
|
423
|
+
var import_Asset = require("@unicitylabs/state-transition-sdk/lib/payment/asset/Asset.js");
|
|
424
|
+
var import_AssetId = require("@unicitylabs/state-transition-sdk/lib/payment/asset/AssetId.js");
|
|
425
|
+
var import_PaymentAssetCollection = require("@unicitylabs/state-transition-sdk/lib/payment/asset/PaymentAssetCollection.js");
|
|
426
|
+
var import_TokenSplit = require("@unicitylabs/state-transition-sdk/lib/payment/TokenSplit.js");
|
|
427
|
+
var import_SplitTokenRequest = require("@unicitylabs/state-transition-sdk/lib/payment/SplitTokenRequest.js");
|
|
428
|
+
var import_SplitToken = require("@unicitylabs/state-transition-sdk/lib/payment/SplitToken.js");
|
|
429
|
+
var import_SplitAssetProof = require("@unicitylabs/state-transition-sdk/lib/payment/SplitAssetProof.js");
|
|
430
|
+
var import_SplitMintJustification = require("@unicitylabs/state-transition-sdk/lib/payment/SplitMintJustification.js");
|
|
431
|
+
var import_SplitMintJustificationVerifier = require("@unicitylabs/state-transition-sdk/lib/payment/SplitMintJustificationVerifier.js");
|
|
432
|
+
var import_VerificationStatus = require("@unicitylabs/state-transition-sdk/lib/verification/VerificationStatus.js");
|
|
433
|
+
var import_VerificationResult = require("@unicitylabs/state-transition-sdk/lib/verification/VerificationResult.js");
|
|
434
|
+
var import_UnicityId = require("@unicitylabs/state-transition-sdk/lib/unicity-id/UnicityId.js");
|
|
435
|
+
var import_UnicityIdMintTransaction = require("@unicitylabs/state-transition-sdk/lib/unicity-id/UnicityIdMintTransaction.js");
|
|
436
|
+
var import_CertifiedUnicityIdMintTransaction = require("@unicitylabs/state-transition-sdk/lib/unicity-id/CertifiedUnicityIdMintTransaction.js");
|
|
437
|
+
var import_UnicityIdToken = require("@unicitylabs/state-transition-sdk/lib/unicity-id/UnicityIdToken.js");
|
|
438
|
+
var import_HexConverter = require("@unicitylabs/state-transition-sdk/lib/util/HexConverter.js");
|
|
439
|
+
var import_BigintConverter = require("@unicitylabs/state-transition-sdk/lib/util/BigintConverter.js");
|
|
440
|
+
var import_BitString = require("@unicitylabs/state-transition-sdk/lib/util/BitString.js");
|
|
441
|
+
|
|
442
|
+
// token-engine/token-blob.ts
|
|
443
|
+
var TOKEN_BLOB_TAG = 39051n;
|
|
444
|
+
var TOKEN_BLOB_VERSION = 1;
|
|
445
|
+
function decodeTokenBlob(bytes) {
|
|
446
|
+
const tag = import_CborDeserializer.CborDeserializer.decodeTag(bytes);
|
|
447
|
+
if (tag.tag !== TOKEN_BLOB_TAG) {
|
|
448
|
+
throw new import_CborError.CborError(`Invalid TokenBlob tag: ${tag.tag}`);
|
|
449
|
+
}
|
|
450
|
+
const fields = import_CborDeserializer.CborDeserializer.decodeArray(tag.data, 4);
|
|
451
|
+
const v = Number(import_CborDeserializer.CborDeserializer.decodeUnsignedInteger(fields[0]));
|
|
452
|
+
if (v !== TOKEN_BLOB_VERSION) {
|
|
453
|
+
throw new import_CborError.CborError(`Unsupported TokenBlob version: ${v}`);
|
|
454
|
+
}
|
|
455
|
+
return {
|
|
456
|
+
v,
|
|
457
|
+
network: Number(import_CborDeserializer.CborDeserializer.decodeUnsignedInteger(fields[1])),
|
|
458
|
+
tokenId: import_CborDeserializer.CborDeserializer.decodeTextString(fields[2]),
|
|
459
|
+
token: import_CborDeserializer.CborDeserializer.decodeByteString(fields[3])
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function unwrapTokenBlobBytes(bytes) {
|
|
463
|
+
try {
|
|
464
|
+
return decodeTokenBlob(bytes).token;
|
|
465
|
+
} catch {
|
|
466
|
+
return bytes;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// core/crypto.ts
|
|
471
|
+
var bip39 = __toESM(require("bip39"), 1);
|
|
472
|
+
var import_crypto_js = __toESM(require("crypto-js"), 1);
|
|
473
|
+
var import_elliptic = __toESM(require("elliptic"), 1);
|
|
474
|
+
|
|
475
|
+
// core/errors.ts
|
|
476
|
+
var SphereError = class extends Error {
|
|
477
|
+
code;
|
|
478
|
+
cause;
|
|
479
|
+
constructor(message, code, cause) {
|
|
480
|
+
super(message);
|
|
481
|
+
this.name = "SphereError";
|
|
482
|
+
this.code = code;
|
|
483
|
+
this.cause = cause;
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
// core/crypto.ts
|
|
488
|
+
var ec = new import_elliptic.default.ec("secp256k1");
|
|
489
|
+
var CURVE_ORDER = BigInt(
|
|
490
|
+
"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
|
|
491
|
+
);
|
|
492
|
+
var SIGN_MESSAGE_PREFIX = "Sphere Signed Message:\n";
|
|
493
|
+
function varint(n) {
|
|
494
|
+
if (n < 253) return new Uint8Array([n]);
|
|
495
|
+
const buf = new Uint8Array(3);
|
|
496
|
+
buf[0] = 253;
|
|
497
|
+
buf[1] = n & 255;
|
|
498
|
+
buf[2] = n >> 8 & 255;
|
|
499
|
+
return buf;
|
|
500
|
+
}
|
|
501
|
+
function hashSignMessage(message) {
|
|
502
|
+
const prefix = new TextEncoder().encode(SIGN_MESSAGE_PREFIX);
|
|
503
|
+
const msg = new TextEncoder().encode(message);
|
|
504
|
+
const prefixLen = varint(prefix.length);
|
|
505
|
+
const msgLen = varint(msg.length);
|
|
506
|
+
const full = new Uint8Array(prefixLen.length + prefix.length + msgLen.length + msg.length);
|
|
507
|
+
let off = 0;
|
|
508
|
+
full.set(prefixLen, off);
|
|
509
|
+
off += prefixLen.length;
|
|
510
|
+
full.set(prefix, off);
|
|
511
|
+
off += prefix.length;
|
|
512
|
+
full.set(msgLen, off);
|
|
513
|
+
off += msgLen.length;
|
|
514
|
+
full.set(msg, off);
|
|
515
|
+
const hex = Array.from(full).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
516
|
+
const h1 = import_crypto_js.default.SHA256(import_crypto_js.default.enc.Hex.parse(hex)).toString();
|
|
517
|
+
return import_crypto_js.default.SHA256(import_crypto_js.default.enc.Hex.parse(h1)).toString();
|
|
518
|
+
}
|
|
519
|
+
function signMessage(privateKeyHex, message) {
|
|
520
|
+
const keyPair = ec.keyFromPrivate(privateKeyHex, "hex");
|
|
521
|
+
const hashHex = hashSignMessage(message);
|
|
522
|
+
const hashBytes = Buffer.from(hashHex, "hex");
|
|
523
|
+
const sig = keyPair.sign(hashBytes, { canonical: true });
|
|
524
|
+
const pub = keyPair.getPublic();
|
|
525
|
+
let recoveryParam = -1;
|
|
526
|
+
for (let i = 0; i < 4; i++) {
|
|
527
|
+
try {
|
|
528
|
+
if (ec.recoverPubKey(hashBytes, sig, i).eq(pub)) {
|
|
529
|
+
recoveryParam = i;
|
|
530
|
+
break;
|
|
531
|
+
}
|
|
532
|
+
} catch {
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
if (recoveryParam === -1) {
|
|
536
|
+
throw new SphereError("Could not find recovery parameter", "SIGNING_ERROR");
|
|
537
|
+
}
|
|
538
|
+
const v = (31 + recoveryParam).toString(16).padStart(2, "0");
|
|
539
|
+
const r = sig.r.toString("hex").padStart(64, "0");
|
|
540
|
+
const s = sig.s.toString("hex").padStart(64, "0");
|
|
541
|
+
return v + r + s;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// wallet-api/errors.ts
|
|
545
|
+
var STATUS_TO_CODE = {
|
|
546
|
+
401: "UNAUTHORIZED",
|
|
547
|
+
403: "FORBIDDEN",
|
|
548
|
+
404: "NOT_FOUND",
|
|
549
|
+
409: "CONFLICT",
|
|
550
|
+
413: "TOO_LARGE",
|
|
551
|
+
422: "VALIDATION",
|
|
552
|
+
429: "RATE_LIMITED"
|
|
553
|
+
};
|
|
554
|
+
var WalletApiError = class _WalletApiError extends Error {
|
|
555
|
+
code;
|
|
556
|
+
/** HTTP status when the error came from a response. */
|
|
557
|
+
status;
|
|
558
|
+
constructor(message, code, status, cause) {
|
|
559
|
+
super(message);
|
|
560
|
+
this.name = "WalletApiError";
|
|
561
|
+
this.code = code;
|
|
562
|
+
this.status = status;
|
|
563
|
+
if (cause !== void 0) this.cause = cause;
|
|
564
|
+
}
|
|
565
|
+
static fromStatus(status, message) {
|
|
566
|
+
return new _WalletApiError(message, STATUS_TO_CODE[status] ?? "SERVER", status);
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
var ChallengeTemplateError = class extends WalletApiError {
|
|
570
|
+
constructor(message) {
|
|
571
|
+
super(message, "CHALLENGE_TEMPLATE");
|
|
572
|
+
this.name = "ChallengeTemplateError";
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
// wallet-api/challenge.ts
|
|
577
|
+
var AUTH_CHALLENGE_PREFIX = "unicity:wallet-api:auth:v1\n";
|
|
578
|
+
var MAX_CLOCK_SKEW_MS = 5 * 60 * 1e3;
|
|
579
|
+
var MAX_VALIDITY_WINDOW_MS = 60 * 60 * 1e3;
|
|
580
|
+
function parseFields(body) {
|
|
581
|
+
let parsed;
|
|
582
|
+
try {
|
|
583
|
+
parsed = JSON.parse(body);
|
|
584
|
+
} catch {
|
|
585
|
+
throw new ChallengeTemplateError("Challenge body is not the single-line JSON object the backend emits");
|
|
586
|
+
}
|
|
587
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
588
|
+
throw new ChallengeTemplateError("Challenge body must be a JSON object");
|
|
589
|
+
}
|
|
590
|
+
const fields = /* @__PURE__ */ new Map();
|
|
591
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
592
|
+
if (typeof value !== "string") {
|
|
593
|
+
throw new ChallengeTemplateError(`Challenge field "${key}" must be a string`);
|
|
594
|
+
}
|
|
595
|
+
fields.set(key, value);
|
|
596
|
+
}
|
|
597
|
+
return fields;
|
|
598
|
+
}
|
|
599
|
+
function requireField(fields, key) {
|
|
600
|
+
const value = fields.get(key);
|
|
601
|
+
if (value === void 0 || value === "") {
|
|
602
|
+
throw new ChallengeTemplateError(`Challenge is missing the "${key}" field`);
|
|
603
|
+
}
|
|
604
|
+
return value;
|
|
605
|
+
}
|
|
606
|
+
function parseTimestamp(value, key) {
|
|
607
|
+
const ms = Date.parse(value);
|
|
608
|
+
if (Number.isNaN(ms)) {
|
|
609
|
+
throw new ChallengeTemplateError(`Challenge "${key}" is not a parseable timestamp: ${value}`);
|
|
610
|
+
}
|
|
611
|
+
return ms;
|
|
612
|
+
}
|
|
613
|
+
function verifyChallengeTemplate(challenge, expect) {
|
|
614
|
+
if (typeof challenge !== "string" || !challenge.startsWith(AUTH_CHALLENGE_PREFIX)) {
|
|
615
|
+
throw new ChallengeTemplateError("Challenge does not start with the unicity:wallet-api:auth:v1 domain prefix");
|
|
616
|
+
}
|
|
617
|
+
const fields = parseFields(challenge.slice(AUTH_CHALLENGE_PREFIX.length));
|
|
618
|
+
const pubkey = requireField(fields, "pubkey");
|
|
619
|
+
if (pubkey.toLowerCase() !== expect.pubkey.toLowerCase()) {
|
|
620
|
+
throw new ChallengeTemplateError("Challenge embeds a different pubkey than this wallet");
|
|
621
|
+
}
|
|
622
|
+
const nonce = requireField(fields, "nonce");
|
|
623
|
+
if (nonce !== expect.nonce) {
|
|
624
|
+
throw new ChallengeTemplateError("Challenge nonce does not match the nonce issued with it");
|
|
625
|
+
}
|
|
626
|
+
const network = requireField(fields, "network");
|
|
627
|
+
if (network !== expect.network) {
|
|
628
|
+
throw new ChallengeTemplateError(`Challenge is for network "${network}", expected "${expect.network}"`);
|
|
629
|
+
}
|
|
630
|
+
const issuedAt = parseTimestamp(requireField(fields, "issuedAt"), "issuedAt");
|
|
631
|
+
const expiresAt = parseTimestamp(requireField(fields, "expiresAt"), "expiresAt");
|
|
632
|
+
if (issuedAt > expect.nowMs + MAX_CLOCK_SKEW_MS) {
|
|
633
|
+
throw new ChallengeTemplateError("Challenge issuedAt is in the future");
|
|
634
|
+
}
|
|
635
|
+
if (expiresAt <= expect.nowMs - MAX_CLOCK_SKEW_MS) {
|
|
636
|
+
throw new ChallengeTemplateError("Challenge is already expired");
|
|
637
|
+
}
|
|
638
|
+
if (expiresAt <= issuedAt || expiresAt - issuedAt > MAX_VALIDITY_WINDOW_MS) {
|
|
639
|
+
throw new ChallengeTemplateError("Challenge validity window is implausible");
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// wallet-api/codec.ts
|
|
644
|
+
function protocolError(message) {
|
|
645
|
+
return new WalletApiError(message, "PROTOCOL");
|
|
646
|
+
}
|
|
647
|
+
function asRecord(value, what) {
|
|
648
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
649
|
+
throw protocolError(`${what} is not an object`);
|
|
650
|
+
}
|
|
651
|
+
return value;
|
|
652
|
+
}
|
|
653
|
+
function asArray(value, what) {
|
|
654
|
+
if (!Array.isArray(value)) throw protocolError(`${what} is not an array`);
|
|
655
|
+
return value;
|
|
656
|
+
}
|
|
657
|
+
function asString(value, what) {
|
|
658
|
+
if (typeof value !== "string") throw protocolError(`${what} is not a string`);
|
|
659
|
+
return value;
|
|
660
|
+
}
|
|
661
|
+
function parseCounter(value, what) {
|
|
662
|
+
if (typeof value === "string" && /^[0-9]+$/.test(value)) return BigInt(value);
|
|
663
|
+
if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) return BigInt(value);
|
|
664
|
+
throw protocolError(`${what} is not a non-negative integer counter`);
|
|
665
|
+
}
|
|
666
|
+
function parseAmount(value, what) {
|
|
667
|
+
if (typeof value === "string" && /^[0-9]+$/.test(value)) return BigInt(value);
|
|
668
|
+
throw protocolError(`${what} is not a decimal-string amount`);
|
|
669
|
+
}
|
|
670
|
+
function parseAssets(value, what) {
|
|
671
|
+
if (value === void 0 || value === null) return void 0;
|
|
672
|
+
return asArray(value, what).map((raw, i) => {
|
|
673
|
+
const rec = asRecord(raw, `${what}[${i}]`);
|
|
674
|
+
return {
|
|
675
|
+
coinId: asString(rec.coinId, `${what}[${i}].coinId`),
|
|
676
|
+
amount: parseAmount(rec.amount, `${what}[${i}].amount`)
|
|
677
|
+
};
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
function parseInventoryItem(raw, what) {
|
|
681
|
+
const rec = asRecord(raw, what);
|
|
682
|
+
const status = asString(rec.status, `${what}.status`);
|
|
683
|
+
if (status !== "active" && status !== "removed") {
|
|
684
|
+
throw protocolError(`${what}.status is neither 'active' nor 'removed'`);
|
|
685
|
+
}
|
|
686
|
+
return {
|
|
687
|
+
tokenId: asString(rec.tokenId, `${what}.tokenId`),
|
|
688
|
+
status,
|
|
689
|
+
assets: parseAssets(rec.assets, `${what}.assets`),
|
|
690
|
+
seq: parseCounter(rec.seq, `${what}.seq`)
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
function parseInventoryPage(body) {
|
|
694
|
+
const rec = asRecord(body, "inventory response");
|
|
695
|
+
if (typeof rec.more !== "boolean") throw protocolError("inventory response .more is not a boolean");
|
|
696
|
+
return {
|
|
697
|
+
cursor: parseCounter(rec.cursor, "inventory response .cursor"),
|
|
698
|
+
syncEpoch: parseCounter(rec.syncEpoch, "inventory response .syncEpoch"),
|
|
699
|
+
more: rec.more,
|
|
700
|
+
items: asArray(rec.items, "inventory response .items").map(
|
|
701
|
+
(raw, i) => parseInventoryItem(raw, `items[${i}]`)
|
|
702
|
+
)
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
function parseBalances(body) {
|
|
706
|
+
const rec = asRecord(body, "balances response");
|
|
707
|
+
return asArray(rec.balances, "balances response .balances").map((raw, i) => {
|
|
708
|
+
const b = asRecord(raw, `balances[${i}]`);
|
|
709
|
+
const tokenCount = b.tokenCount;
|
|
710
|
+
if (typeof tokenCount !== "number" || !Number.isSafeInteger(tokenCount) || tokenCount < 0) {
|
|
711
|
+
throw protocolError(`balances[${i}].tokenCount is not a non-negative integer`);
|
|
712
|
+
}
|
|
713
|
+
return {
|
|
714
|
+
coinId: asString(b.coinId, `balances[${i}].coinId`),
|
|
715
|
+
total: parseAmount(b.total, `balances[${i}].total`),
|
|
716
|
+
tokenCount
|
|
717
|
+
};
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
function parseBlobUrls(body) {
|
|
721
|
+
const rec = asRecord(body, "blob-urls response");
|
|
722
|
+
return asArray(rec.urls, "blob-urls response .urls").map((raw, i) => {
|
|
723
|
+
const u = asRecord(raw, `urls[${i}]`);
|
|
724
|
+
return {
|
|
725
|
+
tokenId: asString(u.tokenId, `urls[${i}].tokenId`),
|
|
726
|
+
getUrl: asString(u.getUrl, `urls[${i}].getUrl`)
|
|
727
|
+
};
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
function parseUploadUrls(body) {
|
|
731
|
+
const rec = asRecord(body, "upload-urls response");
|
|
732
|
+
return asArray(rec.urls, "upload-urls response .urls").map((raw, i) => {
|
|
733
|
+
const u = asRecord(raw, `urls[${i}]`);
|
|
734
|
+
return {
|
|
735
|
+
sha256: asString(u.sha256, `urls[${i}].sha256`),
|
|
736
|
+
key: asString(u.key, `urls[${i}].key`),
|
|
737
|
+
putUrl: asString(u.putUrl, `urls[${i}].putUrl`)
|
|
738
|
+
};
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
function parseApplyResult(body) {
|
|
742
|
+
const rec = asRecord(body, "apply response");
|
|
743
|
+
return parseCounter(rec.cursor, "apply response .cursor");
|
|
744
|
+
}
|
|
745
|
+
function parseIntents(body) {
|
|
746
|
+
const rec = asRecord(body, "intents response");
|
|
747
|
+
return asArray(rec.intents, "intents response .intents").map((raw, i) => {
|
|
748
|
+
const it = asRecord(raw, `intents[${i}]`);
|
|
749
|
+
const status = asString(it.status, `intents[${i}].status`);
|
|
750
|
+
if (status !== "open" && status !== "completed" && status !== "aborted") {
|
|
751
|
+
throw protocolError(`intents[${i}].status is not a known intent status`);
|
|
752
|
+
}
|
|
753
|
+
const createdAt = Date.parse(asString(it.createdAt, `intents[${i}].createdAt`));
|
|
754
|
+
if (Number.isNaN(createdAt)) throw protocolError(`intents[${i}].createdAt is not a timestamp`);
|
|
755
|
+
return {
|
|
756
|
+
transferId: asString(it.transferId, `intents[${i}].transferId`),
|
|
757
|
+
payload: asString(it.payload, `intents[${i}].payload`),
|
|
758
|
+
status,
|
|
759
|
+
createdAt
|
|
760
|
+
};
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
function parseDepositResult(body) {
|
|
764
|
+
const rec = asRecord(body, "mailbox deposit response");
|
|
765
|
+
return asString(rec.entryId, "mailbox deposit response .entryId");
|
|
766
|
+
}
|
|
767
|
+
function parseMailboxEntry(raw, what) {
|
|
768
|
+
const rec = asRecord(raw, what);
|
|
769
|
+
const status = asString(rec.status, `${what}.status`);
|
|
770
|
+
if (status !== "unclaimed" && status !== "claimed" && status !== "rejected") {
|
|
771
|
+
throw protocolError(`${what}.status is not a known mailbox entry status`);
|
|
772
|
+
}
|
|
773
|
+
const createdAt = Date.parse(asString(rec.createdAt, `${what}.createdAt`));
|
|
774
|
+
if (Number.isNaN(createdAt)) throw protocolError(`${what}.createdAt is not a timestamp`);
|
|
775
|
+
return {
|
|
776
|
+
entryId: asString(rec.entryId, `${what}.entryId`),
|
|
777
|
+
seq: parseCounter(rec.seq, `${what}.seq`),
|
|
778
|
+
status,
|
|
779
|
+
transferId: asString(rec.transferId, `${what}.transferId`),
|
|
780
|
+
tokenId: asString(rec.tokenId, `${what}.tokenId`),
|
|
781
|
+
assets: (parseAssets(rec.assets, `${what}.assets`) ?? []).map((a) => ({ ...a })),
|
|
782
|
+
senderPubkey: asString(rec.senderPubkey, `${what}.senderPubkey`),
|
|
783
|
+
...rec.memo !== void 0 && rec.memo !== null ? { memo: asString(rec.memo, `${what}.memo`) } : {},
|
|
784
|
+
createdAt,
|
|
785
|
+
...rec.getUrl !== void 0 && rec.getUrl !== null ? { getUrl: asString(rec.getUrl, `${what}.getUrl`) } : {},
|
|
786
|
+
...rec.blobCollected === true ? { blobCollected: true } : {}
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
function parseMailboxPage(body) {
|
|
790
|
+
const rec = asRecord(body, "mailbox response");
|
|
791
|
+
if (typeof rec.more !== "boolean") throw protocolError("mailbox response .more is not a boolean");
|
|
792
|
+
return {
|
|
793
|
+
readPointer: parseCounter(rec.readPointer, "mailbox response .readPointer"),
|
|
794
|
+
syncEpoch: parseCounter(rec.syncEpoch, "mailbox response .syncEpoch"),
|
|
795
|
+
more: rec.more,
|
|
796
|
+
entries: asArray(rec.entries, "mailbox response .entries").map(
|
|
797
|
+
(raw, i) => parseMailboxEntry(raw, `entries[${i}]`)
|
|
798
|
+
)
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
function parseClaimResult(body) {
|
|
802
|
+
const rec = asRecord(body, "mailbox claim response");
|
|
803
|
+
return {
|
|
804
|
+
claimed: asArray(rec.claimed, "claim response .claimed").map(
|
|
805
|
+
(raw, i) => asString(raw, `claimed[${i}]`)
|
|
806
|
+
),
|
|
807
|
+
alreadyClaimed: asArray(rec.alreadyClaimed, "claim response .alreadyClaimed").map((raw, i) => {
|
|
808
|
+
const a = asRecord(raw, `alreadyClaimed[${i}]`);
|
|
809
|
+
if (typeof a.intoInventory !== "boolean") {
|
|
810
|
+
throw protocolError(`alreadyClaimed[${i}].intoInventory is not a boolean`);
|
|
811
|
+
}
|
|
812
|
+
return { entryId: asString(a.entryId, `alreadyClaimed[${i}].entryId`), intoInventory: a.intoInventory };
|
|
813
|
+
}),
|
|
814
|
+
failed: asArray(rec.failed, "claim response .failed").map((raw, i) => {
|
|
815
|
+
const f = asRecord(raw, `failed[${i}]`);
|
|
816
|
+
return { entryId: asString(f.entryId, `failed[${i}].entryId`), code: asString(f.code, `failed[${i}].code`) };
|
|
817
|
+
})
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
function parseRejectResult(body) {
|
|
821
|
+
const rec = asRecord(body, "mailbox reject response");
|
|
822
|
+
return asArray(rec.rejected, "reject response .rejected").map((raw, i) => asString(raw, `rejected[${i}]`));
|
|
823
|
+
}
|
|
824
|
+
function parsePaymentRequestRecord(raw, what) {
|
|
825
|
+
const rec = asRecord(raw, what);
|
|
826
|
+
const status = asString(rec.status, `${what}.status`);
|
|
827
|
+
if (status !== "open" && status !== "paid" && status !== "declined" && status !== "expired") {
|
|
828
|
+
throw protocolError(`${what}.status is not a known payment-request status`);
|
|
829
|
+
}
|
|
830
|
+
const createdAt = Date.parse(asString(rec.createdAt, `${what}.createdAt`));
|
|
831
|
+
if (Number.isNaN(createdAt)) throw protocolError(`${what}.createdAt is not a timestamp`);
|
|
832
|
+
let expiresAt;
|
|
833
|
+
if (rec.expiresAt !== void 0 && rec.expiresAt !== null) {
|
|
834
|
+
expiresAt = Date.parse(asString(rec.expiresAt, `${what}.expiresAt`));
|
|
835
|
+
if (Number.isNaN(expiresAt)) throw protocolError(`${what}.expiresAt is not a timestamp`);
|
|
836
|
+
}
|
|
837
|
+
if (rec.transferId !== null && typeof rec.transferId !== "string") {
|
|
838
|
+
throw protocolError(`${what}.transferId is neither a string nor null`);
|
|
839
|
+
}
|
|
840
|
+
return {
|
|
841
|
+
id: asString(rec.id, `${what}.id`),
|
|
842
|
+
seq: parseCounter(rec.seq, `${what}.seq`),
|
|
843
|
+
fromPubkey: asString(rec.fromPubkey, `${what}.fromPubkey`),
|
|
844
|
+
toPubkey: asString(rec.toPubkey, `${what}.toPubkey`),
|
|
845
|
+
assets: (parseAssets(rec.assets, `${what}.assets`) ?? []).map((a) => ({ ...a })),
|
|
846
|
+
...rec.memo !== void 0 && rec.memo !== null ? { memo: asString(rec.memo, `${what}.memo`) } : {},
|
|
847
|
+
status,
|
|
848
|
+
transferId: rec.transferId,
|
|
849
|
+
createdAt,
|
|
850
|
+
...expiresAt !== void 0 ? { expiresAt } : {}
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
function parsePaymentRequestsPage(body, role) {
|
|
854
|
+
const rec = asRecord(body, "payment-requests response");
|
|
855
|
+
if (typeof rec.more !== "boolean") throw protocolError("payment-requests response .more is not a boolean");
|
|
856
|
+
const requests = asArray(rec.requests, "payment-requests response .requests").map(
|
|
857
|
+
(raw, i) => parsePaymentRequestRecord(raw, `requests[${i}]`)
|
|
858
|
+
);
|
|
859
|
+
const syncEpoch = parseCounter(rec.syncEpoch, "payment-requests response .syncEpoch");
|
|
860
|
+
if (role === "incoming") {
|
|
861
|
+
return {
|
|
862
|
+
role,
|
|
863
|
+
requests,
|
|
864
|
+
more: rec.more,
|
|
865
|
+
cursor: parseCounter(rec.cursor, "payment-requests response .cursor"),
|
|
866
|
+
syncEpoch
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
if (rec.cursor !== null && typeof rec.cursor !== "string") {
|
|
870
|
+
throw protocolError("payment-requests response .cursor is neither a keyset string nor null");
|
|
871
|
+
}
|
|
872
|
+
return { role, requests, more: rec.more, cursor: rec.cursor, syncEpoch };
|
|
873
|
+
}
|
|
874
|
+
function parseHistoryRecord(raw, what) {
|
|
875
|
+
const rec = asRecord(raw, what);
|
|
876
|
+
return {
|
|
877
|
+
dedupKey: asString(rec.dedupKey, `${what}.dedupKey`),
|
|
878
|
+
id: asString(rec.id, `${what}.id`),
|
|
879
|
+
type: asString(rec.type, `${what}.type`),
|
|
880
|
+
ts: asString(rec.ts, `${what}.ts`),
|
|
881
|
+
assets: asArray(rec.assets, `${what}.assets`).map((a, i) => {
|
|
882
|
+
const asset = asRecord(a, `${what}.assets[${i}]`);
|
|
883
|
+
parseAmount(asset.amount, `${what}.assets[${i}].amount`);
|
|
884
|
+
return {
|
|
885
|
+
coinId: asString(asset.coinId, `${what}.assets[${i}].coinId`),
|
|
886
|
+
amount: asString(asset.amount, `${what}.assets[${i}].amount`)
|
|
887
|
+
};
|
|
888
|
+
}),
|
|
889
|
+
...typeof rec.transferId === "string" ? { transferId: rec.transferId } : {},
|
|
890
|
+
...typeof rec.tokenId === "string" ? { tokenId: rec.tokenId } : {},
|
|
891
|
+
...typeof rec.counterpartyPubkey === "string" ? { counterpartyPubkey: rec.counterpartyPubkey } : {},
|
|
892
|
+
...typeof rec.memo === "string" ? { memo: rec.memo } : {},
|
|
893
|
+
...typeof rec.counterpartyNametag === "string" ? { counterpartyNametag: rec.counterpartyNametag } : {}
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
function parseHistoryPage(body) {
|
|
897
|
+
const rec = asRecord(body, "history response");
|
|
898
|
+
if (typeof rec.more !== "boolean") throw protocolError("history response .more is not a boolean");
|
|
899
|
+
if (rec.cursor !== null && typeof rec.cursor !== "string") {
|
|
900
|
+
throw protocolError("history response .cursor is not a string or null");
|
|
901
|
+
}
|
|
902
|
+
return {
|
|
903
|
+
records: asArray(rec.records, "history response .records").map(
|
|
904
|
+
(raw, i) => parseHistoryRecord(raw, `records[${i}]`)
|
|
905
|
+
),
|
|
906
|
+
more: rec.more,
|
|
907
|
+
cursor: rec.cursor,
|
|
908
|
+
syncEpoch: parseCounter(rec.syncEpoch, "history response .syncEpoch")
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
function parseAuthChallenge(body) {
|
|
912
|
+
const rec = asRecord(body, "auth/challenge response");
|
|
913
|
+
return {
|
|
914
|
+
nonce: asString(rec.nonce, "auth/challenge response .nonce"),
|
|
915
|
+
challenge: asString(rec.challenge, "auth/challenge response .challenge")
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
function parseAuthTokens(body) {
|
|
919
|
+
const rec = asRecord(body, "auth token response");
|
|
920
|
+
return {
|
|
921
|
+
jwt: asString(rec.jwt, "auth token response .jwt"),
|
|
922
|
+
refreshToken: asString(rec.refreshToken, "auth token response .refreshToken")
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
function parseWsTicket(body) {
|
|
926
|
+
const rec = asRecord(body, "ws-ticket response");
|
|
927
|
+
return asString(rec.ticket, "ws-ticket response .ticket");
|
|
928
|
+
}
|
|
929
|
+
function parseWakeFrame(raw) {
|
|
930
|
+
let body;
|
|
931
|
+
try {
|
|
932
|
+
body = JSON.parse(raw);
|
|
933
|
+
} catch {
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
if (typeof body !== "object" || body === null) return null;
|
|
937
|
+
const rec = body;
|
|
938
|
+
if (rec.type !== "wake") return null;
|
|
939
|
+
const stream = rec.stream;
|
|
940
|
+
if (stream !== "inventory" && stream !== "mailbox" && stream !== "payment_requests") return null;
|
|
941
|
+
try {
|
|
942
|
+
return { stream, syncEpoch: parseCounter(rec.syncEpoch, "wake .syncEpoch") };
|
|
943
|
+
} catch {
|
|
944
|
+
return null;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// wallet-api/client.ts
|
|
949
|
+
function isLoopbackHost(hostname) {
|
|
950
|
+
return hostname === "localhost" || hostname.endsWith(".localhost") || /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname) || hostname === "::1" || hostname === "[::1]";
|
|
951
|
+
}
|
|
952
|
+
function assertSecureBaseUrl(baseUrl) {
|
|
953
|
+
let url;
|
|
954
|
+
try {
|
|
955
|
+
url = new URL(baseUrl);
|
|
956
|
+
} catch {
|
|
957
|
+
throw new WalletApiError(`Invalid wallet-api base URL: ${baseUrl}`, "CONFIG");
|
|
958
|
+
}
|
|
959
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && isLoopbackHost(url.hostname))) {
|
|
960
|
+
throw new WalletApiError(
|
|
961
|
+
"wallet-api base URL must be https (plain http is allowed only on loopback \u2014 ARCHITECTURE \xA74)",
|
|
962
|
+
"CONFIG"
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
return url;
|
|
966
|
+
}
|
|
967
|
+
function bytesToBase64(bytes) {
|
|
968
|
+
let bin = "";
|
|
969
|
+
for (const b of bytes) bin += String.fromCharCode(b);
|
|
970
|
+
return btoa(bin);
|
|
971
|
+
}
|
|
972
|
+
function defaultWebSocketFactory(url) {
|
|
973
|
+
const Ctor = globalThis.WebSocket;
|
|
974
|
+
if (!Ctor) {
|
|
975
|
+
throw new WalletApiError(
|
|
976
|
+
'No global WebSocket available \u2014 inject webSocketFactory (e.g. the "ws" package on Node < 22)',
|
|
977
|
+
"CONFIG"
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
return new Ctor(url);
|
|
981
|
+
}
|
|
982
|
+
var WalletApiClient = class {
|
|
983
|
+
/** Network name (also the blob-key prefix `<network>/t/<sha256>` — §5.2). */
|
|
984
|
+
network;
|
|
985
|
+
baseUrl;
|
|
986
|
+
deviceId;
|
|
987
|
+
storage;
|
|
988
|
+
fetchFn;
|
|
989
|
+
wsFactory;
|
|
990
|
+
now;
|
|
991
|
+
identity = null;
|
|
992
|
+
jwt = null;
|
|
993
|
+
/** Serializes concurrent re-auth attempts. */
|
|
994
|
+
authInFlight = null;
|
|
995
|
+
constructor(config) {
|
|
996
|
+
const url = assertSecureBaseUrl(config.baseUrl);
|
|
997
|
+
this.baseUrl = url.toString().replace(/\/$/, "");
|
|
998
|
+
if (!config.network) throw new WalletApiError("network is required", "CONFIG");
|
|
999
|
+
this.network = config.network;
|
|
1000
|
+
this.deviceId = config.deviceId;
|
|
1001
|
+
this.storage = config.storage;
|
|
1002
|
+
if (!config.fetchFn && !globalThis.fetch) {
|
|
1003
|
+
throw new WalletApiError("No fetch available \u2014 inject fetchFn", "CONFIG");
|
|
1004
|
+
}
|
|
1005
|
+
this.fetchFn = config.fetchFn ?? ((u, init) => globalThis.fetch(u, init));
|
|
1006
|
+
this.wsFactory = config.webSocketFactory ?? defaultWebSocketFactory;
|
|
1007
|
+
this.now = config.now ?? (() => Date.now());
|
|
1008
|
+
}
|
|
1009
|
+
/** Bind the wallet identity this client authenticates as. Resets the session. */
|
|
1010
|
+
setIdentity(identity) {
|
|
1011
|
+
this.identity = identity;
|
|
1012
|
+
this.jwt = null;
|
|
1013
|
+
}
|
|
1014
|
+
// ── storage keys (scoped per network + pubkey + device) ────────────────────
|
|
1015
|
+
requireIdentity() {
|
|
1016
|
+
if (!this.identity) throw new WalletApiError("No identity set \u2014 call setIdentity() first", "CONFIG");
|
|
1017
|
+
return this.identity;
|
|
1018
|
+
}
|
|
1019
|
+
scopedKey(kind) {
|
|
1020
|
+
const { chainPubkey } = this.requireIdentity();
|
|
1021
|
+
return `wallet-api:${kind}:${this.network}:${chainPubkey}`;
|
|
1022
|
+
}
|
|
1023
|
+
refreshTokenKey() {
|
|
1024
|
+
return `${this.scopedKey("refresh")}:${this.deviceId}`;
|
|
1025
|
+
}
|
|
1026
|
+
// ── auth (ARCHITECTURE §4) ──────────────────────────────────────────────────
|
|
1027
|
+
/**
|
|
1028
|
+
* Establish a session: try the stored refresh token first (rotating), fall
|
|
1029
|
+
* back to a fresh challenge→sign→verify cycle. Safe to call repeatedly.
|
|
1030
|
+
*/
|
|
1031
|
+
async signIn() {
|
|
1032
|
+
if (this.authInFlight) return this.authInFlight;
|
|
1033
|
+
this.authInFlight = this.signInInner().finally(() => {
|
|
1034
|
+
this.authInFlight = null;
|
|
1035
|
+
});
|
|
1036
|
+
return this.authInFlight;
|
|
1037
|
+
}
|
|
1038
|
+
async signInInner() {
|
|
1039
|
+
if (await this.tryRefresh()) return;
|
|
1040
|
+
await this.challengeSignIn();
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* `POST /v1/auth/refresh` with the stored token; rotates on success. Any
|
|
1044
|
+
* 4xx (expired, revoked, rotation-reuse revocation) clears the stored token
|
|
1045
|
+
* and reports `false` — the caller falls back to a challenge cycle.
|
|
1046
|
+
*/
|
|
1047
|
+
async tryRefresh() {
|
|
1048
|
+
const stored = await this.storage.get(this.refreshTokenKey());
|
|
1049
|
+
if (!stored) return false;
|
|
1050
|
+
const res = await this.rawFetch("POST", "/v1/auth/refresh", { refreshToken: stored });
|
|
1051
|
+
if (res.status === 200) {
|
|
1052
|
+
const tokens = parseAuthTokens(await this.readJson(res, "auth/refresh"));
|
|
1053
|
+
this.jwt = tokens.jwt;
|
|
1054
|
+
await this.storage.set(this.refreshTokenKey(), tokens.refreshToken);
|
|
1055
|
+
return true;
|
|
1056
|
+
}
|
|
1057
|
+
await this.storage.remove(this.refreshTokenKey());
|
|
1058
|
+
return false;
|
|
1059
|
+
}
|
|
1060
|
+
/** The challenge→verify cycle (§4 steps 1–3) with template verification (S1). */
|
|
1061
|
+
async challengeSignIn() {
|
|
1062
|
+
const identity = this.requireIdentity();
|
|
1063
|
+
const challengeRes = await this.rawFetch("POST", "/v1/auth/challenge", {
|
|
1064
|
+
pubkey: identity.chainPubkey
|
|
1065
|
+
});
|
|
1066
|
+
if (challengeRes.status !== 200) throw await this.toError(challengeRes, "auth/challenge");
|
|
1067
|
+
const { nonce, challenge } = parseAuthChallenge(await this.readJson(challengeRes, "auth/challenge"));
|
|
1068
|
+
verifyChallengeTemplate(challenge, {
|
|
1069
|
+
pubkey: identity.chainPubkey,
|
|
1070
|
+
nonce,
|
|
1071
|
+
network: this.network,
|
|
1072
|
+
nowMs: this.now()
|
|
1073
|
+
});
|
|
1074
|
+
const signature = signMessage(identity.privateKey, challenge);
|
|
1075
|
+
const verifyRes = await this.rawFetch("POST", "/v1/auth/verify", {
|
|
1076
|
+
nonce,
|
|
1077
|
+
signature,
|
|
1078
|
+
deviceId: this.deviceId
|
|
1079
|
+
});
|
|
1080
|
+
if (verifyRes.status !== 200) throw await this.toError(verifyRes, "auth/verify");
|
|
1081
|
+
const tokens = parseAuthTokens(await this.readJson(verifyRes, "auth/verify"));
|
|
1082
|
+
this.jwt = tokens.jwt;
|
|
1083
|
+
await this.storage.set(this.refreshTokenKey(), tokens.refreshToken);
|
|
1084
|
+
}
|
|
1085
|
+
/** Revoke the session server-side and drop all local credentials. */
|
|
1086
|
+
async logout() {
|
|
1087
|
+
if (this.jwt) {
|
|
1088
|
+
try {
|
|
1089
|
+
await this.rawFetch("POST", "/v1/auth/logout", {}, this.jwt);
|
|
1090
|
+
} finally {
|
|
1091
|
+
this.jwt = null;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
await this.storage.remove(this.refreshTokenKey());
|
|
1095
|
+
}
|
|
1096
|
+
// ── request core ────────────────────────────────────────────────────────────
|
|
1097
|
+
async rawFetch(method, path, body, jwt) {
|
|
1098
|
+
const headers = {};
|
|
1099
|
+
if (body !== void 0) headers["content-type"] = "application/json";
|
|
1100
|
+
if (jwt) headers["authorization"] = `Bearer ${jwt}`;
|
|
1101
|
+
try {
|
|
1102
|
+
return await this.fetchFn(`${this.baseUrl}${path}`, {
|
|
1103
|
+
method,
|
|
1104
|
+
headers,
|
|
1105
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
1106
|
+
});
|
|
1107
|
+
} catch (err) {
|
|
1108
|
+
throw new WalletApiError(`wallet-api request failed: ${method} ${path}`, "NETWORK", void 0, err);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
async readJson(res, what) {
|
|
1112
|
+
const text = await res.text();
|
|
1113
|
+
if (text === "") return null;
|
|
1114
|
+
try {
|
|
1115
|
+
return JSON.parse(text);
|
|
1116
|
+
} catch (err) {
|
|
1117
|
+
throw new WalletApiError(`${what}: response is not JSON`, "PROTOCOL", res.status, err);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
async toError(res, what) {
|
|
1121
|
+
let message = `${what} failed with status ${res.status}`;
|
|
1122
|
+
try {
|
|
1123
|
+
const body = await this.readJson(res, what);
|
|
1124
|
+
if (body?.error?.message) message = `${what}: ${body.error.code ?? ""} ${body.error.message}`.trim();
|
|
1125
|
+
} catch {
|
|
1126
|
+
}
|
|
1127
|
+
return WalletApiError.fromStatus(res.status, message);
|
|
1128
|
+
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Authenticated JSON request. A 401 triggers one silent re-auth
|
|
1131
|
+
* (refresh → challenge fallback) and one retry.
|
|
1132
|
+
*/
|
|
1133
|
+
async requestJson(method, path, body) {
|
|
1134
|
+
if (!this.jwt) await this.signIn();
|
|
1135
|
+
let res = await this.rawFetch(method, path, body, this.jwt);
|
|
1136
|
+
if (res.status === 401) {
|
|
1137
|
+
this.jwt = null;
|
|
1138
|
+
await this.signIn();
|
|
1139
|
+
res = await this.rawFetch(method, path, body, this.jwt);
|
|
1140
|
+
}
|
|
1141
|
+
if (res.status === 204) return null;
|
|
1142
|
+
if (!res.ok) throw await this.toError(res, `${method} ${path}`);
|
|
1143
|
+
return this.readJson(res, `${method} ${path}`);
|
|
1144
|
+
}
|
|
1145
|
+
// ── inventory / blobs (§16) ─────────────────────────────────────────────────
|
|
1146
|
+
/** `GET /v1/inventory?since=` — one page; the caller loops while `more`. */
|
|
1147
|
+
async listInventory(since) {
|
|
1148
|
+
const query = since !== void 0 ? `?since=${since.toString()}` : "";
|
|
1149
|
+
const page = parseInventoryPage(await this.requestJson("GET", `/v1/inventory${query}`));
|
|
1150
|
+
await this.noteSyncEpoch(page.syncEpoch);
|
|
1151
|
+
return page;
|
|
1152
|
+
}
|
|
1153
|
+
/** `GET /v1/balances` — active rows only. */
|
|
1154
|
+
async getBalances() {
|
|
1155
|
+
return parseBalances(await this.requestJson("GET", "/v1/balances"));
|
|
1156
|
+
}
|
|
1157
|
+
/** `POST /v1/tokens/blob-urls` — owner's active *or tombstoned* rows (§5.3 recovery). */
|
|
1158
|
+
async getBlobUrls(tokenIds) {
|
|
1159
|
+
return parseBlobUrls(await this.requestJson("POST", "/v1/tokens/blob-urls", { tokenIds }));
|
|
1160
|
+
}
|
|
1161
|
+
/** `POST /v1/tokens/upload-urls` — checksum/length-bound presigned PUTs (§5.2). */
|
|
1162
|
+
async getUploadUrls(blobs) {
|
|
1163
|
+
return parseUploadUrls(await this.requestJson("POST", "/v1/tokens/upload-urls", { blobs }));
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* `POST /v1/inventory/apply` (§5.3). Also marks the local intent copy
|
|
1167
|
+
* completed — the server completes the intent in the same transaction (§16).
|
|
1168
|
+
*/
|
|
1169
|
+
async applyInventoryDelta(req) {
|
|
1170
|
+
const cursor = parseApplyResult(
|
|
1171
|
+
await this.requestJson("POST", "/v1/inventory/apply", {
|
|
1172
|
+
transferId: req.transferId,
|
|
1173
|
+
spent: req.spent,
|
|
1174
|
+
added: req.added,
|
|
1175
|
+
...req.externalDelivery !== void 0 ? { externalDelivery: req.externalDelivery } : {}
|
|
1176
|
+
})
|
|
1177
|
+
);
|
|
1178
|
+
await this.setLocalIntentStatus(req.transferId, "completed");
|
|
1179
|
+
return cursor;
|
|
1180
|
+
}
|
|
1181
|
+
/** Download blob bytes from a signed GET URL (the URL itself is the credential). */
|
|
1182
|
+
async fetchBlob(getUrl) {
|
|
1183
|
+
let res;
|
|
1184
|
+
try {
|
|
1185
|
+
res = await this.fetchFn(getUrl, { method: "GET" });
|
|
1186
|
+
} catch (err) {
|
|
1187
|
+
throw new WalletApiError("blob download failed", "NETWORK", void 0, err);
|
|
1188
|
+
}
|
|
1189
|
+
if (!res.ok) throw WalletApiError.fromStatus(res.status, `blob download failed with status ${res.status}`);
|
|
1190
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Upload blob bytes to a signed PUT URL. A `412 Precondition Failed` means
|
|
1194
|
+
* the identical blob already exists (content addressing, `If-None-Match: *`
|
|
1195
|
+
* — §5.2) and is treated as success.
|
|
1196
|
+
*
|
|
1197
|
+
* The §5.2 presign binds `x-amz-checksum-sha256` and `if-none-match` (plus
|
|
1198
|
+
* `content-length`) as SIGNED HEADERS — a real S3 endpoint rejects the
|
|
1199
|
+
* SigV4 signature unless the uploader sends them verbatim. (Caught by the
|
|
1200
|
+
* phase-2 harness on first contact with real MinIO — the in-process fake
|
|
1201
|
+
* had only validated the body, not the signed headers.)
|
|
1202
|
+
*/
|
|
1203
|
+
async uploadBlob(putUrl, bytes) {
|
|
1204
|
+
let res;
|
|
1205
|
+
try {
|
|
1206
|
+
res = await this.fetchFn(putUrl, {
|
|
1207
|
+
method: "PUT",
|
|
1208
|
+
headers: {
|
|
1209
|
+
"content-type": "application/octet-stream",
|
|
1210
|
+
"x-amz-checksum-sha256": bytesToBase64(sha256(bytes)),
|
|
1211
|
+
"if-none-match": "*"
|
|
1212
|
+
},
|
|
1213
|
+
body: bytes
|
|
1214
|
+
});
|
|
1215
|
+
} catch (err) {
|
|
1216
|
+
throw new WalletApiError("blob upload failed", "NETWORK", void 0, err);
|
|
1217
|
+
}
|
|
1218
|
+
if (res.ok || res.status === 412) return;
|
|
1219
|
+
throw WalletApiError.fromStatus(res.status, `blob upload failed with status ${res.status}`);
|
|
1220
|
+
}
|
|
1221
|
+
// ── mailbox (§6/§16) ────────────────────────────────────────────────────────
|
|
1222
|
+
/**
|
|
1223
|
+
* `POST /v1/mailbox` — deposit an already-uploaded finished blob to the
|
|
1224
|
+
* recipient's mailbox. Idempotent by content-derived `entry_id` (§6): an
|
|
1225
|
+
* existing entry in any status returns `200` with its id, provided the
|
|
1226
|
+
* request's recipient and key match the stored entry (a mismatch is `409`).
|
|
1227
|
+
*/
|
|
1228
|
+
async depositMailbox(req) {
|
|
1229
|
+
return parseDepositResult(
|
|
1230
|
+
await this.requestJson("POST", "/v1/mailbox", {
|
|
1231
|
+
recipientPubkey: req.recipientPubkey,
|
|
1232
|
+
key: req.key,
|
|
1233
|
+
transferId: req.transferId,
|
|
1234
|
+
stateHash: req.stateHash,
|
|
1235
|
+
tokenId: req.tokenId,
|
|
1236
|
+
...req.memo !== void 0 ? { memo: req.memo } : {}
|
|
1237
|
+
})
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* `GET /v1/mailbox?since=<seq>` — entries of EVERY status are listable for
|
|
1242
|
+
* any client-chosen `since`; a claimed entry carries a working `getUrl`
|
|
1243
|
+
* while its blob is within retention, `blobCollected: true` afterwards (§6).
|
|
1244
|
+
*/
|
|
1245
|
+
async listMailbox(since) {
|
|
1246
|
+
const query = since !== void 0 ? `?since=${since.toString()}` : "";
|
|
1247
|
+
const page = parseMailboxPage(await this.requestJson("GET", `/v1/mailbox${query}`));
|
|
1248
|
+
await this.noteSyncEpoch(page.syncEpoch);
|
|
1249
|
+
return page;
|
|
1250
|
+
}
|
|
1251
|
+
/**
|
|
1252
|
+
* `POST /v1/mailbox/claim` — addressee-only, idempotent ownership handoff
|
|
1253
|
+
* (§6). `intoInventory:false` is the delivery-only claim: the entry resolves
|
|
1254
|
+
* and the pointer advances with ZERO inventory writes.
|
|
1255
|
+
*/
|
|
1256
|
+
async claimMailbox(entryIds, intoInventory) {
|
|
1257
|
+
return parseClaimResult(
|
|
1258
|
+
await this.requestJson("POST", "/v1/mailbox/claim", { entryIds, intoInventory })
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* `POST /v1/mailbox/reject` — addressee-only; terminal for DISCOVERY only:
|
|
1263
|
+
* the entry counts toward read-pointer contiguity but remains claimable and
|
|
1264
|
+
* its blob is retained (§6 — reject is never a destruction path).
|
|
1265
|
+
*/
|
|
1266
|
+
async rejectMailbox(entryIds) {
|
|
1267
|
+
return parseRejectResult(await this.requestJson("POST", "/v1/mailbox/reject", { entryIds }));
|
|
1268
|
+
}
|
|
1269
|
+
// ── history (§10/§16) ───────────────────────────────────────────────────────
|
|
1270
|
+
/**
|
|
1271
|
+
* `POST /v1/history` — client-asserted records, deduped by `dedupKey` (the
|
|
1272
|
+
* server never writes history rows — §10). `memo`/`counterpartyNametag`
|
|
1273
|
+
* MUST already be S6 envelopes.
|
|
1274
|
+
*/
|
|
1275
|
+
async postHistoryRecords(records) {
|
|
1276
|
+
await this.requestJson("POST", "/v1/history", { records });
|
|
1277
|
+
}
|
|
1278
|
+
/** `GET /v1/history?before=&limit=` — newest-first keyset pages (§10). */
|
|
1279
|
+
async listHistory(options = {}) {
|
|
1280
|
+
const params = new URLSearchParams();
|
|
1281
|
+
if (options.before !== void 0) params.set("before", options.before);
|
|
1282
|
+
if (options.limit !== void 0) params.set("limit", String(options.limit));
|
|
1283
|
+
const qs = params.toString();
|
|
1284
|
+
return parseHistoryPage(await this.requestJson("GET", `/v1/history${qs ? `?${qs}` : ""}`));
|
|
1285
|
+
}
|
|
1286
|
+
// ── payment requests (§10/§16) ──────────────────────────────────────────────
|
|
1287
|
+
/**
|
|
1288
|
+
* `POST /v1/payment-requests` — create a request addressed to a payer (the
|
|
1289
|
+
* payer's account is auto-provisioned even if they never authenticated —
|
|
1290
|
+
* §4/§9; the per-payer open cap → 429, §5.5). `memo` MUST already be an S6
|
|
1291
|
+
* `enc1.` envelope (§8.3) — only the requester's wallet key decrypts it.
|
|
1292
|
+
*/
|
|
1293
|
+
async createPaymentRequest(input) {
|
|
1294
|
+
return parsePaymentRequestRecord(
|
|
1295
|
+
await this.requestJson("POST", "/v1/payment-requests", {
|
|
1296
|
+
toPubkey: input.toPubkey,
|
|
1297
|
+
// Amounts are decimal strings in every JSON body (§11).
|
|
1298
|
+
assets: input.assets.map((a) => ({ coinId: a.coinId, amount: a.amount.toString() })),
|
|
1299
|
+
...input.memo !== void 0 ? { memo: input.memo } : {},
|
|
1300
|
+
...input.expiresAt !== void 0 ? { expiresAt: new Date(input.expiresAt).toISOString() } : {}
|
|
1301
|
+
}),
|
|
1302
|
+
"payment-request create response"
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* `GET /v1/payment-requests?role=…` (§16): `'incoming'` is the payer's
|
|
1307
|
+
* gap-free `?since=<seq>` stream (cursor = bigint, the standard §16 since
|
|
1308
|
+
* contract); `'outgoing'` is the requester's newest-first
|
|
1309
|
+
* `?before=<opaque keyset>` backfill (cursor = string | null). The two
|
|
1310
|
+
* cursor families never mix — the server 422s a mismatched parameter, and
|
|
1311
|
+
* the parameter types make it unrepresentable here.
|
|
1312
|
+
*/
|
|
1313
|
+
async listPaymentRequests(params) {
|
|
1314
|
+
const p = params;
|
|
1315
|
+
const query = new URLSearchParams();
|
|
1316
|
+
query.set("role", p.role);
|
|
1317
|
+
if (p.status !== void 0) query.set("status", p.status);
|
|
1318
|
+
if (p.role === "incoming") {
|
|
1319
|
+
if (p.since !== void 0) query.set("since", p.since.toString());
|
|
1320
|
+
} else if (p.before !== void 0) {
|
|
1321
|
+
query.set("before", p.before);
|
|
1322
|
+
}
|
|
1323
|
+
const page = parsePaymentRequestsPage(
|
|
1324
|
+
await this.requestJson("GET", `/v1/payment-requests?${query.toString()}`),
|
|
1325
|
+
p.role
|
|
1326
|
+
);
|
|
1327
|
+
await this.noteSyncEpoch(page.syncEpoch);
|
|
1328
|
+
return page;
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* `POST /v1/payment-requests/{id}/respond` — addressee-only (§10, non-
|
|
1332
|
+
* addressee → 403); only an `open` request may be responded to (else 409).
|
|
1333
|
+
* `paid` REQUIRES and links the fulfilling send's transferId, `declined`
|
|
1334
|
+
* carries none — the pairing is enforced by {@link RespondPaymentRequestInput}.
|
|
1335
|
+
*/
|
|
1336
|
+
async respondPaymentRequest(id, response) {
|
|
1337
|
+
return parsePaymentRequestRecord(
|
|
1338
|
+
await this.requestJson("POST", `/v1/payment-requests/${id}/respond`, response),
|
|
1339
|
+
"payment-request respond response"
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
1342
|
+
// ── intents (E.3) ───────────────────────────────────────────────────────────
|
|
1343
|
+
intentsKey() {
|
|
1344
|
+
return this.scopedKey("intents");
|
|
1345
|
+
}
|
|
1346
|
+
async readLocalIntents() {
|
|
1347
|
+
const raw = await this.storage.get(this.intentsKey());
|
|
1348
|
+
if (!raw) return {};
|
|
1349
|
+
try {
|
|
1350
|
+
return JSON.parse(raw);
|
|
1351
|
+
} catch {
|
|
1352
|
+
return {};
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
async writeLocalIntents(intents) {
|
|
1356
|
+
await this.storage.set(this.intentsKey(), JSON.stringify(intents));
|
|
1357
|
+
}
|
|
1358
|
+
async setLocalIntentStatus(transferId, status) {
|
|
1359
|
+
const intents = await this.readLocalIntents();
|
|
1360
|
+
const intent = intents[transferId];
|
|
1361
|
+
if (!intent || intent.status === status) return;
|
|
1362
|
+
if (intent.status === "completed") return;
|
|
1363
|
+
intent.status = status;
|
|
1364
|
+
await this.writeLocalIntents(intents);
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Persist a (client-encrypted — S6) intent payload: LOCAL copy first, then
|
|
1368
|
+
* `PUT /v1/intents/{transferId}` and await the server ack (E.3 — the engine
|
|
1369
|
+
* MUST NOT be called before this resolves). A failed server PUT throws, but
|
|
1370
|
+
* the local copy stays as the restore backstop and is re-PUT by
|
|
1371
|
+
* {@link resyncOpenIntents}.
|
|
1372
|
+
*/
|
|
1373
|
+
async putIntent(transferId, payloadEnvelope) {
|
|
1374
|
+
const intents = await this.readLocalIntents();
|
|
1375
|
+
if (!intents[transferId]) {
|
|
1376
|
+
intents[transferId] = { payload: payloadEnvelope, status: "open", createdAt: this.now() };
|
|
1377
|
+
await this.writeLocalIntents(intents);
|
|
1378
|
+
}
|
|
1379
|
+
await this.requestJson("PUT", `/v1/intents/${transferId}`, { payload: payloadEnvelope });
|
|
1380
|
+
}
|
|
1381
|
+
/** `GET /v1/intents?status=` — server-side intent list. */
|
|
1382
|
+
async listIntents(status) {
|
|
1383
|
+
return parseIntents(await this.requestJson("GET", `/v1/intents?status=${status}`));
|
|
1384
|
+
}
|
|
1385
|
+
/** `POST /v1/intents/{id}/abort` — soft, recoverable (§16). */
|
|
1386
|
+
async abortIntent(transferId) {
|
|
1387
|
+
await this.requestJson("POST", `/v1/intents/${transferId}/abort`);
|
|
1388
|
+
await this.setLocalIntentStatus(transferId, "aborted");
|
|
1389
|
+
}
|
|
1390
|
+
/** `POST /v1/intents/{id}/complete` — the uniform client-side close (E.3). */
|
|
1391
|
+
async completeIntent(transferId) {
|
|
1392
|
+
await this.requestJson("POST", `/v1/intents/${transferId}/complete`);
|
|
1393
|
+
await this.setLocalIntentStatus(transferId, "completed");
|
|
1394
|
+
}
|
|
1395
|
+
/** The locally-known open intents (the E.3 restore backstop). */
|
|
1396
|
+
async listLocalOpenIntents() {
|
|
1397
|
+
const intents = await this.readLocalIntents();
|
|
1398
|
+
return Object.entries(intents).filter(([, i]) => i.status === "open").map(([transferId, i]) => ({ transferId, payload: i.payload, status: "open", createdAt: i.createdAt }));
|
|
1399
|
+
}
|
|
1400
|
+
/**
|
|
1401
|
+
* Re-PUT every locally-known open intent (idempotent — the server PUT is
|
|
1402
|
+
* write-once while open/completed). Called after a `syncEpoch` change
|
|
1403
|
+
* (server restore — §5.4): intents are the one server table not
|
|
1404
|
+
* re-derivable from blobs.
|
|
1405
|
+
*/
|
|
1406
|
+
async resyncOpenIntents() {
|
|
1407
|
+
for (const intent of await this.listLocalOpenIntents()) {
|
|
1408
|
+
await this.requestJson("PUT", `/v1/intents/${intent.transferId}`, { payload: intent.payload });
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
// ── syncEpoch (§5.4/§9) ─────────────────────────────────────────────────────
|
|
1412
|
+
syncEpochKey() {
|
|
1413
|
+
return this.scopedKey("syncEpoch");
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Track the server `syncEpoch` carried by cursor-bearing responses and
|
|
1417
|
+
* wakes. On a change (server restore), re-PUT locally-known open intents
|
|
1418
|
+
* before anything resumes (E.3). Storage-provider cursor invalidation is
|
|
1419
|
+
* the provider's own duty (S2) — it observes the same epoch values.
|
|
1420
|
+
*/
|
|
1421
|
+
async noteSyncEpoch(epoch) {
|
|
1422
|
+
const stored = await this.storage.get(this.syncEpochKey());
|
|
1423
|
+
if (stored === epoch.toString()) return;
|
|
1424
|
+
await this.storage.set(this.syncEpochKey(), epoch.toString());
|
|
1425
|
+
if (stored !== null) {
|
|
1426
|
+
await this.resyncOpenIntents();
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
// ── realtime (§9) ───────────────────────────────────────────────────────────
|
|
1430
|
+
/**
|
|
1431
|
+
* Open the wake channel: `POST /v1/ws-ticket` (JWT-authed, single-use,
|
|
1432
|
+
* short TTL) then `GET /v1/ws?ticket=…` — the JWT never appears in a URL.
|
|
1433
|
+
* Wakes are nudges only; correctness comes from the `?since=` cursors.
|
|
1434
|
+
*/
|
|
1435
|
+
async connectWakeSocket(onWake) {
|
|
1436
|
+
const ticket = parseWsTicket(await this.requestJson("POST", "/v1/ws-ticket"));
|
|
1437
|
+
const wsBase = this.baseUrl.replace(/^http/, "ws");
|
|
1438
|
+
const ws = this.wsFactory(`${wsBase}/v1/ws?ticket=${encodeURIComponent(ticket)}`);
|
|
1439
|
+
await new Promise((resolve, reject) => {
|
|
1440
|
+
ws.onopen = () => resolve();
|
|
1441
|
+
ws.onerror = (err) => reject(new WalletApiError("wake socket failed to connect", "NETWORK", void 0, err));
|
|
1442
|
+
ws.onclose = () => reject(new WalletApiError("wake socket closed before connecting", "NETWORK"));
|
|
1443
|
+
});
|
|
1444
|
+
ws.onerror = null;
|
|
1445
|
+
ws.onclose = null;
|
|
1446
|
+
ws.onmessage = (ev) => {
|
|
1447
|
+
const frame = parseWakeFrame(typeof ev.data === "string" ? ev.data : String(ev.data));
|
|
1448
|
+
if (!frame) return;
|
|
1449
|
+
void this.noteSyncEpoch(frame.syncEpoch);
|
|
1450
|
+
onWake(frame);
|
|
1451
|
+
};
|
|
1452
|
+
return { close: () => ws.close() };
|
|
1453
|
+
}
|
|
1454
|
+
};
|
|
1455
|
+
|
|
1456
|
+
// impl/shared/wallet-api/WalletApiTokenStorageProvider.ts
|
|
1457
|
+
function bytesToHex(bytes) {
|
|
1458
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1459
|
+
}
|
|
1460
|
+
function hexToBytes(hex) {
|
|
1461
|
+
const out = new Uint8Array(hex.length / 2);
|
|
1462
|
+
for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
1463
|
+
return out;
|
|
1464
|
+
}
|
|
1465
|
+
function newTransferId() {
|
|
1466
|
+
return globalThis.crypto.randomUUID();
|
|
1467
|
+
}
|
|
1468
|
+
function isV2BlobEntry(entry) {
|
|
1469
|
+
if (typeof entry !== "object" || entry === null || "genesis" in entry) return false;
|
|
1470
|
+
const sdkData = entry.sdkData;
|
|
1471
|
+
return typeof sdkData === "string" && sdkData.length >= 2 && sdkData.length % 2 === 0 && /^[0-9a-f]+$/i.test(sdkData);
|
|
1472
|
+
}
|
|
1473
|
+
var WalletApiTokenStorageProvider = class {
|
|
1474
|
+
id = "wallet-api-token-storage";
|
|
1475
|
+
name = "Wallet API Token Storage";
|
|
1476
|
+
type = "cloud";
|
|
1477
|
+
client;
|
|
1478
|
+
stateStore;
|
|
1479
|
+
verifyToken;
|
|
1480
|
+
status = "disconnected";
|
|
1481
|
+
identity = null;
|
|
1482
|
+
/** Local mirror of the inventory view — active rows and tombstones. */
|
|
1483
|
+
view = /* @__PURE__ */ new Map();
|
|
1484
|
+
/**
|
|
1485
|
+
* Empty-import protection (§5.1): no removal is ever pushed before this
|
|
1486
|
+
* flips on the first successful inventory load.
|
|
1487
|
+
*/
|
|
1488
|
+
hadSuccessfulLoad = false;
|
|
1489
|
+
constructor(config) {
|
|
1490
|
+
this.client = config.client;
|
|
1491
|
+
this.stateStore = config.stateStore;
|
|
1492
|
+
this.verifyToken = config.verifyToken;
|
|
1493
|
+
}
|
|
1494
|
+
// ── provider lifecycle ──────────────────────────────────────────────────────
|
|
1495
|
+
setIdentity(identity) {
|
|
1496
|
+
this.identity = identity;
|
|
1497
|
+
this.client.setIdentity({ privateKey: identity.privateKey, chainPubkey: identity.chainPubkey });
|
|
1498
|
+
this.view.clear();
|
|
1499
|
+
this.hadSuccessfulLoad = false;
|
|
1500
|
+
}
|
|
1501
|
+
async initialize() {
|
|
1502
|
+
this.status = "connected";
|
|
1503
|
+
return true;
|
|
1504
|
+
}
|
|
1505
|
+
async shutdown() {
|
|
1506
|
+
this.status = "disconnected";
|
|
1507
|
+
}
|
|
1508
|
+
async connect() {
|
|
1509
|
+
await this.initialize();
|
|
1510
|
+
}
|
|
1511
|
+
async disconnect() {
|
|
1512
|
+
this.status = "disconnected";
|
|
1513
|
+
}
|
|
1514
|
+
isConnected() {
|
|
1515
|
+
return this.status === "connected";
|
|
1516
|
+
}
|
|
1517
|
+
getStatus() {
|
|
1518
|
+
return this.status;
|
|
1519
|
+
}
|
|
1520
|
+
// ── persisted sync state (per network + identity) ───────────────────────────
|
|
1521
|
+
stateKey(kind) {
|
|
1522
|
+
if (!this.identity) {
|
|
1523
|
+
throw new WalletApiError("No identity set \u2014 call setIdentity() first", "CONFIG");
|
|
1524
|
+
}
|
|
1525
|
+
return `wallet-api-storage:${kind}:${this.client.network}:${this.identity.chainPubkey}`;
|
|
1526
|
+
}
|
|
1527
|
+
async readCursor() {
|
|
1528
|
+
const raw = await this.stateStore.get(this.stateKey("cursor"));
|
|
1529
|
+
return raw !== null && /^[0-9]+$/.test(raw) ? BigInt(raw) : null;
|
|
1530
|
+
}
|
|
1531
|
+
async readSyncEpoch() {
|
|
1532
|
+
const raw = await this.stateStore.get(this.stateKey("syncEpoch"));
|
|
1533
|
+
return raw !== null && /^[0-9]+$/.test(raw) ? BigInt(raw) : null;
|
|
1534
|
+
}
|
|
1535
|
+
async persistSyncState(cursor, syncEpoch) {
|
|
1536
|
+
await this.stateStore.set(this.stateKey("cursor"), cursor.toString());
|
|
1537
|
+
await this.stateStore.set(this.stateKey("syncEpoch"), syncEpoch.toString());
|
|
1538
|
+
}
|
|
1539
|
+
/** TokenIds this provider itself spent — `recoverRemoved()` skips these. */
|
|
1540
|
+
async readKnownSpends() {
|
|
1541
|
+
const raw = await this.stateStore.get(this.stateKey("knownSpends"));
|
|
1542
|
+
if (!raw) return /* @__PURE__ */ new Set();
|
|
1543
|
+
try {
|
|
1544
|
+
return new Set(JSON.parse(raw));
|
|
1545
|
+
} catch {
|
|
1546
|
+
return /* @__PURE__ */ new Set();
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
async addKnownSpends(tokenIds) {
|
|
1550
|
+
if (tokenIds.length === 0) return;
|
|
1551
|
+
const known = await this.readKnownSpends();
|
|
1552
|
+
for (const id of tokenIds) known.add(id);
|
|
1553
|
+
await this.stateStore.set(this.stateKey("knownSpends"), JSON.stringify([...known]));
|
|
1554
|
+
}
|
|
1555
|
+
// ── inventory sync (§5.1) ───────────────────────────────────────────────────
|
|
1556
|
+
applyItems(items) {
|
|
1557
|
+
for (const item of items) {
|
|
1558
|
+
this.view.set(item.tokenId, item);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
/** Full pull + the §5.1 closing delta; replaces the whole local view. */
|
|
1562
|
+
async fullPull() {
|
|
1563
|
+
this.view.clear();
|
|
1564
|
+
let page = await this.client.listInventory(void 0);
|
|
1565
|
+
const firstPageCursor = page.cursor;
|
|
1566
|
+
this.applyItems(page.items);
|
|
1567
|
+
while (page.more) {
|
|
1568
|
+
page = await this.client.listInventory(page.cursor);
|
|
1569
|
+
this.applyItems(page.items);
|
|
1570
|
+
}
|
|
1571
|
+
let delta = await this.client.listInventory(firstPageCursor);
|
|
1572
|
+
this.applyItems(delta.items);
|
|
1573
|
+
while (delta.more) {
|
|
1574
|
+
delta = await this.client.listInventory(delta.cursor);
|
|
1575
|
+
this.applyItems(delta.items);
|
|
1576
|
+
}
|
|
1577
|
+
await this.persistSyncState(delta.cursor, delta.syncEpoch);
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* Delta loop from the persisted cursor. Returns `true` when the server's
|
|
1581
|
+
* `syncEpoch` no longer matches the persisted one — the cursors are invalid
|
|
1582
|
+
* (server restore, §5.4) and the caller must resync from scratch.
|
|
1583
|
+
*/
|
|
1584
|
+
async deltaLoop(since) {
|
|
1585
|
+
const knownEpoch = await this.readSyncEpoch();
|
|
1586
|
+
let page = await this.client.listInventory(since);
|
|
1587
|
+
if (knownEpoch !== null && page.syncEpoch !== knownEpoch) return true;
|
|
1588
|
+
this.applyItems(page.items);
|
|
1589
|
+
while (page.more) {
|
|
1590
|
+
page = await this.client.listInventory(page.cursor);
|
|
1591
|
+
if (knownEpoch !== null && page.syncEpoch !== knownEpoch) return true;
|
|
1592
|
+
this.applyItems(page.items);
|
|
1593
|
+
}
|
|
1594
|
+
await this.persistSyncState(page.cursor, page.syncEpoch);
|
|
1595
|
+
return false;
|
|
1596
|
+
}
|
|
1597
|
+
/** §5.4: discard cursors → full pull → re-PUT locally-known open intents. */
|
|
1598
|
+
async handleSyncEpochChange() {
|
|
1599
|
+
await this.stateStore.remove(this.stateKey("cursor"));
|
|
1600
|
+
await this.stateStore.remove(this.stateKey("syncEpoch"));
|
|
1601
|
+
await this.fullPull();
|
|
1602
|
+
await this.client.resyncOpenIntents();
|
|
1603
|
+
}
|
|
1604
|
+
/** Converge the local view with the server (delta when possible). */
|
|
1605
|
+
async syncInventory() {
|
|
1606
|
+
const cursor = await this.readCursor();
|
|
1607
|
+
if (cursor === null) {
|
|
1608
|
+
await this.fullPull();
|
|
1609
|
+
} else if (await this.deltaLoop(cursor)) {
|
|
1610
|
+
await this.handleSyncEpochChange();
|
|
1611
|
+
}
|
|
1612
|
+
this.hadSuccessfulLoad = true;
|
|
1613
|
+
}
|
|
1614
|
+
// ── lazy inventory port (S2) ────────────────────────────────────────────────
|
|
1615
|
+
/**
|
|
1616
|
+
* Without `since`: converge with the server, then return the full active
|
|
1617
|
+
* view (`more:false`) — a fresh device renders balances with zero blob
|
|
1618
|
+
* downloads. With `since`: a true server delta page (tombstones included),
|
|
1619
|
+
* also applied to the local view.
|
|
1620
|
+
*/
|
|
1621
|
+
async listInventory(since) {
|
|
1622
|
+
if (since === void 0) {
|
|
1623
|
+
await this.syncInventory();
|
|
1624
|
+
return this.snapshotView();
|
|
1625
|
+
}
|
|
1626
|
+
const knownEpoch = await this.readSyncEpoch();
|
|
1627
|
+
const page = await this.client.listInventory(since);
|
|
1628
|
+
if (knownEpoch !== null && page.syncEpoch !== knownEpoch) {
|
|
1629
|
+
await this.handleSyncEpochChange();
|
|
1630
|
+
this.hadSuccessfulLoad = true;
|
|
1631
|
+
return this.snapshotView();
|
|
1632
|
+
}
|
|
1633
|
+
this.applyItems(page.items);
|
|
1634
|
+
if (!page.more) {
|
|
1635
|
+
const stored = await this.readCursor();
|
|
1636
|
+
if (stored === null || page.cursor > stored) {
|
|
1637
|
+
await this.persistSyncState(page.cursor, page.syncEpoch);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
return page;
|
|
1641
|
+
}
|
|
1642
|
+
async snapshotView() {
|
|
1643
|
+
const items = [...this.view.values()].filter((i) => i.status === "active");
|
|
1644
|
+
const cursor = await this.readCursor() ?? 0n;
|
|
1645
|
+
const syncEpoch = await this.readSyncEpoch() ?? 0n;
|
|
1646
|
+
return { cursor, syncEpoch, more: false, items };
|
|
1647
|
+
}
|
|
1648
|
+
/** Fetch + decode one blob on demand via a short-lived signed GET (§5.1). */
|
|
1649
|
+
async getToken(tokenId) {
|
|
1650
|
+
const urls = await this.client.getBlobUrls([tokenId]);
|
|
1651
|
+
const entry = urls.find((u) => u.tokenId === tokenId);
|
|
1652
|
+
if (!entry) {
|
|
1653
|
+
throw new WalletApiError(`No blob URL for token ${tokenId} (not owned here)`, "NOT_FOUND");
|
|
1654
|
+
}
|
|
1655
|
+
const bytes = await this.client.fetchBlob(entry.getUrl);
|
|
1656
|
+
const blob = this.wrapWireBlob(tokenId, bytes);
|
|
1657
|
+
if (blob.tokenId !== tokenId) {
|
|
1658
|
+
throw new WalletApiError(
|
|
1659
|
+
`Blob tokenId mismatch: requested ${tokenId}, blob carries ${blob.tokenId}`,
|
|
1660
|
+
"PROTOCOL"
|
|
1661
|
+
);
|
|
1662
|
+
}
|
|
1663
|
+
return blob;
|
|
1664
|
+
}
|
|
1665
|
+
/**
|
|
1666
|
+
* The §16 wire serves RAW token bytes (§5.2/§8.2 — the sphere 39051
|
|
1667
|
+
* envelope never crosses the API): re-wrap them for the engine-facing
|
|
1668
|
+
* {@link TokenBlob} surface. Envelope bytes (older rows, fake-world seeds)
|
|
1669
|
+
* still decode — their embedded tokenId is then checked by the caller. The
|
|
1670
|
+
* `network` of a raw wrap is not recoverable without the engine; consumers
|
|
1671
|
+
* derive it from the decoded token (`engine.decodeToken` re-wraps), so it
|
|
1672
|
+
* is never read from this value.
|
|
1673
|
+
*/
|
|
1674
|
+
wrapWireBlob(tokenId, bytes) {
|
|
1675
|
+
try {
|
|
1676
|
+
return decodeTokenBlob(bytes);
|
|
1677
|
+
} catch {
|
|
1678
|
+
return { v: TOKEN_BLOB_VERSION, network: 0, tokenId, token: bytes };
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
/**
|
|
1682
|
+
* Record a spend (§5.3) — idempotent by `transferId` server-side. MUST be
|
|
1683
|
+
* called after the mailbox deposit of the same `transferId` (the backend
|
|
1684
|
+
* evidence-checks removals against it). Refreshes the local view from the
|
|
1685
|
+
* resulting delta.
|
|
1686
|
+
*/
|
|
1687
|
+
async applyDelta(transferId, spent, added, opts) {
|
|
1688
|
+
await this.client.applyInventoryDelta({
|
|
1689
|
+
transferId,
|
|
1690
|
+
spent,
|
|
1691
|
+
added,
|
|
1692
|
+
...opts?.externalDelivery !== void 0 ? { externalDelivery: opts.externalDelivery } : {}
|
|
1693
|
+
});
|
|
1694
|
+
await this.addKnownSpends(spent);
|
|
1695
|
+
await this.syncInventory();
|
|
1696
|
+
}
|
|
1697
|
+
// ── recovery (§5.3, sdk-changes S2) ─────────────────────────────────────────
|
|
1698
|
+
/**
|
|
1699
|
+
* Re-add tombstoned tokens this client cannot match to a known spend (a
|
|
1700
|
+
* wiped view — stolen JWT or a buggy client). Blobs are retained server-side
|
|
1701
|
+
* and blob-urls works for own tombstoned rows; each candidate is re-fetched,
|
|
1702
|
+
* locally verified, and re-added (reactivation). A `409` is the server's
|
|
1703
|
+
* verdict that the tombstone is an evidenced spend — kept as spent.
|
|
1704
|
+
*/
|
|
1705
|
+
async recoverRemoved() {
|
|
1706
|
+
await this.syncInventory();
|
|
1707
|
+
const knownSpends = await this.readKnownSpends();
|
|
1708
|
+
const result = { recovered: [], spent: [], skipped: [] };
|
|
1709
|
+
const candidates = [...this.view.values()].filter(
|
|
1710
|
+
(i) => i.status === "removed" && !knownSpends.has(i.tokenId)
|
|
1711
|
+
);
|
|
1712
|
+
for (const item of candidates) {
|
|
1713
|
+
const blobBytes = await this.fetchRemovedBlob(item.tokenId);
|
|
1714
|
+
if (blobBytes === null || !await this.locallyValid(item.tokenId, blobBytes)) {
|
|
1715
|
+
result.skipped.push(item.tokenId);
|
|
1716
|
+
continue;
|
|
1717
|
+
}
|
|
1718
|
+
const key = `${this.client.network}/t/${bytesToHex(sha256(blobBytes))}`;
|
|
1719
|
+
try {
|
|
1720
|
+
await this.client.applyInventoryDelta({
|
|
1721
|
+
transferId: newTransferId(),
|
|
1722
|
+
spent: [],
|
|
1723
|
+
added: [{ tokenId: item.tokenId, key }]
|
|
1724
|
+
});
|
|
1725
|
+
result.recovered.push(item.tokenId);
|
|
1726
|
+
} catch (err) {
|
|
1727
|
+
if (err instanceof WalletApiError && err.code === "CONFLICT") {
|
|
1728
|
+
result.spent.push(item.tokenId);
|
|
1729
|
+
await this.addKnownSpends([item.tokenId]);
|
|
1730
|
+
} else {
|
|
1731
|
+
throw err;
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
await this.syncInventory();
|
|
1736
|
+
return result;
|
|
1737
|
+
}
|
|
1738
|
+
async fetchRemovedBlob(tokenId) {
|
|
1739
|
+
const urls = await this.client.getBlobUrls([tokenId]);
|
|
1740
|
+
const entry = urls.find((u) => u.tokenId === tokenId);
|
|
1741
|
+
if (!entry) return null;
|
|
1742
|
+
try {
|
|
1743
|
+
return await this.client.fetchBlob(entry.getUrl);
|
|
1744
|
+
} catch {
|
|
1745
|
+
return null;
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
async locallyValid(tokenId, bytes) {
|
|
1749
|
+
const blob = this.wrapWireBlob(tokenId, bytes);
|
|
1750
|
+
if (blob.tokenId !== tokenId) return false;
|
|
1751
|
+
if (this.verifyToken) return this.verifyToken(blob);
|
|
1752
|
+
return true;
|
|
1753
|
+
}
|
|
1754
|
+
// ── whole-blob surface (thin: the hot path is the lazy port above) ──────────
|
|
1755
|
+
/**
|
|
1756
|
+
* Thin view: converges with the server and reports tombstones, but carries
|
|
1757
|
+
* NO token entries — blobs are fetched on demand via `getToken()`
|
|
1758
|
+
* (sdk-changes S2: `load()` no longer eagerly pulls every blob).
|
|
1759
|
+
*/
|
|
1760
|
+
async load() {
|
|
1761
|
+
try {
|
|
1762
|
+
await this.syncInventory();
|
|
1763
|
+
} catch (error) {
|
|
1764
|
+
return {
|
|
1765
|
+
success: false,
|
|
1766
|
+
error: error instanceof Error ? error.message : "inventory sync failed",
|
|
1767
|
+
source: "remote",
|
|
1768
|
+
timestamp: Date.now()
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
const data = {
|
|
1772
|
+
_meta: {
|
|
1773
|
+
version: 1,
|
|
1774
|
+
address: this.identity?.l1Address ?? "",
|
|
1775
|
+
formatVersion: "2.0",
|
|
1776
|
+
updatedAt: Date.now()
|
|
1777
|
+
},
|
|
1778
|
+
_tombstones: [...this.view.values()].filter((i) => i.status === "removed").map((i) => ({ tokenId: i.tokenId, stateHash: "", timestamp: Date.now() }))
|
|
1779
|
+
};
|
|
1780
|
+
return { success: true, data, source: "remote", timestamp: Date.now() };
|
|
1781
|
+
}
|
|
1782
|
+
/**
|
|
1783
|
+
* Write-behind push of a whole-blob snapshot:
|
|
1784
|
+
* - unknown tokens are uploaded (content-addressed, 412 = already present)
|
|
1785
|
+
* and added via one idempotent apply;
|
|
1786
|
+
* - removals are pushed ONLY for confirmed spends (`_tombstones` entries)
|
|
1787
|
+
* and ONLY after a successful inventory load (empty-import protection,
|
|
1788
|
+
* §5.1) — a merely-absent token is never removed.
|
|
1789
|
+
* A failed sync fails the save: pushing against an unknown server view
|
|
1790
|
+
* could only do harm.
|
|
1791
|
+
*/
|
|
1792
|
+
async save(data) {
|
|
1793
|
+
try {
|
|
1794
|
+
await this.syncInventory();
|
|
1795
|
+
const tombstoneIds = new Set((data._tombstones ?? []).map((t) => t.tokenId));
|
|
1796
|
+
await this.pushAdditions(data, tombstoneIds);
|
|
1797
|
+
await this.pushRemovals(tombstoneIds);
|
|
1798
|
+
await this.syncInventory();
|
|
1799
|
+
return { success: true, timestamp: Date.now() };
|
|
1800
|
+
} catch (error) {
|
|
1801
|
+
return {
|
|
1802
|
+
success: false,
|
|
1803
|
+
error: error instanceof Error ? error.message : "wallet-api save failed",
|
|
1804
|
+
timestamp: Date.now()
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
async pushAdditions(data, excluded) {
|
|
1809
|
+
const toAdd = [];
|
|
1810
|
+
for (const [key, value] of Object.entries(data)) {
|
|
1811
|
+
if (!/^_[0-9a-f]{4,}$/i.test(key) || !isV2BlobEntry(value)) continue;
|
|
1812
|
+
const tokenId = key.slice(1);
|
|
1813
|
+
if (excluded.has(tokenId) || this.view.has(tokenId)) continue;
|
|
1814
|
+
toAdd.push({ tokenId, bytes: unwrapTokenBlobBytes(hexToBytes(value.sdkData)) });
|
|
1815
|
+
}
|
|
1816
|
+
if (toAdd.length === 0) return;
|
|
1817
|
+
const urls = await this.client.getUploadUrls(
|
|
1818
|
+
toAdd.map((t) => ({ sha256: bytesToHex(sha256(t.bytes)), size: t.bytes.length }))
|
|
1819
|
+
);
|
|
1820
|
+
const added = [];
|
|
1821
|
+
for (const token of toAdd) {
|
|
1822
|
+
const sha = bytesToHex(sha256(token.bytes));
|
|
1823
|
+
const url = urls.find((u) => u.sha256 === sha);
|
|
1824
|
+
if (!url) {
|
|
1825
|
+
throw new WalletApiError(`upload-urls response missing sha256 ${sha}`, "PROTOCOL");
|
|
1826
|
+
}
|
|
1827
|
+
await this.client.uploadBlob(url.putUrl, token.bytes);
|
|
1828
|
+
added.push({ tokenId: token.tokenId, key: url.key });
|
|
1829
|
+
}
|
|
1830
|
+
await this.client.applyInventoryDelta({ transferId: newTransferId(), spent: [], added });
|
|
1831
|
+
}
|
|
1832
|
+
async pushRemovals(tombstoneIds) {
|
|
1833
|
+
if (!this.hadSuccessfulLoad || tombstoneIds.size === 0) return;
|
|
1834
|
+
const spent = [...tombstoneIds].filter((id) => this.view.get(id)?.status === "active");
|
|
1835
|
+
if (spent.length === 0) return;
|
|
1836
|
+
await this.client.applyInventoryDelta({ transferId: newTransferId(), spent, added: [] });
|
|
1837
|
+
await this.addKnownSpends(spent);
|
|
1838
|
+
}
|
|
1839
|
+
async sync(localData) {
|
|
1840
|
+
const result = await this.save(localData);
|
|
1841
|
+
return {
|
|
1842
|
+
success: result.success,
|
|
1843
|
+
merged: localData,
|
|
1844
|
+
added: 0,
|
|
1845
|
+
removed: 0,
|
|
1846
|
+
conflicts: 0,
|
|
1847
|
+
error: result.error
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1850
|
+
};
|
|
1851
|
+
function createWalletApiTokenStorageProvider(config) {
|
|
1852
|
+
return new WalletApiTokenStorageProvider(config);
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
// transport/delivery-provider.ts
|
|
1856
|
+
function hexToBytes2(hex) {
|
|
1857
|
+
const out = new Uint8Array(hex.length / 2);
|
|
1858
|
+
for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
1859
|
+
return out;
|
|
1860
|
+
}
|
|
1861
|
+
function bytesToHex2(bytes) {
|
|
1862
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1863
|
+
}
|
|
1864
|
+
function computeDeliveryId(tokenIdHex, stateHashHex) {
|
|
1865
|
+
const tokenId = hexToBytes2(tokenIdHex);
|
|
1866
|
+
const stateHash = hexToBytes2(stateHashHex);
|
|
1867
|
+
const joined = new Uint8Array(tokenId.length + stateHash.length);
|
|
1868
|
+
joined.set(tokenId, 0);
|
|
1869
|
+
joined.set(stateHash, tokenId.length);
|
|
1870
|
+
return bytesToHex2(sha256(joined));
|
|
1871
|
+
}
|
|
1872
|
+
function composeDeliveryKeys(keys) {
|
|
1873
|
+
return { ...keys, deliveryId: computeDeliveryId(keys.tokenId, keys.stateHash) };
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
// node_modules/@noble/hashes/hmac.js
|
|
1877
|
+
var _HMAC = class {
|
|
1878
|
+
oHash;
|
|
1879
|
+
iHash;
|
|
1880
|
+
blockLen;
|
|
1881
|
+
outputLen;
|
|
1882
|
+
canXOF = false;
|
|
1883
|
+
finished = false;
|
|
1884
|
+
destroyed = false;
|
|
1885
|
+
constructor(hash, key) {
|
|
1886
|
+
ahash(hash);
|
|
1887
|
+
abytes(key, void 0, "key");
|
|
1888
|
+
this.iHash = hash.create();
|
|
1889
|
+
if (typeof this.iHash.update !== "function")
|
|
1890
|
+
throw new Error("Expected instance of class which extends utils.Hash");
|
|
1891
|
+
this.blockLen = this.iHash.blockLen;
|
|
1892
|
+
this.outputLen = this.iHash.outputLen;
|
|
1893
|
+
const blockLen = this.blockLen;
|
|
1894
|
+
const pad = new Uint8Array(blockLen);
|
|
1895
|
+
pad.set(key.length > blockLen ? hash.create().update(key).digest() : key);
|
|
1896
|
+
for (let i = 0; i < pad.length; i++)
|
|
1897
|
+
pad[i] ^= 54;
|
|
1898
|
+
this.iHash.update(pad);
|
|
1899
|
+
this.oHash = hash.create();
|
|
1900
|
+
for (let i = 0; i < pad.length; i++)
|
|
1901
|
+
pad[i] ^= 54 ^ 92;
|
|
1902
|
+
this.oHash.update(pad);
|
|
1903
|
+
clean(pad);
|
|
1904
|
+
}
|
|
1905
|
+
update(buf) {
|
|
1906
|
+
aexists(this);
|
|
1907
|
+
this.iHash.update(buf);
|
|
1908
|
+
return this;
|
|
1909
|
+
}
|
|
1910
|
+
digestInto(out) {
|
|
1911
|
+
aexists(this);
|
|
1912
|
+
aoutput(out, this);
|
|
1913
|
+
this.finished = true;
|
|
1914
|
+
const buf = out.subarray(0, this.outputLen);
|
|
1915
|
+
this.iHash.digestInto(buf);
|
|
1916
|
+
this.oHash.update(buf);
|
|
1917
|
+
this.oHash.digestInto(buf);
|
|
1918
|
+
this.destroy();
|
|
1919
|
+
}
|
|
1920
|
+
digest() {
|
|
1921
|
+
const out = new Uint8Array(this.oHash.outputLen);
|
|
1922
|
+
this.digestInto(out);
|
|
1923
|
+
return out;
|
|
1924
|
+
}
|
|
1925
|
+
_cloneInto(to) {
|
|
1926
|
+
to ||= Object.create(Object.getPrototypeOf(this), {});
|
|
1927
|
+
const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this;
|
|
1928
|
+
to = to;
|
|
1929
|
+
to.finished = finished;
|
|
1930
|
+
to.destroyed = destroyed;
|
|
1931
|
+
to.blockLen = blockLen;
|
|
1932
|
+
to.outputLen = outputLen;
|
|
1933
|
+
to.oHash = oHash._cloneInto(to.oHash);
|
|
1934
|
+
to.iHash = iHash._cloneInto(to.iHash);
|
|
1935
|
+
return to;
|
|
1936
|
+
}
|
|
1937
|
+
clone() {
|
|
1938
|
+
return this._cloneInto();
|
|
1939
|
+
}
|
|
1940
|
+
destroy() {
|
|
1941
|
+
this.destroyed = true;
|
|
1942
|
+
this.oHash.destroy();
|
|
1943
|
+
this.iHash.destroy();
|
|
1944
|
+
}
|
|
1945
|
+
};
|
|
1946
|
+
var hmac = /* @__PURE__ */ (() => {
|
|
1947
|
+
const hmac_ = ((hash, key, message) => new _HMAC(hash, key).update(message).digest());
|
|
1948
|
+
hmac_.create = (hash, key) => new _HMAC(hash, key);
|
|
1949
|
+
return hmac_;
|
|
1950
|
+
})();
|
|
1951
|
+
|
|
1952
|
+
// node_modules/@noble/hashes/hkdf.js
|
|
1953
|
+
function extract(hash, ikm, salt) {
|
|
1954
|
+
ahash(hash);
|
|
1955
|
+
if (salt === void 0)
|
|
1956
|
+
salt = new Uint8Array(hash.outputLen);
|
|
1957
|
+
return hmac(hash, salt, ikm);
|
|
1958
|
+
}
|
|
1959
|
+
var HKDF_COUNTER = /* @__PURE__ */ Uint8Array.of(0);
|
|
1960
|
+
var EMPTY_BUFFER = /* @__PURE__ */ Uint8Array.of();
|
|
1961
|
+
function expand(hash, prk, info, length = 32) {
|
|
1962
|
+
ahash(hash);
|
|
1963
|
+
anumber(length, "length");
|
|
1964
|
+
abytes(prk, void 0, "prk");
|
|
1965
|
+
const olen = hash.outputLen;
|
|
1966
|
+
if (prk.length < olen)
|
|
1967
|
+
throw new Error('"prk" must be at least HashLen octets');
|
|
1968
|
+
if (length > 255 * olen)
|
|
1969
|
+
throw new Error("Length must be <= 255*HashLen");
|
|
1970
|
+
const blocks = Math.ceil(length / olen);
|
|
1971
|
+
if (info === void 0)
|
|
1972
|
+
info = EMPTY_BUFFER;
|
|
1973
|
+
else
|
|
1974
|
+
abytes(info, void 0, "info");
|
|
1975
|
+
const okm = new Uint8Array(blocks * olen);
|
|
1976
|
+
const HMAC = hmac.create(hash, prk);
|
|
1977
|
+
const HMACTmp = HMAC._cloneInto();
|
|
1978
|
+
const T = new Uint8Array(HMAC.outputLen);
|
|
1979
|
+
for (let counter = 0; counter < blocks; counter++) {
|
|
1980
|
+
HKDF_COUNTER[0] = counter + 1;
|
|
1981
|
+
HMACTmp.update(counter === 0 ? EMPTY_BUFFER : T).update(info).update(HKDF_COUNTER).digestInto(T);
|
|
1982
|
+
okm.set(T, olen * counter);
|
|
1983
|
+
HMAC._cloneInto(HMACTmp);
|
|
1984
|
+
}
|
|
1985
|
+
HMAC.destroy();
|
|
1986
|
+
HMACTmp.destroy();
|
|
1987
|
+
clean(T, HKDF_COUNTER);
|
|
1988
|
+
return okm.slice(0, length);
|
|
1989
|
+
}
|
|
1990
|
+
var hkdf = (hash, ikm, salt, info, length) => expand(hash, extract(hash, ikm, salt), info, length);
|
|
1991
|
+
|
|
1992
|
+
// node_modules/@noble/ciphers/utils.js
|
|
1993
|
+
function isBytes2(a) {
|
|
1994
|
+
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in a && a.BYTES_PER_ELEMENT === 1;
|
|
1995
|
+
}
|
|
1996
|
+
function abool(b) {
|
|
1997
|
+
if (typeof b !== "boolean")
|
|
1998
|
+
throw new TypeError(`boolean expected, not ${b}`);
|
|
1999
|
+
}
|
|
2000
|
+
function anumber2(n) {
|
|
2001
|
+
if (typeof n !== "number")
|
|
2002
|
+
throw new TypeError("number expected, got " + typeof n);
|
|
2003
|
+
if (!Number.isSafeInteger(n) || n < 0)
|
|
2004
|
+
throw new RangeError("positive integer expected, got " + n);
|
|
2005
|
+
}
|
|
2006
|
+
function abytes2(value, length, title = "") {
|
|
2007
|
+
const bytes = isBytes2(value);
|
|
2008
|
+
const len = value?.length;
|
|
2009
|
+
const needsLen = length !== void 0;
|
|
2010
|
+
if (!bytes || needsLen && len !== length) {
|
|
2011
|
+
const prefix = title && `"${title}" `;
|
|
2012
|
+
const ofLen = needsLen ? ` of length ${length}` : "";
|
|
2013
|
+
const got = bytes ? `length=${len}` : `type=${typeof value}`;
|
|
2014
|
+
const message = prefix + "expected Uint8Array" + ofLen + ", got " + got;
|
|
2015
|
+
if (!bytes)
|
|
2016
|
+
throw new TypeError(message);
|
|
2017
|
+
throw new RangeError(message);
|
|
2018
|
+
}
|
|
2019
|
+
return value;
|
|
2020
|
+
}
|
|
2021
|
+
function aexists2(instance, checkFinished = true) {
|
|
2022
|
+
if (instance.destroyed)
|
|
2023
|
+
throw new Error("Hash instance has been destroyed");
|
|
2024
|
+
if (checkFinished && instance.finished)
|
|
2025
|
+
throw new Error("Hash#digest() has already been called");
|
|
2026
|
+
}
|
|
2027
|
+
function aoutput2(out, instance, onlyAligned = false) {
|
|
2028
|
+
abytes2(out, void 0, "output");
|
|
2029
|
+
const min = instance.outputLen;
|
|
2030
|
+
if (out.length < min) {
|
|
2031
|
+
throw new RangeError("digestInto() expects output buffer of length at least " + min);
|
|
2032
|
+
}
|
|
2033
|
+
if (onlyAligned && !isAligned32(out))
|
|
2034
|
+
throw new Error("invalid output, must be aligned");
|
|
2035
|
+
}
|
|
2036
|
+
function u32(arr) {
|
|
2037
|
+
return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
|
2038
|
+
}
|
|
2039
|
+
function clean2(...arrays) {
|
|
2040
|
+
for (let i = 0; i < arrays.length; i++) {
|
|
2041
|
+
arrays[i].fill(0);
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
function createView2(arr) {
|
|
2045
|
+
return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
2046
|
+
}
|
|
2047
|
+
var isLE = /* @__PURE__ */ (() => new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68)();
|
|
2048
|
+
var byteSwap = (word) => word << 24 & 4278190080 | word << 8 & 16711680 | word >>> 8 & 65280 | word >>> 24 & 255;
|
|
2049
|
+
var swap8IfBE = isLE ? (n) => n : (n) => byteSwap(n) >>> 0;
|
|
2050
|
+
var byteSwap32 = (arr) => {
|
|
2051
|
+
for (let i = 0; i < arr.length; i++)
|
|
2052
|
+
arr[i] = byteSwap(arr[i]);
|
|
2053
|
+
return arr;
|
|
2054
|
+
};
|
|
2055
|
+
var swap32IfBE = isLE ? (u) => u : byteSwap32;
|
|
2056
|
+
function checkOpts(defaults, opts) {
|
|
2057
|
+
if (opts == null || typeof opts !== "object")
|
|
2058
|
+
throw new Error("options must be defined");
|
|
2059
|
+
const merged = Object.assign(defaults, opts);
|
|
2060
|
+
return merged;
|
|
2061
|
+
}
|
|
2062
|
+
function equalBytes(a, b) {
|
|
2063
|
+
if (a.length !== b.length)
|
|
2064
|
+
return false;
|
|
2065
|
+
let diff = 0;
|
|
2066
|
+
for (let i = 0; i < a.length; i++)
|
|
2067
|
+
diff |= a[i] ^ b[i];
|
|
2068
|
+
return diff === 0;
|
|
2069
|
+
}
|
|
2070
|
+
function wrapMacConstructor(keyLen, macCons, fromMsg) {
|
|
2071
|
+
const mac = macCons;
|
|
2072
|
+
const getArgs = fromMsg || (() => []);
|
|
2073
|
+
const macC = (msg, key) => mac(key, ...getArgs(msg)).update(msg).digest();
|
|
2074
|
+
const tmp = mac(new Uint8Array(keyLen), ...getArgs(new Uint8Array(0)));
|
|
2075
|
+
macC.outputLen = tmp.outputLen;
|
|
2076
|
+
macC.blockLen = tmp.blockLen;
|
|
2077
|
+
macC.create = (key, ...args) => mac(key, ...args);
|
|
2078
|
+
return macC;
|
|
2079
|
+
}
|
|
2080
|
+
var wrapCipher = /* @__NO_SIDE_EFFECTS__ */ (params, constructor) => {
|
|
2081
|
+
function wrappedCipher(key, ...args) {
|
|
2082
|
+
abytes2(key, void 0, "key");
|
|
2083
|
+
if (params.nonceLength !== void 0) {
|
|
2084
|
+
const nonce = args[0];
|
|
2085
|
+
abytes2(nonce, params.varSizeNonce ? void 0 : params.nonceLength, "nonce");
|
|
2086
|
+
}
|
|
2087
|
+
const tagl = params.tagLength;
|
|
2088
|
+
if (tagl && args[1] !== void 0)
|
|
2089
|
+
abytes2(args[1], void 0, "AAD");
|
|
2090
|
+
const cipher = constructor(key, ...args);
|
|
2091
|
+
const checkOutput = (fnLength, output) => {
|
|
2092
|
+
if (output !== void 0) {
|
|
2093
|
+
if (fnLength !== 2)
|
|
2094
|
+
throw new Error("cipher output not supported");
|
|
2095
|
+
abytes2(output, void 0, "output");
|
|
2096
|
+
}
|
|
2097
|
+
};
|
|
2098
|
+
let called = false;
|
|
2099
|
+
const wrCipher = {
|
|
2100
|
+
encrypt(data, output) {
|
|
2101
|
+
if (called)
|
|
2102
|
+
throw new Error("cannot encrypt() twice with same key + nonce");
|
|
2103
|
+
called = true;
|
|
2104
|
+
abytes2(data);
|
|
2105
|
+
checkOutput(cipher.encrypt.length, output);
|
|
2106
|
+
return cipher.encrypt(data, output);
|
|
2107
|
+
},
|
|
2108
|
+
decrypt(data, output) {
|
|
2109
|
+
abytes2(data);
|
|
2110
|
+
if (tagl && data.length < tagl)
|
|
2111
|
+
throw new Error('"ciphertext" expected length bigger than tagLength=' + tagl);
|
|
2112
|
+
checkOutput(cipher.decrypt.length, output);
|
|
2113
|
+
return cipher.decrypt(data, output);
|
|
2114
|
+
}
|
|
2115
|
+
};
|
|
2116
|
+
return wrCipher;
|
|
2117
|
+
}
|
|
2118
|
+
Object.assign(wrappedCipher, params);
|
|
2119
|
+
return wrappedCipher;
|
|
2120
|
+
};
|
|
2121
|
+
function getOutput(expectedLength, out, onlyAligned = true) {
|
|
2122
|
+
if (out === void 0)
|
|
2123
|
+
return new Uint8Array(expectedLength);
|
|
2124
|
+
abytes2(out, void 0, "output");
|
|
2125
|
+
if (out.length !== expectedLength)
|
|
2126
|
+
throw new Error('"output" expected Uint8Array of length ' + expectedLength + ", got: " + out.length);
|
|
2127
|
+
if (onlyAligned && !isAligned32(out))
|
|
2128
|
+
throw new Error("invalid output, must be aligned");
|
|
2129
|
+
return out;
|
|
2130
|
+
}
|
|
2131
|
+
function u64Lengths(dataLength, aadLength, isLE2) {
|
|
2132
|
+
anumber2(dataLength);
|
|
2133
|
+
anumber2(aadLength);
|
|
2134
|
+
abool(isLE2);
|
|
2135
|
+
const num = new Uint8Array(16);
|
|
2136
|
+
const view = createView2(num);
|
|
2137
|
+
view.setBigUint64(0, BigInt(aadLength), isLE2);
|
|
2138
|
+
view.setBigUint64(8, BigInt(dataLength), isLE2);
|
|
2139
|
+
return num;
|
|
2140
|
+
}
|
|
2141
|
+
function isAligned32(bytes) {
|
|
2142
|
+
return bytes.byteOffset % 4 === 0;
|
|
2143
|
+
}
|
|
2144
|
+
function copyBytes(bytes) {
|
|
2145
|
+
return Uint8Array.from(abytes2(bytes));
|
|
2146
|
+
}
|
|
2147
|
+
function randomBytes(bytesLength = 32) {
|
|
2148
|
+
anumber2(bytesLength);
|
|
2149
|
+
const cr = typeof globalThis === "object" ? globalThis.crypto : null;
|
|
2150
|
+
if (typeof cr?.getRandomValues !== "function")
|
|
2151
|
+
throw new Error("crypto.getRandomValues must be defined");
|
|
2152
|
+
return cr.getRandomValues(new Uint8Array(bytesLength));
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
// node_modules/@noble/ciphers/_arx.js
|
|
2156
|
+
var encodeStr = (str) => Uint8Array.from(str.split(""), (c) => c.charCodeAt(0));
|
|
2157
|
+
var sigma16_32 = /* @__PURE__ */ (() => swap32IfBE(u32(encodeStr("expand 16-byte k"))))();
|
|
2158
|
+
var sigma32_32 = /* @__PURE__ */ (() => swap32IfBE(u32(encodeStr("expand 32-byte k"))))();
|
|
2159
|
+
function rotl(a, b) {
|
|
2160
|
+
return a << b | a >>> 32 - b;
|
|
2161
|
+
}
|
|
2162
|
+
var BLOCK_LEN = 64;
|
|
2163
|
+
var BLOCK_LEN32 = 16;
|
|
2164
|
+
var MAX_COUNTER = /* @__PURE__ */ (() => 2 ** 32 - 1)();
|
|
2165
|
+
var U32_EMPTY = /* @__PURE__ */ Uint32Array.of();
|
|
2166
|
+
function runCipher(core, sigma, key, nonce, data, output, counter, rounds) {
|
|
2167
|
+
const len = data.length;
|
|
2168
|
+
const block = new Uint8Array(BLOCK_LEN);
|
|
2169
|
+
const b32 = u32(block);
|
|
2170
|
+
const isAligned = isLE && isAligned32(data) && isAligned32(output);
|
|
2171
|
+
const d32 = isAligned ? u32(data) : U32_EMPTY;
|
|
2172
|
+
const o32 = isAligned ? u32(output) : U32_EMPTY;
|
|
2173
|
+
if (!isLE) {
|
|
2174
|
+
for (let pos = 0; pos < len; counter++) {
|
|
2175
|
+
core(sigma, key, nonce, b32, counter, rounds);
|
|
2176
|
+
swap32IfBE(b32);
|
|
2177
|
+
if (counter >= MAX_COUNTER)
|
|
2178
|
+
throw new Error("arx: counter overflow");
|
|
2179
|
+
const take = Math.min(BLOCK_LEN, len - pos);
|
|
2180
|
+
for (let j = 0, posj; j < take; j++) {
|
|
2181
|
+
posj = pos + j;
|
|
2182
|
+
output[posj] = data[posj] ^ block[j];
|
|
2183
|
+
}
|
|
2184
|
+
pos += take;
|
|
2185
|
+
}
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
for (let pos = 0; pos < len; counter++) {
|
|
2189
|
+
core(sigma, key, nonce, b32, counter, rounds);
|
|
2190
|
+
if (counter >= MAX_COUNTER)
|
|
2191
|
+
throw new Error("arx: counter overflow");
|
|
2192
|
+
const take = Math.min(BLOCK_LEN, len - pos);
|
|
2193
|
+
if (isAligned && take === BLOCK_LEN) {
|
|
2194
|
+
const pos32 = pos / 4;
|
|
2195
|
+
if (pos % 4 !== 0)
|
|
2196
|
+
throw new Error("arx: invalid block position");
|
|
2197
|
+
for (let j = 0, posj; j < BLOCK_LEN32; j++) {
|
|
2198
|
+
posj = pos32 + j;
|
|
2199
|
+
o32[posj] = d32[posj] ^ b32[j];
|
|
2200
|
+
}
|
|
2201
|
+
pos += BLOCK_LEN;
|
|
2202
|
+
continue;
|
|
2203
|
+
}
|
|
2204
|
+
for (let j = 0, posj; j < take; j++) {
|
|
2205
|
+
posj = pos + j;
|
|
2206
|
+
output[posj] = data[posj] ^ block[j];
|
|
2207
|
+
}
|
|
2208
|
+
pos += take;
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
function createCipher(core, opts) {
|
|
2212
|
+
const { allowShortKeys, extendNonceFn, counterLength, counterRight, rounds } = checkOpts({ allowShortKeys: false, counterLength: 8, counterRight: false, rounds: 20 }, opts);
|
|
2213
|
+
if (typeof core !== "function")
|
|
2214
|
+
throw new Error("core must be a function");
|
|
2215
|
+
anumber2(counterLength);
|
|
2216
|
+
anumber2(rounds);
|
|
2217
|
+
abool(counterRight);
|
|
2218
|
+
abool(allowShortKeys);
|
|
2219
|
+
return (key, nonce, data, output, counter = 0) => {
|
|
2220
|
+
abytes2(key, void 0, "key");
|
|
2221
|
+
abytes2(nonce, void 0, "nonce");
|
|
2222
|
+
abytes2(data, void 0, "data");
|
|
2223
|
+
const len = data.length;
|
|
2224
|
+
output = getOutput(len, output, false);
|
|
2225
|
+
anumber2(counter);
|
|
2226
|
+
if (counter < 0 || counter >= MAX_COUNTER)
|
|
2227
|
+
throw new Error("arx: counter overflow");
|
|
2228
|
+
const toClean = [];
|
|
2229
|
+
let l = key.length;
|
|
2230
|
+
let k;
|
|
2231
|
+
let sigma;
|
|
2232
|
+
if (l === 32) {
|
|
2233
|
+
toClean.push(k = copyBytes(key));
|
|
2234
|
+
sigma = sigma32_32;
|
|
2235
|
+
} else if (l === 16 && allowShortKeys) {
|
|
2236
|
+
k = new Uint8Array(32);
|
|
2237
|
+
k.set(key);
|
|
2238
|
+
k.set(key, 16);
|
|
2239
|
+
sigma = sigma16_32;
|
|
2240
|
+
toClean.push(k);
|
|
2241
|
+
} else {
|
|
2242
|
+
abytes2(key, 32, "arx key");
|
|
2243
|
+
throw new Error("invalid key size");
|
|
2244
|
+
}
|
|
2245
|
+
if (!isLE || !isAligned32(nonce))
|
|
2246
|
+
toClean.push(nonce = copyBytes(nonce));
|
|
2247
|
+
let k32 = u32(k);
|
|
2248
|
+
if (extendNonceFn) {
|
|
2249
|
+
if (nonce.length !== 24)
|
|
2250
|
+
throw new Error(`arx: extended nonce must be 24 bytes`);
|
|
2251
|
+
const n16 = nonce.subarray(0, 16);
|
|
2252
|
+
if (isLE)
|
|
2253
|
+
extendNonceFn(sigma, k32, u32(n16), k32);
|
|
2254
|
+
else {
|
|
2255
|
+
const sigmaRaw = swap32IfBE(Uint32Array.from(sigma));
|
|
2256
|
+
extendNonceFn(sigmaRaw, k32, u32(n16), k32);
|
|
2257
|
+
clean2(sigmaRaw);
|
|
2258
|
+
swap32IfBE(k32);
|
|
2259
|
+
}
|
|
2260
|
+
nonce = nonce.subarray(16);
|
|
2261
|
+
} else if (!isLE)
|
|
2262
|
+
swap32IfBE(k32);
|
|
2263
|
+
const nonceNcLen = 16 - counterLength;
|
|
2264
|
+
if (nonceNcLen !== nonce.length)
|
|
2265
|
+
throw new Error(`arx: nonce must be ${nonceNcLen} or 16 bytes`);
|
|
2266
|
+
if (nonceNcLen !== 12) {
|
|
2267
|
+
const nc = new Uint8Array(12);
|
|
2268
|
+
nc.set(nonce, counterRight ? 0 : 12 - nonce.length);
|
|
2269
|
+
nonce = nc;
|
|
2270
|
+
toClean.push(nonce);
|
|
2271
|
+
}
|
|
2272
|
+
const n32 = swap32IfBE(u32(nonce));
|
|
2273
|
+
try {
|
|
2274
|
+
runCipher(core, sigma, k32, n32, data, output, counter, rounds);
|
|
2275
|
+
return output;
|
|
2276
|
+
} finally {
|
|
2277
|
+
clean2(...toClean);
|
|
2278
|
+
}
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
// node_modules/@noble/ciphers/_poly1305.js
|
|
2283
|
+
function u8to16(a, i) {
|
|
2284
|
+
return a[i++] & 255 | (a[i++] & 255) << 8;
|
|
2285
|
+
}
|
|
2286
|
+
var Poly1305 = class {
|
|
2287
|
+
blockLen = 16;
|
|
2288
|
+
outputLen = 16;
|
|
2289
|
+
buffer = new Uint8Array(16);
|
|
2290
|
+
r = new Uint16Array(10);
|
|
2291
|
+
// Allocating 1 array with .subarray() here is slower than 3
|
|
2292
|
+
h = new Uint16Array(10);
|
|
2293
|
+
pad = new Uint16Array(8);
|
|
2294
|
+
pos = 0;
|
|
2295
|
+
finished = false;
|
|
2296
|
+
destroyed = false;
|
|
2297
|
+
// Can be speed-up using BigUint64Array, at the cost of complexity
|
|
2298
|
+
constructor(key) {
|
|
2299
|
+
key = copyBytes(abytes2(key, 32, "key"));
|
|
2300
|
+
const t0 = u8to16(key, 0);
|
|
2301
|
+
const t1 = u8to16(key, 2);
|
|
2302
|
+
const t2 = u8to16(key, 4);
|
|
2303
|
+
const t3 = u8to16(key, 6);
|
|
2304
|
+
const t4 = u8to16(key, 8);
|
|
2305
|
+
const t5 = u8to16(key, 10);
|
|
2306
|
+
const t6 = u8to16(key, 12);
|
|
2307
|
+
const t7 = u8to16(key, 14);
|
|
2308
|
+
this.r[0] = t0 & 8191;
|
|
2309
|
+
this.r[1] = (t0 >>> 13 | t1 << 3) & 8191;
|
|
2310
|
+
this.r[2] = (t1 >>> 10 | t2 << 6) & 7939;
|
|
2311
|
+
this.r[3] = (t2 >>> 7 | t3 << 9) & 8191;
|
|
2312
|
+
this.r[4] = (t3 >>> 4 | t4 << 12) & 255;
|
|
2313
|
+
this.r[5] = t4 >>> 1 & 8190;
|
|
2314
|
+
this.r[6] = (t4 >>> 14 | t5 << 2) & 8191;
|
|
2315
|
+
this.r[7] = (t5 >>> 11 | t6 << 5) & 8065;
|
|
2316
|
+
this.r[8] = (t6 >>> 8 | t7 << 8) & 8191;
|
|
2317
|
+
this.r[9] = t7 >>> 5 & 127;
|
|
2318
|
+
for (let i = 0; i < 8; i++)
|
|
2319
|
+
this.pad[i] = u8to16(key, 16 + 2 * i);
|
|
2320
|
+
}
|
|
2321
|
+
process(data, offset, isLast = false) {
|
|
2322
|
+
const hibit = isLast ? 0 : 1 << 11;
|
|
2323
|
+
const { h, r } = this;
|
|
2324
|
+
const r0 = r[0];
|
|
2325
|
+
const r1 = r[1];
|
|
2326
|
+
const r2 = r[2];
|
|
2327
|
+
const r3 = r[3];
|
|
2328
|
+
const r4 = r[4];
|
|
2329
|
+
const r5 = r[5];
|
|
2330
|
+
const r6 = r[6];
|
|
2331
|
+
const r7 = r[7];
|
|
2332
|
+
const r8 = r[8];
|
|
2333
|
+
const r9 = r[9];
|
|
2334
|
+
const t0 = u8to16(data, offset + 0);
|
|
2335
|
+
const t1 = u8to16(data, offset + 2);
|
|
2336
|
+
const t2 = u8to16(data, offset + 4);
|
|
2337
|
+
const t3 = u8to16(data, offset + 6);
|
|
2338
|
+
const t4 = u8to16(data, offset + 8);
|
|
2339
|
+
const t5 = u8to16(data, offset + 10);
|
|
2340
|
+
const t6 = u8to16(data, offset + 12);
|
|
2341
|
+
const t7 = u8to16(data, offset + 14);
|
|
2342
|
+
let h0 = h[0] + (t0 & 8191);
|
|
2343
|
+
let h1 = h[1] + ((t0 >>> 13 | t1 << 3) & 8191);
|
|
2344
|
+
let h2 = h[2] + ((t1 >>> 10 | t2 << 6) & 8191);
|
|
2345
|
+
let h3 = h[3] + ((t2 >>> 7 | t3 << 9) & 8191);
|
|
2346
|
+
let h4 = h[4] + ((t3 >>> 4 | t4 << 12) & 8191);
|
|
2347
|
+
let h5 = h[5] + (t4 >>> 1 & 8191);
|
|
2348
|
+
let h6 = h[6] + ((t4 >>> 14 | t5 << 2) & 8191);
|
|
2349
|
+
let h7 = h[7] + ((t5 >>> 11 | t6 << 5) & 8191);
|
|
2350
|
+
let h8 = h[8] + ((t6 >>> 8 | t7 << 8) & 8191);
|
|
2351
|
+
let h9 = h[9] + (t7 >>> 5 | hibit);
|
|
2352
|
+
let c = 0;
|
|
2353
|
+
let d0 = c + h0 * r0 + h1 * (5 * r9) + h2 * (5 * r8) + h3 * (5 * r7) + h4 * (5 * r6);
|
|
2354
|
+
c = d0 >>> 13;
|
|
2355
|
+
d0 &= 8191;
|
|
2356
|
+
d0 += h5 * (5 * r5) + h6 * (5 * r4) + h7 * (5 * r3) + h8 * (5 * r2) + h9 * (5 * r1);
|
|
2357
|
+
c += d0 >>> 13;
|
|
2358
|
+
d0 &= 8191;
|
|
2359
|
+
let d1 = c + h0 * r1 + h1 * r0 + h2 * (5 * r9) + h3 * (5 * r8) + h4 * (5 * r7);
|
|
2360
|
+
c = d1 >>> 13;
|
|
2361
|
+
d1 &= 8191;
|
|
2362
|
+
d1 += h5 * (5 * r6) + h6 * (5 * r5) + h7 * (5 * r4) + h8 * (5 * r3) + h9 * (5 * r2);
|
|
2363
|
+
c += d1 >>> 13;
|
|
2364
|
+
d1 &= 8191;
|
|
2365
|
+
let d2 = c + h0 * r2 + h1 * r1 + h2 * r0 + h3 * (5 * r9) + h4 * (5 * r8);
|
|
2366
|
+
c = d2 >>> 13;
|
|
2367
|
+
d2 &= 8191;
|
|
2368
|
+
d2 += h5 * (5 * r7) + h6 * (5 * r6) + h7 * (5 * r5) + h8 * (5 * r4) + h9 * (5 * r3);
|
|
2369
|
+
c += d2 >>> 13;
|
|
2370
|
+
d2 &= 8191;
|
|
2371
|
+
let d3 = c + h0 * r3 + h1 * r2 + h2 * r1 + h3 * r0 + h4 * (5 * r9);
|
|
2372
|
+
c = d3 >>> 13;
|
|
2373
|
+
d3 &= 8191;
|
|
2374
|
+
d3 += h5 * (5 * r8) + h6 * (5 * r7) + h7 * (5 * r6) + h8 * (5 * r5) + h9 * (5 * r4);
|
|
2375
|
+
c += d3 >>> 13;
|
|
2376
|
+
d3 &= 8191;
|
|
2377
|
+
let d4 = c + h0 * r4 + h1 * r3 + h2 * r2 + h3 * r1 + h4 * r0;
|
|
2378
|
+
c = d4 >>> 13;
|
|
2379
|
+
d4 &= 8191;
|
|
2380
|
+
d4 += h5 * (5 * r9) + h6 * (5 * r8) + h7 * (5 * r7) + h8 * (5 * r6) + h9 * (5 * r5);
|
|
2381
|
+
c += d4 >>> 13;
|
|
2382
|
+
d4 &= 8191;
|
|
2383
|
+
let d5 = c + h0 * r5 + h1 * r4 + h2 * r3 + h3 * r2 + h4 * r1;
|
|
2384
|
+
c = d5 >>> 13;
|
|
2385
|
+
d5 &= 8191;
|
|
2386
|
+
d5 += h5 * r0 + h6 * (5 * r9) + h7 * (5 * r8) + h8 * (5 * r7) + h9 * (5 * r6);
|
|
2387
|
+
c += d5 >>> 13;
|
|
2388
|
+
d5 &= 8191;
|
|
2389
|
+
let d6 = c + h0 * r6 + h1 * r5 + h2 * r4 + h3 * r3 + h4 * r2;
|
|
2390
|
+
c = d6 >>> 13;
|
|
2391
|
+
d6 &= 8191;
|
|
2392
|
+
d6 += h5 * r1 + h6 * r0 + h7 * (5 * r9) + h8 * (5 * r8) + h9 * (5 * r7);
|
|
2393
|
+
c += d6 >>> 13;
|
|
2394
|
+
d6 &= 8191;
|
|
2395
|
+
let d7 = c + h0 * r7 + h1 * r6 + h2 * r5 + h3 * r4 + h4 * r3;
|
|
2396
|
+
c = d7 >>> 13;
|
|
2397
|
+
d7 &= 8191;
|
|
2398
|
+
d7 += h5 * r2 + h6 * r1 + h7 * r0 + h8 * (5 * r9) + h9 * (5 * r8);
|
|
2399
|
+
c += d7 >>> 13;
|
|
2400
|
+
d7 &= 8191;
|
|
2401
|
+
let d8 = c + h0 * r8 + h1 * r7 + h2 * r6 + h3 * r5 + h4 * r4;
|
|
2402
|
+
c = d8 >>> 13;
|
|
2403
|
+
d8 &= 8191;
|
|
2404
|
+
d8 += h5 * r3 + h6 * r2 + h7 * r1 + h8 * r0 + h9 * (5 * r9);
|
|
2405
|
+
c += d8 >>> 13;
|
|
2406
|
+
d8 &= 8191;
|
|
2407
|
+
let d9 = c + h0 * r9 + h1 * r8 + h2 * r7 + h3 * r6 + h4 * r5;
|
|
2408
|
+
c = d9 >>> 13;
|
|
2409
|
+
d9 &= 8191;
|
|
2410
|
+
d9 += h5 * r4 + h6 * r3 + h7 * r2 + h8 * r1 + h9 * r0;
|
|
2411
|
+
c += d9 >>> 13;
|
|
2412
|
+
d9 &= 8191;
|
|
2413
|
+
c = (c << 2) + c | 0;
|
|
2414
|
+
c = c + d0 | 0;
|
|
2415
|
+
d0 = c & 8191;
|
|
2416
|
+
c = c >>> 13;
|
|
2417
|
+
d1 += c;
|
|
2418
|
+
h[0] = d0;
|
|
2419
|
+
h[1] = d1;
|
|
2420
|
+
h[2] = d2;
|
|
2421
|
+
h[3] = d3;
|
|
2422
|
+
h[4] = d4;
|
|
2423
|
+
h[5] = d5;
|
|
2424
|
+
h[6] = d6;
|
|
2425
|
+
h[7] = d7;
|
|
2426
|
+
h[8] = d8;
|
|
2427
|
+
h[9] = d9;
|
|
2428
|
+
}
|
|
2429
|
+
finalize() {
|
|
2430
|
+
const { h, pad } = this;
|
|
2431
|
+
const g = new Uint16Array(10);
|
|
2432
|
+
let c = h[1] >>> 13;
|
|
2433
|
+
h[1] &= 8191;
|
|
2434
|
+
for (let i = 2; i < 10; i++) {
|
|
2435
|
+
h[i] += c;
|
|
2436
|
+
c = h[i] >>> 13;
|
|
2437
|
+
h[i] &= 8191;
|
|
2438
|
+
}
|
|
2439
|
+
h[0] += c * 5;
|
|
2440
|
+
c = h[0] >>> 13;
|
|
2441
|
+
h[0] &= 8191;
|
|
2442
|
+
h[1] += c;
|
|
2443
|
+
c = h[1] >>> 13;
|
|
2444
|
+
h[1] &= 8191;
|
|
2445
|
+
h[2] += c;
|
|
2446
|
+
g[0] = h[0] + 5;
|
|
2447
|
+
c = g[0] >>> 13;
|
|
2448
|
+
g[0] &= 8191;
|
|
2449
|
+
for (let i = 1; i < 10; i++) {
|
|
2450
|
+
g[i] = h[i] + c;
|
|
2451
|
+
c = g[i] >>> 13;
|
|
2452
|
+
g[i] &= 8191;
|
|
2453
|
+
}
|
|
2454
|
+
g[9] -= 1 << 13;
|
|
2455
|
+
let mask = (c ^ 1) - 1;
|
|
2456
|
+
for (let i = 0; i < 10; i++)
|
|
2457
|
+
g[i] &= mask;
|
|
2458
|
+
mask = ~mask;
|
|
2459
|
+
for (let i = 0; i < 10; i++)
|
|
2460
|
+
h[i] = h[i] & mask | g[i];
|
|
2461
|
+
h[0] = (h[0] | h[1] << 13) & 65535;
|
|
2462
|
+
h[1] = (h[1] >>> 3 | h[2] << 10) & 65535;
|
|
2463
|
+
h[2] = (h[2] >>> 6 | h[3] << 7) & 65535;
|
|
2464
|
+
h[3] = (h[3] >>> 9 | h[4] << 4) & 65535;
|
|
2465
|
+
h[4] = (h[4] >>> 12 | h[5] << 1 | h[6] << 14) & 65535;
|
|
2466
|
+
h[5] = (h[6] >>> 2 | h[7] << 11) & 65535;
|
|
2467
|
+
h[6] = (h[7] >>> 5 | h[8] << 8) & 65535;
|
|
2468
|
+
h[7] = (h[8] >>> 8 | h[9] << 5) & 65535;
|
|
2469
|
+
let f = h[0] + pad[0];
|
|
2470
|
+
h[0] = f & 65535;
|
|
2471
|
+
for (let i = 1; i < 8; i++) {
|
|
2472
|
+
f = (h[i] + pad[i] | 0) + (f >>> 16) | 0;
|
|
2473
|
+
h[i] = f & 65535;
|
|
2474
|
+
}
|
|
2475
|
+
clean2(g);
|
|
2476
|
+
}
|
|
2477
|
+
update(data) {
|
|
2478
|
+
aexists2(this);
|
|
2479
|
+
abytes2(data);
|
|
2480
|
+
data = copyBytes(data);
|
|
2481
|
+
const { buffer, blockLen } = this;
|
|
2482
|
+
const len = data.length;
|
|
2483
|
+
for (let pos = 0; pos < len; ) {
|
|
2484
|
+
const take = Math.min(blockLen - this.pos, len - pos);
|
|
2485
|
+
if (take === blockLen) {
|
|
2486
|
+
for (; blockLen <= len - pos; pos += blockLen)
|
|
2487
|
+
this.process(data, pos);
|
|
2488
|
+
continue;
|
|
2489
|
+
}
|
|
2490
|
+
buffer.set(data.subarray(pos, pos + take), this.pos);
|
|
2491
|
+
this.pos += take;
|
|
2492
|
+
pos += take;
|
|
2493
|
+
if (this.pos === blockLen) {
|
|
2494
|
+
this.process(buffer, 0, false);
|
|
2495
|
+
this.pos = 0;
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
return this;
|
|
2499
|
+
}
|
|
2500
|
+
destroy() {
|
|
2501
|
+
this.destroyed = true;
|
|
2502
|
+
clean2(this.h, this.r, this.buffer, this.pad);
|
|
2503
|
+
}
|
|
2504
|
+
digestInto(out) {
|
|
2505
|
+
aexists2(this);
|
|
2506
|
+
aoutput2(out, this);
|
|
2507
|
+
this.finished = true;
|
|
2508
|
+
const { buffer, h } = this;
|
|
2509
|
+
let { pos } = this;
|
|
2510
|
+
if (pos) {
|
|
2511
|
+
buffer[pos++] = 1;
|
|
2512
|
+
for (; pos < 16; pos++)
|
|
2513
|
+
buffer[pos] = 0;
|
|
2514
|
+
this.process(buffer, 0, true);
|
|
2515
|
+
}
|
|
2516
|
+
this.finalize();
|
|
2517
|
+
let opos = 0;
|
|
2518
|
+
for (let i = 0; i < 8; i++) {
|
|
2519
|
+
out[opos++] = h[i] >>> 0;
|
|
2520
|
+
out[opos++] = h[i] >>> 8;
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
digest() {
|
|
2524
|
+
const { buffer, outputLen } = this;
|
|
2525
|
+
this.digestInto(buffer);
|
|
2526
|
+
const res = buffer.slice(0, outputLen);
|
|
2527
|
+
this.destroy();
|
|
2528
|
+
return res;
|
|
2529
|
+
}
|
|
2530
|
+
};
|
|
2531
|
+
var poly1305 = /* @__PURE__ */ wrapMacConstructor(32, (key) => new Poly1305(key));
|
|
2532
|
+
|
|
2533
|
+
// node_modules/@noble/ciphers/chacha.js
|
|
2534
|
+
function chachaCore(s, k, n, out, cnt, rounds = 20) {
|
|
2535
|
+
let y00 = s[0], y01 = s[1], y02 = s[2], y03 = s[3], y04 = k[0], y05 = k[1], y06 = k[2], y07 = k[3], y08 = k[4], y09 = k[5], y10 = k[6], y11 = k[7], y12 = cnt, y13 = n[0], y14 = n[1], y15 = n[2];
|
|
2536
|
+
let x00 = y00, x01 = y01, x02 = y02, x03 = y03, x04 = y04, x05 = y05, x06 = y06, x07 = y07, x08 = y08, x09 = y09, x10 = y10, x11 = y11, x12 = y12, x13 = y13, x14 = y14, x15 = y15;
|
|
2537
|
+
for (let r = 0; r < rounds; r += 2) {
|
|
2538
|
+
x00 = x00 + x04 | 0;
|
|
2539
|
+
x12 = rotl(x12 ^ x00, 16);
|
|
2540
|
+
x08 = x08 + x12 | 0;
|
|
2541
|
+
x04 = rotl(x04 ^ x08, 12);
|
|
2542
|
+
x00 = x00 + x04 | 0;
|
|
2543
|
+
x12 = rotl(x12 ^ x00, 8);
|
|
2544
|
+
x08 = x08 + x12 | 0;
|
|
2545
|
+
x04 = rotl(x04 ^ x08, 7);
|
|
2546
|
+
x01 = x01 + x05 | 0;
|
|
2547
|
+
x13 = rotl(x13 ^ x01, 16);
|
|
2548
|
+
x09 = x09 + x13 | 0;
|
|
2549
|
+
x05 = rotl(x05 ^ x09, 12);
|
|
2550
|
+
x01 = x01 + x05 | 0;
|
|
2551
|
+
x13 = rotl(x13 ^ x01, 8);
|
|
2552
|
+
x09 = x09 + x13 | 0;
|
|
2553
|
+
x05 = rotl(x05 ^ x09, 7);
|
|
2554
|
+
x02 = x02 + x06 | 0;
|
|
2555
|
+
x14 = rotl(x14 ^ x02, 16);
|
|
2556
|
+
x10 = x10 + x14 | 0;
|
|
2557
|
+
x06 = rotl(x06 ^ x10, 12);
|
|
2558
|
+
x02 = x02 + x06 | 0;
|
|
2559
|
+
x14 = rotl(x14 ^ x02, 8);
|
|
2560
|
+
x10 = x10 + x14 | 0;
|
|
2561
|
+
x06 = rotl(x06 ^ x10, 7);
|
|
2562
|
+
x03 = x03 + x07 | 0;
|
|
2563
|
+
x15 = rotl(x15 ^ x03, 16);
|
|
2564
|
+
x11 = x11 + x15 | 0;
|
|
2565
|
+
x07 = rotl(x07 ^ x11, 12);
|
|
2566
|
+
x03 = x03 + x07 | 0;
|
|
2567
|
+
x15 = rotl(x15 ^ x03, 8);
|
|
2568
|
+
x11 = x11 + x15 | 0;
|
|
2569
|
+
x07 = rotl(x07 ^ x11, 7);
|
|
2570
|
+
x00 = x00 + x05 | 0;
|
|
2571
|
+
x15 = rotl(x15 ^ x00, 16);
|
|
2572
|
+
x10 = x10 + x15 | 0;
|
|
2573
|
+
x05 = rotl(x05 ^ x10, 12);
|
|
2574
|
+
x00 = x00 + x05 | 0;
|
|
2575
|
+
x15 = rotl(x15 ^ x00, 8);
|
|
2576
|
+
x10 = x10 + x15 | 0;
|
|
2577
|
+
x05 = rotl(x05 ^ x10, 7);
|
|
2578
|
+
x01 = x01 + x06 | 0;
|
|
2579
|
+
x12 = rotl(x12 ^ x01, 16);
|
|
2580
|
+
x11 = x11 + x12 | 0;
|
|
2581
|
+
x06 = rotl(x06 ^ x11, 12);
|
|
2582
|
+
x01 = x01 + x06 | 0;
|
|
2583
|
+
x12 = rotl(x12 ^ x01, 8);
|
|
2584
|
+
x11 = x11 + x12 | 0;
|
|
2585
|
+
x06 = rotl(x06 ^ x11, 7);
|
|
2586
|
+
x02 = x02 + x07 | 0;
|
|
2587
|
+
x13 = rotl(x13 ^ x02, 16);
|
|
2588
|
+
x08 = x08 + x13 | 0;
|
|
2589
|
+
x07 = rotl(x07 ^ x08, 12);
|
|
2590
|
+
x02 = x02 + x07 | 0;
|
|
2591
|
+
x13 = rotl(x13 ^ x02, 8);
|
|
2592
|
+
x08 = x08 + x13 | 0;
|
|
2593
|
+
x07 = rotl(x07 ^ x08, 7);
|
|
2594
|
+
x03 = x03 + x04 | 0;
|
|
2595
|
+
x14 = rotl(x14 ^ x03, 16);
|
|
2596
|
+
x09 = x09 + x14 | 0;
|
|
2597
|
+
x04 = rotl(x04 ^ x09, 12);
|
|
2598
|
+
x03 = x03 + x04 | 0;
|
|
2599
|
+
x14 = rotl(x14 ^ x03, 8);
|
|
2600
|
+
x09 = x09 + x14 | 0;
|
|
2601
|
+
x04 = rotl(x04 ^ x09, 7);
|
|
2602
|
+
}
|
|
2603
|
+
let oi = 0;
|
|
2604
|
+
out[oi++] = y00 + x00 | 0;
|
|
2605
|
+
out[oi++] = y01 + x01 | 0;
|
|
2606
|
+
out[oi++] = y02 + x02 | 0;
|
|
2607
|
+
out[oi++] = y03 + x03 | 0;
|
|
2608
|
+
out[oi++] = y04 + x04 | 0;
|
|
2609
|
+
out[oi++] = y05 + x05 | 0;
|
|
2610
|
+
out[oi++] = y06 + x06 | 0;
|
|
2611
|
+
out[oi++] = y07 + x07 | 0;
|
|
2612
|
+
out[oi++] = y08 + x08 | 0;
|
|
2613
|
+
out[oi++] = y09 + x09 | 0;
|
|
2614
|
+
out[oi++] = y10 + x10 | 0;
|
|
2615
|
+
out[oi++] = y11 + x11 | 0;
|
|
2616
|
+
out[oi++] = y12 + x12 | 0;
|
|
2617
|
+
out[oi++] = y13 + x13 | 0;
|
|
2618
|
+
out[oi++] = y14 + x14 | 0;
|
|
2619
|
+
out[oi++] = y15 + x15 | 0;
|
|
2620
|
+
}
|
|
2621
|
+
function hchacha(s, k, i, out) {
|
|
2622
|
+
let x00 = swap8IfBE(s[0]), x01 = swap8IfBE(s[1]), x02 = swap8IfBE(s[2]), x03 = swap8IfBE(s[3]), x04 = swap8IfBE(k[0]), x05 = swap8IfBE(k[1]), x06 = swap8IfBE(k[2]), x07 = swap8IfBE(k[3]), x08 = swap8IfBE(k[4]), x09 = swap8IfBE(k[5]), x10 = swap8IfBE(k[6]), x11 = swap8IfBE(k[7]), x12 = swap8IfBE(i[0]), x13 = swap8IfBE(i[1]), x14 = swap8IfBE(i[2]), x15 = swap8IfBE(i[3]);
|
|
2623
|
+
for (let r = 0; r < 20; r += 2) {
|
|
2624
|
+
x00 = x00 + x04 | 0;
|
|
2625
|
+
x12 = rotl(x12 ^ x00, 16);
|
|
2626
|
+
x08 = x08 + x12 | 0;
|
|
2627
|
+
x04 = rotl(x04 ^ x08, 12);
|
|
2628
|
+
x00 = x00 + x04 | 0;
|
|
2629
|
+
x12 = rotl(x12 ^ x00, 8);
|
|
2630
|
+
x08 = x08 + x12 | 0;
|
|
2631
|
+
x04 = rotl(x04 ^ x08, 7);
|
|
2632
|
+
x01 = x01 + x05 | 0;
|
|
2633
|
+
x13 = rotl(x13 ^ x01, 16);
|
|
2634
|
+
x09 = x09 + x13 | 0;
|
|
2635
|
+
x05 = rotl(x05 ^ x09, 12);
|
|
2636
|
+
x01 = x01 + x05 | 0;
|
|
2637
|
+
x13 = rotl(x13 ^ x01, 8);
|
|
2638
|
+
x09 = x09 + x13 | 0;
|
|
2639
|
+
x05 = rotl(x05 ^ x09, 7);
|
|
2640
|
+
x02 = x02 + x06 | 0;
|
|
2641
|
+
x14 = rotl(x14 ^ x02, 16);
|
|
2642
|
+
x10 = x10 + x14 | 0;
|
|
2643
|
+
x06 = rotl(x06 ^ x10, 12);
|
|
2644
|
+
x02 = x02 + x06 | 0;
|
|
2645
|
+
x14 = rotl(x14 ^ x02, 8);
|
|
2646
|
+
x10 = x10 + x14 | 0;
|
|
2647
|
+
x06 = rotl(x06 ^ x10, 7);
|
|
2648
|
+
x03 = x03 + x07 | 0;
|
|
2649
|
+
x15 = rotl(x15 ^ x03, 16);
|
|
2650
|
+
x11 = x11 + x15 | 0;
|
|
2651
|
+
x07 = rotl(x07 ^ x11, 12);
|
|
2652
|
+
x03 = x03 + x07 | 0;
|
|
2653
|
+
x15 = rotl(x15 ^ x03, 8);
|
|
2654
|
+
x11 = x11 + x15 | 0;
|
|
2655
|
+
x07 = rotl(x07 ^ x11, 7);
|
|
2656
|
+
x00 = x00 + x05 | 0;
|
|
2657
|
+
x15 = rotl(x15 ^ x00, 16);
|
|
2658
|
+
x10 = x10 + x15 | 0;
|
|
2659
|
+
x05 = rotl(x05 ^ x10, 12);
|
|
2660
|
+
x00 = x00 + x05 | 0;
|
|
2661
|
+
x15 = rotl(x15 ^ x00, 8);
|
|
2662
|
+
x10 = x10 + x15 | 0;
|
|
2663
|
+
x05 = rotl(x05 ^ x10, 7);
|
|
2664
|
+
x01 = x01 + x06 | 0;
|
|
2665
|
+
x12 = rotl(x12 ^ x01, 16);
|
|
2666
|
+
x11 = x11 + x12 | 0;
|
|
2667
|
+
x06 = rotl(x06 ^ x11, 12);
|
|
2668
|
+
x01 = x01 + x06 | 0;
|
|
2669
|
+
x12 = rotl(x12 ^ x01, 8);
|
|
2670
|
+
x11 = x11 + x12 | 0;
|
|
2671
|
+
x06 = rotl(x06 ^ x11, 7);
|
|
2672
|
+
x02 = x02 + x07 | 0;
|
|
2673
|
+
x13 = rotl(x13 ^ x02, 16);
|
|
2674
|
+
x08 = x08 + x13 | 0;
|
|
2675
|
+
x07 = rotl(x07 ^ x08, 12);
|
|
2676
|
+
x02 = x02 + x07 | 0;
|
|
2677
|
+
x13 = rotl(x13 ^ x02, 8);
|
|
2678
|
+
x08 = x08 + x13 | 0;
|
|
2679
|
+
x07 = rotl(x07 ^ x08, 7);
|
|
2680
|
+
x03 = x03 + x04 | 0;
|
|
2681
|
+
x14 = rotl(x14 ^ x03, 16);
|
|
2682
|
+
x09 = x09 + x14 | 0;
|
|
2683
|
+
x04 = rotl(x04 ^ x09, 12);
|
|
2684
|
+
x03 = x03 + x04 | 0;
|
|
2685
|
+
x14 = rotl(x14 ^ x03, 8);
|
|
2686
|
+
x09 = x09 + x14 | 0;
|
|
2687
|
+
x04 = rotl(x04 ^ x09, 7);
|
|
2688
|
+
}
|
|
2689
|
+
let oi = 0;
|
|
2690
|
+
out[oi++] = x00;
|
|
2691
|
+
out[oi++] = x01;
|
|
2692
|
+
out[oi++] = x02;
|
|
2693
|
+
out[oi++] = x03;
|
|
2694
|
+
out[oi++] = x12;
|
|
2695
|
+
out[oi++] = x13;
|
|
2696
|
+
out[oi++] = x14;
|
|
2697
|
+
out[oi++] = x15;
|
|
2698
|
+
swap32IfBE(out);
|
|
2699
|
+
}
|
|
2700
|
+
var xchacha20 = /* @__PURE__ */ createCipher(chachaCore, {
|
|
2701
|
+
counterRight: false,
|
|
2702
|
+
counterLength: 8,
|
|
2703
|
+
extendNonceFn: hchacha,
|
|
2704
|
+
allowShortKeys: false
|
|
2705
|
+
});
|
|
2706
|
+
var ZEROS16 = /* @__PURE__ */ new Uint8Array(16);
|
|
2707
|
+
var updatePadded = (h, msg) => {
|
|
2708
|
+
h.update(msg);
|
|
2709
|
+
const leftover = msg.length % 16;
|
|
2710
|
+
if (leftover)
|
|
2711
|
+
h.update(ZEROS16.subarray(leftover));
|
|
2712
|
+
};
|
|
2713
|
+
var ZEROS32 = /* @__PURE__ */ new Uint8Array(32);
|
|
2714
|
+
function computeTag(fn, key, nonce, ciphertext, AAD) {
|
|
2715
|
+
if (AAD !== void 0)
|
|
2716
|
+
abytes2(AAD, void 0, "AAD");
|
|
2717
|
+
const authKey = fn(key, nonce, ZEROS32);
|
|
2718
|
+
const lengths = u64Lengths(ciphertext.length, AAD ? AAD.length : 0, true);
|
|
2719
|
+
const h = poly1305.create(authKey);
|
|
2720
|
+
if (AAD)
|
|
2721
|
+
updatePadded(h, AAD);
|
|
2722
|
+
updatePadded(h, ciphertext);
|
|
2723
|
+
h.update(lengths);
|
|
2724
|
+
const res = h.digest();
|
|
2725
|
+
clean2(authKey, lengths);
|
|
2726
|
+
return res;
|
|
2727
|
+
}
|
|
2728
|
+
var _poly1305_aead = (xorStream) => (key, nonce, AAD) => {
|
|
2729
|
+
const tagLength = 16;
|
|
2730
|
+
return {
|
|
2731
|
+
encrypt(plaintext, output) {
|
|
2732
|
+
const plength = plaintext.length;
|
|
2733
|
+
output = getOutput(plength + tagLength, output, false);
|
|
2734
|
+
output.set(plaintext);
|
|
2735
|
+
const oPlain = output.subarray(0, -tagLength);
|
|
2736
|
+
xorStream(key, nonce, oPlain, oPlain, 1);
|
|
2737
|
+
const tag = computeTag(xorStream, key, nonce, oPlain, AAD);
|
|
2738
|
+
output.set(tag, plength);
|
|
2739
|
+
clean2(tag);
|
|
2740
|
+
return output;
|
|
2741
|
+
},
|
|
2742
|
+
decrypt(ciphertext, output) {
|
|
2743
|
+
output = getOutput(ciphertext.length - tagLength, output, false);
|
|
2744
|
+
const data = ciphertext.subarray(0, -tagLength);
|
|
2745
|
+
const passedTag = ciphertext.subarray(-tagLength);
|
|
2746
|
+
const tag = computeTag(xorStream, key, nonce, data, AAD);
|
|
2747
|
+
if (!equalBytes(passedTag, tag)) {
|
|
2748
|
+
clean2(tag);
|
|
2749
|
+
throw new Error("invalid tag");
|
|
2750
|
+
}
|
|
2751
|
+
output.set(ciphertext.subarray(0, -tagLength));
|
|
2752
|
+
xorStream(key, nonce, output, output, 1);
|
|
2753
|
+
clean2(tag);
|
|
2754
|
+
return output;
|
|
2755
|
+
}
|
|
2756
|
+
};
|
|
2757
|
+
};
|
|
2758
|
+
var xchacha20poly1305 = /* @__PURE__ */ wrapCipher(
|
|
2759
|
+
{ blockSize: 64, nonceLength: 24, tagLength: 16 },
|
|
2760
|
+
/* @__PURE__ */ _poly1305_aead(xchacha20)
|
|
2761
|
+
);
|
|
2762
|
+
|
|
2763
|
+
// core/field-encryption.ts
|
|
2764
|
+
var FIELD_ENCRYPTION_HKDF_INFO = "sphere-fieldenc-v1";
|
|
2765
|
+
var FIELD_ENVELOPE_PREFIX = "enc1.";
|
|
2766
|
+
var FIELD_ENVELOPE_NONCE_BYTES = 24;
|
|
2767
|
+
var POLY1305_TAG_BYTES = 16;
|
|
2768
|
+
var B64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
2769
|
+
var B64_LOOKUP = {};
|
|
2770
|
+
for (let i = 0; i < B64_ALPHABET.length; i++) B64_LOOKUP[B64_ALPHABET[i]] = i;
|
|
2771
|
+
function base64Encode(bytes) {
|
|
2772
|
+
let out = "";
|
|
2773
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
2774
|
+
const b0 = bytes[i];
|
|
2775
|
+
const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
2776
|
+
const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
|
|
2777
|
+
out += B64_ALPHABET[b0 >> 2];
|
|
2778
|
+
out += B64_ALPHABET[(b0 & 3) << 4 | b1 >> 4];
|
|
2779
|
+
out += i + 1 < bytes.length ? B64_ALPHABET[(b1 & 15) << 2 | b2 >> 6] : "=";
|
|
2780
|
+
out += i + 2 < bytes.length ? B64_ALPHABET[b2 & 63] : "=";
|
|
2781
|
+
}
|
|
2782
|
+
return out;
|
|
2783
|
+
}
|
|
2784
|
+
function base64Decode(s) {
|
|
2785
|
+
if (s.length % 4 !== 0) {
|
|
2786
|
+
throw new SphereError("Invalid field envelope: base64 length not a multiple of 4", "DECRYPTION_ERROR");
|
|
2787
|
+
}
|
|
2788
|
+
let padding = 0;
|
|
2789
|
+
if (s.endsWith("==")) padding = 2;
|
|
2790
|
+
else if (s.endsWith("=")) padding = 1;
|
|
2791
|
+
const body = s.slice(0, s.length - padding);
|
|
2792
|
+
if (body.includes("=")) {
|
|
2793
|
+
throw new SphereError("Invalid field envelope: misplaced base64 padding", "DECRYPTION_ERROR");
|
|
2794
|
+
}
|
|
2795
|
+
const out = new Uint8Array(s.length / 4 * 3 - padding);
|
|
2796
|
+
let o = 0;
|
|
2797
|
+
for (let i = 0; i < body.length; i += 4) {
|
|
2798
|
+
const n0 = B64_LOOKUP[body[i]];
|
|
2799
|
+
const n1 = B64_LOOKUP[body[i + 1]];
|
|
2800
|
+
const n2 = body[i + 2] !== void 0 ? B64_LOOKUP[body[i + 2]] : 0;
|
|
2801
|
+
const n3 = body[i + 3] !== void 0 ? B64_LOOKUP[body[i + 3]] : 0;
|
|
2802
|
+
if (n0 === void 0 || n1 === void 0 || n2 === void 0 || n3 === void 0) {
|
|
2803
|
+
throw new SphereError("Invalid field envelope: non-base64 character", "DECRYPTION_ERROR");
|
|
2804
|
+
}
|
|
2805
|
+
const triple = n0 << 18 | n1 << 12 | n2 << 6 | n3;
|
|
2806
|
+
if (o < out.length) out[o++] = triple >> 16 & 255;
|
|
2807
|
+
if (o < out.length) out[o++] = triple >> 8 & 255;
|
|
2808
|
+
if (o < out.length) out[o++] = triple & 255;
|
|
2809
|
+
}
|
|
2810
|
+
return out;
|
|
2811
|
+
}
|
|
2812
|
+
function hexToBytesStrict(hex) {
|
|
2813
|
+
if (hex.length === 0 || hex.length % 2 !== 0 || !/^[0-9a-fA-F]+$/.test(hex)) {
|
|
2814
|
+
throw new SphereError("Invalid private key hex for field-encryption key derivation", "INVALID_CONFIG");
|
|
2815
|
+
}
|
|
2816
|
+
const out = new Uint8Array(hex.length / 2);
|
|
2817
|
+
for (let i = 0; i < out.length; i++) {
|
|
2818
|
+
out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
2819
|
+
}
|
|
2820
|
+
return out;
|
|
2821
|
+
}
|
|
2822
|
+
function deriveFieldEncryptionKey(privKeyHex) {
|
|
2823
|
+
const ikm = hexToBytesStrict(privKeyHex);
|
|
2824
|
+
const info = new TextEncoder().encode(FIELD_ENCRYPTION_HKDF_INFO);
|
|
2825
|
+
return hkdf(sha256, ikm, void 0, info, 32);
|
|
2826
|
+
}
|
|
2827
|
+
function encryptField(key, plaintext) {
|
|
2828
|
+
const nonce = randomBytes(FIELD_ENVELOPE_NONCE_BYTES);
|
|
2829
|
+
const ciphertext = xchacha20poly1305(key, nonce).encrypt(new TextEncoder().encode(plaintext));
|
|
2830
|
+
const packed = new Uint8Array(nonce.length + ciphertext.length);
|
|
2831
|
+
packed.set(nonce, 0);
|
|
2832
|
+
packed.set(ciphertext, nonce.length);
|
|
2833
|
+
return FIELD_ENVELOPE_PREFIX + base64Encode(packed);
|
|
2834
|
+
}
|
|
2835
|
+
function decryptField(key, envelope) {
|
|
2836
|
+
if (typeof envelope !== "string" || !envelope.startsWith(FIELD_ENVELOPE_PREFIX)) {
|
|
2837
|
+
throw new SphereError('Invalid field envelope: missing "enc1." prefix', "DECRYPTION_ERROR");
|
|
2838
|
+
}
|
|
2839
|
+
const packed = base64Decode(envelope.slice(FIELD_ENVELOPE_PREFIX.length));
|
|
2840
|
+
if (packed.length < FIELD_ENVELOPE_NONCE_BYTES + POLY1305_TAG_BYTES) {
|
|
2841
|
+
throw new SphereError("Invalid field envelope: too short for nonce + tag", "DECRYPTION_ERROR");
|
|
2842
|
+
}
|
|
2843
|
+
const nonce = packed.slice(0, FIELD_ENVELOPE_NONCE_BYTES);
|
|
2844
|
+
const ciphertext = packed.slice(FIELD_ENVELOPE_NONCE_BYTES);
|
|
2845
|
+
try {
|
|
2846
|
+
const plain = xchacha20poly1305(key, nonce).decrypt(ciphertext);
|
|
2847
|
+
return new TextDecoder().decode(plain);
|
|
2848
|
+
} catch (err) {
|
|
2849
|
+
throw new SphereError("Field envelope authentication failed (tampered or wrong key)", "DECRYPTION_ERROR", err);
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
// core/logger.ts
|
|
2854
|
+
var LOGGER_KEY = "__sphere_sdk_logger__";
|
|
2855
|
+
function getState() {
|
|
2856
|
+
const g = globalThis;
|
|
2857
|
+
if (!g[LOGGER_KEY]) {
|
|
2858
|
+
g[LOGGER_KEY] = { debug: false, tags: {}, handler: null };
|
|
2859
|
+
}
|
|
2860
|
+
return g[LOGGER_KEY];
|
|
2861
|
+
}
|
|
2862
|
+
function isEnabled(tag) {
|
|
2863
|
+
const state = getState();
|
|
2864
|
+
if (tag in state.tags) return state.tags[tag];
|
|
2865
|
+
return state.debug;
|
|
2866
|
+
}
|
|
2867
|
+
var logger = {
|
|
2868
|
+
/**
|
|
2869
|
+
* Configure the logger. Can be called multiple times (last write wins).
|
|
2870
|
+
* Typically called by createBrowserProviders(), createNodeProviders(), or Sphere.init().
|
|
2871
|
+
*/
|
|
2872
|
+
configure(config) {
|
|
2873
|
+
const state = getState();
|
|
2874
|
+
if (config.debug !== void 0) state.debug = config.debug;
|
|
2875
|
+
if (config.handler !== void 0) state.handler = config.handler;
|
|
2876
|
+
},
|
|
2877
|
+
/**
|
|
2878
|
+
* Enable/disable debug logging for a specific tag.
|
|
2879
|
+
* Per-tag setting overrides the global debug flag.
|
|
2880
|
+
*
|
|
2881
|
+
* @example
|
|
2882
|
+
* ```ts
|
|
2883
|
+
* logger.setTagDebug('Nostr', true); // enable only Nostr logs
|
|
2884
|
+
* logger.setTagDebug('Nostr', false); // disable Nostr logs even if global debug=true
|
|
2885
|
+
* ```
|
|
2886
|
+
*/
|
|
2887
|
+
setTagDebug(tag, enabled) {
|
|
2888
|
+
getState().tags[tag] = enabled;
|
|
2889
|
+
},
|
|
2890
|
+
/**
|
|
2891
|
+
* Clear per-tag override, falling back to global debug flag.
|
|
2892
|
+
*/
|
|
2893
|
+
clearTagDebug(tag) {
|
|
2894
|
+
delete getState().tags[tag];
|
|
2895
|
+
},
|
|
2896
|
+
/** Returns true if debug mode is enabled for the given tag (or globally). */
|
|
2897
|
+
isDebugEnabled(tag) {
|
|
2898
|
+
if (tag) return isEnabled(tag);
|
|
2899
|
+
return getState().debug;
|
|
2900
|
+
},
|
|
2901
|
+
/**
|
|
2902
|
+
* Debug-level log. Only shown when debug is enabled (globally or for this tag).
|
|
2903
|
+
* Use for detailed operational information.
|
|
2904
|
+
*/
|
|
2905
|
+
debug(tag, message, ...args) {
|
|
2906
|
+
if (!isEnabled(tag)) return;
|
|
2907
|
+
const state = getState();
|
|
2908
|
+
if (state.handler) {
|
|
2909
|
+
state.handler("debug", tag, message, ...args);
|
|
2910
|
+
} else {
|
|
2911
|
+
console.log(`[${tag}]`, message, ...args);
|
|
2912
|
+
}
|
|
2913
|
+
},
|
|
2914
|
+
/**
|
|
2915
|
+
* Warning-level log. ALWAYS shown regardless of debug flag.
|
|
2916
|
+
* Use for important but non-critical issues (timeouts, retries, degraded state).
|
|
2917
|
+
*/
|
|
2918
|
+
warn(tag, message, ...args) {
|
|
2919
|
+
const state = getState();
|
|
2920
|
+
if (state.handler) {
|
|
2921
|
+
state.handler("warn", tag, message, ...args);
|
|
2922
|
+
} else {
|
|
2923
|
+
console.warn(`[${tag}]`, message, ...args);
|
|
2924
|
+
}
|
|
2925
|
+
},
|
|
2926
|
+
/**
|
|
2927
|
+
* Error-level log. ALWAYS shown regardless of debug flag.
|
|
2928
|
+
* Use for critical failures that should never be silenced.
|
|
2929
|
+
*/
|
|
2930
|
+
error(tag, message, ...args) {
|
|
2931
|
+
const state = getState();
|
|
2932
|
+
if (state.handler) {
|
|
2933
|
+
state.handler("error", tag, message, ...args);
|
|
2934
|
+
} else {
|
|
2935
|
+
console.error(`[${tag}]`, message, ...args);
|
|
2936
|
+
}
|
|
2937
|
+
},
|
|
2938
|
+
/** Reset all logger state (debug flag, tags, handler). Primarily for tests. */
|
|
2939
|
+
reset() {
|
|
2940
|
+
const g = globalThis;
|
|
2941
|
+
delete g[LOGGER_KEY];
|
|
2942
|
+
}
|
|
2943
|
+
};
|
|
2944
|
+
|
|
2945
|
+
// impl/shared/wallet-api/WalletApiMailboxProvider.ts
|
|
2946
|
+
function bytesToHex4(bytes) {
|
|
2947
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
2948
|
+
}
|
|
2949
|
+
var TAG = "WalletApiMailbox";
|
|
2950
|
+
var WalletApiMailboxProvider = class {
|
|
2951
|
+
custody;
|
|
2952
|
+
client;
|
|
2953
|
+
stateStore;
|
|
2954
|
+
deriveKeysFn = null;
|
|
2955
|
+
identity = null;
|
|
2956
|
+
fieldKey = null;
|
|
2957
|
+
constructor(config) {
|
|
2958
|
+
this.client = config.client;
|
|
2959
|
+
this.custody = config.custody;
|
|
2960
|
+
this.stateStore = config.stateStore;
|
|
2961
|
+
this.deriveKeysFn = config.deliveryKeys ?? null;
|
|
2962
|
+
}
|
|
2963
|
+
/** @inheritDoc — wired by PaymentsModule at init (engine-owning seam). */
|
|
2964
|
+
bindDeliveryKeys(derive) {
|
|
2965
|
+
this.deriveKeysFn = derive;
|
|
2966
|
+
}
|
|
2967
|
+
deriveKeys(blobBytes) {
|
|
2968
|
+
if (this.deriveKeysFn === null) {
|
|
2969
|
+
throw new WalletApiError(
|
|
2970
|
+
"deliveryKeys derivation not bound \u2014 PaymentsModule binds ITokenEngine.deliveryKeys at init (S7)",
|
|
2971
|
+
"PROTOCOL"
|
|
2972
|
+
);
|
|
2973
|
+
}
|
|
2974
|
+
return this.deriveKeysFn(blobBytes);
|
|
2975
|
+
}
|
|
2976
|
+
/** Bind the wallet identity (derives the S6 field key; binds the client). */
|
|
2977
|
+
setIdentity(identity) {
|
|
2978
|
+
this.identity = identity;
|
|
2979
|
+
this.fieldKey = deriveFieldEncryptionKey(identity.privateKey);
|
|
2980
|
+
this.client.setIdentity(identity);
|
|
2981
|
+
}
|
|
2982
|
+
// ── persisted state (per network + identity) ────────────────────────────────
|
|
2983
|
+
stateKey(kind) {
|
|
2984
|
+
if (!this.identity) {
|
|
2985
|
+
throw new WalletApiError("No identity set \u2014 call setIdentity() first", "CONFIG");
|
|
2986
|
+
}
|
|
2987
|
+
return `wallet-api-mailbox:${kind}:${this.client.network}:${this.identity.chainPubkey}`;
|
|
2988
|
+
}
|
|
2989
|
+
/**
|
|
2990
|
+
* The persistent seen-set (S7): content-derived delivery ids — i.e. the
|
|
2991
|
+
* canonical `SHA-256(tokenId ‖ stateHash)` encoding of the (tokenId,
|
|
2992
|
+
* stateHash) pairs this wallet has already resolved.
|
|
2993
|
+
*/
|
|
2994
|
+
async readSeen() {
|
|
2995
|
+
const raw = await this.stateStore.get(this.stateKey("seen"));
|
|
2996
|
+
if (!raw) return /* @__PURE__ */ new Set();
|
|
2997
|
+
try {
|
|
2998
|
+
return new Set(JSON.parse(raw));
|
|
2999
|
+
} catch {
|
|
3000
|
+
return /* @__PURE__ */ new Set();
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
async addSeen(deliveryIds) {
|
|
3004
|
+
if (deliveryIds.length === 0) return;
|
|
3005
|
+
const seen = await this.readSeen();
|
|
3006
|
+
for (const id of deliveryIds) seen.add(id);
|
|
3007
|
+
await this.stateStore.set(this.stateKey("seen"), JSON.stringify([...seen]));
|
|
3008
|
+
}
|
|
3009
|
+
async readCursorState() {
|
|
3010
|
+
const raw = await this.stateStore.get(this.stateKey("cursor"));
|
|
3011
|
+
if (!raw) return null;
|
|
3012
|
+
try {
|
|
3013
|
+
const parsed = JSON.parse(raw);
|
|
3014
|
+
if (typeof parsed.cursor !== "string" || typeof parsed.syncEpoch !== "string") return null;
|
|
3015
|
+
return { cursor: BigInt(parsed.cursor), syncEpoch: BigInt(parsed.syncEpoch) };
|
|
3016
|
+
} catch {
|
|
3017
|
+
return null;
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
async persistCursorState(cursor, syncEpoch) {
|
|
3021
|
+
await this.stateStore.set(
|
|
3022
|
+
this.stateKey("cursor"),
|
|
3023
|
+
JSON.stringify({ cursor: cursor.toString(), syncEpoch: syncEpoch.toString() })
|
|
3024
|
+
);
|
|
3025
|
+
}
|
|
3026
|
+
// ── deliver (S3: upload → deposit) ──────────────────────────────────────────
|
|
3027
|
+
/**
|
|
3028
|
+
* Idempotent per (token, state): the upload is content-addressed (412 =
|
|
3029
|
+
* already present = success — §5.2) and the deposit is idempotent by the
|
|
3030
|
+
* content-derived entry_id — it succeeds even after the recipient claimed
|
|
3031
|
+
* (§6), which is what makes the sender's journal replay safe.
|
|
3032
|
+
*/
|
|
3033
|
+
async deliver(recipientPubkey, blob, options) {
|
|
3034
|
+
if (!this.fieldKey) {
|
|
3035
|
+
throw new WalletApiError("No identity set \u2014 call setIdentity() first", "CONFIG");
|
|
3036
|
+
}
|
|
3037
|
+
const keys = composeDeliveryKeys(await this.deriveKeys(blob));
|
|
3038
|
+
const wire = unwrapTokenBlobBytes(blob);
|
|
3039
|
+
const sha = bytesToHex4(sha256(wire));
|
|
3040
|
+
const urls = await this.client.getUploadUrls([{ sha256: sha, size: wire.length }]);
|
|
3041
|
+
const upload = urls.find((u) => u.sha256 === sha);
|
|
3042
|
+
if (!upload) {
|
|
3043
|
+
throw new WalletApiError(`upload-urls response missing sha256 ${sha}`, "PROTOCOL");
|
|
3044
|
+
}
|
|
3045
|
+
await this.client.uploadBlob(upload.putUrl, wire);
|
|
3046
|
+
let entryId;
|
|
3047
|
+
try {
|
|
3048
|
+
entryId = await this.client.depositMailbox({
|
|
3049
|
+
recipientPubkey,
|
|
3050
|
+
key: upload.key,
|
|
3051
|
+
transferId: options.transferId,
|
|
3052
|
+
stateHash: keys.stateHash,
|
|
3053
|
+
tokenId: keys.tokenId,
|
|
3054
|
+
...options.memo !== void 0 ? { memo: encryptField(this.fieldKey, options.memo) } : {}
|
|
3055
|
+
});
|
|
3056
|
+
} catch (err) {
|
|
3057
|
+
if (err instanceof WalletApiError && err.status === 409) {
|
|
3058
|
+
return { deliveryId: keys.deliveryId };
|
|
3059
|
+
}
|
|
3060
|
+
throw err;
|
|
3061
|
+
}
|
|
3062
|
+
if (entryId !== keys.deliveryId) {
|
|
3063
|
+
throw new WalletApiError(
|
|
3064
|
+
`mailbox deposit returned a non-content-derived entryId (got ${entryId}, expected ${keys.deliveryId})`,
|
|
3065
|
+
"PROTOCOL"
|
|
3066
|
+
);
|
|
3067
|
+
}
|
|
3068
|
+
return { deliveryId: keys.deliveryId };
|
|
3069
|
+
}
|
|
3070
|
+
// ── incoming (S3: mailbox pull) ─────────────────────────────────────────────
|
|
3071
|
+
/**
|
|
3072
|
+
* Pull entries since the given cursor (or the persisted one), loop while
|
|
3073
|
+
* `more`, and yield claimable deliveries not yet in the seen-set. The
|
|
3074
|
+
* persisted cursor advances to the server's read pointer (§6) — entries at
|
|
3075
|
+
* or below it are all resolved; the seen-set (not the cursor) is the replay
|
|
3076
|
+
* guard, so a cursor reset is safe.
|
|
3077
|
+
*/
|
|
3078
|
+
async *incoming(sinceCursor) {
|
|
3079
|
+
const persisted = await this.readCursorState();
|
|
3080
|
+
let since;
|
|
3081
|
+
if (sinceCursor !== void 0) {
|
|
3082
|
+
since = BigInt(sinceCursor);
|
|
3083
|
+
} else {
|
|
3084
|
+
since = persisted?.cursor ?? 0n;
|
|
3085
|
+
}
|
|
3086
|
+
const seen = await this.readSeen();
|
|
3087
|
+
let page = await this.client.listMailbox(since);
|
|
3088
|
+
if (persisted !== null && page.syncEpoch !== persisted.syncEpoch && sinceCursor === void 0) {
|
|
3089
|
+
page = await this.client.listMailbox(0n);
|
|
3090
|
+
}
|
|
3091
|
+
for (; ; ) {
|
|
3092
|
+
for (const entry of page.entries) {
|
|
3093
|
+
if (seen.has(entry.entryId)) continue;
|
|
3094
|
+
if (entry.status !== "unclaimed") {
|
|
3095
|
+
seen.add(entry.entryId);
|
|
3096
|
+
await this.addSeen([entry.entryId]);
|
|
3097
|
+
continue;
|
|
3098
|
+
}
|
|
3099
|
+
yield this.toIncomingDelivery(entry);
|
|
3100
|
+
}
|
|
3101
|
+
await this.persistCursorState(page.readPointer, page.syncEpoch);
|
|
3102
|
+
if (!page.more) break;
|
|
3103
|
+
const lastSeq = page.entries.length > 0 ? page.entries[page.entries.length - 1].seq : since;
|
|
3104
|
+
page = await this.client.listMailbox(lastSeq);
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
toIncomingDelivery(entry) {
|
|
3108
|
+
const client = this.client;
|
|
3109
|
+
const fieldKey = this.fieldKey;
|
|
3110
|
+
const deriveKeys = (bytes) => this.deriveKeys(bytes);
|
|
3111
|
+
let memo;
|
|
3112
|
+
if (entry.memo !== void 0 && fieldKey) {
|
|
3113
|
+
try {
|
|
3114
|
+
memo = decryptField(fieldKey, entry.memo);
|
|
3115
|
+
} catch {
|
|
3116
|
+
logger.debug(TAG, `memo for entry ${entry.entryId.slice(0, 12)}\u2026 not decryptable with this wallet's key`);
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
return {
|
|
3120
|
+
deliveryId: entry.entryId,
|
|
3121
|
+
transferId: entry.transferId,
|
|
3122
|
+
senderPubkey: entry.senderPubkey,
|
|
3123
|
+
...memo !== void 0 ? { memo } : {},
|
|
3124
|
+
cursor: entry.seq.toString(),
|
|
3125
|
+
async fetchBlob() {
|
|
3126
|
+
if (entry.blobCollected || !entry.getUrl) {
|
|
3127
|
+
throw new WalletApiError(
|
|
3128
|
+
`mailbox entry ${entry.entryId} has no fetchable blob (blobCollected)`,
|
|
3129
|
+
"PROTOCOL"
|
|
3130
|
+
);
|
|
3131
|
+
}
|
|
3132
|
+
const bytes = await client.fetchBlob(entry.getUrl);
|
|
3133
|
+
const keys = composeDeliveryKeys(await deriveKeys(bytes));
|
|
3134
|
+
if (keys.deliveryId !== entry.entryId) {
|
|
3135
|
+
throw new WalletApiError(
|
|
3136
|
+
`mailbox blob does not match its entry id (expected ${entry.entryId}, bytes derive ${keys.deliveryId})`,
|
|
3137
|
+
"PROTOCOL"
|
|
3138
|
+
);
|
|
3139
|
+
}
|
|
3140
|
+
return bytes;
|
|
3141
|
+
}
|
|
3142
|
+
};
|
|
3143
|
+
}
|
|
3144
|
+
// ── ack (claim / reject) ────────────────────────────────────────────────────
|
|
3145
|
+
async ack(deliveryId, disposition) {
|
|
3146
|
+
if (disposition === "claimed") {
|
|
3147
|
+
const intoInventory = this.custody === "inventory";
|
|
3148
|
+
const result = await this.client.claimMailbox([deliveryId], intoInventory);
|
|
3149
|
+
const failed = result.failed.find((f) => f.entryId === deliveryId);
|
|
3150
|
+
if (failed) {
|
|
3151
|
+
throw new WalletApiError(
|
|
3152
|
+
`mailbox claim failed for ${deliveryId}: ${failed.code}`,
|
|
3153
|
+
"CONFLICT"
|
|
3154
|
+
);
|
|
3155
|
+
}
|
|
3156
|
+
} else {
|
|
3157
|
+
await this.client.rejectMailbox([deliveryId]);
|
|
3158
|
+
}
|
|
3159
|
+
await this.addSeen([deliveryId]);
|
|
3160
|
+
}
|
|
3161
|
+
// ── wake (optional hook — §9: a nudge, never a correctness dependency) ──────
|
|
3162
|
+
onWake(callback) {
|
|
3163
|
+
let closed = false;
|
|
3164
|
+
let close = null;
|
|
3165
|
+
void this.client.connectWakeSocket((wake) => {
|
|
3166
|
+
if (wake.stream === "mailbox") callback();
|
|
3167
|
+
}).then((handle) => {
|
|
3168
|
+
if (closed) handle.close();
|
|
3169
|
+
else close = () => handle.close();
|
|
3170
|
+
}).catch((err) => {
|
|
3171
|
+
logger.debug(TAG, "wake socket unavailable (polling continues):", err);
|
|
3172
|
+
});
|
|
3173
|
+
return () => {
|
|
3174
|
+
closed = true;
|
|
3175
|
+
close?.();
|
|
3176
|
+
};
|
|
3177
|
+
}
|
|
3178
|
+
};
|
|
3179
|
+
function createWalletApiMailboxProvider(config) {
|
|
3180
|
+
return new WalletApiMailboxProvider(config);
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3183
|
+
// impl/shared/wallet-api/composition.ts
|
|
3184
|
+
function createSphereProviders(base, ports = {}) {
|
|
3185
|
+
return {
|
|
3186
|
+
...base,
|
|
3187
|
+
...ports.storage ? { tokenStorage: ports.storage } : {},
|
|
3188
|
+
...ports.engine ? { oracle: ports.engine } : {},
|
|
3189
|
+
...ports.delivery ? { delivery: ports.delivery } : {}
|
|
3190
|
+
};
|
|
3191
|
+
}
|
|
3192
|
+
function buildClient(base, config) {
|
|
3193
|
+
const stateStore = config.stateStore ?? base.storage;
|
|
3194
|
+
const client = config.client ?? new WalletApiClient({
|
|
3195
|
+
baseUrl: config.baseUrl,
|
|
3196
|
+
network: config.network,
|
|
3197
|
+
deviceId: config.deviceId ?? `sphere-${crypto.randomUUID()}`,
|
|
3198
|
+
storage: stateStore,
|
|
3199
|
+
fetchFn: config.fetchFn,
|
|
3200
|
+
webSocketFactory: config.webSocketFactory
|
|
3201
|
+
});
|
|
3202
|
+
return { client, stateStore };
|
|
3203
|
+
}
|
|
3204
|
+
function createWalletApiProviders(base, config) {
|
|
3205
|
+
const { client, stateStore } = buildClient(base, config);
|
|
3206
|
+
const tokenStorage = new WalletApiTokenStorageProvider({
|
|
3207
|
+
client,
|
|
3208
|
+
stateStore,
|
|
3209
|
+
verifyToken: config.verifyToken
|
|
3210
|
+
});
|
|
3211
|
+
const delivery = new WalletApiMailboxProvider({ client, custody: "inventory", stateStore });
|
|
3212
|
+
return { ...base, tokenStorage, delivery, walletApi: client };
|
|
3213
|
+
}
|
|
3214
|
+
function createOwnStorageWalletApiProviders(base, config) {
|
|
3215
|
+
const { client, stateStore } = buildClient(base, config);
|
|
3216
|
+
const delivery = new WalletApiMailboxProvider({ client, custody: "external", stateStore });
|
|
3217
|
+
return { ...base, delivery, walletApi: client };
|
|
3218
|
+
}
|
|
3219
|
+
/*! Bundled license information:
|
|
3220
|
+
|
|
3221
|
+
@noble/ciphers/utils.js:
|
|
3222
|
+
(*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) *)
|
|
3223
|
+
*/
|
|
3224
|
+
//# sourceMappingURL=index.cjs.map
|