@cavos/kit 0.0.4 → 0.0.5
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/{Cavos-x9qFpOvJ.d.mts → Cavos-D20EtgOK.d.mts} +289 -188
- package/dist/{Cavos-x9qFpOvJ.d.ts → Cavos-D20EtgOK.d.ts} +289 -188
- package/dist/{chunk-F2J25XSL.mjs → chunk-M5BGBODC.mjs} +1517 -608
- package/dist/chunk-M5BGBODC.mjs.map +1 -0
- package/dist/index.d.mts +290 -92
- package/dist/index.d.ts +290 -92
- package/dist/index.js +1533 -610
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/react/index.d.mts +8 -3
- package/dist/react/index.d.ts +8 -3
- package/dist/react/index.js +1501 -603
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +32 -10
- package/dist/react/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-F2J25XSL.mjs.map +0 -1
package/dist/react/index.js
CHANGED
|
@@ -172,7 +172,7 @@ var CAVOS_PAYMASTER_URL = {
|
|
|
172
172
|
mainnet: "https://paymaster.cavos.xyz"
|
|
173
173
|
};
|
|
174
174
|
var DEVICE_ACCOUNT_CLASS_HASH = {
|
|
175
|
-
sepolia: "
|
|
175
|
+
sepolia: "0x765f000b7021577aa0d6c206fb3d8ac73571686b3e76b9dc9b6d59a372e8c2c",
|
|
176
176
|
mainnet: "0x1840aded59e8a0d2b440a134cb9079a7fc11b06c77f58ed189ab436a034ca6a"
|
|
177
177
|
};
|
|
178
178
|
|
|
@@ -182,21 +182,34 @@ var StarknetAdapter = class {
|
|
|
182
182
|
this.opts = opts;
|
|
183
183
|
this.chain = "starknet";
|
|
184
184
|
}
|
|
185
|
-
|
|
185
|
+
/**
|
|
186
|
+
* Deterministic address = f(addressSeed) ONLY. The device pubkey is NOT
|
|
187
|
+
* part of the derivation — anti-squatting is the integrator's responsibility
|
|
188
|
+
* (keep `appSalt` secret; deploy on first login). This makes the address
|
|
189
|
+
* recomputable by the user from (userId, appSalt) alone, even after losing
|
|
190
|
+
* every device.
|
|
191
|
+
*
|
|
192
|
+
* `initialSigner` in `ComputeAddressParams` is IGNORED on Starknet (kept in
|
|
193
|
+
* the shared type for Solana/Stellar, which still include it).
|
|
194
|
+
*/
|
|
195
|
+
computeAddress({ addressSeed, salt }) {
|
|
186
196
|
return starknet.hash.calculateContractAddressFromHash(
|
|
187
197
|
starknet.num.toHex(salt ?? addressSeed),
|
|
188
198
|
this.opts.classHash,
|
|
189
|
-
this.constructorCalldata(addressSeed
|
|
199
|
+
this.constructorCalldata(addressSeed),
|
|
190
200
|
0
|
|
191
201
|
// deployerAddress 0 => deterministic counterfactual address
|
|
192
202
|
);
|
|
193
203
|
}
|
|
194
|
-
/**
|
|
195
|
-
*
|
|
196
|
-
*
|
|
204
|
+
/**
|
|
205
|
+
* UDC deploy call. The constructor takes ONLY the seed — no device pubkey —
|
|
206
|
+
* so the account is born with no signers. The caller MUST follow up with
|
|
207
|
+
* `buildInitialize` in the same multicall (or a separate tx) to register the
|
|
208
|
+
* first device signer; otherwise the account is unusable.
|
|
209
|
+
*/
|
|
197
210
|
buildDeploy(params) {
|
|
198
211
|
const salt = params.salt ?? params.addressSeed;
|
|
199
|
-
const calldata = this.constructorCalldata(params.addressSeed
|
|
212
|
+
const calldata = this.constructorCalldata(params.addressSeed);
|
|
200
213
|
return [
|
|
201
214
|
{
|
|
202
215
|
contractAddress: UDC_ADDRESS,
|
|
@@ -212,9 +225,22 @@ var StarknetAdapter = class {
|
|
|
212
225
|
}
|
|
213
226
|
];
|
|
214
227
|
}
|
|
215
|
-
/** Constructor calldata: [address_seed
|
|
216
|
-
constructorCalldata(addressSeed
|
|
217
|
-
return [starknet.num.toHex(addressSeed)
|
|
228
|
+
/** Constructor calldata: [address_seed]. Device pubkey is registered post-deploy via initialize. */
|
|
229
|
+
constructorCalldata(addressSeed) {
|
|
230
|
+
return [starknet.num.toHex(addressSeed)];
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* `initialize` call: registers the first device signer. Callable only while
|
|
234
|
+
* the account has no signers (one-shot). In production this is bundled with
|
|
235
|
+
* the UDC deploy in a single sponsored multicall — see `connectStarknet`.
|
|
236
|
+
* Anti-squatting is NOT enforced on-chain.
|
|
237
|
+
*/
|
|
238
|
+
buildInitialize(accountAddress, devicePubkey) {
|
|
239
|
+
return {
|
|
240
|
+
contractAddress: accountAddress,
|
|
241
|
+
entrypoint: "initialize",
|
|
242
|
+
calldata: pubkeyCalldata(devicePubkey)
|
|
243
|
+
};
|
|
218
244
|
}
|
|
219
245
|
buildAddSigner(accountAddress, signer) {
|
|
220
246
|
return { contractAddress: accountAddress, entrypoint: "add_signer", calldata: pubkeyCalldata(signer) };
|
|
@@ -450,26 +476,30 @@ var SolanaAdapter = class {
|
|
|
450
476
|
this.chain = "solana";
|
|
451
477
|
this.programId = new web3_js.PublicKey(opts.programId ?? DEVICE_ACCOUNT_PROGRAM_ID);
|
|
452
478
|
}
|
|
453
|
-
/**
|
|
454
|
-
|
|
455
|
-
|
|
479
|
+
/**
|
|
480
|
+
* Deterministic account address: PDA of [ACCOUNT_SEED, addressSeed] — the
|
|
481
|
+
* device pubkey is NOT part of the seeds, so the address is recomputable
|
|
482
|
+
* from (userId, appSalt) alone. Anti-squatting is the integrator's
|
|
483
|
+
* responsibility (keep `appSalt` secret; deploy on first login).
|
|
484
|
+
*/
|
|
485
|
+
computeAddress(addressSeed) {
|
|
486
|
+
return this.pda(addressSeed).toBase58();
|
|
456
487
|
}
|
|
457
|
-
pda(addressSeed
|
|
488
|
+
pda(addressSeed) {
|
|
458
489
|
const [pda] = web3_js.PublicKey.findProgramAddressSync(
|
|
459
|
-
[
|
|
460
|
-
Buffer.from(ACCOUNT_SEED),
|
|
461
|
-
Buffer.from(addressSeed),
|
|
462
|
-
Buffer.from(initialCompressed.slice(1, 33))
|
|
463
|
-
// x-coordinate
|
|
464
|
-
],
|
|
490
|
+
[Buffer.from(ACCOUNT_SEED), Buffer.from(addressSeed)],
|
|
465
491
|
this.programId
|
|
466
492
|
);
|
|
467
493
|
return pda;
|
|
468
494
|
}
|
|
469
|
-
/**
|
|
495
|
+
/**
|
|
496
|
+
* `initialize` instruction: creates the account PDA and registers the first
|
|
497
|
+
* device signer. No attestation is required — anti-squatting is NOT enforced
|
|
498
|
+
* on-chain.
|
|
499
|
+
*/
|
|
470
500
|
buildInitialize(addressSeed, payer, initialSigner) {
|
|
471
501
|
const initialCompressed = compressedPubkey(initialSigner);
|
|
472
|
-
const account = this.pda(addressSeed
|
|
502
|
+
const account = this.pda(addressSeed);
|
|
473
503
|
const data = Buffer.concat([
|
|
474
504
|
anchorDiscriminator("initialize"),
|
|
475
505
|
Buffer.from(addressSeed),
|
|
@@ -477,7 +507,7 @@ var SolanaAdapter = class {
|
|
|
477
507
|
Buffer.from(initialCompressed)
|
|
478
508
|
// [u8;33]
|
|
479
509
|
]);
|
|
480
|
-
|
|
510
|
+
const programIx = new web3_js.TransactionInstruction({
|
|
481
511
|
programId: this.programId,
|
|
482
512
|
keys: [
|
|
483
513
|
{ pubkey: account, isSigner: false, isWritable: true },
|
|
@@ -486,6 +516,7 @@ var SolanaAdapter = class {
|
|
|
486
516
|
],
|
|
487
517
|
data
|
|
488
518
|
});
|
|
519
|
+
return [programIx];
|
|
489
520
|
}
|
|
490
521
|
/** `[precompile, add_signer]` bundle, authorized by an existing device signer. */
|
|
491
522
|
async buildAddSigner(account, newSigner) {
|
|
@@ -824,8 +855,8 @@ function u64le(n) {
|
|
|
824
855
|
new DataView(b.buffer, b.byteOffset, 8).setBigUint64(0, BigInt(n), true);
|
|
825
856
|
return b;
|
|
826
857
|
}
|
|
827
|
-
function readU64le(
|
|
828
|
-
return new DataView(
|
|
858
|
+
function readU64le(buf3, offset) {
|
|
859
|
+
return new DataView(buf3.buffer, buf3.byteOffset, buf3.length).getBigUint64(
|
|
829
860
|
offset,
|
|
830
861
|
true
|
|
831
862
|
);
|
|
@@ -884,11 +915,11 @@ var SolanaRelayer = class {
|
|
|
884
915
|
async send(instructions) {
|
|
885
916
|
const feePayer = await this.getFeePayer();
|
|
886
917
|
const { blockhash } = await this.opts.connection.getLatestBlockhash("confirmed");
|
|
887
|
-
const
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
const serialized =
|
|
918
|
+
const tx3 = new web3_js.Transaction();
|
|
919
|
+
tx3.feePayer = feePayer;
|
|
920
|
+
tx3.recentBlockhash = blockhash;
|
|
921
|
+
tx3.add(...instructions);
|
|
922
|
+
const serialized = tx3.serialize({ requireAllSignatures: false, verifySignatures: false }).toString("base64");
|
|
892
923
|
const res = await fetch(`${this.opts.baseUrl}/api/solana/relay`, {
|
|
893
924
|
method: "POST",
|
|
894
925
|
headers: { "Content-Type": "application/json" },
|
|
@@ -1330,16 +1361,16 @@ var CavosSolana = class _CavosSolana {
|
|
|
1330
1361
|
opts.feePayer
|
|
1331
1362
|
);
|
|
1332
1363
|
}
|
|
1333
|
-
const address = adapter.computeAddress(addressSeed
|
|
1364
|
+
const address = adapter.computeAddress(addressSeed);
|
|
1334
1365
|
const deployed = await connection.getAccountInfo(new web3_js.PublicKey(address)) !== null;
|
|
1335
1366
|
if (!deployed) {
|
|
1336
1367
|
if (relayer) {
|
|
1337
1368
|
const payer = await relayer.getFeePayer();
|
|
1338
|
-
const
|
|
1339
|
-
await relayer.send(
|
|
1369
|
+
const ixs = adapter.buildInitialize(addressSeed, payer.toBase58(), devicePubkey);
|
|
1370
|
+
await relayer.send(ixs);
|
|
1340
1371
|
} else if (opts.feePayer) {
|
|
1341
|
-
const
|
|
1342
|
-
await web3_js.sendAndConfirmTransaction(connection, new web3_js.Transaction().add(
|
|
1372
|
+
const ixs = adapter.buildInitialize(addressSeed, opts.feePayer.publicKey.toBase58(), devicePubkey);
|
|
1373
|
+
await web3_js.sendAndConfirmTransaction(connection, new web3_js.Transaction().add(...ixs), [opts.feePayer]);
|
|
1343
1374
|
} else {
|
|
1344
1375
|
throw new Error("kit/solana: a relayer (appId) or feePayer is required to initialize a new account");
|
|
1345
1376
|
}
|
|
@@ -1438,12 +1469,12 @@ var CavosSolana = class _CavosSolana {
|
|
|
1438
1469
|
return { transactionHash: await this.send(ixs) };
|
|
1439
1470
|
}
|
|
1440
1471
|
/** Move `amount` lamports out of the account to `destination` (device-signed). */
|
|
1441
|
-
async execute(amount, destination) {
|
|
1472
|
+
async execute(amount, destination, opts) {
|
|
1442
1473
|
if (this.status !== "ready") {
|
|
1443
1474
|
throw new Error("kit/solana: this device is not yet an authorized signer of the wallet");
|
|
1444
1475
|
}
|
|
1445
1476
|
const ixs = await this.adapter.buildExecuteTransfer(this.address, destination, amount);
|
|
1446
|
-
return this.send(ixs);
|
|
1477
|
+
return this.send(ixs, opts);
|
|
1447
1478
|
}
|
|
1448
1479
|
/**
|
|
1449
1480
|
* Run arbitrary CPI `instructions` with the account PDA as signer (device-
|
|
@@ -1453,14 +1484,16 @@ var CavosSolana = class _CavosSolana {
|
|
|
1453
1484
|
*
|
|
1454
1485
|
* What the relayer will sponsor is constrained by the app's Solana program
|
|
1455
1486
|
* allowlist (configured in the dashboard) — programs outside the allowlist are
|
|
1456
|
-
* rejected before co-signing.
|
|
1487
|
+
* rejected before co-signing. Pass `{ sponsored: false }` to bypass the relayer
|
|
1488
|
+
* and pay the fee from a configured `feePayer` (e.g. for allowlisted programs
|
|
1489
|
+
* the relayer rejects, or to test the device signature end-to-end).
|
|
1457
1490
|
*/
|
|
1458
|
-
async executeInstructions(instructions) {
|
|
1491
|
+
async executeInstructions(instructions, opts) {
|
|
1459
1492
|
if (this.status !== "ready") {
|
|
1460
1493
|
throw new Error("kit/solana: this device is not yet an authorized signer of the wallet");
|
|
1461
1494
|
}
|
|
1462
1495
|
const ixs = await this.adapter.buildExecute(this.address, instructions);
|
|
1463
|
-
return this.send(ixs);
|
|
1496
|
+
return this.send(ixs, opts);
|
|
1464
1497
|
}
|
|
1465
1498
|
/**
|
|
1466
1499
|
* Register the backup signer derived from `code` as an authorized signer of this
|
|
@@ -1539,319 +1572,974 @@ var CavosSolana = class _CavosSolana {
|
|
|
1539
1572
|
opts.feePayer
|
|
1540
1573
|
);
|
|
1541
1574
|
}
|
|
1542
|
-
|
|
1543
|
-
|
|
1575
|
+
/**
|
|
1576
|
+
* Submit a built instruction bundle. Sponsored by default (relayer pays the
|
|
1577
|
+
* fee); pass `{ sponsored: false }` to self-fund via the configured `feePayer`.
|
|
1578
|
+
* The device signature is embedded inside the secp256r1 precompile instruction,
|
|
1579
|
+
* NOT applied as a Solana tx signature — so switching only changes who pays,
|
|
1580
|
+
* never the signing identity.
|
|
1581
|
+
*/
|
|
1582
|
+
async send(ixs, opts) {
|
|
1583
|
+
const sponsored = opts?.sponsored !== false;
|
|
1584
|
+
if (sponsored && this.relayer) return this.relayer.send(ixs);
|
|
1544
1585
|
if (this.feePayer) {
|
|
1545
1586
|
return web3_js.sendAndConfirmTransaction(this.connection, new web3_js.Transaction().add(...ixs), [this.feePayer]);
|
|
1546
1587
|
}
|
|
1547
|
-
throw new Error(
|
|
1588
|
+
throw new Error(
|
|
1589
|
+
`kit/solana: cannot ${sponsored ? "sponsor" : "self-fund"} \u2014 no ${sponsored ? "relayer" : "feePayer"} configured`
|
|
1590
|
+
);
|
|
1548
1591
|
}
|
|
1549
1592
|
};
|
|
1550
1593
|
var defaultRegistry = new InMemoryWalletRegistry();
|
|
1551
1594
|
|
|
1552
1595
|
// src/chains/stellar/constants.ts
|
|
1553
|
-
var FACTORY_CONTRACT_ID = {
|
|
1554
|
-
// Re-deployed 2026-07-01 with the passkey-approval device-account wasm (batched
|
|
1555
|
-
// multi-chain challenge). The factory pins the wasm hash immutably, so a new
|
|
1556
|
-
// wasm needs a new factory → new account addresses; testnet has no prod wallets.
|
|
1557
|
-
"stellar-testnet": "CBCJIODXIEBOXXD66KCUCF7ZDYJARKI4ZIVQOVWPULOBH5XGNCDP6W3I",
|
|
1558
|
-
// Set once the factory is deployed to mainnet (its address differs — network id
|
|
1559
|
-
// is part of contract-address derivation).
|
|
1560
|
-
"stellar-mainnet": ""
|
|
1561
|
-
};
|
|
1562
1596
|
var STELLAR_NETWORKS = {
|
|
1563
1597
|
"stellar-testnet": {
|
|
1564
|
-
rpcUrl: "https://soroban-testnet.stellar.org",
|
|
1565
1598
|
passphrase: "Test SDF Network ; September 2015"
|
|
1566
1599
|
},
|
|
1567
1600
|
"stellar-mainnet": {
|
|
1568
|
-
rpcUrl: "https://soroban-rpc.mainnet.stellar.gateway.fm",
|
|
1569
1601
|
passphrase: "Public Global Stellar Network ; September 2015"
|
|
1570
1602
|
}
|
|
1571
1603
|
};
|
|
1572
|
-
var
|
|
1573
|
-
"stellar-testnet": "
|
|
1574
|
-
"stellar-mainnet": "
|
|
1604
|
+
var HORIZON_URL = {
|
|
1605
|
+
"stellar-testnet": "https://horizon-testnet.stellar.org",
|
|
1606
|
+
"stellar-mainnet": "https://horizon.stellar.org"
|
|
1607
|
+
};
|
|
1608
|
+
|
|
1609
|
+
// node_modules/@noble/ciphers/esm/utils.js
|
|
1610
|
+
function isBytes(a) {
|
|
1611
|
+
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
|
|
1612
|
+
}
|
|
1613
|
+
function abytes(b, ...lengths) {
|
|
1614
|
+
if (!isBytes(b))
|
|
1615
|
+
throw new Error("Uint8Array expected");
|
|
1616
|
+
if (lengths.length > 0 && !lengths.includes(b.length))
|
|
1617
|
+
throw new Error("Uint8Array expected of length " + lengths + ", got length=" + b.length);
|
|
1618
|
+
}
|
|
1619
|
+
function aexists(instance, checkFinished = true) {
|
|
1620
|
+
if (instance.destroyed)
|
|
1621
|
+
throw new Error("Hash instance has been destroyed");
|
|
1622
|
+
if (checkFinished && instance.finished)
|
|
1623
|
+
throw new Error("Hash#digest() has already been called");
|
|
1624
|
+
}
|
|
1625
|
+
function aoutput(out, instance) {
|
|
1626
|
+
abytes(out);
|
|
1627
|
+
const min = instance.outputLen;
|
|
1628
|
+
if (out.length < min) {
|
|
1629
|
+
throw new Error("digestInto() expects output buffer of length at least " + min);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
function u8(arr) {
|
|
1633
|
+
return new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
1634
|
+
}
|
|
1635
|
+
function u32(arr) {
|
|
1636
|
+
return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
|
1637
|
+
}
|
|
1638
|
+
function clean(...arrays) {
|
|
1639
|
+
for (let i = 0; i < arrays.length; i++) {
|
|
1640
|
+
arrays[i].fill(0);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
function createView(arr) {
|
|
1644
|
+
return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
1645
|
+
}
|
|
1646
|
+
var isLE = /* @__PURE__ */ (() => new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68)();
|
|
1647
|
+
function utf8ToBytes(str) {
|
|
1648
|
+
if (typeof str !== "string")
|
|
1649
|
+
throw new Error("string expected");
|
|
1650
|
+
return new Uint8Array(new TextEncoder().encode(str));
|
|
1651
|
+
}
|
|
1652
|
+
function toBytes(data) {
|
|
1653
|
+
if (typeof data === "string")
|
|
1654
|
+
data = utf8ToBytes(data);
|
|
1655
|
+
else if (isBytes(data))
|
|
1656
|
+
data = copyBytes(data);
|
|
1657
|
+
else
|
|
1658
|
+
throw new Error("Uint8Array expected, got " + typeof data);
|
|
1659
|
+
return data;
|
|
1660
|
+
}
|
|
1661
|
+
function equalBytes(a, b) {
|
|
1662
|
+
if (a.length !== b.length)
|
|
1663
|
+
return false;
|
|
1664
|
+
let diff = 0;
|
|
1665
|
+
for (let i = 0; i < a.length; i++)
|
|
1666
|
+
diff |= a[i] ^ b[i];
|
|
1667
|
+
return diff === 0;
|
|
1668
|
+
}
|
|
1669
|
+
var wrapCipher = /* @__NO_SIDE_EFFECTS__ */ (params, constructor) => {
|
|
1670
|
+
function wrappedCipher(key, ...args) {
|
|
1671
|
+
abytes(key);
|
|
1672
|
+
if (!isLE)
|
|
1673
|
+
throw new Error("Non little-endian hardware is not yet supported");
|
|
1674
|
+
if (params.nonceLength !== void 0) {
|
|
1675
|
+
const nonce = args[0];
|
|
1676
|
+
if (!nonce)
|
|
1677
|
+
throw new Error("nonce / iv required");
|
|
1678
|
+
if (params.varSizeNonce)
|
|
1679
|
+
abytes(nonce);
|
|
1680
|
+
else
|
|
1681
|
+
abytes(nonce, params.nonceLength);
|
|
1682
|
+
}
|
|
1683
|
+
const tagl = params.tagLength;
|
|
1684
|
+
if (tagl && args[1] !== void 0) {
|
|
1685
|
+
abytes(args[1]);
|
|
1686
|
+
}
|
|
1687
|
+
const cipher = constructor(key, ...args);
|
|
1688
|
+
const checkOutput = (fnLength, output) => {
|
|
1689
|
+
if (output !== void 0) {
|
|
1690
|
+
if (fnLength !== 2)
|
|
1691
|
+
throw new Error("cipher output not supported");
|
|
1692
|
+
abytes(output);
|
|
1693
|
+
}
|
|
1694
|
+
};
|
|
1695
|
+
let called = false;
|
|
1696
|
+
const wrCipher = {
|
|
1697
|
+
encrypt(data, output) {
|
|
1698
|
+
if (called)
|
|
1699
|
+
throw new Error("cannot encrypt() twice with same key + nonce");
|
|
1700
|
+
called = true;
|
|
1701
|
+
abytes(data);
|
|
1702
|
+
checkOutput(cipher.encrypt.length, output);
|
|
1703
|
+
return cipher.encrypt(data, output);
|
|
1704
|
+
},
|
|
1705
|
+
decrypt(data, output) {
|
|
1706
|
+
abytes(data);
|
|
1707
|
+
if (tagl && data.length < tagl)
|
|
1708
|
+
throw new Error("invalid ciphertext length: smaller than tagLength=" + tagl);
|
|
1709
|
+
checkOutput(cipher.decrypt.length, output);
|
|
1710
|
+
return cipher.decrypt(data, output);
|
|
1711
|
+
}
|
|
1712
|
+
};
|
|
1713
|
+
return wrCipher;
|
|
1714
|
+
}
|
|
1715
|
+
Object.assign(wrappedCipher, params);
|
|
1716
|
+
return wrappedCipher;
|
|
1717
|
+
};
|
|
1718
|
+
function getOutput(expectedLength, out, onlyAligned = true) {
|
|
1719
|
+
if (out === void 0)
|
|
1720
|
+
return new Uint8Array(expectedLength);
|
|
1721
|
+
if (out.length !== expectedLength)
|
|
1722
|
+
throw new Error("invalid output length, expected " + expectedLength + ", got: " + out.length);
|
|
1723
|
+
if (onlyAligned && !isAligned32(out))
|
|
1724
|
+
throw new Error("invalid output, must be aligned");
|
|
1725
|
+
return out;
|
|
1726
|
+
}
|
|
1727
|
+
function setBigUint64(view, byteOffset, value, isLE2) {
|
|
1728
|
+
if (typeof view.setBigUint64 === "function")
|
|
1729
|
+
return view.setBigUint64(byteOffset, value, isLE2);
|
|
1730
|
+
const _32n = BigInt(32);
|
|
1731
|
+
const _u32_max = BigInt(4294967295);
|
|
1732
|
+
const wh = Number(value >> _32n & _u32_max);
|
|
1733
|
+
const wl = Number(value & _u32_max);
|
|
1734
|
+
const h = 0;
|
|
1735
|
+
const l = 4;
|
|
1736
|
+
view.setUint32(byteOffset + h, wh, isLE2);
|
|
1737
|
+
view.setUint32(byteOffset + l, wl, isLE2);
|
|
1738
|
+
}
|
|
1739
|
+
function u64Lengths(dataLength, aadLength, isLE2) {
|
|
1740
|
+
const num5 = new Uint8Array(16);
|
|
1741
|
+
const view = createView(num5);
|
|
1742
|
+
setBigUint64(view, 0, BigInt(aadLength), isLE2);
|
|
1743
|
+
setBigUint64(view, 8, BigInt(dataLength), isLE2);
|
|
1744
|
+
return num5;
|
|
1745
|
+
}
|
|
1746
|
+
function isAligned32(bytes) {
|
|
1747
|
+
return bytes.byteOffset % 4 === 0;
|
|
1748
|
+
}
|
|
1749
|
+
function copyBytes(bytes) {
|
|
1750
|
+
return Uint8Array.from(bytes);
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
// node_modules/@noble/ciphers/esm/_polyval.js
|
|
1754
|
+
var BLOCK_SIZE = 16;
|
|
1755
|
+
var ZEROS16 = /* @__PURE__ */ new Uint8Array(16);
|
|
1756
|
+
var ZEROS32 = u32(ZEROS16);
|
|
1757
|
+
var POLY = 225;
|
|
1758
|
+
var mul2 = (s0, s1, s2, s3) => {
|
|
1759
|
+
const hiBit = s3 & 1;
|
|
1760
|
+
return {
|
|
1761
|
+
s3: s2 << 31 | s3 >>> 1,
|
|
1762
|
+
s2: s1 << 31 | s2 >>> 1,
|
|
1763
|
+
s1: s0 << 31 | s1 >>> 1,
|
|
1764
|
+
s0: s0 >>> 1 ^ POLY << 24 & -(hiBit & 1)
|
|
1765
|
+
// reduce % poly
|
|
1766
|
+
};
|
|
1767
|
+
};
|
|
1768
|
+
var swapLE = (n) => (n >>> 0 & 255) << 24 | (n >>> 8 & 255) << 16 | (n >>> 16 & 255) << 8 | n >>> 24 & 255 | 0;
|
|
1769
|
+
function _toGHASHKey(k) {
|
|
1770
|
+
k.reverse();
|
|
1771
|
+
const hiBit = k[15] & 1;
|
|
1772
|
+
let carry = 0;
|
|
1773
|
+
for (let i = 0; i < k.length; i++) {
|
|
1774
|
+
const t = k[i];
|
|
1775
|
+
k[i] = t >>> 1 | carry;
|
|
1776
|
+
carry = (t & 1) << 7;
|
|
1777
|
+
}
|
|
1778
|
+
k[0] ^= -hiBit & 225;
|
|
1779
|
+
return k;
|
|
1780
|
+
}
|
|
1781
|
+
var estimateWindow = (bytes) => {
|
|
1782
|
+
if (bytes > 64 * 1024)
|
|
1783
|
+
return 8;
|
|
1784
|
+
if (bytes > 1024)
|
|
1785
|
+
return 4;
|
|
1786
|
+
return 2;
|
|
1787
|
+
};
|
|
1788
|
+
var GHASH = class {
|
|
1789
|
+
// We select bits per window adaptively based on expectedLength
|
|
1790
|
+
constructor(key, expectedLength) {
|
|
1791
|
+
this.blockLen = BLOCK_SIZE;
|
|
1792
|
+
this.outputLen = BLOCK_SIZE;
|
|
1793
|
+
this.s0 = 0;
|
|
1794
|
+
this.s1 = 0;
|
|
1795
|
+
this.s2 = 0;
|
|
1796
|
+
this.s3 = 0;
|
|
1797
|
+
this.finished = false;
|
|
1798
|
+
key = toBytes(key);
|
|
1799
|
+
abytes(key, 16);
|
|
1800
|
+
const kView = createView(key);
|
|
1801
|
+
let k0 = kView.getUint32(0, false);
|
|
1802
|
+
let k1 = kView.getUint32(4, false);
|
|
1803
|
+
let k2 = kView.getUint32(8, false);
|
|
1804
|
+
let k3 = kView.getUint32(12, false);
|
|
1805
|
+
const doubles = [];
|
|
1806
|
+
for (let i = 0; i < 128; i++) {
|
|
1807
|
+
doubles.push({ s0: swapLE(k0), s1: swapLE(k1), s2: swapLE(k2), s3: swapLE(k3) });
|
|
1808
|
+
({ s0: k0, s1: k1, s2: k2, s3: k3 } = mul2(k0, k1, k2, k3));
|
|
1809
|
+
}
|
|
1810
|
+
const W = estimateWindow(expectedLength || 1024);
|
|
1811
|
+
if (![1, 2, 4, 8].includes(W))
|
|
1812
|
+
throw new Error("ghash: invalid window size, expected 2, 4 or 8");
|
|
1813
|
+
this.W = W;
|
|
1814
|
+
const bits = 128;
|
|
1815
|
+
const windows = bits / W;
|
|
1816
|
+
const windowSize = this.windowSize = 2 ** W;
|
|
1817
|
+
const items = [];
|
|
1818
|
+
for (let w = 0; w < windows; w++) {
|
|
1819
|
+
for (let byte = 0; byte < windowSize; byte++) {
|
|
1820
|
+
let s0 = 0, s1 = 0, s2 = 0, s3 = 0;
|
|
1821
|
+
for (let j = 0; j < W; j++) {
|
|
1822
|
+
const bit = byte >>> W - j - 1 & 1;
|
|
1823
|
+
if (!bit)
|
|
1824
|
+
continue;
|
|
1825
|
+
const { s0: d0, s1: d1, s2: d2, s3: d3 } = doubles[W * w + j];
|
|
1826
|
+
s0 ^= d0, s1 ^= d1, s2 ^= d2, s3 ^= d3;
|
|
1827
|
+
}
|
|
1828
|
+
items.push({ s0, s1, s2, s3 });
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
this.t = items;
|
|
1832
|
+
}
|
|
1833
|
+
_updateBlock(s0, s1, s2, s3) {
|
|
1834
|
+
s0 ^= this.s0, s1 ^= this.s1, s2 ^= this.s2, s3 ^= this.s3;
|
|
1835
|
+
const { W, t, windowSize } = this;
|
|
1836
|
+
let o0 = 0, o1 = 0, o2 = 0, o3 = 0;
|
|
1837
|
+
const mask = (1 << W) - 1;
|
|
1838
|
+
let w = 0;
|
|
1839
|
+
for (const num5 of [s0, s1, s2, s3]) {
|
|
1840
|
+
for (let bytePos = 0; bytePos < 4; bytePos++) {
|
|
1841
|
+
const byte = num5 >>> 8 * bytePos & 255;
|
|
1842
|
+
for (let bitPos = 8 / W - 1; bitPos >= 0; bitPos--) {
|
|
1843
|
+
const bit = byte >>> W * bitPos & mask;
|
|
1844
|
+
const { s0: e0, s1: e1, s2: e2, s3: e3 } = t[w * windowSize + bit];
|
|
1845
|
+
o0 ^= e0, o1 ^= e1, o2 ^= e2, o3 ^= e3;
|
|
1846
|
+
w += 1;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
this.s0 = o0;
|
|
1851
|
+
this.s1 = o1;
|
|
1852
|
+
this.s2 = o2;
|
|
1853
|
+
this.s3 = o3;
|
|
1854
|
+
}
|
|
1855
|
+
update(data) {
|
|
1856
|
+
aexists(this);
|
|
1857
|
+
data = toBytes(data);
|
|
1858
|
+
abytes(data);
|
|
1859
|
+
const b32 = u32(data);
|
|
1860
|
+
const blocks = Math.floor(data.length / BLOCK_SIZE);
|
|
1861
|
+
const left = data.length % BLOCK_SIZE;
|
|
1862
|
+
for (let i = 0; i < blocks; i++) {
|
|
1863
|
+
this._updateBlock(b32[i * 4 + 0], b32[i * 4 + 1], b32[i * 4 + 2], b32[i * 4 + 3]);
|
|
1864
|
+
}
|
|
1865
|
+
if (left) {
|
|
1866
|
+
ZEROS16.set(data.subarray(blocks * BLOCK_SIZE));
|
|
1867
|
+
this._updateBlock(ZEROS32[0], ZEROS32[1], ZEROS32[2], ZEROS32[3]);
|
|
1868
|
+
clean(ZEROS32);
|
|
1869
|
+
}
|
|
1870
|
+
return this;
|
|
1871
|
+
}
|
|
1872
|
+
destroy() {
|
|
1873
|
+
const { t } = this;
|
|
1874
|
+
for (const elm of t) {
|
|
1875
|
+
elm.s0 = 0, elm.s1 = 0, elm.s2 = 0, elm.s3 = 0;
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
digestInto(out) {
|
|
1879
|
+
aexists(this);
|
|
1880
|
+
aoutput(out, this);
|
|
1881
|
+
this.finished = true;
|
|
1882
|
+
const { s0, s1, s2, s3 } = this;
|
|
1883
|
+
const o32 = u32(out);
|
|
1884
|
+
o32[0] = s0;
|
|
1885
|
+
o32[1] = s1;
|
|
1886
|
+
o32[2] = s2;
|
|
1887
|
+
o32[3] = s3;
|
|
1888
|
+
return out;
|
|
1889
|
+
}
|
|
1890
|
+
digest() {
|
|
1891
|
+
const res = new Uint8Array(BLOCK_SIZE);
|
|
1892
|
+
this.digestInto(res);
|
|
1893
|
+
this.destroy();
|
|
1894
|
+
return res;
|
|
1895
|
+
}
|
|
1896
|
+
};
|
|
1897
|
+
var Polyval = class extends GHASH {
|
|
1898
|
+
constructor(key, expectedLength) {
|
|
1899
|
+
key = toBytes(key);
|
|
1900
|
+
abytes(key);
|
|
1901
|
+
const ghKey = _toGHASHKey(copyBytes(key));
|
|
1902
|
+
super(ghKey, expectedLength);
|
|
1903
|
+
clean(ghKey);
|
|
1904
|
+
}
|
|
1905
|
+
update(data) {
|
|
1906
|
+
data = toBytes(data);
|
|
1907
|
+
aexists(this);
|
|
1908
|
+
const b32 = u32(data);
|
|
1909
|
+
const left = data.length % BLOCK_SIZE;
|
|
1910
|
+
const blocks = Math.floor(data.length / BLOCK_SIZE);
|
|
1911
|
+
for (let i = 0; i < blocks; i++) {
|
|
1912
|
+
this._updateBlock(swapLE(b32[i * 4 + 3]), swapLE(b32[i * 4 + 2]), swapLE(b32[i * 4 + 1]), swapLE(b32[i * 4 + 0]));
|
|
1913
|
+
}
|
|
1914
|
+
if (left) {
|
|
1915
|
+
ZEROS16.set(data.subarray(blocks * BLOCK_SIZE));
|
|
1916
|
+
this._updateBlock(swapLE(ZEROS32[3]), swapLE(ZEROS32[2]), swapLE(ZEROS32[1]), swapLE(ZEROS32[0]));
|
|
1917
|
+
clean(ZEROS32);
|
|
1918
|
+
}
|
|
1919
|
+
return this;
|
|
1920
|
+
}
|
|
1921
|
+
digestInto(out) {
|
|
1922
|
+
aexists(this);
|
|
1923
|
+
aoutput(out, this);
|
|
1924
|
+
this.finished = true;
|
|
1925
|
+
const { s0, s1, s2, s3 } = this;
|
|
1926
|
+
const o32 = u32(out);
|
|
1927
|
+
o32[0] = s0;
|
|
1928
|
+
o32[1] = s1;
|
|
1929
|
+
o32[2] = s2;
|
|
1930
|
+
o32[3] = s3;
|
|
1931
|
+
return out.reverse();
|
|
1932
|
+
}
|
|
1575
1933
|
};
|
|
1934
|
+
function wrapConstructorWithKey(hashCons) {
|
|
1935
|
+
const hashC = (msg, key) => hashCons(key, msg.length).update(toBytes(msg)).digest();
|
|
1936
|
+
const tmp = hashCons(new Uint8Array(16), 0);
|
|
1937
|
+
hashC.outputLen = tmp.outputLen;
|
|
1938
|
+
hashC.blockLen = tmp.blockLen;
|
|
1939
|
+
hashC.create = (key, expectedLength) => hashCons(key, expectedLength);
|
|
1940
|
+
return hashC;
|
|
1941
|
+
}
|
|
1942
|
+
var ghash = wrapConstructorWithKey((key, expectedLength) => new GHASH(key, expectedLength));
|
|
1943
|
+
wrapConstructorWithKey((key, expectedLength) => new Polyval(key, expectedLength));
|
|
1944
|
+
|
|
1945
|
+
// node_modules/@noble/ciphers/esm/aes.js
|
|
1946
|
+
var BLOCK_SIZE2 = 16;
|
|
1947
|
+
var BLOCK_SIZE32 = 4;
|
|
1948
|
+
var EMPTY_BLOCK = /* @__PURE__ */ new Uint8Array(BLOCK_SIZE2);
|
|
1949
|
+
var POLY2 = 283;
|
|
1950
|
+
function mul22(n) {
|
|
1951
|
+
return n << 1 ^ POLY2 & -(n >> 7);
|
|
1952
|
+
}
|
|
1953
|
+
function mul(a, b) {
|
|
1954
|
+
let res = 0;
|
|
1955
|
+
for (; b > 0; b >>= 1) {
|
|
1956
|
+
res ^= a & -(b & 1);
|
|
1957
|
+
a = mul22(a);
|
|
1958
|
+
}
|
|
1959
|
+
return res;
|
|
1960
|
+
}
|
|
1961
|
+
var sbox = /* @__PURE__ */ (() => {
|
|
1962
|
+
const t = new Uint8Array(256);
|
|
1963
|
+
for (let i = 0, x = 1; i < 256; i++, x ^= mul22(x))
|
|
1964
|
+
t[i] = x;
|
|
1965
|
+
const box = new Uint8Array(256);
|
|
1966
|
+
box[0] = 99;
|
|
1967
|
+
for (let i = 0; i < 255; i++) {
|
|
1968
|
+
let x = t[255 - i];
|
|
1969
|
+
x |= x << 8;
|
|
1970
|
+
box[t[i]] = (x ^ x >> 4 ^ x >> 5 ^ x >> 6 ^ x >> 7 ^ 99) & 255;
|
|
1971
|
+
}
|
|
1972
|
+
clean(t);
|
|
1973
|
+
return box;
|
|
1974
|
+
})();
|
|
1975
|
+
var rotr32_8 = (n) => n << 24 | n >>> 8;
|
|
1976
|
+
var rotl32_8 = (n) => n << 8 | n >>> 24;
|
|
1977
|
+
function genTtable(sbox2, fn) {
|
|
1978
|
+
if (sbox2.length !== 256)
|
|
1979
|
+
throw new Error("Wrong sbox length");
|
|
1980
|
+
const T0 = new Uint32Array(256).map((_, j) => fn(sbox2[j]));
|
|
1981
|
+
const T1 = T0.map(rotl32_8);
|
|
1982
|
+
const T2 = T1.map(rotl32_8);
|
|
1983
|
+
const T3 = T2.map(rotl32_8);
|
|
1984
|
+
const T01 = new Uint32Array(256 * 256);
|
|
1985
|
+
const T23 = new Uint32Array(256 * 256);
|
|
1986
|
+
const sbox22 = new Uint16Array(256 * 256);
|
|
1987
|
+
for (let i = 0; i < 256; i++) {
|
|
1988
|
+
for (let j = 0; j < 256; j++) {
|
|
1989
|
+
const idx = i * 256 + j;
|
|
1990
|
+
T01[idx] = T0[i] ^ T1[j];
|
|
1991
|
+
T23[idx] = T2[i] ^ T3[j];
|
|
1992
|
+
sbox22[idx] = sbox2[i] << 8 | sbox2[j];
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
return { sbox: sbox2, sbox2: sbox22, T0, T1, T2, T3, T01, T23 };
|
|
1996
|
+
}
|
|
1997
|
+
var tableEncoding = /* @__PURE__ */ genTtable(sbox, (s) => mul(s, 3) << 24 | s << 16 | s << 8 | mul(s, 2));
|
|
1998
|
+
var xPowers = /* @__PURE__ */ (() => {
|
|
1999
|
+
const p = new Uint8Array(16);
|
|
2000
|
+
for (let i = 0, x = 1; i < 16; i++, x = mul22(x))
|
|
2001
|
+
p[i] = x;
|
|
2002
|
+
return p;
|
|
2003
|
+
})();
|
|
2004
|
+
function expandKeyLE(key) {
|
|
2005
|
+
abytes(key);
|
|
2006
|
+
const len = key.length;
|
|
2007
|
+
if (![16, 24, 32].includes(len))
|
|
2008
|
+
throw new Error("aes: invalid key size, should be 16, 24 or 32, got " + len);
|
|
2009
|
+
const { sbox2 } = tableEncoding;
|
|
2010
|
+
const toClean = [];
|
|
2011
|
+
if (!isAligned32(key))
|
|
2012
|
+
toClean.push(key = copyBytes(key));
|
|
2013
|
+
const k32 = u32(key);
|
|
2014
|
+
const Nk = k32.length;
|
|
2015
|
+
const subByte = (n) => applySbox(sbox2, n, n, n, n);
|
|
2016
|
+
const xk = new Uint32Array(len + 28);
|
|
2017
|
+
xk.set(k32);
|
|
2018
|
+
for (let i = Nk; i < xk.length; i++) {
|
|
2019
|
+
let t = xk[i - 1];
|
|
2020
|
+
if (i % Nk === 0)
|
|
2021
|
+
t = subByte(rotr32_8(t)) ^ xPowers[i / Nk - 1];
|
|
2022
|
+
else if (Nk > 6 && i % Nk === 4)
|
|
2023
|
+
t = subByte(t);
|
|
2024
|
+
xk[i] = xk[i - Nk] ^ t;
|
|
2025
|
+
}
|
|
2026
|
+
clean(...toClean);
|
|
2027
|
+
return xk;
|
|
2028
|
+
}
|
|
2029
|
+
function apply0123(T01, T23, s0, s1, s2, s3) {
|
|
2030
|
+
return T01[s0 << 8 & 65280 | s1 >>> 8 & 255] ^ T23[s2 >>> 8 & 65280 | s3 >>> 24 & 255];
|
|
2031
|
+
}
|
|
2032
|
+
function applySbox(sbox2, s0, s1, s2, s3) {
|
|
2033
|
+
return sbox2[s0 & 255 | s1 & 65280] | sbox2[s2 >>> 16 & 255 | s3 >>> 16 & 65280] << 16;
|
|
2034
|
+
}
|
|
2035
|
+
function encrypt(xk, s0, s1, s2, s3) {
|
|
2036
|
+
const { sbox2, T01, T23 } = tableEncoding;
|
|
2037
|
+
let k = 0;
|
|
2038
|
+
s0 ^= xk[k++], s1 ^= xk[k++], s2 ^= xk[k++], s3 ^= xk[k++];
|
|
2039
|
+
const rounds = xk.length / 4 - 2;
|
|
2040
|
+
for (let i = 0; i < rounds; i++) {
|
|
2041
|
+
const t02 = xk[k++] ^ apply0123(T01, T23, s0, s1, s2, s3);
|
|
2042
|
+
const t12 = xk[k++] ^ apply0123(T01, T23, s1, s2, s3, s0);
|
|
2043
|
+
const t22 = xk[k++] ^ apply0123(T01, T23, s2, s3, s0, s1);
|
|
2044
|
+
const t32 = xk[k++] ^ apply0123(T01, T23, s3, s0, s1, s2);
|
|
2045
|
+
s0 = t02, s1 = t12, s2 = t22, s3 = t32;
|
|
2046
|
+
}
|
|
2047
|
+
const t0 = xk[k++] ^ applySbox(sbox2, s0, s1, s2, s3);
|
|
2048
|
+
const t1 = xk[k++] ^ applySbox(sbox2, s1, s2, s3, s0);
|
|
2049
|
+
const t2 = xk[k++] ^ applySbox(sbox2, s2, s3, s0, s1);
|
|
2050
|
+
const t3 = xk[k++] ^ applySbox(sbox2, s3, s0, s1, s2);
|
|
2051
|
+
return { s0: t0, s1: t1, s2: t2, s3: t3 };
|
|
2052
|
+
}
|
|
2053
|
+
function ctr32(xk, isLE2, nonce, src, dst) {
|
|
2054
|
+
abytes(nonce, BLOCK_SIZE2);
|
|
2055
|
+
abytes(src);
|
|
2056
|
+
dst = getOutput(src.length, dst);
|
|
2057
|
+
const ctr = nonce;
|
|
2058
|
+
const c32 = u32(ctr);
|
|
2059
|
+
const view = createView(ctr);
|
|
2060
|
+
const src32 = u32(src);
|
|
2061
|
+
const dst32 = u32(dst);
|
|
2062
|
+
const ctrPos = 12;
|
|
2063
|
+
const srcLen = src.length;
|
|
2064
|
+
let ctrNum = view.getUint32(ctrPos, isLE2);
|
|
2065
|
+
let { s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]);
|
|
2066
|
+
for (let i = 0; i + 4 <= src32.length; i += 4) {
|
|
2067
|
+
dst32[i + 0] = src32[i + 0] ^ s0;
|
|
2068
|
+
dst32[i + 1] = src32[i + 1] ^ s1;
|
|
2069
|
+
dst32[i + 2] = src32[i + 2] ^ s2;
|
|
2070
|
+
dst32[i + 3] = src32[i + 3] ^ s3;
|
|
2071
|
+
ctrNum = ctrNum + 1 >>> 0;
|
|
2072
|
+
view.setUint32(ctrPos, ctrNum, isLE2);
|
|
2073
|
+
({ s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]));
|
|
2074
|
+
}
|
|
2075
|
+
const start = BLOCK_SIZE2 * Math.floor(src32.length / BLOCK_SIZE32);
|
|
2076
|
+
if (start < srcLen) {
|
|
2077
|
+
const b32 = new Uint32Array([s0, s1, s2, s3]);
|
|
2078
|
+
const buf3 = u8(b32);
|
|
2079
|
+
for (let i = start, pos = 0; i < srcLen; i++, pos++)
|
|
2080
|
+
dst[i] = src[i] ^ buf3[pos];
|
|
2081
|
+
clean(b32);
|
|
2082
|
+
}
|
|
2083
|
+
return dst;
|
|
2084
|
+
}
|
|
2085
|
+
function computeTag(fn, isLE2, key, data, AAD) {
|
|
2086
|
+
const aadLength = AAD ? AAD.length : 0;
|
|
2087
|
+
const h = fn.create(key, data.length + aadLength);
|
|
2088
|
+
if (AAD)
|
|
2089
|
+
h.update(AAD);
|
|
2090
|
+
const num5 = u64Lengths(8 * data.length, 8 * aadLength, isLE2);
|
|
2091
|
+
h.update(data);
|
|
2092
|
+
h.update(num5);
|
|
2093
|
+
const res = h.digest();
|
|
2094
|
+
clean(num5);
|
|
2095
|
+
return res;
|
|
2096
|
+
}
|
|
2097
|
+
var gcm = /* @__PURE__ */ wrapCipher({ blockSize: 16, nonceLength: 12, tagLength: 16, varSizeNonce: true }, function aesgcm(key, nonce, AAD) {
|
|
2098
|
+
if (nonce.length < 8)
|
|
2099
|
+
throw new Error("aes/gcm: invalid nonce length");
|
|
2100
|
+
const tagLength = 16;
|
|
2101
|
+
function _computeTag(authKey, tagMask, data) {
|
|
2102
|
+
const tag = computeTag(ghash, false, authKey, data, AAD);
|
|
2103
|
+
for (let i = 0; i < tagMask.length; i++)
|
|
2104
|
+
tag[i] ^= tagMask[i];
|
|
2105
|
+
return tag;
|
|
2106
|
+
}
|
|
2107
|
+
function deriveKeys() {
|
|
2108
|
+
const xk = expandKeyLE(key);
|
|
2109
|
+
const authKey = EMPTY_BLOCK.slice();
|
|
2110
|
+
const counter = EMPTY_BLOCK.slice();
|
|
2111
|
+
ctr32(xk, false, counter, counter, authKey);
|
|
2112
|
+
if (nonce.length === 12) {
|
|
2113
|
+
counter.set(nonce);
|
|
2114
|
+
} else {
|
|
2115
|
+
const nonceLen = EMPTY_BLOCK.slice();
|
|
2116
|
+
const view = createView(nonceLen);
|
|
2117
|
+
setBigUint64(view, 8, BigInt(nonce.length * 8), false);
|
|
2118
|
+
const g = ghash.create(authKey).update(nonce).update(nonceLen);
|
|
2119
|
+
g.digestInto(counter);
|
|
2120
|
+
g.destroy();
|
|
2121
|
+
}
|
|
2122
|
+
const tagMask = ctr32(xk, false, counter, EMPTY_BLOCK);
|
|
2123
|
+
return { xk, authKey, counter, tagMask };
|
|
2124
|
+
}
|
|
2125
|
+
return {
|
|
2126
|
+
encrypt(plaintext) {
|
|
2127
|
+
const { xk, authKey, counter, tagMask } = deriveKeys();
|
|
2128
|
+
const out = new Uint8Array(plaintext.length + tagLength);
|
|
2129
|
+
const toClean = [xk, authKey, counter, tagMask];
|
|
2130
|
+
if (!isAligned32(plaintext))
|
|
2131
|
+
toClean.push(plaintext = copyBytes(plaintext));
|
|
2132
|
+
ctr32(xk, false, counter, plaintext, out.subarray(0, plaintext.length));
|
|
2133
|
+
const tag = _computeTag(authKey, tagMask, out.subarray(0, out.length - tagLength));
|
|
2134
|
+
toClean.push(tag);
|
|
2135
|
+
out.set(tag, plaintext.length);
|
|
2136
|
+
clean(...toClean);
|
|
2137
|
+
return out;
|
|
2138
|
+
},
|
|
2139
|
+
decrypt(ciphertext) {
|
|
2140
|
+
const { xk, authKey, counter, tagMask } = deriveKeys();
|
|
2141
|
+
const toClean = [xk, authKey, tagMask, counter];
|
|
2142
|
+
if (!isAligned32(ciphertext))
|
|
2143
|
+
toClean.push(ciphertext = copyBytes(ciphertext));
|
|
2144
|
+
const data = ciphertext.subarray(0, -tagLength);
|
|
2145
|
+
const passedTag = ciphertext.subarray(-tagLength);
|
|
2146
|
+
const tag = _computeTag(authKey, tagMask, data);
|
|
2147
|
+
toClean.push(tag);
|
|
2148
|
+
if (!equalBytes(tag, passedTag))
|
|
2149
|
+
throw new Error("aes/gcm: invalid ghash tag");
|
|
2150
|
+
const out = ctr32(xk, false, counter, data);
|
|
2151
|
+
clean(...toClean);
|
|
2152
|
+
return out;
|
|
2153
|
+
}
|
|
2154
|
+
};
|
|
2155
|
+
});
|
|
2156
|
+
var NONCE_LEN = 12;
|
|
2157
|
+
var RECOVERY_INFO = "cavos-stellar-dek-recovery";
|
|
2158
|
+
var PASSKEY_INFO = "cavos-stellar-dek-passkey";
|
|
2159
|
+
var ECIES_INFO = "cavos-stellar-dek-ecies";
|
|
2160
|
+
var RECOVERY_PBKDF2_ITERATIONS = 21e4;
|
|
2161
|
+
var RECOVERY_KDF_SALT = "cavos-stellar-recovery-v1";
|
|
2162
|
+
function generateDEK() {
|
|
2163
|
+
return utils.randomBytes(32);
|
|
2164
|
+
}
|
|
2165
|
+
function sealControlSeed(controlSeed, dek) {
|
|
2166
|
+
const nonce = utils.randomBytes(NONCE_LEN);
|
|
2167
|
+
const ct = gcm(dek, nonce).encrypt(controlSeed);
|
|
2168
|
+
return concat(nonce, ct);
|
|
2169
|
+
}
|
|
2170
|
+
function openControlSeed(sealed, dek) {
|
|
2171
|
+
const { nonce, ct } = splitNonce(sealed);
|
|
2172
|
+
return gcm(dek, nonce).decrypt(ct);
|
|
2173
|
+
}
|
|
2174
|
+
function wrapDEK(dek, kek) {
|
|
2175
|
+
const nonce = utils.randomBytes(NONCE_LEN);
|
|
2176
|
+
return concat(nonce, gcm(kek, nonce).encrypt(dek));
|
|
2177
|
+
}
|
|
2178
|
+
function unwrapDEK(wrapped, kek) {
|
|
2179
|
+
const { nonce, ct } = splitNonce(wrapped);
|
|
2180
|
+
return gcm(kek, nonce).decrypt(ct);
|
|
2181
|
+
}
|
|
2182
|
+
function deriveRecoveryKEK(code) {
|
|
2183
|
+
const normalised = code.trim().replace(/\s+/g, " ").toLowerCase();
|
|
2184
|
+
if (!normalised) throw new Error("kit/stellar: recovery code is empty");
|
|
2185
|
+
const stretched = pbkdf2.pbkdf2(sha256.sha256, new TextEncoder().encode(normalised), new TextEncoder().encode(RECOVERY_KDF_SALT), {
|
|
2186
|
+
c: RECOVERY_PBKDF2_ITERATIONS,
|
|
2187
|
+
dkLen: 32
|
|
2188
|
+
});
|
|
2189
|
+
return hkdf.hkdf(sha256.sha256, stretched, void 0, RECOVERY_INFO, 32);
|
|
2190
|
+
}
|
|
2191
|
+
function derivePasskeyKEK(prfOutput) {
|
|
2192
|
+
if (prfOutput.length < 32) throw new Error("kit/stellar: passkey PRF output too short");
|
|
2193
|
+
return hkdf.hkdf(sha256.sha256, prfOutput, void 0, PASSKEY_INFO, 32);
|
|
2194
|
+
}
|
|
2195
|
+
function eciesWrapDEK(dek, recipientPubSec1) {
|
|
2196
|
+
const ephPriv = p256.p256.utils.randomPrivateKey();
|
|
2197
|
+
const ephPubCompressed = p256.p256.getPublicKey(ephPriv, true);
|
|
2198
|
+
const kek = eciesKEK(p256.p256.getSharedSecret(ephPriv, recipientPubSec1, false), ephPubCompressed);
|
|
2199
|
+
const wrapped = wrapDEK(dek, kek);
|
|
2200
|
+
return concat(ephPubCompressed, wrapped);
|
|
2201
|
+
}
|
|
2202
|
+
function eciesKEK(sharedUncompressed, ephPubCompressed) {
|
|
2203
|
+
return eciesKEKFromX(sharedUncompressed.subarray(1, 33), ephPubCompressed);
|
|
2204
|
+
}
|
|
2205
|
+
function eciesKEKFromX(sharedX, ephPubCompressed) {
|
|
2206
|
+
return hkdf.hkdf(sha256.sha256, sharedX, ephPubCompressed, ECIES_INFO, 32);
|
|
2207
|
+
}
|
|
2208
|
+
var DATA_ENTRY_MAX = 64;
|
|
2209
|
+
function chunkTo64(blob) {
|
|
2210
|
+
if (blob.length <= DATA_ENTRY_MAX) return [blob];
|
|
2211
|
+
const chunks = [];
|
|
2212
|
+
for (let i = 0; i < blob.length; i += DATA_ENTRY_MAX) {
|
|
2213
|
+
chunks.push(blob.subarray(i, i + DATA_ENTRY_MAX));
|
|
2214
|
+
}
|
|
2215
|
+
return chunks;
|
|
2216
|
+
}
|
|
2217
|
+
function unchunk(chunks) {
|
|
2218
|
+
const total = chunks.reduce((n, c) => n + c.length, 0);
|
|
2219
|
+
const out = new Uint8Array(total);
|
|
2220
|
+
let off = 0;
|
|
2221
|
+
for (const c of chunks) {
|
|
2222
|
+
out.set(c, off);
|
|
2223
|
+
off += c.length;
|
|
2224
|
+
}
|
|
2225
|
+
return out;
|
|
2226
|
+
}
|
|
2227
|
+
function concat(a, b) {
|
|
2228
|
+
const out = new Uint8Array(a.length + b.length);
|
|
2229
|
+
out.set(a, 0);
|
|
2230
|
+
out.set(b, a.length);
|
|
2231
|
+
return out;
|
|
2232
|
+
}
|
|
2233
|
+
function splitNonce(blob) {
|
|
2234
|
+
return { nonce: blob.subarray(0, NONCE_LEN), ct: blob.subarray(NONCE_LEN) };
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
// src/chains/stellar/datamap.ts
|
|
2238
|
+
var CT_BASE = "cv:ct";
|
|
2239
|
+
var PASSKEY_BASE = "cv:wp";
|
|
2240
|
+
var RECOVERY_BASE = "cv:wr";
|
|
2241
|
+
var VERSION_KEY = "cv:v";
|
|
2242
|
+
var ENVELOPE_VERSION = 1;
|
|
2243
|
+
function deviceBase(slot) {
|
|
2244
|
+
return `cv:wd:${slot}`;
|
|
2245
|
+
}
|
|
2246
|
+
function toDataEntries(env) {
|
|
2247
|
+
const out = {};
|
|
2248
|
+
writeChunked(out, CT_BASE, env.ct);
|
|
2249
|
+
for (const [slot, blob] of Object.entries(env.deviceWraps)) {
|
|
2250
|
+
writeChunked(out, deviceBase(slot), blob);
|
|
2251
|
+
}
|
|
2252
|
+
if (env.passkeyWrap) writeChunked(out, PASSKEY_BASE, env.passkeyWrap);
|
|
2253
|
+
if (env.recoveryWrap) writeChunked(out, RECOVERY_BASE, env.recoveryWrap);
|
|
2254
|
+
out[VERSION_KEY] = Uint8Array.of(ENVELOPE_VERSION);
|
|
2255
|
+
return out;
|
|
2256
|
+
}
|
|
2257
|
+
function fromDataEntries(entries) {
|
|
2258
|
+
const ct = readChunked(entries, CT_BASE);
|
|
2259
|
+
if (!ct) throw new Error("kit/stellar: account has no control-seed ciphertext (cv:ct)");
|
|
2260
|
+
const deviceWraps = {};
|
|
2261
|
+
const seenSlots = /* @__PURE__ */ new Set();
|
|
2262
|
+
for (const key of Object.keys(entries)) {
|
|
2263
|
+
const m = key.match(/^cv:wd:([^/]+)\/\d+$/);
|
|
2264
|
+
if (m) seenSlots.add(m[1]);
|
|
2265
|
+
}
|
|
2266
|
+
for (const slot of seenSlots) {
|
|
2267
|
+
const blob = readChunked(entries, deviceBase(slot));
|
|
2268
|
+
if (blob) deviceWraps[slot] = blob;
|
|
2269
|
+
}
|
|
2270
|
+
return {
|
|
2271
|
+
ct,
|
|
2272
|
+
deviceWraps,
|
|
2273
|
+
passkeyWrap: readChunked(entries, PASSKEY_BASE),
|
|
2274
|
+
recoveryWrap: readChunked(entries, RECOVERY_BASE)
|
|
2275
|
+
};
|
|
2276
|
+
}
|
|
2277
|
+
function deviceWrapEntries(slot, blob) {
|
|
2278
|
+
const out = {};
|
|
2279
|
+
writeChunked(out, deviceBase(slot), blob);
|
|
2280
|
+
return out;
|
|
2281
|
+
}
|
|
2282
|
+
function writeChunked(out, base, blob) {
|
|
2283
|
+
const chunks = chunkTo64(blob);
|
|
2284
|
+
chunks.forEach((chunk, i) => {
|
|
2285
|
+
out[`${base}/${i}`] = chunk;
|
|
2286
|
+
});
|
|
2287
|
+
}
|
|
2288
|
+
function readChunked(entries, base) {
|
|
2289
|
+
const chunks = [];
|
|
2290
|
+
for (let i = 0; ; i++) {
|
|
2291
|
+
const chunk = entries[`${base}/${i}`];
|
|
2292
|
+
if (!chunk) break;
|
|
2293
|
+
chunks.push(chunk);
|
|
2294
|
+
}
|
|
2295
|
+
return chunks.length ? unchunk(chunks) : void 0;
|
|
2296
|
+
}
|
|
1576
2297
|
|
|
1577
2298
|
// src/chains/stellar/StellarAdapter.ts
|
|
1578
|
-
var
|
|
2299
|
+
var TX_TIMEOUT = 180;
|
|
1579
2300
|
var StellarAdapter = class {
|
|
1580
2301
|
constructor(opts) {
|
|
1581
2302
|
this.chain = "stellar";
|
|
1582
2303
|
this.network = opts.network;
|
|
1583
2304
|
this.passphrase = STELLAR_NETWORKS[opts.network].passphrase;
|
|
1584
|
-
this.
|
|
1585
|
-
this.factoryId = opts.factoryId ?? FACTORY_CONTRACT_ID[opts.network];
|
|
1586
|
-
if (!this.factoryId) {
|
|
1587
|
-
throw new Error(`kit/stellar: no factory contract id configured for ${opts.network}`);
|
|
1588
|
-
}
|
|
1589
|
-
this.signer = opts.signer;
|
|
2305
|
+
this.horizonUrl = opts.horizonUrl ?? HORIZON_URL[opts.network];
|
|
1590
2306
|
}
|
|
1591
2307
|
server() {
|
|
1592
2308
|
if (!this._server) {
|
|
1593
|
-
this._server = new stellarSdk.
|
|
1594
|
-
allowHttp: this.
|
|
2309
|
+
this._server = new stellarSdk.Horizon.Server(this.horizonUrl, {
|
|
2310
|
+
allowHttp: this.horizonUrl.startsWith("http://")
|
|
1595
2311
|
});
|
|
1596
2312
|
}
|
|
1597
2313
|
return this._server;
|
|
1598
2314
|
}
|
|
1599
|
-
|
|
1600
|
-
|
|
2315
|
+
/** Whether the classic account already exists on-chain. */
|
|
2316
|
+
async isDeployed(address) {
|
|
2317
|
+
try {
|
|
2318
|
+
await this.server().loadAccount(address);
|
|
2319
|
+
return true;
|
|
2320
|
+
} catch (e) {
|
|
2321
|
+
if (isNotFound(e)) return false;
|
|
2322
|
+
throw e;
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
/** Read the account's `MANAGE_DATA` entries as raw bytes (name → value). */
|
|
2326
|
+
async loadDataEntries(address) {
|
|
2327
|
+
const account = await this.server().loadAccount(address);
|
|
2328
|
+
const out = {};
|
|
2329
|
+
for (const [name, b64] of Object.entries(account.data_attr ?? {})) {
|
|
2330
|
+
out[name] = new Uint8Array(Buffer.from(b64, "base64"));
|
|
2331
|
+
}
|
|
2332
|
+
return out;
|
|
2333
|
+
}
|
|
2334
|
+
/** Native XLM balance in stroops. Returns 0n if the account doesn't exist. */
|
|
2335
|
+
async balance(address) {
|
|
2336
|
+
try {
|
|
2337
|
+
const account = await this.server().loadAccount(address);
|
|
2338
|
+
const native = account.balances.find((b) => b.asset_type === "native");
|
|
2339
|
+
return native ? toStroops(native.balance) : 0n;
|
|
2340
|
+
} catch (e) {
|
|
2341
|
+
if (isNotFound(e)) return 0n;
|
|
2342
|
+
throw e;
|
|
2343
|
+
}
|
|
1601
2344
|
}
|
|
1602
2345
|
/**
|
|
1603
|
-
*
|
|
1604
|
-
*
|
|
1605
|
-
* `
|
|
1606
|
-
* `
|
|
2346
|
+
* Build the account-creation transaction (source = funder, the relayer or a
|
|
2347
|
+
* self-funded keypair):
|
|
2348
|
+
* 1. `createAccount` funds the deterministic `G…` master address,
|
|
2349
|
+
* 2. `manageData` writes the control-key envelope entries (authorized by the
|
|
2350
|
+
* still-weight-1 master),
|
|
2351
|
+
* 3. `setOptions` adds the control signer (weight 1), sets all thresholds to
|
|
2352
|
+
* 1, and zeroes the master weight — after this the master can never sign.
|
|
2353
|
+
*
|
|
2354
|
+
* The returned tx must be signed by BOTH the master keypair (for the account's
|
|
2355
|
+
* own ops) and the funder (source + fee). Sponsorship of reserves is layered on
|
|
2356
|
+
* in Phase 3.
|
|
1607
2357
|
*/
|
|
1608
|
-
|
|
1609
|
-
const
|
|
1610
|
-
const
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
)
|
|
2358
|
+
async buildCreateTx(params) {
|
|
2359
|
+
const funderAccount = await this.server().loadAccount(params.funder);
|
|
2360
|
+
const builder = new stellarSdk.TransactionBuilder(funderAccount, {
|
|
2361
|
+
fee: stellarSdk.BASE_FEE,
|
|
2362
|
+
networkPassphrase: this.passphrase
|
|
2363
|
+
});
|
|
2364
|
+
builder.addOperation(
|
|
2365
|
+
stellarSdk.Operation.createAccount({
|
|
2366
|
+
destination: params.masterAddress,
|
|
2367
|
+
startingBalance: fromStroops(params.startingBalance)
|
|
1619
2368
|
})
|
|
1620
2369
|
);
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
return stellarSdk.hash(Buffer.concat([Buffer.from(addressSeed), Buffer.from(sec1Pubkey(initialSigner))]));
|
|
1626
|
-
}
|
|
1627
|
-
/** Host function: `factory.deploy(address_seed, initial_signer)`. */
|
|
1628
|
-
buildDeploy(addressSeed, initialSigner) {
|
|
1629
|
-
return invokeFunc(this.factoryId, "deploy", [
|
|
1630
|
-
bytesScVal(addressSeed),
|
|
1631
|
-
bytesScVal(sec1Pubkey(initialSigner))
|
|
1632
|
-
]);
|
|
1633
|
-
}
|
|
1634
|
-
/** Host function: `account.add_signer(new_signer)` (requires device auth). */
|
|
1635
|
-
buildAddSigner(accountAddress, signer) {
|
|
1636
|
-
return invokeFunc(accountAddress, "add_signer", [bytesScVal(sec1Pubkey(signer))]);
|
|
1637
|
-
}
|
|
1638
|
-
/** Host function: `account.remove_signer(signer)` (requires device auth). */
|
|
1639
|
-
buildRemoveSigner(accountAddress, signer) {
|
|
1640
|
-
return invokeFunc(accountAddress, "remove_signer", [bytesScVal(sec1Pubkey(signer))]);
|
|
1641
|
-
}
|
|
1642
|
-
/** Host function: `account.add_approver(passkey)` (requires device auth). */
|
|
1643
|
-
buildAddApprover(accountAddress, passkey) {
|
|
1644
|
-
return invokeFunc(accountAddress, "add_approver", [bytesScVal(sec1Pubkey(passkey))]);
|
|
1645
|
-
}
|
|
1646
|
-
/** Host function: `account.remove_approver(passkey)` (requires device auth). */
|
|
1647
|
-
buildRemoveApprover(accountAddress, passkey) {
|
|
1648
|
-
return invokeFunc(accountAddress, "remove_approver", [bytesScVal(sec1Pubkey(passkey))]);
|
|
1649
|
-
}
|
|
1650
|
-
/** This chain's leaf for approving `add_signer(newSigner)` at `nonce`:
|
|
1651
|
-
* `sha256(sec1(new_signer) || nonce_be8)`. The batch challenge the passkey signs
|
|
1652
|
-
* is `sha256(concat(leaves))` across chains. */
|
|
1653
|
-
passkeyLeaf(newSigner, nonce) {
|
|
1654
|
-
const msg = new Uint8Array(65 + 8);
|
|
1655
|
-
msg.set(sec1Pubkey(newSigner), 0);
|
|
1656
|
-
const n = new Uint8Array(8);
|
|
1657
|
-
let v = nonce;
|
|
1658
|
-
for (let i = 7; i >= 0; i--) {
|
|
1659
|
-
n[i] = Number(v & 0xffn);
|
|
1660
|
-
v >>= 8n;
|
|
2370
|
+
for (const [name, value] of Object.entries(toDataEntries(params.envelope))) {
|
|
2371
|
+
builder.addOperation(
|
|
2372
|
+
stellarSdk.Operation.manageData({ name, value: Buffer.from(value), source: params.masterAddress })
|
|
2373
|
+
);
|
|
1661
2374
|
}
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
bytesScVal(sec1Pubkey(passkey)),
|
|
1674
|
-
stellarSdk.nativeToScVal(nonce, { type: "u64" }),
|
|
1675
|
-
leavesScVal,
|
|
1676
|
-
stellarSdk.nativeToScVal(leafIndex, { type: "u32" }),
|
|
1677
|
-
bytesScVal(assertion.authenticatorData),
|
|
1678
|
-
bytesScVal(assertion.clientDataJSON),
|
|
1679
|
-
stellarSdk.nativeToScVal(assertion.challengeOffset, { type: "u32" }),
|
|
1680
|
-
bytesScVal(sig)
|
|
1681
|
-
]);
|
|
2375
|
+
builder.addOperation(
|
|
2376
|
+
stellarSdk.Operation.setOptions({
|
|
2377
|
+
source: params.masterAddress,
|
|
2378
|
+
masterWeight: 0,
|
|
2379
|
+
lowThreshold: 1,
|
|
2380
|
+
medThreshold: 1,
|
|
2381
|
+
highThreshold: 1,
|
|
2382
|
+
signer: { ed25519PublicKey: params.controlAddress, weight: 1 }
|
|
2383
|
+
})
|
|
2384
|
+
);
|
|
2385
|
+
return builder.setTimeout(TX_TIMEOUT).build();
|
|
1682
2386
|
}
|
|
1683
|
-
/**
|
|
1684
|
-
|
|
1685
|
-
*
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
const
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
async isApprover(accountAddress, passkey, readSource) {
|
|
1704
|
-
if (!await this.isDeployed(accountAddress)) return false;
|
|
1705
|
-
const { Account: Account3, TransactionBuilder: TransactionBuilder2, BASE_FEE: BASE_FEE2 } = await import('@stellar/stellar-sdk');
|
|
1706
|
-
const src = new Account3(readSource, "0");
|
|
1707
|
-
const op = stellarSdk.Operation.invokeHostFunction({
|
|
1708
|
-
func: invokeFunc(accountAddress, "is_approver", [bytesScVal(sec1Pubkey(passkey))]),
|
|
1709
|
-
auth: []
|
|
1710
|
-
});
|
|
1711
|
-
const tx2 = new TransactionBuilder2(src, { fee: BASE_FEE2, networkPassphrase: this.passphrase }).addOperation(op).setTimeout(30).build();
|
|
1712
|
-
const sim = await this.server().simulateTransaction(tx2);
|
|
1713
|
-
if (stellarSdk.rpc.Api.isSimulationError(sim)) {
|
|
1714
|
-
throw new Error(`kit/stellar: is_approver simulation failed: ${sim.error}`);
|
|
1715
|
-
}
|
|
1716
|
-
if (!sim.result?.retval) return false;
|
|
1717
|
-
return stellarSdk.scValToNative(sim.result.retval) === true;
|
|
1718
|
-
}
|
|
1719
|
-
/** Read the current passkey-approval nonce (read-only simulation). */
|
|
1720
|
-
async passkeyNonce(accountAddress, readSource) {
|
|
1721
|
-
if (!await this.isDeployed(accountAddress)) return 0n;
|
|
1722
|
-
const { Account: Account3, TransactionBuilder: TransactionBuilder2, BASE_FEE: BASE_FEE2 } = await import('@stellar/stellar-sdk');
|
|
1723
|
-
const src = new Account3(readSource, "0");
|
|
1724
|
-
const op = stellarSdk.Operation.invokeHostFunction({
|
|
1725
|
-
func: invokeFunc(accountAddress, "passkey_nonce", []),
|
|
1726
|
-
auth: []
|
|
2387
|
+
/**
|
|
2388
|
+
* Build a **sponsored** account-creation transaction whose source is the
|
|
2389
|
+
* relayer. Wraps the account setup in `beginSponsoringFutureReserves` /
|
|
2390
|
+
* `endSponsoringFutureReserves`, so the relayer (not the user) pays every
|
|
2391
|
+
* reserve — the account is created with a 0 starting balance and holds no
|
|
2392
|
+
* locked XLM of the user's. Ops:
|
|
2393
|
+
* 0. beginSponsoringFutureReserves(G) source = relayer
|
|
2394
|
+
* 1. createAccount(G, 0) source = relayer
|
|
2395
|
+
* 2. manageData(cv:… envelope) source = G (master-signed)
|
|
2396
|
+
* 3. setOptions(control signer, master → 0) source = G
|
|
2397
|
+
* 4. endSponsoringFutureReserves() source = G
|
|
2398
|
+
*
|
|
2399
|
+
* Signed by the master (for the G ops, while it's still weight 1); the relayer
|
|
2400
|
+
* co-signs (source + fee + sponsorship) before submitting.
|
|
2401
|
+
*/
|
|
2402
|
+
async buildSponsoredCreateTx(params) {
|
|
2403
|
+
const relayerAccount = await this.server().loadAccount(params.relayer);
|
|
2404
|
+
const builder = new stellarSdk.TransactionBuilder(relayerAccount, {
|
|
2405
|
+
fee: stellarSdk.BASE_FEE,
|
|
2406
|
+
networkPassphrase: this.passphrase
|
|
1727
2407
|
});
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
2408
|
+
builder.addOperation(
|
|
2409
|
+
stellarSdk.Operation.beginSponsoringFutureReserves({ sponsoredId: params.masterAddress, source: params.relayer })
|
|
2410
|
+
);
|
|
2411
|
+
builder.addOperation(
|
|
2412
|
+
stellarSdk.Operation.createAccount({ destination: params.masterAddress, startingBalance: "0", source: params.relayer })
|
|
2413
|
+
);
|
|
2414
|
+
for (const [name, value] of Object.entries(toDataEntries(params.envelope))) {
|
|
2415
|
+
builder.addOperation(
|
|
2416
|
+
stellarSdk.Operation.manageData({ name, value: Buffer.from(value), source: params.masterAddress })
|
|
2417
|
+
);
|
|
1732
2418
|
}
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
2419
|
+
builder.addOperation(
|
|
2420
|
+
stellarSdk.Operation.setOptions({
|
|
2421
|
+
source: params.masterAddress,
|
|
2422
|
+
masterWeight: 0,
|
|
2423
|
+
lowThreshold: 1,
|
|
2424
|
+
medThreshold: 1,
|
|
2425
|
+
highThreshold: 1,
|
|
2426
|
+
signer: { ed25519PublicKey: params.controlAddress, weight: 1 }
|
|
2427
|
+
})
|
|
2428
|
+
);
|
|
2429
|
+
builder.addOperation(stellarSdk.Operation.endSponsoringFutureReserves({ source: params.masterAddress }));
|
|
2430
|
+
return builder.setTimeout(TX_TIMEOUT).build();
|
|
1743
2431
|
}
|
|
1744
2432
|
/**
|
|
1745
|
-
*
|
|
1746
|
-
*
|
|
1747
|
-
*
|
|
1748
|
-
* exactly what the contract recomputes. Mutates + returns the entry.
|
|
2433
|
+
* Build a classic native-XLM payment as an *inner* transaction whose source is
|
|
2434
|
+
* the account itself (`G…`), signed by the control key. Wrap it in a fee-bump
|
|
2435
|
+
* (see `wrapFeeBump`) so the relayer pays the fee — gasless.
|
|
1749
2436
|
*/
|
|
1750
|
-
async
|
|
1751
|
-
const
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
signatureExpirationLedger: validUntilLedger,
|
|
1758
|
-
invocation: entry.rootInvocation()
|
|
2437
|
+
async buildPaymentTx(params) {
|
|
2438
|
+
const source = await this.server().loadAccount(params.from);
|
|
2439
|
+
return new stellarSdk.TransactionBuilder(source, { fee: stellarSdk.BASE_FEE, networkPassphrase: this.passphrase }).addOperation(
|
|
2440
|
+
stellarSdk.Operation.payment({
|
|
2441
|
+
destination: params.to,
|
|
2442
|
+
asset: stellarSdk.Asset.native(),
|
|
2443
|
+
amount: fromStroops(params.amount)
|
|
1759
2444
|
})
|
|
1760
|
-
);
|
|
1761
|
-
const payload = stellarSdk.hash(preimage.toXDR());
|
|
1762
|
-
const sig = await this.signer.sign(new Uint8Array(payload));
|
|
1763
|
-
const pubkey = await this.signer.getPublicKey();
|
|
1764
|
-
addrCreds.signature(deviceSignatureScVal(pubkey, sig));
|
|
1765
|
-
return entry;
|
|
2445
|
+
).setTimeout(TX_TIMEOUT).build();
|
|
1766
2446
|
}
|
|
1767
2447
|
/**
|
|
1768
|
-
*
|
|
1769
|
-
*
|
|
1770
|
-
* none. `readSource` is any funded G-account (used only for the simulation).
|
|
2448
|
+
* Build a data-entry write (e.g. re-wrapping the DEK for a newly approved
|
|
2449
|
+
* device) as an inner tx sourced by the account, signed by the control key.
|
|
1771
2450
|
*/
|
|
1772
|
-
async
|
|
1773
|
-
|
|
1774
|
-
const
|
|
1775
|
-
const
|
|
1776
|
-
|
|
1777
|
-
func: invokeFunc(tokenId, "balance", [new stellarSdk.Address(account).toScVal()]),
|
|
1778
|
-
auth: []
|
|
1779
|
-
});
|
|
1780
|
-
const tx2 = new TransactionBuilder2(src, { fee: BASE_FEE2, networkPassphrase: this.passphrase }).addOperation(op).setTimeout(30).build();
|
|
1781
|
-
const sim = await this.server().simulateTransaction(tx2);
|
|
1782
|
-
if (stellarSdk.rpc.Api.isSimulationError(sim) || !sim.result?.retval) return 0n;
|
|
1783
|
-
return BigInt(stellarSdk.scValToNative(sim.result.retval));
|
|
1784
|
-
}
|
|
1785
|
-
/** Whether the account contract instance exists on-chain (is deployed). */
|
|
1786
|
-
async isDeployed(accountAddress) {
|
|
1787
|
-
try {
|
|
1788
|
-
const res = await this.server().getContractData(
|
|
1789
|
-
accountAddress,
|
|
1790
|
-
stellarSdk.xdr.ScVal.scvLedgerKeyContractInstance(),
|
|
1791
|
-
stellarSdk.rpc.Durability.Persistent
|
|
1792
|
-
);
|
|
1793
|
-
return !!res;
|
|
1794
|
-
} catch {
|
|
1795
|
-
return false;
|
|
2451
|
+
async buildDataTx(params) {
|
|
2452
|
+
const source = await this.server().loadAccount(params.account);
|
|
2453
|
+
const builder = new stellarSdk.TransactionBuilder(source, { fee: stellarSdk.BASE_FEE, networkPassphrase: this.passphrase });
|
|
2454
|
+
for (const [name, value] of Object.entries(params.entries)) {
|
|
2455
|
+
builder.addOperation(stellarSdk.Operation.manageData({ name, value: Buffer.from(value) }));
|
|
1796
2456
|
}
|
|
2457
|
+
return builder.setTimeout(TX_TIMEOUT).build();
|
|
1797
2458
|
}
|
|
1798
2459
|
/**
|
|
1799
|
-
*
|
|
1800
|
-
*
|
|
1801
|
-
*
|
|
2460
|
+
* Build a **sponsored** data-entry write whose source is the relayer, so the
|
|
2461
|
+
* relayer (not the 0-balance account) pays the reserve of any NEW subentry.
|
|
2462
|
+
* Adding a factor (passkey / recovery) or a device slot creates a data entry
|
|
2463
|
+
* that needs ~0.5 XLM of reserve; a sponsored account holds no XLM, so — like
|
|
2464
|
+
* account creation — the write must be wrapped in begin/endSponsoringFuture
|
|
2465
|
+
* reserves with the relayer as sponsor. Ops:
|
|
2466
|
+
* 0. beginSponsoringFutureReserves(account) source = relayer
|
|
2467
|
+
* 1..n. manageData(cv:…) source = account (control-signed)
|
|
2468
|
+
* last. endSponsoringFutureReserves() source = account
|
|
2469
|
+
*
|
|
2470
|
+
* Signed by the control key (account ops) + the relayer (source + fee + sponsor).
|
|
1802
2471
|
*/
|
|
1803
|
-
async
|
|
1804
|
-
|
|
1805
|
-
const
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
func: invokeFunc(accountAddress, "is_authorized", [bytesScVal(sec1Pubkey(signer))]),
|
|
1809
|
-
auth: []
|
|
2472
|
+
async buildSponsoredDataTx(params) {
|
|
2473
|
+
const relayerAccount = await this.server().loadAccount(params.relayer);
|
|
2474
|
+
const builder = new stellarSdk.TransactionBuilder(relayerAccount, {
|
|
2475
|
+
fee: stellarSdk.BASE_FEE,
|
|
2476
|
+
networkPassphrase: this.passphrase
|
|
1810
2477
|
});
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
2478
|
+
builder.addOperation(
|
|
2479
|
+
stellarSdk.Operation.beginSponsoringFutureReserves({ sponsoredId: params.account, source: params.relayer })
|
|
2480
|
+
);
|
|
2481
|
+
for (const [name, value] of Object.entries(params.entries)) {
|
|
2482
|
+
builder.addOperation(stellarSdk.Operation.manageData({ name, value: Buffer.from(value), source: params.account }));
|
|
1815
2483
|
}
|
|
1816
|
-
|
|
1817
|
-
return
|
|
2484
|
+
builder.addOperation(stellarSdk.Operation.endSponsoringFutureReserves({ source: params.account }));
|
|
2485
|
+
return builder.setTimeout(TX_TIMEOUT).build();
|
|
2486
|
+
}
|
|
2487
|
+
/** Wrap a control-signed inner tx in a fee-bump whose fee source is `feeSource`
|
|
2488
|
+
* (the relayer). The inner tx pays nothing; the relayer pays all fees. */
|
|
2489
|
+
wrapFeeBump(inner, feeSource) {
|
|
2490
|
+
return stellarSdk.TransactionBuilder.buildFeeBumpTransaction(feeSource, stellarSdk.BASE_FEE, inner, this.passphrase);
|
|
2491
|
+
}
|
|
2492
|
+
/** Submit a fully-signed classic transaction and return its hash. Throws with
|
|
2493
|
+
* the Horizon result codes on failure. */
|
|
2494
|
+
async submit(tx3) {
|
|
2495
|
+
try {
|
|
2496
|
+
const res = await this.server().submitTransaction(tx3);
|
|
2497
|
+
return res.hash;
|
|
2498
|
+
} catch (e) {
|
|
2499
|
+
throw new Error(`kit/stellar: submit failed: ${horizonError(e)}`);
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
/** Build an `Account` handle for a known address + sequence (avoids a Horizon
|
|
2503
|
+
* round-trip when the caller already has the sequence). */
|
|
2504
|
+
accountAt(address, sequence) {
|
|
2505
|
+
return new stellarSdk.Account(address, sequence);
|
|
1818
2506
|
}
|
|
1819
2507
|
};
|
|
1820
|
-
function
|
|
1821
|
-
const
|
|
1822
|
-
|
|
1823
|
-
out.set(bigIntTo32Bytes(pk.x), 1);
|
|
1824
|
-
out.set(bigIntTo32Bytes(pk.y), 33);
|
|
1825
|
-
return out;
|
|
2508
|
+
function isNotFound(e) {
|
|
2509
|
+
const status = e?.response?.status ?? e?.status;
|
|
2510
|
+
return status === 404;
|
|
1826
2511
|
}
|
|
1827
|
-
function
|
|
1828
|
-
const
|
|
1829
|
-
|
|
1830
|
-
out.set(bigIntTo32Bytes(sig.r), 0);
|
|
1831
|
-
out.set(bigIntTo32Bytes(lowS), 32);
|
|
1832
|
-
return out;
|
|
2512
|
+
function horizonError(e) {
|
|
2513
|
+
const codes = e?.response?.data?.extras?.result_codes;
|
|
2514
|
+
return codes ? JSON.stringify(codes) : String(e?.message ?? e);
|
|
1833
2515
|
}
|
|
1834
|
-
function
|
|
1835
|
-
const
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
signature: Buffer.from(encodeLowSSignature2(sig))
|
|
1839
|
-
},
|
|
1840
|
-
{ type: { public_key: ["symbol", "bytes"], signature: ["symbol", "bytes"] } }
|
|
1841
|
-
);
|
|
1842
|
-
return stellarSdk.xdr.ScVal.scvVec([element]);
|
|
1843
|
-
}
|
|
1844
|
-
function invokeFunc(contractId, method, args) {
|
|
1845
|
-
return stellarSdk.xdr.HostFunction.hostFunctionTypeInvokeContract(
|
|
1846
|
-
new stellarSdk.xdr.InvokeContractArgs({
|
|
1847
|
-
contractAddress: new stellarSdk.Address(contractId).toScAddress(),
|
|
1848
|
-
functionName: method,
|
|
1849
|
-
args
|
|
1850
|
-
})
|
|
1851
|
-
);
|
|
2516
|
+
function toStroops(amount) {
|
|
2517
|
+
const [whole, frac = ""] = amount.split(".");
|
|
2518
|
+
const fracPadded = (frac + "0000000").slice(0, 7);
|
|
2519
|
+
return BigInt(whole) * 10000000n + BigInt(fracPadded);
|
|
1852
2520
|
}
|
|
1853
|
-
function
|
|
1854
|
-
|
|
2521
|
+
function fromStroops(stroops) {
|
|
2522
|
+
const neg = stroops < 0n;
|
|
2523
|
+
const abs = neg ? -stroops : stroops;
|
|
2524
|
+
const whole = abs / 10000000n;
|
|
2525
|
+
const frac = (abs % 10000000n).toString().padStart(7, "0");
|
|
2526
|
+
return `${neg ? "-" : ""}${whole}.${frac}`;
|
|
2527
|
+
}
|
|
2528
|
+
var MASTER_HKDF_INFO = "cavos-stellar-master";
|
|
2529
|
+
function deriveStellarMasterSeed(identity) {
|
|
2530
|
+
const ikm = deriveAddressSeedStellar(identity);
|
|
2531
|
+
return hkdf.hkdf(sha256.sha256, ikm, void 0, MASTER_HKDF_INFO, 32);
|
|
2532
|
+
}
|
|
2533
|
+
function deriveStellarMasterKeypair(identity) {
|
|
2534
|
+
return stellarSdk.Keypair.fromRawEd25519Seed(Buffer.from(deriveStellarMasterSeed(identity)));
|
|
2535
|
+
}
|
|
2536
|
+
function generateControlKey() {
|
|
2537
|
+
const seed = utils.randomBytes(32);
|
|
2538
|
+
return { keypair: stellarSdk.Keypair.fromRawEd25519Seed(Buffer.from(seed)), seed };
|
|
2539
|
+
}
|
|
2540
|
+
function controlKeypairFromSeed(seed) {
|
|
2541
|
+
if (seed.length !== 32) throw new Error("kit/stellar: control seed must be 32 bytes");
|
|
2542
|
+
return stellarSdk.Keypair.fromRawEd25519Seed(Buffer.from(seed));
|
|
1855
2543
|
}
|
|
1856
2544
|
|
|
1857
2545
|
// src/chains/stellar/StellarRelayer.ts
|
|
@@ -1859,7 +2547,7 @@ var StellarRelayer = class {
|
|
|
1859
2547
|
constructor(opts) {
|
|
1860
2548
|
this.opts = opts;
|
|
1861
2549
|
}
|
|
1862
|
-
/** The relayer's source/fee-payer G-account (fetched + cached
|
|
2550
|
+
/** The relayer's source/fee-payer/sponsor G-account (fetched + cached). */
|
|
1863
2551
|
async getSource() {
|
|
1864
2552
|
if (this.source) return this.source;
|
|
1865
2553
|
const res = await fetch(`${this.opts.baseUrl}/api/stellar/relay?network=${this.opts.network}`);
|
|
@@ -1868,17 +2556,16 @@ var StellarRelayer = class {
|
|
|
1868
2556
|
this.source = fee_payer;
|
|
1869
2557
|
return this.source;
|
|
1870
2558
|
}
|
|
1871
|
-
/**
|
|
1872
|
-
*
|
|
1873
|
-
|
|
1874
|
-
*/
|
|
1875
|
-
async submit(transactionXdr) {
|
|
2559
|
+
/** POST a (partially) signed transaction XDR for the relayer to co-sign + submit.
|
|
2560
|
+
* `kind` selects the validation gate. Returns the confirmed transaction hash. */
|
|
2561
|
+
async submit(kind, transactionXdr) {
|
|
1876
2562
|
const res = await fetch(`${this.opts.baseUrl}/api/stellar/relay`, {
|
|
1877
2563
|
method: "POST",
|
|
1878
2564
|
headers: { "Content-Type": "application/json" },
|
|
1879
2565
|
body: JSON.stringify({
|
|
1880
2566
|
app_id: this.opts.appId,
|
|
1881
2567
|
network: this.opts.network,
|
|
2568
|
+
kind,
|
|
1882
2569
|
transaction: transactionXdr
|
|
1883
2570
|
})
|
|
1884
2571
|
});
|
|
@@ -1886,313 +2573,388 @@ var StellarRelayer = class {
|
|
|
1886
2573
|
const detail = await res.text().catch(() => "");
|
|
1887
2574
|
throw new Error(`kit/stellar: relay failed (${res.status}) ${detail}`);
|
|
1888
2575
|
}
|
|
1889
|
-
const { hash:
|
|
1890
|
-
return
|
|
2576
|
+
const { hash: hash5 } = await res.json();
|
|
2577
|
+
return hash5;
|
|
1891
2578
|
}
|
|
1892
2579
|
};
|
|
1893
2580
|
|
|
1894
2581
|
// src/chains/stellar/CavosStellar.ts
|
|
2582
|
+
var DEFAULT_STARTING_BALANCE = 50000000n;
|
|
1895
2583
|
var CavosStellar = class _CavosStellar {
|
|
1896
|
-
constructor(identity, address, status, network, adapter,
|
|
2584
|
+
constructor(identity, address, status, network, adapter, deviceKey, control, dek, relayer) {
|
|
1897
2585
|
this.identity = identity;
|
|
1898
2586
|
this.address = address;
|
|
1899
|
-
this.status = status;
|
|
1900
2587
|
this.network = network;
|
|
1901
2588
|
this.adapter = adapter;
|
|
1902
|
-
this.
|
|
2589
|
+
this.deviceKey = deviceKey;
|
|
2590
|
+
this.control = control;
|
|
2591
|
+
this.dek = dek;
|
|
1903
2592
|
this.relayer = relayer;
|
|
1904
|
-
|
|
1905
|
-
|
|
2593
|
+
// Discriminant for the `CavosWallet` union. Classic `G…` IS the Stellar chain
|
|
2594
|
+
// now (the Soroban `C…` path was removed), so this is "stellar".
|
|
1906
2595
|
this.chain = "stellar";
|
|
1907
|
-
/** True when this connect just created a brand-new account (first sign-up). */
|
|
1908
2596
|
this.isNewAccount = false;
|
|
2597
|
+
this.statusValue = status;
|
|
1909
2598
|
}
|
|
1910
|
-
get
|
|
1911
|
-
return this.
|
|
2599
|
+
get status() {
|
|
2600
|
+
return this.statusValue;
|
|
1912
2601
|
}
|
|
1913
2602
|
static async connect(opts) {
|
|
1914
2603
|
const identity = opts.identity ?? await opts.auth?.authenticate();
|
|
1915
2604
|
if (!identity) throw new Error("kit/stellar: connect requires `identity` or `auth`");
|
|
1916
|
-
const
|
|
1917
|
-
const
|
|
1918
|
-
const
|
|
1919
|
-
|
|
1920
|
-
rpcUrl: opts.rpcUrl,
|
|
1921
|
-
factoryId: opts.factoryId,
|
|
1922
|
-
signer
|
|
1923
|
-
});
|
|
1924
|
-
const addressSeed = deriveAddressSeedStellar({ userId: identity.userId, appSalt: opts.appSalt });
|
|
2605
|
+
const adapter = new StellarAdapter({ network: opts.network, horizonUrl: opts.horizonUrl });
|
|
2606
|
+
const master = deriveStellarMasterKeypair({ userId: identity.userId, appSalt: opts.appSalt });
|
|
2607
|
+
const address = master.publicKey();
|
|
2608
|
+
const startingBalance = opts.startingBalance ?? DEFAULT_STARTING_BALANCE;
|
|
1925
2609
|
const backendUrl = opts.backendUrl ?? "https://cavos.xyz";
|
|
1926
|
-
const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : defaultRegistry2);
|
|
1927
2610
|
const relayer = opts.relayer ?? (opts.appId ? new StellarRelayer({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : void 0);
|
|
1928
|
-
const build = (
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
2611
|
+
const build = (status, unlocked) => new _CavosStellar(
|
|
2612
|
+
identity,
|
|
2613
|
+
address,
|
|
2614
|
+
status,
|
|
2615
|
+
opts.network,
|
|
2616
|
+
adapter,
|
|
2617
|
+
opts.deviceKey,
|
|
2618
|
+
unlocked?.control,
|
|
2619
|
+
unlocked?.dek,
|
|
2620
|
+
relayer
|
|
2621
|
+
);
|
|
2622
|
+
if (await adapter.isDeployed(address)) {
|
|
2623
|
+
const unlocked = await unlockViaDevice(adapter, address, opts.deviceKey);
|
|
2624
|
+
return build(unlocked ? "ready" : "needs-device-approval", unlocked ?? void 0);
|
|
1935
2625
|
}
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
if (!wasDeployed) {
|
|
1939
|
-
const func = adapter.buildDeploy(addressSeed, devicePubkey);
|
|
1940
|
-
await self.submitHostFunction(func, void 0);
|
|
2626
|
+
if (!relayer && !opts.sourceKeypair) {
|
|
2627
|
+
throw new Error("kit/stellar: a relayer (appId) or sourceKeypair is required to create the account");
|
|
1941
2628
|
}
|
|
1942
|
-
|
|
1943
|
-
const
|
|
1944
|
-
const
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
2629
|
+
const { keypair: control, seed: controlSeed } = generateControlKey();
|
|
2630
|
+
const dek = generateDEK();
|
|
2631
|
+
const envelope = {
|
|
2632
|
+
ct: sealControlSeed(controlSeed, dek),
|
|
2633
|
+
deviceWraps: { [opts.deviceKey.slotId()]: eciesWrapDEK(dek, opts.deviceKey.publicKeySec1()) }
|
|
2634
|
+
};
|
|
2635
|
+
if (relayer) {
|
|
2636
|
+
const relayerSource = await relayer.getSource();
|
|
2637
|
+
const tx3 = await adapter.buildSponsoredCreateTx({
|
|
2638
|
+
relayer: relayerSource,
|
|
2639
|
+
masterAddress: address,
|
|
2640
|
+
controlAddress: control.publicKey(),
|
|
2641
|
+
envelope
|
|
2642
|
+
});
|
|
2643
|
+
tx3.sign(master);
|
|
2644
|
+
await relayer.submit("create", tx3.toXDR());
|
|
2645
|
+
} else {
|
|
2646
|
+
const funder = opts.sourceKeypair;
|
|
2647
|
+
const tx3 = await adapter.buildCreateTx({
|
|
2648
|
+
funder: funder.publicKey(),
|
|
2649
|
+
masterAddress: address,
|
|
2650
|
+
controlAddress: control.publicKey(),
|
|
2651
|
+
envelope,
|
|
2652
|
+
startingBalance
|
|
2653
|
+
});
|
|
2654
|
+
tx3.sign(master, funder);
|
|
2655
|
+
await adapter.submit(tx3);
|
|
1967
2656
|
}
|
|
1968
|
-
const
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
const transactionHash = await this.submitHostFunction(func, this.address);
|
|
1972
|
-
return { transactionHash };
|
|
2657
|
+
const wallet = build("ready", { control, dek });
|
|
2658
|
+
wallet.isNewAccount = true;
|
|
2659
|
+
return wallet;
|
|
1973
2660
|
}
|
|
1974
|
-
/**
|
|
1975
|
-
|
|
2661
|
+
/** Native XLM balance of the account, in stroops. */
|
|
2662
|
+
async balance() {
|
|
2663
|
+
return this.adapter.balance(this.address);
|
|
2664
|
+
}
|
|
2665
|
+
/** True if the account has a passkey factor enrolled (`cv:wp`), so a new device
|
|
2666
|
+
* can be approved with the passkey instead of a recovery code. Mirrors the
|
|
2667
|
+
* other chains' `hasPasskey()` for the React provider. */
|
|
1976
2668
|
async hasPasskey() {
|
|
1977
|
-
|
|
1978
|
-
|
|
2669
|
+
try {
|
|
2670
|
+
const env = fromDataEntries(await this.adapter.loadDataEntries(this.address));
|
|
2671
|
+
return !!env.passkeyWrap;
|
|
2672
|
+
} catch {
|
|
2673
|
+
return false;
|
|
2674
|
+
}
|
|
1979
2675
|
}
|
|
1980
|
-
/**
|
|
1981
|
-
*
|
|
2676
|
+
/** Whether the control key is unlocked on this device (status ready). Classic
|
|
2677
|
+
* approvals land synchronously via Horizon, so this reflects state immediately
|
|
2678
|
+
* (no indexing delay to poll for). */
|
|
1982
2679
|
async isReady() {
|
|
1983
|
-
|
|
1984
|
-
return this.adapter.isAuthorizedSigner(this.address, this.devicePubkey, readSource);
|
|
2680
|
+
return this.statusValue === "ready";
|
|
1985
2681
|
}
|
|
1986
2682
|
/**
|
|
1987
|
-
*
|
|
1988
|
-
*
|
|
1989
|
-
*
|
|
1990
|
-
*
|
|
2683
|
+
* Move `amount` stroops of native XLM to `destination`, signed by the control
|
|
2684
|
+
* key. Sponsored by default (the relayer fee-bumps and pays the fee); pass
|
|
2685
|
+
* `{ sponsored: false }` to submit directly — the account pays its own (tiny)
|
|
2686
|
+
* fee from its XLM balance. The control key signs identically in both modes;
|
|
2687
|
+
* only the fee payer differs.
|
|
1991
2688
|
*/
|
|
1992
|
-
async
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
const { leaf, nonce } = await this.passkeyLeafForThisDevice();
|
|
1997
|
-
const leaves = [leaf];
|
|
1998
|
-
const assertion = await passkey.assert(batchChallenge(leaves));
|
|
1999
|
-
const { transactionHash } = await this.submitPasskeyApproval(assertion, leaves, 0, nonce);
|
|
2000
|
-
return transactionHash;
|
|
2689
|
+
async execute(amount, destination, opts) {
|
|
2690
|
+
const control = this.requireControl();
|
|
2691
|
+
const inner = await this.adapter.buildPaymentTx({ from: this.address, to: destination, amount });
|
|
2692
|
+
return this.submitInner(inner, control, opts);
|
|
2001
2693
|
}
|
|
2002
|
-
/**
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2694
|
+
/**
|
|
2695
|
+
* Enroll a passkey as an unlock factor: wrap the DEK under the passkey's PRF
|
|
2696
|
+
* output and write the `cv:wp` entry. This is the synced anchor used to approve
|
|
2697
|
+
* a new device or recover — it survives device loss. Idempotent-ish: writing it
|
|
2698
|
+
* again just overwrites the wrap of the same DEK. Requires a ready device.
|
|
2699
|
+
*/
|
|
2700
|
+
async enrollPasskey(prfOutput) {
|
|
2701
|
+
const { control, dek } = this.requireUnlocked();
|
|
2702
|
+
const wrap = wrapDEK(dek, derivePasskeyKEK(prfOutput));
|
|
2703
|
+
return this.writeFactor(PASSKEY_BASE, wrap, control);
|
|
2007
2704
|
}
|
|
2008
|
-
/**
|
|
2009
|
-
*
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
assertion
|
|
2705
|
+
/**
|
|
2706
|
+
* Set up a recovery code as an unlock factor: wrap the DEK under the code's KEK
|
|
2707
|
+
* and write the `cv:wr` entry. Optional in v1 — the integrating app decides when
|
|
2708
|
+
* to surface it. The code never leaves the device; only the wrap goes on-chain.
|
|
2709
|
+
* Requires a ready device.
|
|
2710
|
+
*/
|
|
2711
|
+
async setupRecovery(code) {
|
|
2712
|
+
const { control, dek } = this.requireUnlocked();
|
|
2713
|
+
const wrap = wrapDEK(dek, deriveRecoveryKEK(code));
|
|
2714
|
+
return this.writeFactor(RECOVERY_BASE, wrap, control);
|
|
2715
|
+
}
|
|
2716
|
+
/**
|
|
2717
|
+
* From a new browser/device (`needs-device-approval`), approve THIS device using
|
|
2718
|
+
* the user's synced passkey: unlock the DEK via the passkey factor, then wrap it
|
|
2719
|
+
* to this device's slot so future sessions unlock silently. Flips status to
|
|
2720
|
+
* `ready`. No trip back to an already-authorized device.
|
|
2721
|
+
*/
|
|
2722
|
+
async approveThisDeviceWithPasskey(prfOutput) {
|
|
2723
|
+
return this.approveThisDevice(
|
|
2724
|
+
await unlockViaPasskey(this.adapter, this.address, prfOutput),
|
|
2725
|
+
"passkey"
|
|
2030
2726
|
);
|
|
2031
|
-
return { transactionHash: await this.submitHostFunction(func, void 0) };
|
|
2032
2727
|
}
|
|
2033
|
-
/**
|
|
2034
|
-
|
|
2035
|
-
|
|
2728
|
+
/** Approve THIS device using the recovery code (same as the passkey path, for
|
|
2729
|
+
* the backup factor). */
|
|
2730
|
+
async approveThisDeviceWithRecovery(code) {
|
|
2731
|
+
return this.approveThisDevice(
|
|
2732
|
+
await unlockViaRecovery(this.adapter, this.address, code),
|
|
2733
|
+
"recovery code"
|
|
2734
|
+
);
|
|
2036
2735
|
}
|
|
2037
|
-
/**
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
return this.adapter.readBalance(tokenId, this.address, readSource);
|
|
2736
|
+
/** The control key's public G address (the weight-1 real signer), for display. */
|
|
2737
|
+
get controlAddress() {
|
|
2738
|
+
return this.control?.publicKey();
|
|
2041
2739
|
}
|
|
2042
|
-
|
|
2043
|
-
async
|
|
2044
|
-
if (this.
|
|
2045
|
-
throw new Error("kit/stellar: this device is
|
|
2740
|
+
// --- internals ----------------------------------------------------------
|
|
2741
|
+
async approveThisDevice(unlocked, factor) {
|
|
2742
|
+
if (this.statusValue === "ready") {
|
|
2743
|
+
throw new Error("kit/stellar: this device is already authorized");
|
|
2744
|
+
}
|
|
2745
|
+
if (!unlocked) {
|
|
2746
|
+
throw new Error(`kit/stellar: could not unlock the account with the ${factor} \u2014 wrong factor or not enrolled`);
|
|
2046
2747
|
}
|
|
2047
|
-
const
|
|
2048
|
-
|
|
2748
|
+
const slot = this.deviceKey.slotId();
|
|
2749
|
+
const wrap = eciesWrapDEK(unlocked.dek, this.deviceKey.publicKeySec1());
|
|
2750
|
+
const hash5 = await this.submitDataWrite(deviceWrapEntries(slot, wrap), unlocked.control);
|
|
2751
|
+
this.control = unlocked.control;
|
|
2752
|
+
this.dek = unlocked.dek;
|
|
2753
|
+
this.statusValue = "ready";
|
|
2754
|
+
return hash5;
|
|
2755
|
+
}
|
|
2756
|
+
/** Write a single-factor wrap (passkey/recovery) into the account data entries,
|
|
2757
|
+
* signed by the control key. Overwrites cleanly if the base already existed and
|
|
2758
|
+
* the new blob has the same chunk count. */
|
|
2759
|
+
async writeFactor(base, wrap, control) {
|
|
2760
|
+
const entries = {};
|
|
2761
|
+
chunkTo64(wrap).forEach((chunk, i) => {
|
|
2762
|
+
entries[`${base}/${i}`] = chunk;
|
|
2763
|
+
});
|
|
2764
|
+
return this.submitDataWrite(entries, control);
|
|
2049
2765
|
}
|
|
2050
2766
|
/**
|
|
2051
|
-
*
|
|
2052
|
-
*
|
|
2053
|
-
*
|
|
2767
|
+
* Sign an inner (account-sourced) payment tx with the control key and submit it:
|
|
2768
|
+
* - sponsored (default) → with a relayer, wrap in a fee-bump (relayer pays
|
|
2769
|
+
* the fee) and POST; falls back to self-funded if no relayer;
|
|
2770
|
+
* - `{ sponsored: false }` → submit directly (the account pays its own fee).
|
|
2771
|
+
* Payments add no subentries, so no reserve sponsorship is needed here.
|
|
2054
2772
|
*/
|
|
2055
|
-
async
|
|
2056
|
-
|
|
2057
|
-
|
|
2773
|
+
async submitInner(inner, control, opts) {
|
|
2774
|
+
inner.sign(control);
|
|
2775
|
+
const sponsored = opts?.sponsored !== false;
|
|
2776
|
+
if (sponsored && this.relayer) {
|
|
2777
|
+
const feeSource = await this.relayer.getSource();
|
|
2778
|
+
const bump = this.adapter.wrapFeeBump(inner, feeSource);
|
|
2779
|
+
return this.relayer.submit("fee-bump", bump.toXDR());
|
|
2058
2780
|
}
|
|
2059
|
-
|
|
2060
|
-
const readSource = await this.resolveSource();
|
|
2061
|
-
if (await this.adapter.isAuthorizedSigner(this.address, backupPubkey, readSource)) return void 0;
|
|
2062
|
-
return this.addSigner(backupPubkey);
|
|
2781
|
+
return this.adapter.submit(inner);
|
|
2063
2782
|
}
|
|
2064
2783
|
/**
|
|
2065
|
-
*
|
|
2066
|
-
*
|
|
2067
|
-
*
|
|
2784
|
+
* Write data entries (add a factor / device slot) — which create NEW subentries
|
|
2785
|
+
* that each need ~0.5 XLM of reserve. A relayer-sponsored account holds no XLM,
|
|
2786
|
+
* so the write must be sponsored by the relayer (source + sponsor), exactly like
|
|
2787
|
+
* account creation — a plain fee-bump would fail with `op_low_reserve`.
|
|
2788
|
+
* - sponsored (default) → with a relayer, build a sponsored write (relayer
|
|
2789
|
+
* source + begin/end sponsoring), control-sign the account ops, relay
|
|
2790
|
+
* co-signs + submits; falls back to self-funded if no relayer;
|
|
2791
|
+
* - `{ sponsored: false }` → the account writes directly (it must hold its
|
|
2792
|
+
* own reserve for the new subentries).
|
|
2068
2793
|
*/
|
|
2069
|
-
|
|
2070
|
-
const
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : defaultRegistry2);
|
|
2081
|
-
const existing = await registry.lookup(opts.identity.userId);
|
|
2082
|
-
if (!existing) {
|
|
2083
|
-
throw new Error("kit/stellar: no account found for this identity \u2014 nothing to recover");
|
|
2794
|
+
async submitDataWrite(entries, control, opts) {
|
|
2795
|
+
const sponsored = opts?.sponsored !== false;
|
|
2796
|
+
if (sponsored && this.relayer) {
|
|
2797
|
+
const relayerSource = await this.relayer.getSource();
|
|
2798
|
+
const tx4 = await this.adapter.buildSponsoredDataTx({
|
|
2799
|
+
relayer: relayerSource,
|
|
2800
|
+
account: this.address,
|
|
2801
|
+
entries
|
|
2802
|
+
});
|
|
2803
|
+
tx4.sign(control);
|
|
2804
|
+
return this.relayer.submit("sponsored-data", tx4.toXDR());
|
|
2084
2805
|
}
|
|
2085
|
-
const
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
devicePubkey,
|
|
2093
|
-
relayer,
|
|
2094
|
-
opts.sourceKeypair
|
|
2095
|
-
);
|
|
2096
|
-
const readSource = await backupHandle.resolveSource();
|
|
2097
|
-
if (!await backupAdapter.isAuthorizedSigner(existing.address, devicePubkey, readSource)) {
|
|
2098
|
-
await backupHandle.addSigner(devicePubkey);
|
|
2806
|
+
const tx3 = await this.adapter.buildDataTx({ account: this.address, entries });
|
|
2807
|
+
tx3.sign(control);
|
|
2808
|
+
return this.adapter.submit(tx3);
|
|
2809
|
+
}
|
|
2810
|
+
requireControl() {
|
|
2811
|
+
if (this.statusValue !== "ready" || !this.control) {
|
|
2812
|
+
throw new Error("kit/stellar: control key not unlocked on this device (needs approval)");
|
|
2099
2813
|
}
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
}
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2814
|
+
return this.control;
|
|
2815
|
+
}
|
|
2816
|
+
requireUnlocked() {
|
|
2817
|
+
const control = this.requireControl();
|
|
2818
|
+
if (!this.dek) throw new Error("kit/stellar: DEK unavailable on this device");
|
|
2819
|
+
return { control, dek: this.dek };
|
|
2820
|
+
}
|
|
2821
|
+
};
|
|
2822
|
+
async function unlockViaDevice(adapter, address, deviceKey) {
|
|
2823
|
+
const env = await loadEnvelope(adapter, address);
|
|
2824
|
+
const wrap = env.deviceWraps[deviceKey.slotId()];
|
|
2825
|
+
if (!wrap) return null;
|
|
2826
|
+
try {
|
|
2827
|
+
const dek = await deviceKey.unwrap(wrap);
|
|
2828
|
+
return openControl(env, dek);
|
|
2829
|
+
} catch {
|
|
2830
|
+
return null;
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
async function unlockViaPasskey(adapter, address, prfOutput) {
|
|
2834
|
+
const env = await loadEnvelope(adapter, address);
|
|
2835
|
+
if (!env.passkeyWrap) return null;
|
|
2836
|
+
try {
|
|
2837
|
+
const dek = unwrapDEK(env.passkeyWrap, derivePasskeyKEK(prfOutput));
|
|
2838
|
+
return openControl(env, dek);
|
|
2839
|
+
} catch {
|
|
2840
|
+
return null;
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
async function unlockViaRecovery(adapter, address, code) {
|
|
2844
|
+
const env = await loadEnvelope(adapter, address);
|
|
2845
|
+
if (!env.recoveryWrap) return null;
|
|
2846
|
+
try {
|
|
2847
|
+
const dek = unwrapDEK(env.recoveryWrap, deriveRecoveryKEK(code));
|
|
2848
|
+
return openControl(env, dek);
|
|
2849
|
+
} catch {
|
|
2850
|
+
return null;
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
async function loadEnvelope(adapter, address) {
|
|
2854
|
+
return fromDataEntries(await adapter.loadDataEntries(address));
|
|
2855
|
+
}
|
|
2856
|
+
function openControl(env, dek) {
|
|
2857
|
+
const controlSeed = openControlSeed(env.ct, dek);
|
|
2858
|
+
return { control: controlKeypairFromSeed(controlSeed), dek };
|
|
2859
|
+
}
|
|
2860
|
+
function deviceSlotId(publicKeySec1) {
|
|
2861
|
+
const h = sha256.sha256(publicKeySec1);
|
|
2862
|
+
let s = "";
|
|
2863
|
+
for (const b of h.subarray(0, 4)) s += b.toString(16).padStart(2, "0");
|
|
2864
|
+
return s;
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2867
|
+
// src/chains/stellar/WebCryptoDeviceUnwrapKey.ts
|
|
2868
|
+
var IDB_NAME2 = "cavos-kit-stellar";
|
|
2869
|
+
var IDB_STORE2 = "unwrap-keys";
|
|
2870
|
+
var WebCryptoDeviceUnwrapKey = class _WebCryptoDeviceUnwrapKey {
|
|
2871
|
+
constructor(privateKey, publicRaw, keyId) {
|
|
2872
|
+
this.privateKey = privateKey;
|
|
2873
|
+
this.publicRaw = publicRaw;
|
|
2874
|
+
this.keyId = keyId;
|
|
2875
|
+
}
|
|
2876
|
+
/** Create a fresh device unwrap key and persist it. */
|
|
2877
|
+
static async create(opts) {
|
|
2878
|
+
assertSecureContext2();
|
|
2879
|
+
const pair = await crypto.subtle.generateKey(
|
|
2880
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
2881
|
+
false,
|
|
2882
|
+
// private key is NON-extractable
|
|
2883
|
+
["deriveBits"]
|
|
2115
2884
|
);
|
|
2885
|
+
const publicRaw = new Uint8Array(await crypto.subtle.exportKey("raw", pair.publicKey));
|
|
2886
|
+
await idbPut2(opts.keyId, { privateKey: pair.privateKey, publicRaw });
|
|
2887
|
+
return new _WebCryptoDeviceUnwrapKey(pair.privateKey, publicRaw, opts.keyId);
|
|
2116
2888
|
}
|
|
2117
|
-
/**
|
|
2118
|
-
async
|
|
2119
|
-
|
|
2120
|
-
if (
|
|
2121
|
-
|
|
2889
|
+
/** Load an existing device unwrap key, or null if none exists yet. */
|
|
2890
|
+
static async load(opts) {
|
|
2891
|
+
const rec = await idbGet2(opts.keyId);
|
|
2892
|
+
if (!rec) return null;
|
|
2893
|
+
return new _WebCryptoDeviceUnwrapKey(rec.privateKey, rec.publicRaw, opts.keyId);
|
|
2122
2894
|
}
|
|
2123
|
-
/**
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
const
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
}
|
|
2150
|
-
}
|
|
2151
|
-
const account = await server.getAccount(sourceAddr);
|
|
2152
|
-
const finalOp = stellarSdk.Operation.invokeHostFunction({ func, auth: signedAuth });
|
|
2153
|
-
const built = new stellarSdk.TransactionBuilder(account, {
|
|
2154
|
-
fee: stellarSdk.BASE_FEE,
|
|
2155
|
-
networkPassphrase: this.adapter.passphrase
|
|
2156
|
-
}).addOperation(finalOp).setTimeout(180).build();
|
|
2157
|
-
const authSim = await server.simulateTransaction(built);
|
|
2158
|
-
if (stellarSdk.rpc.Api.isSimulationError(authSim)) {
|
|
2159
|
-
throw new Error(`kit/stellar: auth simulation failed: ${authSim.error}`);
|
|
2160
|
-
}
|
|
2161
|
-
const assembled = stellarSdk.rpc.assembleTransaction(built, authSim).build();
|
|
2162
|
-
if (this.relayer) {
|
|
2163
|
-
return this.relayer.submit(assembled.toXDR());
|
|
2164
|
-
}
|
|
2165
|
-
if (this.sourceKeypair) {
|
|
2166
|
-
assembled.sign(this.sourceKeypair);
|
|
2167
|
-
return this.sendAndConfirm(assembled);
|
|
2168
|
-
}
|
|
2169
|
-
throw new Error("kit/stellar: no relayer or sourceKeypair configured to submit");
|
|
2170
|
-
}
|
|
2171
|
-
/** Submit a signed tx via RPC and poll to confirmation. Returns the hash. */
|
|
2172
|
-
async sendAndConfirm(tx2) {
|
|
2173
|
-
const server = this.adapter.server();
|
|
2174
|
-
const sent = await server.sendTransaction(tx2);
|
|
2175
|
-
if (sent.status === "ERROR") {
|
|
2176
|
-
throw new Error(`kit/stellar: submit rejected: ${JSON.stringify(sent.errorResult)}`);
|
|
2177
|
-
}
|
|
2178
|
-
const hash6 = sent.hash;
|
|
2179
|
-
for (let i = 0; i < 30; i++) {
|
|
2180
|
-
const got = await server.getTransaction(hash6);
|
|
2181
|
-
if (got.status === stellarSdk.rpc.Api.GetTransactionStatus.SUCCESS) return hash6;
|
|
2182
|
-
if (got.status === stellarSdk.rpc.Api.GetTransactionStatus.FAILED) {
|
|
2183
|
-
throw new Error(`kit/stellar: tx ${hash6} failed`);
|
|
2184
|
-
}
|
|
2185
|
-
await new Promise((r) => setTimeout(r, 1e3));
|
|
2186
|
-
}
|
|
2187
|
-
throw new Error(`kit/stellar: tx ${hash6} not confirmed in time`);
|
|
2895
|
+
/** Load the device unwrap key, creating one on first use. */
|
|
2896
|
+
static async loadOrCreate(opts) {
|
|
2897
|
+
return await _WebCryptoDeviceUnwrapKey.load(opts) ?? await _WebCryptoDeviceUnwrapKey.create(opts);
|
|
2898
|
+
}
|
|
2899
|
+
publicKeySec1() {
|
|
2900
|
+
return this.publicRaw;
|
|
2901
|
+
}
|
|
2902
|
+
slotId() {
|
|
2903
|
+
return deviceSlotId(this.publicRaw);
|
|
2904
|
+
}
|
|
2905
|
+
async unwrap(blob) {
|
|
2906
|
+
const ephPubCompressed = blob.subarray(0, 33);
|
|
2907
|
+
const wrapped = blob.subarray(33);
|
|
2908
|
+
const ephUncompressed = p256.p256.ProjectivePoint.fromHex(ephPubCompressed).toRawBytes(false);
|
|
2909
|
+
const ephKey = await crypto.subtle.importKey(
|
|
2910
|
+
"raw",
|
|
2911
|
+
ephUncompressed,
|
|
2912
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
2913
|
+
false,
|
|
2914
|
+
[]
|
|
2915
|
+
);
|
|
2916
|
+
const sharedX = new Uint8Array(
|
|
2917
|
+
await crypto.subtle.deriveBits({ name: "ECDH", public: ephKey }, this.privateKey, 256)
|
|
2918
|
+
);
|
|
2919
|
+
const kek = eciesKEKFromX(sharedX, ephPubCompressed);
|
|
2920
|
+
return unwrapDEK(wrapped, kek);
|
|
2188
2921
|
}
|
|
2189
2922
|
};
|
|
2190
|
-
function
|
|
2191
|
-
const
|
|
2192
|
-
if (
|
|
2193
|
-
|
|
2923
|
+
function assertSecureContext2() {
|
|
2924
|
+
const ok = typeof crypto !== "undefined" && typeof crypto.subtle !== "undefined" && (typeof window === "undefined" || window.isSecureContext);
|
|
2925
|
+
if (!ok) {
|
|
2926
|
+
throw new Error(
|
|
2927
|
+
"Cavos: WebCrypto is unavailable. Device keys require a secure context \u2014 use HTTPS, or http://localhost."
|
|
2928
|
+
);
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
function openDb2() {
|
|
2932
|
+
return new Promise((resolve, reject) => {
|
|
2933
|
+
const req = indexedDB.open(IDB_NAME2, 1);
|
|
2934
|
+
req.onupgradeneeded = () => req.result.createObjectStore(IDB_STORE2);
|
|
2935
|
+
req.onsuccess = () => resolve(req.result);
|
|
2936
|
+
req.onerror = () => reject(req.error);
|
|
2937
|
+
});
|
|
2938
|
+
}
|
|
2939
|
+
async function idbPut2(keyId, value) {
|
|
2940
|
+
const db = await openDb2();
|
|
2941
|
+
await tx2(db, "readwrite", (store) => store.put(value, keyId));
|
|
2942
|
+
db.close();
|
|
2943
|
+
}
|
|
2944
|
+
async function idbGet2(keyId) {
|
|
2945
|
+
const db = await openDb2();
|
|
2946
|
+
const result = await tx2(db, "readonly", (store) => store.get(keyId));
|
|
2947
|
+
db.close();
|
|
2948
|
+
return result ?? null;
|
|
2949
|
+
}
|
|
2950
|
+
function tx2(db, mode, run) {
|
|
2951
|
+
return new Promise((resolve, reject) => {
|
|
2952
|
+
const store = db.transaction(IDB_STORE2, mode).objectStore(IDB_STORE2);
|
|
2953
|
+
const req = run(store);
|
|
2954
|
+
req.onsuccess = () => resolve(req.result);
|
|
2955
|
+
req.onerror = () => reject(req.error);
|
|
2956
|
+
});
|
|
2194
2957
|
}
|
|
2195
|
-
var defaultRegistry2 = new InMemoryWalletRegistry();
|
|
2196
2958
|
|
|
2197
2959
|
// src/recovery/HttpRecoveryClient.ts
|
|
2198
2960
|
function toHex2(n) {
|
|
@@ -2324,17 +3086,16 @@ var Cavos = class _Cavos {
|
|
|
2324
3086
|
});
|
|
2325
3087
|
}
|
|
2326
3088
|
if (opts.chain === "stellar") {
|
|
3089
|
+
const identity = opts.identity ?? (opts.auth ? await opts.auth.authenticate() : void 0);
|
|
3090
|
+
if (!identity) throw new Error("kit: Stellar connect requires `identity` or `auth`");
|
|
3091
|
+
const deviceKey = opts.stellarDeviceKey ?? await WebCryptoDeviceUnwrapKey.loadOrCreate({ keyId: `${identity.userId}:${opts.appSalt}` });
|
|
2327
3092
|
return CavosStellar.connect({
|
|
2328
3093
|
network: STELLAR_ENV[opts.network],
|
|
2329
|
-
|
|
2330
|
-
...opts.identity ? { identity: opts.identity } : {},
|
|
3094
|
+
identity,
|
|
2331
3095
|
appSalt: opts.appSalt,
|
|
3096
|
+
deviceKey,
|
|
2332
3097
|
...opts.appId ? { appId: opts.appId } : {},
|
|
2333
3098
|
...opts.backendUrl ? { backendUrl: opts.backendUrl } : {},
|
|
2334
|
-
...opts.registry ? { registry: opts.registry } : {},
|
|
2335
|
-
...opts.rpcUrl ? { rpcUrl: opts.rpcUrl } : {},
|
|
2336
|
-
...opts.factoryId ? { factoryId: opts.factoryId } : {},
|
|
2337
|
-
...opts.createSigner ? { createSigner: opts.createSigner } : {},
|
|
2338
3099
|
...opts.stellarRelayer ? { relayer: opts.stellarRelayer } : {},
|
|
2339
3100
|
...opts.stellarSourceKeypair ? { sourceKeypair: opts.stellarSourceKeypair } : {}
|
|
2340
3101
|
});
|
|
@@ -2384,7 +3145,7 @@ var Cavos = class _Cavos {
|
|
|
2384
3145
|
cairoVersion: "1"
|
|
2385
3146
|
});
|
|
2386
3147
|
const backendUrl = opts.backendUrl ?? "https://cavos.xyz";
|
|
2387
|
-
const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) :
|
|
3148
|
+
const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : defaultRegistry2);
|
|
2388
3149
|
const recovery = opts.recovery ?? (opts.appId ? new HttpRecoveryClient({ baseUrl: backendUrl, appId: opts.appId }) : null);
|
|
2389
3150
|
const existing = await registry.lookup(identity.userId);
|
|
2390
3151
|
if (existing) {
|
|
@@ -2421,7 +3182,7 @@ var Cavos = class _Cavos {
|
|
|
2421
3182
|
}
|
|
2422
3183
|
return cavos2;
|
|
2423
3184
|
}
|
|
2424
|
-
const address = adapter.computeAddress({ addressSeed
|
|
3185
|
+
const address = adapter.computeAddress({ addressSeed });
|
|
2425
3186
|
const account = makeAccount(address);
|
|
2426
3187
|
const alreadyDeployed = await isDeployed(provider, address);
|
|
2427
3188
|
if (!alreadyDeployed) {
|
|
@@ -2429,10 +3190,11 @@ var Cavos = class _Cavos {
|
|
|
2429
3190
|
address,
|
|
2430
3191
|
class_hash: classHash,
|
|
2431
3192
|
salt: starknet.num.toHex(addressSeed),
|
|
2432
|
-
calldata: adapter.constructorCalldata(addressSeed
|
|
3193
|
+
calldata: adapter.constructorCalldata(addressSeed),
|
|
2433
3194
|
version: 1
|
|
2434
3195
|
};
|
|
2435
|
-
const
|
|
3196
|
+
const initCall = adapter.buildInitialize(address, devicePubkey);
|
|
3197
|
+
const deployRes = await account.executePaymasterTransaction([initCall], {
|
|
2436
3198
|
feeMode: { mode: "sponsored" },
|
|
2437
3199
|
deploymentData
|
|
2438
3200
|
});
|
|
@@ -2467,18 +3229,25 @@ var Cavos = class _Cavos {
|
|
|
2467
3229
|
return this.devicePubkey;
|
|
2468
3230
|
}
|
|
2469
3231
|
/** Execute a sponsored (gasless) multicall, signed silently by the device. */
|
|
2470
|
-
async execute(calls) {
|
|
3232
|
+
async execute(calls, opts) {
|
|
2471
3233
|
if (this.status !== "ready") {
|
|
2472
3234
|
throw new Error("kit: this device is not yet an authorized signer of the wallet");
|
|
2473
3235
|
}
|
|
3236
|
+
if (opts?.sponsored === false) {
|
|
3237
|
+
const res2 = await this.account.execute(calls);
|
|
3238
|
+
return { transactionHash: res2.transaction_hash };
|
|
3239
|
+
}
|
|
2474
3240
|
const res = await this.account.executePaymasterTransaction(calls, {
|
|
2475
3241
|
feeMode: { mode: "sponsored" }
|
|
2476
3242
|
});
|
|
2477
3243
|
return { transactionHash: res.transaction_hash };
|
|
2478
3244
|
}
|
|
2479
|
-
/**
|
|
2480
|
-
|
|
2481
|
-
|
|
3245
|
+
/**
|
|
3246
|
+
* Authorize an additional device signer. Sponsored by default; pass
|
|
3247
|
+
* `{ sponsored: false }` to pay the fee from the account's own ETH balance.
|
|
3248
|
+
*/
|
|
3249
|
+
async addSigner(pubkey, opts) {
|
|
3250
|
+
return this.execute([this.adapter.buildAddSigner(this.address, pubkey)], opts);
|
|
2482
3251
|
}
|
|
2483
3252
|
/**
|
|
2484
3253
|
* Enroll a passkey as an APPROVER so the user can later add devices from any
|
|
@@ -2487,25 +3256,27 @@ var Cavos = class _Cavos {
|
|
|
2487
3256
|
* approver. Call this whenever the app decides to prompt "turn on device
|
|
2488
3257
|
* approvals". Returns the passkey's public key + the enrollment tx hash.
|
|
2489
3258
|
*/
|
|
2490
|
-
async enrollPasskey(passkey, params) {
|
|
3259
|
+
async enrollPasskey(passkey, params, opts) {
|
|
2491
3260
|
const enrolled = await passkey.enroll(params);
|
|
2492
|
-
const { transactionHash } = await this.addApprover(enrolled.publicKey);
|
|
3261
|
+
const { transactionHash } = await this.addApprover(enrolled.publicKey, opts);
|
|
2493
3262
|
return { publicKey: enrolled.publicKey, transactionHash };
|
|
2494
3263
|
}
|
|
2495
3264
|
/**
|
|
2496
|
-
* Register an ALREADY-enrolled passkey public key as an approver (gasless
|
|
2497
|
-
* device-signed). Idempotent. Use this to register ONE passkey across
|
|
2498
|
-
* chains without re-prompting `passkey.enroll()` on each: enroll once,
|
|
2499
|
-
* call `addApprover(pubkey)` on each chain's wallet.
|
|
3265
|
+
* Register an ALREADY-enrolled passkey public key as an approver (gasless by
|
|
3266
|
+
* default, device-signed). Idempotent. Use this to register ONE passkey across
|
|
3267
|
+
* multiple chains without re-prompting `passkey.enroll()` on each: enroll once,
|
|
3268
|
+
* then call `addApprover(pubkey)` on each chain's wallet. Pass
|
|
3269
|
+
* `{ sponsored: false }` to pay the fee from the account's own balance.
|
|
2500
3270
|
*/
|
|
2501
|
-
async addApprover(pubkey) {
|
|
3271
|
+
async addApprover(pubkey, opts) {
|
|
2502
3272
|
if (this.status !== "ready") {
|
|
2503
3273
|
throw new Error("kit: addApprover requires a ready, authorized device");
|
|
2504
3274
|
}
|
|
2505
3275
|
if (await this.adapter.isApprover(this.address, pubkey)) return {};
|
|
2506
|
-
const { transactionHash } = await this.execute(
|
|
2507
|
-
this.adapter.buildAddApprover(this.address, pubkey)
|
|
2508
|
-
|
|
3276
|
+
const { transactionHash } = await this.execute(
|
|
3277
|
+
[this.adapter.buildAddApprover(this.address, pubkey)],
|
|
3278
|
+
opts
|
|
3279
|
+
);
|
|
2509
3280
|
try {
|
|
2510
3281
|
await this.account.waitForTransaction(transactionHash);
|
|
2511
3282
|
} catch (e) {
|
|
@@ -2592,11 +3363,11 @@ var Cavos = class _Cavos {
|
|
|
2592
3363
|
* add_signer (gasless). Returns the transaction hash (or undefined when the
|
|
2593
3364
|
* backup was already set up).
|
|
2594
3365
|
*/
|
|
2595
|
-
async setupRecovery(code) {
|
|
3366
|
+
async setupRecovery(code, opts) {
|
|
2596
3367
|
const { publicKey: backupPubkey } = deriveBackupKey(code);
|
|
2597
3368
|
const already = await this.adapter.isAuthorizedSigner(this.address, backupPubkey);
|
|
2598
3369
|
if (already) return void 0;
|
|
2599
|
-
return this.addSigner(backupPubkey);
|
|
3370
|
+
return this.addSigner(backupPubkey, opts);
|
|
2600
3371
|
}
|
|
2601
3372
|
/**
|
|
2602
3373
|
* Recover an account after losing every device signer. Derives the backup key
|
|
@@ -2623,11 +3394,11 @@ var Cavos = class _Cavos {
|
|
|
2623
3394
|
const backup = BackupSigner.fromCode(opts.code);
|
|
2624
3395
|
const backupAdapter = new StarknetAdapter({ classHash, signer: backup, provider });
|
|
2625
3396
|
const backendUrl = opts.backendUrl ?? "https://cavos.xyz";
|
|
2626
|
-
const
|
|
2627
|
-
|
|
2628
|
-
if (!existing) {
|
|
3397
|
+
const address = opts.address ?? await lookupAddress(opts, backendUrl, network);
|
|
3398
|
+
if (!address) {
|
|
2629
3399
|
throw new Error("kit: no account found for this identity \u2014 nothing to recover");
|
|
2630
3400
|
}
|
|
3401
|
+
const existing = { address };
|
|
2631
3402
|
const backupAccount = new starknet.Account({
|
|
2632
3403
|
provider,
|
|
2633
3404
|
address: existing.address,
|
|
@@ -2658,7 +3429,7 @@ var Cavos = class _Cavos {
|
|
|
2658
3429
|
return new _Cavos(opts.identity, existing.address, "ready", account, adapter, devicePubkey);
|
|
2659
3430
|
}
|
|
2660
3431
|
};
|
|
2661
|
-
var
|
|
3432
|
+
var defaultRegistry2 = new InMemoryWalletRegistry();
|
|
2662
3433
|
var DEVICE_REQUEST_DEDUP_MS = 5 * 60 * 1e3;
|
|
2663
3434
|
var lastDeviceRequest = /* @__PURE__ */ new Map();
|
|
2664
3435
|
async function isDeployed(provider, address) {
|
|
@@ -2669,6 +3440,11 @@ async function isDeployed(provider, address) {
|
|
|
2669
3440
|
return false;
|
|
2670
3441
|
}
|
|
2671
3442
|
}
|
|
3443
|
+
async function lookupAddress(opts, backendUrl, network) {
|
|
3444
|
+
const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network }) : defaultRegistry2);
|
|
3445
|
+
const existing = await registry.lookup(opts.identity.userId);
|
|
3446
|
+
return existing?.address ?? null;
|
|
3447
|
+
}
|
|
2672
3448
|
async function paymasterExecuteDirect(paymaster, userAddress, call) {
|
|
2673
3449
|
const body = {
|
|
2674
3450
|
jsonrpc: "2.0",
|
|
@@ -2703,6 +3479,101 @@ async function paymasterExecuteDirect(paymaster, userAddress, call) {
|
|
|
2703
3479
|
}
|
|
2704
3480
|
return { transactionHash: json.result?.transaction_hash ?? json.result?.tracking_id };
|
|
2705
3481
|
}
|
|
3482
|
+
var PRF_SALT = sha256.sha256(new TextEncoder().encode("cavos-stellar-prf-v1"));
|
|
3483
|
+
var PasskeyPrf = class {
|
|
3484
|
+
constructor(opts = {}) {
|
|
3485
|
+
if (typeof window === "undefined" || !navigator.credentials) {
|
|
3486
|
+
throw new Error("kit/passkey-prf: WebAuthn is only available in a browser");
|
|
3487
|
+
}
|
|
3488
|
+
this.rpId = opts.rpId ?? window.location.hostname;
|
|
3489
|
+
this.rpName = opts.rpName ?? this.rpId;
|
|
3490
|
+
if (isIpAddress(this.rpId)) {
|
|
3491
|
+
throw new Error(
|
|
3492
|
+
`kit/passkey-prf: passkeys can't use an IP address as the domain ("${this.rpId}"). Use http://localhost, a real HTTPS domain, or a tunnel \u2014 or pass an explicit \`rpId\`.`
|
|
3493
|
+
);
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3496
|
+
/** True if this platform advertises a usable passkey (platform authenticator). */
|
|
3497
|
+
static async isSupported() {
|
|
3498
|
+
if (typeof window === "undefined" || !window.PublicKeyCredential) return false;
|
|
3499
|
+
try {
|
|
3500
|
+
return await window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
|
|
3501
|
+
} catch {
|
|
3502
|
+
return false;
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
/**
|
|
3506
|
+
* Create a new synced, discoverable passkey with the PRF extension enabled, then
|
|
3507
|
+
* immediately read its PRF secret. Returns the credential id and the 32-byte
|
|
3508
|
+
* secret so the caller can enroll the passkey factor in one step. Some
|
|
3509
|
+
* authenticators don't return PRF results on create — in that case `secret` is
|
|
3510
|
+
* undefined and the caller should follow up with `getSecret()`.
|
|
3511
|
+
*/
|
|
3512
|
+
async enroll(params) {
|
|
3513
|
+
const cred = await navigator.credentials.create({
|
|
3514
|
+
publicKey: {
|
|
3515
|
+
challenge: buf(crypto.getRandomValues(new Uint8Array(32))),
|
|
3516
|
+
rp: { id: this.rpId, name: this.rpName },
|
|
3517
|
+
user: {
|
|
3518
|
+
id: buf(userHandle(params.userId)),
|
|
3519
|
+
name: params.userName,
|
|
3520
|
+
displayName: params.displayName ?? params.userName
|
|
3521
|
+
},
|
|
3522
|
+
pubKeyCredParams: [{ type: "public-key", alg: -7 }],
|
|
3523
|
+
// ES256 (P-256)
|
|
3524
|
+
authenticatorSelection: {
|
|
3525
|
+
residentKey: "required",
|
|
3526
|
+
requireResidentKey: true,
|
|
3527
|
+
userVerification: "required"
|
|
3528
|
+
},
|
|
3529
|
+
attestation: "none",
|
|
3530
|
+
extensions: { prf: { eval: { first: buf(PRF_SALT) } } }
|
|
3531
|
+
}
|
|
3532
|
+
});
|
|
3533
|
+
if (!cred) throw new Error("kit/passkey-prf: enrollment cancelled");
|
|
3534
|
+
return { credentialId: new Uint8Array(cred.rawId), secret: readPrf(cred) };
|
|
3535
|
+
}
|
|
3536
|
+
/**
|
|
3537
|
+
* Get the passkey's 32-byte PRF secret. Uses discoverable credentials (no
|
|
3538
|
+
* `allowCredentials`), so it works from a brand-new browser — the OS shows the
|
|
3539
|
+
* synced passkey picker. Throws if the authenticator doesn't support PRF.
|
|
3540
|
+
*/
|
|
3541
|
+
async getSecret() {
|
|
3542
|
+
const cred = await navigator.credentials.get({
|
|
3543
|
+
publicKey: {
|
|
3544
|
+
challenge: buf(crypto.getRandomValues(new Uint8Array(32))),
|
|
3545
|
+
rpId: this.rpId,
|
|
3546
|
+
allowCredentials: [],
|
|
3547
|
+
userVerification: "required",
|
|
3548
|
+
extensions: { prf: { eval: { first: buf(PRF_SALT) } } }
|
|
3549
|
+
}
|
|
3550
|
+
});
|
|
3551
|
+
if (!cred) throw new Error("kit/passkey-prf: assertion cancelled");
|
|
3552
|
+
const secret = readPrf(cred);
|
|
3553
|
+
if (!secret) {
|
|
3554
|
+
throw new Error(
|
|
3555
|
+
"kit/passkey-prf: this authenticator did not return a PRF result \u2014 PRF is unsupported here"
|
|
3556
|
+
);
|
|
3557
|
+
}
|
|
3558
|
+
return secret;
|
|
3559
|
+
}
|
|
3560
|
+
};
|
|
3561
|
+
function readPrf(cred) {
|
|
3562
|
+
const results = cred.getClientExtensionResults().prf?.results?.first;
|
|
3563
|
+
return results ? new Uint8Array(results) : void 0;
|
|
3564
|
+
}
|
|
3565
|
+
function isIpAddress(host) {
|
|
3566
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host)) return true;
|
|
3567
|
+
if (host.includes(":")) return true;
|
|
3568
|
+
return false;
|
|
3569
|
+
}
|
|
3570
|
+
function userHandle(userId) {
|
|
3571
|
+
const bytes = new TextEncoder().encode(userId);
|
|
3572
|
+
return bytes.length <= 64 ? bytes : sha256.sha256(bytes);
|
|
3573
|
+
}
|
|
3574
|
+
function buf(bytes) {
|
|
3575
|
+
return bytes.slice();
|
|
3576
|
+
}
|
|
2706
3577
|
var CavosAuth = class {
|
|
2707
3578
|
constructor(opts = {}) {
|
|
2708
3579
|
this.opts = opts;
|
|
@@ -2847,7 +3718,7 @@ var PasskeySigner = class {
|
|
|
2847
3718
|
}
|
|
2848
3719
|
this.rpId = opts.rpId ?? window.location.hostname;
|
|
2849
3720
|
this.rpName = opts.rpName ?? this.rpId;
|
|
2850
|
-
if (
|
|
3721
|
+
if (isIpAddress2(this.rpId)) {
|
|
2851
3722
|
throw new Error(
|
|
2852
3723
|
`kit/passkey: passkeys can't use an IP address as the domain ("${this.rpId}"). Use http://localhost, a real HTTPS domain, or a tunnel (cloudflared/ngrok) \u2014 or pass an explicit \`rpId\`. (The silent device key works over an IP; passkeys don't.)`
|
|
2853
3724
|
);
|
|
@@ -2867,10 +3738,10 @@ var PasskeySigner = class {
|
|
|
2867
3738
|
const challenge = crypto.getRandomValues(new Uint8Array(32));
|
|
2868
3739
|
const cred = await navigator.credentials.create({
|
|
2869
3740
|
publicKey: {
|
|
2870
|
-
challenge:
|
|
3741
|
+
challenge: buf2(challenge),
|
|
2871
3742
|
rp: { id: this.rpId, name: this.rpName },
|
|
2872
3743
|
user: {
|
|
2873
|
-
id:
|
|
3744
|
+
id: buf2(userHandle2(params.userId)),
|
|
2874
3745
|
name: params.userName,
|
|
2875
3746
|
displayName: params.displayName ?? params.userName
|
|
2876
3747
|
},
|
|
@@ -2897,7 +3768,7 @@ var PasskeySigner = class {
|
|
|
2897
3768
|
async assert(challenge) {
|
|
2898
3769
|
const cred = await navigator.credentials.get({
|
|
2899
3770
|
publicKey: {
|
|
2900
|
-
challenge:
|
|
3771
|
+
challenge: buf2(challenge),
|
|
2901
3772
|
rpId: this.rpId,
|
|
2902
3773
|
allowCredentials: [],
|
|
2903
3774
|
userVerification: "preferred"
|
|
@@ -2912,16 +3783,16 @@ var PasskeySigner = class {
|
|
|
2912
3783
|
return { authenticatorData, clientDataJSON, r, s, challengeOffset };
|
|
2913
3784
|
}
|
|
2914
3785
|
};
|
|
2915
|
-
function
|
|
3786
|
+
function isIpAddress2(host) {
|
|
2916
3787
|
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host)) return true;
|
|
2917
3788
|
if (host.includes(":")) return true;
|
|
2918
3789
|
return false;
|
|
2919
3790
|
}
|
|
2920
|
-
function
|
|
3791
|
+
function userHandle2(userId) {
|
|
2921
3792
|
const bytes = new TextEncoder().encode(userId);
|
|
2922
3793
|
return bytes.length <= 64 ? bytes : sha256.sha256(bytes);
|
|
2923
3794
|
}
|
|
2924
|
-
function
|
|
3795
|
+
function buf2(bytes) {
|
|
2925
3796
|
return bytes.slice();
|
|
2926
3797
|
}
|
|
2927
3798
|
var cavosLogoBase64 = "iVBORw0KGgoAAAANSUhEUgAAADMAAAA/CAYAAABNY/BRAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAlZSURBVHgB7VpbaCRZGf6r6lR3VV+STieZTBKCmZlO59LDisZBR2Q3D8v6MMrCgswirIqi4LI+7OKyIDp4YUAUXWFdxBurIIw+rcIgroMsKqvoMATdzSbpJEwmM8nk0rn3pbrrVJ39T3X3TE/S6a5TXVn2Yb88hKquc/nPOd/3X6oAjgfK4ODA47FY7AO1N9vb20/395+8wH+HY4AM/kOJx+PhcLjtjzs7O5naHyRJ2orFOq/29PRoeEnAZ/htDJ+g1dvbu8mYDWjUAztg27bM73d1da7jJQWfDfLTGN4XHR4aulE0siDhXz3g7oBRyAWHh5P/gbJBvs3Br474zO2hoTM/YGB9CJhEGj8sKcyi5xKJ05fwkoFP8MsY5dTAwMOKrDxvW7aryeGRYyoh3xkYOPkR8EkQ/DBGQUIH9Wjk79Qsihwb2TRN2haN/7evry+I1yq0iFaNcQjf1dW1VTLyaIgkRmjGSNHI0Y6O2BpemdCiILRiDOcJ5UQuGnlZ2JD73RAUBB37eQNaFASvDR2pQuX6LrOtc3yFoQU4gmDT88OJxAvQgiB4NUbp7+//mCTL37Ityxc14sIhK/L3sd8PgkdB8GIMSSQSSizW/i9qGn76CZlSk8ba2ycHBwe5GAjvtuhE+AA0GAzs4jmn3nlSH6wiCOGwvgpl/gjtkIgxFcIPv1EyCorksyE1wxAjX4jgOK/jhQUCc3T7oEP4kWTiG8w2P85aJHzTwSTggjCRTJx+FgQEwa0xysDAwDhI8mUkqg3vAlBYbELUH+O4KXDJHzfGkPHxcaktGrlOqZCHh62trXzt9d7eXl5AeOWSWaJtbdE3K2M2NajZxBzCF4vGtsE9PHPPE9u2SlAmcS0MBswEt8DjbOSzNJVKuRKERsZUCD/0ulHIB0UJbzN2F+rOj22BACRJJsVCrm1kZPg1aCIIDXcGCf8cMHvCi4e3aGmy3n3DMCZBHIptmY8lk4mnGz10lDHE8cSS8iOLWsKEV4gC2dzelXq/FQrGH2RZ3M9y4SGK8jIGtUNwBH/q9UomJiYAPfwkpd48vKIQ3AH7rwCH081CofAnEgiCB8ilokEx3ZipzvPQAweuHcJnMhsbRj5niRC+Bjxcu4PFjB2o4yN2d3e3mc02+XMgDO5Q92lqbKyuINQa4xB+JJm8hoSPYK7uKdhTFEXOZ7PPw9E7Ku/u7b0gKbIEHsAFAZW1A+d5FQ4IwgMDjowkn2GMPuo9pGeUBDT79srK76GB515ZWfl1IKBR/jx4AK4Cr/JcSCROfan2ftUYnvqexf8vWR49PM7cVtQA2d3cPA/l7T/KGN6/sr29M4HcIeDpuOGWWNQOqIFfnThx4hRU+FP1rAxV4v+0VPREePQdVlDTsJ38+J3V1esuJmjh7vwbV/diIKjhKjMvBjmC0N3dNV8ZT3HO7djY2AaG9B2yCE8kpwbG1IAmMQZv5nK5R5aWlvYqHbsJWvjYEp6Iro6O9ms49kOmWUK7mCSSazKwqaZHtqam3u6RRkaGrimy+gnsWiCCtqlZMueyueyVXM74OVcoKB8tC8ThtItEIifa26NfDunhJwMBNQHlhXVnFq5msWS8Vu2sFXhSpWPo5zjq5u/jfRyE1Nt74pOlkrXDPbebBqg2Cs3nd9u7u+cWFxcNqKgSePQXFfCxOdkZqlu4WCwmQqFAhFJ3fWLgit7RjhFJIp09PZ1/wZAd3ELpjGMGLcPIcHLdpMVfLizc+nbNzyJGyRUDdF1XL+l69Cso9zFgFqYQplMMcDUfRYWt7c3POE8nk0OvYL7wOVwbUUVAUQSm6SGZlgqXZ+dufhPKBXA32aTzzdCZMz9Ug8GvY626KsNCqoa7YkuK8ovZ2bmv8obcAHt0dHSxZOT6eSAH4rCxGol9Bm5Mz8ycq/bZ4HnHt6RSo/8rGoWzFW/iKfIgajA9m06P8fbVQcn09PSgFopyQ9zn6PchY/IkmSXjw5gNvgTNnZ2VTCZ/UzGEz8GDj2BUC0WUiiFOjFftxHm/iCnIQEDTVCw6eCWzhOWhZ5ADoQYTlPEtdEwl8ufREE+OEudnBTSdrK2t9UF5l53ou3ZAury8vFIq0aeIQqrqIgqpVCwwXI8XG7S3u7vjLxtGgS+YF2MYUQNKMVd4IpPJ8Be990KoQ6s3Pz//O0lRX2VeXy1gq0i47Slo0B5zmSclt1J1ANiKx6JXFm7dehUO8PKgMU4oPTMz8wQqVAYjcy/Jk2RjIMs/YIDDKy9hQJnCXEQGD7tiO4TXbqfT6c/Cfd90D/XONd82FUPqvrIgiGeD1DQhFNIuwuHdYRgZX3R8iDAY1ZHwqJaDUJb1Q7w+iqR8NGl7e21EDerC2SCeA9D10IV6v4VC4U8xW/gE25jEkY2NjQSUCV93NRpJIl1ZycxZlvk12WWoUwtN00br3VdV9RQIQiEqOuXiF9bX129Cg5yp6STT6YWfoh/9Gy62qFy31bvphCsC4DzBsf+cXlj8LTQRpWbGOIIwOzv7qB7S90X4U6lNawduh0U0Eg2x9VB4Oz03V/0SqiVjOPi2kmyucDKohQlI7g2Kx+OB2utoNKq51jCJEz4s89weKt8bNGvilgsUw30zu7MzjrGQ5/IQL4C4fNRWiUZ2dzcfgkpx0k0jEWJbt1ZWJlGpLskeq5FuwXMrrPE+e+fO2lsgUCQRVSkJQ+3vYQ50ncdHcBzA48Uk5R+z8/M/AdF0AMTgRNgYqX4UHVjRa3n1KOAZxGJixEQP/whUImGR9l7KM06Evbq6hoIQEhIEhzNHsgZDej3EFYtL971IWARea0342iOTzxeyDxPiCIIrYvOsEOprAOPCkslsnp+amuK74ekIt1I4s27evP1Pm9kvYoQg0OwwDVBQMINilzE/4Z86euZiq1VAOZ2ef46QwDSv+YInMCrL5Mbs3ByvH3BL3/WvmqrgR0J9e3o6pelREBcERoN6BGZm07xu4Nl/VeFHfdb5gg/fADiCwIsM7pqhIRhRYI2sEzwS/iD8KjbT/f397b3dvU+rgaCb6j3jL6b2s9nHMLPlX3H44rP8rJyzpeXlq6i9r0CzgNApCsk/w928Bj4Z4vQL/oEbIGOE8EVM6JaOdidYWyJkGiPxp6FO6vteg1NETKVSWHbFkP9BRPn92uf8xHG8oHHez9+9e/esacbjtT9gShBbXV0drYzrayjE8Q6F7uAm3ZgyLQAAAABJRU5ErkJggg==";
|
|
@@ -3987,14 +4858,14 @@ function CavosProvider({ config, modal, children }) {
|
|
|
3987
4858
|
const id = await auth.verifyOtp(email, code);
|
|
3988
4859
|
await connect(id);
|
|
3989
4860
|
}, [auth, connect]);
|
|
3990
|
-
const execute = react.useCallback(async (calls) => {
|
|
4861
|
+
const execute = react.useCallback(async (calls, opts) => {
|
|
3991
4862
|
if (!wallet) throw new Error("Not logged in");
|
|
3992
4863
|
if (wallet.chain !== "starknet") {
|
|
3993
4864
|
throw new Error(
|
|
3994
4865
|
"kit: useCavos().execute(calls) is Starknet-only. On Solana/Stellar use the `wallet` handle: wallet.execute(amount, dest)."
|
|
3995
4866
|
);
|
|
3996
4867
|
}
|
|
3997
|
-
return wallet.execute(calls);
|
|
4868
|
+
return wallet.execute(calls, opts);
|
|
3998
4869
|
}, [wallet]);
|
|
3999
4870
|
const addSigner = react.useCallback(
|
|
4000
4871
|
async (pubkey) => {
|
|
@@ -4009,6 +4880,11 @@ function CavosProvider({ config, modal, children }) {
|
|
|
4009
4880
|
const enrollPasskey = react.useCallback(
|
|
4010
4881
|
async (passkey, params) => {
|
|
4011
4882
|
if (!wallet) throw new Error("Not logged in");
|
|
4883
|
+
if (wallet.chain === "stellar") {
|
|
4884
|
+
throw new Error(
|
|
4885
|
+
"kit: on Stellar, use enrollPasskeyDefault() \u2014 the passkey factor is a WebAuthn PRF secret, not a signer object."
|
|
4886
|
+
);
|
|
4887
|
+
}
|
|
4012
4888
|
return wallet.enrollPasskey(passkey, params);
|
|
4013
4889
|
},
|
|
4014
4890
|
[wallet]
|
|
@@ -4017,6 +4893,16 @@ function CavosProvider({ config, modal, children }) {
|
|
|
4017
4893
|
const enrollPasskeyDefault = react.useCallback(async () => {
|
|
4018
4894
|
if (!wallet || !identity) throw new Error("Not logged in");
|
|
4019
4895
|
if (wallet.status !== "ready") throw new Error("kit: no ready device to enroll a passkey on");
|
|
4896
|
+
if (wallet.chain === "stellar") {
|
|
4897
|
+
const prf = new PasskeyPrf({ rpName });
|
|
4898
|
+
const { secret } = await prf.enroll({
|
|
4899
|
+
userId: identity.userId,
|
|
4900
|
+
userName: identity.email ?? identity.userId,
|
|
4901
|
+
...identity.email ? { displayName: identity.email } : {}
|
|
4902
|
+
});
|
|
4903
|
+
await wallet.enrollPasskey(secret ?? await prf.getSecret());
|
|
4904
|
+
return;
|
|
4905
|
+
}
|
|
4020
4906
|
const passkey = new PasskeySigner({ rpName });
|
|
4021
4907
|
await wallet.enrollPasskey(passkey, {
|
|
4022
4908
|
userId: identity.userId,
|
|
@@ -4030,10 +4916,14 @@ function CavosProvider({ config, modal, children }) {
|
|
|
4030
4916
|
await connect(identity);
|
|
4031
4917
|
return;
|
|
4032
4918
|
}
|
|
4033
|
-
|
|
4034
|
-
|
|
4919
|
+
if (wallet.chain === "stellar") {
|
|
4920
|
+
const prf = new PasskeyPrf({ rpName });
|
|
4921
|
+
await wallet.approveThisDeviceWithPasskey(await prf.getSecret());
|
|
4922
|
+
} else if (wallet.chain === "starknet") {
|
|
4923
|
+
const passkey = new PasskeySigner({ rpName });
|
|
4035
4924
|
await wallet.approveThisDeviceWithPasskey({ passkey });
|
|
4036
4925
|
} else {
|
|
4926
|
+
const passkey = new PasskeySigner({ rpName });
|
|
4037
4927
|
await wallet.approveThisDeviceWithPasskey(passkey);
|
|
4038
4928
|
}
|
|
4039
4929
|
setWalletStatus((s) => ({ ...s, isDeploying: true, needsDeviceApproval: false, awaitingApproval: false }));
|
|
@@ -4080,15 +4970,18 @@ function CavosProvider({ config, modal, children }) {
|
|
|
4080
4970
|
...cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}
|
|
4081
4971
|
});
|
|
4082
4972
|
} else if (chain === "stellar") {
|
|
4083
|
-
|
|
4084
|
-
|
|
4973
|
+
const sw = await Cavos.connect({
|
|
4974
|
+
chain: "stellar",
|
|
4975
|
+
network: cfg.network,
|
|
4085
4976
|
identity,
|
|
4086
|
-
network: cfg.network === "mainnet" ? "stellar-mainnet" : "stellar-testnet",
|
|
4087
4977
|
appSalt: cfg.appSalt,
|
|
4088
4978
|
...cfg.appId ? { appId: cfg.appId } : {},
|
|
4089
|
-
...cfg.authBackendUrl ? { backendUrl: cfg.authBackendUrl } : {}
|
|
4090
|
-
...cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}
|
|
4979
|
+
...cfg.authBackendUrl ? { backendUrl: cfg.authBackendUrl } : {}
|
|
4091
4980
|
});
|
|
4981
|
+
if (sw.chain === "stellar" && sw.status === "needs-device-approval") {
|
|
4982
|
+
await sw.approveThisDeviceWithRecovery(code);
|
|
4983
|
+
}
|
|
4984
|
+
w = sw;
|
|
4092
4985
|
} else {
|
|
4093
4986
|
w = await Cavos.recover({
|
|
4094
4987
|
code,
|
|
@@ -4195,6 +5088,11 @@ function useCavos() {
|
|
|
4195
5088
|
if (!ctx) throw new Error("useCavos must be used within a CavosProvider");
|
|
4196
5089
|
return ctx;
|
|
4197
5090
|
}
|
|
5091
|
+
/*! Bundled license information:
|
|
5092
|
+
|
|
5093
|
+
@noble/ciphers/esm/utils.js:
|
|
5094
|
+
(*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) *)
|
|
5095
|
+
*/
|
|
4198
5096
|
|
|
4199
5097
|
exports.CavosAuthModal = CavosAuthModal;
|
|
4200
5098
|
exports.CavosProvider = CavosProvider;
|