@parity/product-sdk-terminal 0.3.0 → 0.3.2
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/host.js +12 -3
- package/dist/host.js.map +1 -1
- package/dist/testing.d.ts +3 -3
- package/dist/testing.js +38 -29
- package/dist/testing.js.map +1 -1
- package/package.json +4 -4
package/dist/host.js
CHANGED
|
@@ -517,9 +517,17 @@ if (void 0) {
|
|
|
517
517
|
}
|
|
518
518
|
|
|
519
519
|
// src/host-signer.ts
|
|
520
|
+
import { bytesToNumberLE, numberToBytesLE } from "@noble/curves/utils.js";
|
|
520
521
|
import { fromHex } from "@polkadot-api/utils";
|
|
521
522
|
import { createDerive, sr25519, sr25519Derive } from "@polkadot-labs/hdkd-helpers";
|
|
522
523
|
import { getPolkadotSigner } from "polkadot-api/signer";
|
|
524
|
+
function canonicalSr25519SecretToEd25519Bytes(secret) {
|
|
525
|
+
const ed25519Scalar = numberToBytesLE(bytesToNumberLE(secret.subarray(0, 32)) << 3n, 32);
|
|
526
|
+
const out = new Uint8Array(64);
|
|
527
|
+
out.set(ed25519Scalar, 0);
|
|
528
|
+
out.set(secret.subarray(32, 64), 32);
|
|
529
|
+
return out;
|
|
530
|
+
}
|
|
523
531
|
function buildKeypair(secret) {
|
|
524
532
|
if (secret.length === 32) {
|
|
525
533
|
const derive = createDerive({ seed: secret, curve: sr25519, derive: sr25519Derive });
|
|
@@ -527,9 +535,10 @@ function buildKeypair(secret) {
|
|
|
527
535
|
return { publicKey: keypair.publicKey, sign: (data) => keypair.sign(data) };
|
|
528
536
|
}
|
|
529
537
|
if (secret.length === 64) {
|
|
538
|
+
const ed25519Secret = canonicalSr25519SecretToEd25519Bytes(secret);
|
|
530
539
|
return {
|
|
531
|
-
publicKey: sr25519.getPublicKey(
|
|
532
|
-
sign: (data) => sr25519.sign(data,
|
|
540
|
+
publicKey: sr25519.getPublicKey(ed25519Secret),
|
|
541
|
+
sign: (data) => sr25519.sign(data, ed25519Secret)
|
|
533
542
|
};
|
|
534
543
|
}
|
|
535
544
|
throw new Error(
|
|
@@ -633,7 +642,7 @@ if (void 0) {
|
|
|
633
642
|
expect(sr25519.verify(signature, wrapped, publicKey)).toBe(true);
|
|
634
643
|
});
|
|
635
644
|
test("64-byte expanded-secret form produces a valid signer", async () => {
|
|
636
|
-
const SECRET_64_HEX = "
|
|
645
|
+
const SECRET_64_HEX = "0x05d65584630d16cd4af6d0bec10f34bb504a5dcb62dba2122d49f5a663763d0afd190cce74df356432b410bd64682309d6dedb27c76845daf388557cbac3ca34";
|
|
637
646
|
await saveCache2(
|
|
638
647
|
"p",
|
|
639
648
|
{
|
package/dist/host.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/host.ts","../src/host-cache.ts","../src/host-signer.ts"],"sourcesContent":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Host-runner facet of `@parity/product-sdk-terminal`. A CLI using this\n * package plays the Host role per RFC-10; this module implements the\n * three §Stakeholders Host responsibilities (AP client, cache, signer)\n * over `@novasamatech/host-papp@0.7.7`'s `UserSession`.\n *\n * @module\n */\nimport type { UserSession } from \"@novasamatech/host-papp\";\nimport { createLogger } from \"@parity/product-sdk-logger\";\nimport type { ResultAsync } from \"neverthrow\";\nimport type { PolkadotSigner } from \"polkadot-api\";\n\nimport type { TerminalAdapter } from \"./adapter.js\";\nimport {\n type CachedAllocation,\n loadCache,\n mergeOutcomes,\n pickOnExistingPolicy,\n readCacheEntry,\n saveCache,\n withCacheLock,\n} from \"./host-cache.js\";\nimport { buildSignerFromEntry, createSlotAccountSigner } from \"./host-signer.js\";\n\n// `terminal` namespace matches adapter.ts / node-storage.ts.\nconst log = createLogger(\"terminal\");\n\n// Public re-exports — internal helpers (loadCache, mergeOutcomes, etc.)\n// stay private. CachedAllocation must be exported because it's the return\n// type of getCachedAllocation.\nexport type { CachedAllocation };\nexport { createSlotAccountSigner };\n\n// Types derived from `UserSession['requestResourceAllocation']` so upstream\n// codec changes surface as compile errors here, not runtime decode failures.\n// host-papp doesn't re-export these codec types from its root.\ntype ResourceAllocationRequest = Parameters<UserSession[\"requestResourceAllocation\"]>[0];\n\n/**\n * One resource a Host can request from the Account Holder. AutoSigning\n * currently returns `NotAvailable` on both Android and iOS wallets.\n */\nexport type AllocatableResource = ResourceAllocationRequest[\"resources\"][number];\n\n/**\n * `\"Ignore\"`: return existing keys if any, else allocate one slot.\n * `\"Increase\"`: add one slot to an existing allowance account.\n */\nexport type OnExistingAllowancePolicy = ResourceAllocationRequest[\"onExisting\"];\n\ntype ExtractOk<R> = R extends ResultAsync<infer T, unknown> ? T : never;\n\n/**\n * Per-resource outcome. `Allocated.value` carries the materialized payload\n * (slot account key for Bulletin/SSS; subtree key + secret for AutoSigning;\n * undefined for SC). Each entry is independent — no rollback on partial\n * success.\n */\nexport type ApAllocationOutcome = ExtractOk<\n ReturnType<UserSession[\"requestResourceAllocation\"]>\n>[number];\n\nexport interface RequestResourceAllocationOptions {\n /**\n * Override the auto-picked `onExisting`. Default: `Ignore` unless every\n * requested resource is a cached slot-table variant, then `Increase`.\n */\n onExisting?: OnExistingAllowancePolicy;\n}\n\n/**\n * Send the AP `request_resource_allocation` message over the paired\n * session; block on the user's mobile dialog; return outcomes in request\n * order. Granted key material is cached on disk so subsequent calls skip\n * the wallet prompt.\n *\n * @throws If the session call fails (transport, timeout, AH protocol error).\n *\n * @example\n * ```ts\n * const [session] = adapter.sessions.sessions.read();\n * const outcomes = await requestResourceAllocation(session, adapter, [\n * { tag: \"BulletInAllowance\", value: undefined },\n * ]);\n * ```\n */\nexport async function requestResourceAllocation(\n session: UserSession,\n adapter: TerminalAdapter,\n resources: AllocatableResource[],\n options: RequestResourceAllocationOptions = {},\n): Promise<ApAllocationOutcome[]> {\n return withCacheLock(adapter.appId, adapter.storageDir, async () => {\n const cache = await loadCache(adapter.appId, adapter.storageDir);\n const onExisting = options.onExisting ?? pickOnExistingPolicy(cache, resources);\n log.debug(\"requestResourceAllocation\", {\n productId: adapter.appId,\n resources: resources.map((r) => r.tag),\n onExisting,\n autoPolicy: options.onExisting === undefined,\n });\n\n const result = await session.requestResourceAllocation({\n callingProductId: adapter.appId,\n resources,\n onExisting,\n });\n\n if (result.isErr()) {\n throw new Error(`requestResourceAllocation failed: ${result.error.message}`);\n }\n\n const next = mergeOutcomes(cache, resources, result.value);\n if (next !== cache) {\n await saveCache(adapter.appId, next, adapter.storageDir);\n }\n\n return result.value;\n });\n}\n\n/**\n * Read a cached allowance entry without going over the wire. Returns\n * `null` if nothing's cached for `(adapter.appId, resource)`. For\n * `SmartContractAllowance`, the same `dest` must be passed — entries\n * are keyed per-dest.\n */\nexport async function getCachedAllocation(\n adapter: TerminalAdapter,\n resource: AllocatableResource,\n): Promise<CachedAllocation | null> {\n const cache = await loadCache(adapter.appId, adapter.storageDir);\n return readCacheEntry(cache, resource);\n}\n\n/**\n * Cache hit → return signer. Cache miss → call\n * {@link requestResourceAllocation} for `[resource]`, then build the signer.\n * Throws on `Rejected` / `NotAvailable`.\n *\n * @example\n * ```ts\n * const signer = await ensureSlotAccountSigner(session, adapter, {\n * tag: \"BulletInAllowance\", value: undefined,\n * });\n * await someBulletinTx.submitAndWatch(signer);\n * ```\n */\nexport async function ensureSlotAccountSigner(\n session: UserSession,\n adapter: TerminalAdapter,\n resource: AllocatableResource,\n): Promise<PolkadotSigner> {\n // The cache-hit check and the allocation path must share the same\n // critical section: otherwise two concurrent calls for the same\n // resource both see an empty cache, then both serialize through the\n // lock but each issues its own wallet prompt and burns a slot.\n return withCacheLock(adapter.appId, adapter.storageDir, async () => {\n // Single loadCache for the whole critical section — we hold the\n // lock, so no other writer can change disk under us. The fast\n // cache-hit path and the slow allocate-then-build path both\n // reuse this in-memory `cache` reference.\n const cache = await loadCache(adapter.appId, adapter.storageDir);\n const hit = readCacheEntry(cache, resource);\n if (hit) return buildSignerFromEntry(hit);\n\n log.debug(\"ensureSlotAccountSigner: cache miss, allocating\", { resource: resource.tag });\n\n // Inline the wire → merge → save sequence rather than calling the\n // public `requestResourceAllocation` wrapper. `withCacheLock` is\n // non-reentrant, so calling the wrapper from inside would deadlock\n // on the second lock acquisition.\n const onExisting = pickOnExistingPolicy(cache, [resource]);\n const result = await session.requestResourceAllocation({\n callingProductId: adapter.appId,\n resources: [resource],\n onExisting,\n });\n if (result.isErr()) {\n throw new Error(`requestResourceAllocation failed: ${result.error.message}`);\n }\n const outcomes = result.value;\n const next = mergeOutcomes(cache, [resource], outcomes);\n if (next !== cache) {\n await saveCache(adapter.appId, next, adapter.storageDir);\n }\n\n const outcome = outcomes[0];\n if (!outcome) {\n // Unreachable: mergeOutcomes throws on length mismatch first.\n // Kept as a typed-non-null guard.\n throw new Error(\n `ensureSlotAccountSigner: protocol violation — empty outcomes for ${resource.tag}`,\n );\n }\n if (outcome.tag !== \"Allocated\") {\n throw new Error(\n `ensureSlotAccountSigner: allocation ${outcome.tag} for ${resource.tag}`,\n );\n }\n\n // Build the signer from the in-memory post-merge cache — no third\n // disk read.\n const freshEntry = readCacheEntry(next, resource);\n if (!freshEntry) {\n // Unreachable: the entry was just merged in.\n // Kept as a typed-non-null guard against mergeOutcomes regressions.\n throw new Error(\n `ensureSlotAccountSigner: allocation succeeded but cache lookup returned null for ${resource.tag}`,\n );\n }\n return buildSignerFromEntry(freshEntry);\n });\n}\n\nif (import.meta.vitest) {\n const { describe, test, expect, vi, beforeEach } = import.meta.vitest;\n const { ok, err } = await import(\"neverthrow\");\n const { mkdtempSync, rmSync } = await import(\"node:fs\");\n const { tmpdir } = await import(\"node:os\");\n const { join: pathJoin } = await import(\"node:path\");\n // Needed by the ensureSlotAccountSigner suite. Lifted up here because\n // top-level await is only allowed at module top-level (or inside this\n // top-level vitest block), not inside `describe`.\n const { mnemonicToMiniSecret, DEV_PHRASE } = await import(\"@polkadot-labs/hdkd-helpers\");\n const { toHex } = await import(\"@polkadot-api/utils\");\n\n let testStorageDir: string;\n beforeEach(() => {\n // Per-test temp dir so cache writes don't leak between tests or\n // touch the real default storage directory. The wrapper inherits\n // `storageDir` from `adapter.storageDir`; we pin it here.\n testStorageDir = mkdtempSync(pathJoin(tmpdir(), \"host-test-\"));\n return () => rmSync(testStorageDir, { recursive: true, force: true });\n });\n\n function fakeAdapter(appId: string): TerminalAdapter {\n // appId + storageDir are the only fields the wrapper touches.\n return { appId, storageDir: testStorageDir } as unknown as TerminalAdapter;\n }\n\n type RequestCapture = ResourceAllocationRequest;\n type StubReturn = Awaited<ReturnType<UserSession[\"requestResourceAllocation\"]>>;\n\n function makeSession(opts: {\n requestResourceAllocation?: (req: RequestCapture) => Promise<StubReturn>;\n }): UserSession {\n return {\n requestResourceAllocation: vi.fn(\n opts.requestResourceAllocation ??\n (async () => {\n throw new Error(\"requestResourceAllocation not stubbed in this test\");\n }),\n ),\n } as unknown as UserSession;\n }\n\n describe(\"requestResourceAllocation\", () => {\n test(\"forwards resources verbatim and returns outcomes in order\", async () => {\n const captured: RequestCapture[] = [];\n const stubbed = [\n {\n tag: \"Allocated\" as const,\n value: {\n tag: \"BulletInAllowance\" as const,\n value: { slotAccountKey: new Uint8Array([1, 2, 3]) },\n },\n },\n { tag: \"Rejected\" as const, value: undefined },\n ];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return ok(stubbed) as StubReturn;\n },\n });\n\n const outcomes = await requestResourceAllocation(session, fakeAdapter(\"my-app\"), [\n { tag: \"BulletInAllowance\", value: undefined },\n { tag: \"StatementStoreAllowance\", value: undefined },\n ]);\n\n expect(captured).toHaveLength(1);\n expect(captured[0].callingProductId).toBe(\"my-app\");\n expect(captured[0].resources.map((r) => r.tag)).toEqual([\n \"BulletInAllowance\",\n \"StatementStoreAllowance\",\n ]);\n expect(outcomes).toEqual(stubbed);\n });\n\n test(\"uses adapter.appId as callingProductId\", async () => {\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return ok([]) as StubReturn;\n },\n });\n\n await requestResourceAllocation(session, fakeAdapter(\"alt-product.dot\"), []);\n\n expect(captured[0].callingProductId).toBe(\"alt-product.dot\");\n });\n\n test(\"auto-picks Ignore for an empty resources array\", async () => {\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return ok([]) as StubReturn;\n },\n });\n\n await requestResourceAllocation(session, fakeAdapter(\"p\"), []);\n\n expect(captured[0].onExisting).toBe(\"Ignore\");\n });\n\n test(\"forwards onExisting when explicitly set to 'Increase'\", async () => {\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return ok([{ tag: \"Rejected\" as const, value: undefined }]) as StubReturn;\n },\n });\n\n await requestResourceAllocation(\n session,\n fakeAdapter(\"p\"),\n [{ tag: \"BulletInAllowance\", value: undefined }],\n { onExisting: \"Increase\" },\n );\n\n expect(captured[0].onExisting).toBe(\"Increase\");\n });\n\n test(\"forwards SmartContractAllowance derivation index\", async () => {\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return ok([{ tag: \"Rejected\" as const, value: undefined }]) as StubReturn;\n },\n });\n\n await requestResourceAllocation(session, fakeAdapter(\"p\"), [\n { tag: \"SmartContractAllowance\", value: 7 },\n ]);\n\n const sent = captured[0].resources[0];\n expect(sent.tag).toBe(\"SmartContractAllowance\");\n if (sent.tag === \"SmartContractAllowance\") {\n expect(sent.value).toBe(7);\n }\n });\n\n test(\"throws a clear error when the session call fails\", async () => {\n const session = makeSession({\n requestResourceAllocation: async () =>\n err(new Error(\"mobile disconnected\")) as StubReturn,\n });\n\n await expect(\n requestResourceAllocation(session, fakeAdapter(\"p\"), [\n { tag: \"BulletInAllowance\", value: undefined },\n ]),\n ).rejects.toThrow(\"requestResourceAllocation failed: mobile disconnected\");\n });\n\n test(\"forwards AutoSigning and returns NotAvailable verbatim (mobile-default path)\", async () => {\n // Both Android (paritytech/polkadot-app-android-v2#563) and iOS\n // (paritytech/polkadot-app-ios-v2#759) currently return NotAvailable\n // for any AutoSigning request. The wrapper must round-trip both\n // the request variant and the NotAvailable outcome without\n // filtering, transforming, or throwing — that's the only state\n // real users see for one of the four AllocatableResource variants\n // today.\n const captured: RequestCapture[] = [];\n const stubbed = [{ tag: \"NotAvailable\" as const, value: undefined }];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return ok(stubbed) as StubReturn;\n },\n });\n\n const outcomes = await requestResourceAllocation(session, fakeAdapter(\"p\"), [\n { tag: \"AutoSigning\", value: undefined },\n ]);\n\n const sent = captured[0].resources[0];\n expect(sent.tag).toBe(\"AutoSigning\");\n expect(outcomes).toEqual(stubbed);\n });\n\n test(\"passes empty resources through (no-op request still hits the wire)\", async () => {\n // Wrappers shouldn't second-guess the caller: an empty resource\n // list is a legitimate (if degenerate) request and the wire\n // accepts it. The Account Holder may use it as a probe.\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return ok([]) as StubReturn;\n },\n });\n\n const outcomes = await requestResourceAllocation(session, fakeAdapter(\"p\"), []);\n\n expect(captured).toHaveLength(1);\n expect(captured[0].resources).toEqual([]);\n expect(outcomes).toEqual([]);\n });\n });\n\n describe(\"requestResourceAllocation — cache integration\", () => {\n function bulletinAllocated(slotKey: Uint8Array): StubReturn {\n return ok([\n {\n tag: \"Allocated\" as const,\n value: {\n tag: \"BulletInAllowance\" as const,\n value: { slotAccountKey: slotKey },\n },\n },\n ]) as StubReturn;\n }\n\n test(\"first call sends Ignore when cache is empty\", async () => {\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return bulletinAllocated(new Uint8Array([1, 2, 3]));\n },\n });\n\n await requestResourceAllocation(session, fakeAdapter(\"p\"), [\n { tag: \"BulletInAllowance\", value: undefined },\n ]);\n\n expect(captured[0].onExisting).toBe(\"Ignore\");\n });\n\n test(\"second call for same slot-table resource auto-picks Increase\", async () => {\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return bulletinAllocated(new Uint8Array([1, 2, 3]));\n },\n });\n\n const adapter = fakeAdapter(\"p\");\n const req: AllocatableResource[] = [{ tag: \"BulletInAllowance\", value: undefined }];\n\n await requestResourceAllocation(session, adapter, req);\n await requestResourceAllocation(session, adapter, req);\n\n expect(captured.map((c) => c.onExisting)).toEqual([\"Ignore\", \"Increase\"]);\n });\n\n test(\"explicit options.onExisting overrides the auto-pick\", async () => {\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return bulletinAllocated(new Uint8Array([1, 2, 3]));\n },\n });\n\n const adapter = fakeAdapter(\"p\");\n const req: AllocatableResource[] = [{ tag: \"BulletInAllowance\", value: undefined }];\n\n // Even after a cache-populating first call, passing Ignore\n // explicitly forces Ignore — useful for callers that want\n // idempotent \"ensure I have allowance\" semantics rather than\n // the additive-scaling default.\n await requestResourceAllocation(session, adapter, req);\n await requestResourceAllocation(session, adapter, req, { onExisting: \"Ignore\" });\n\n expect(captured.map((c) => c.onExisting)).toEqual([\"Ignore\", \"Ignore\"]);\n });\n\n test(\"populates cache on Allocated and survives across separate calls\", async () => {\n const slotKey = new Uint8Array([0xde, 0xad, 0xbe, 0xef]);\n const session = makeSession({\n requestResourceAllocation: async () => bulletinAllocated(slotKey),\n });\n\n const adapter = fakeAdapter(\"p\");\n await requestResourceAllocation(session, adapter, [\n { tag: \"BulletInAllowance\", value: undefined },\n ]);\n\n // Second call reads cache via the public lookup, no wire.\n const cached = await getCachedAllocation(adapter, {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n expect(cached).toEqual({\n tag: \"BulletInAllowance\",\n slotAccountKey: \"0xdeadbeef\",\n });\n });\n\n test(\"does not cache Rejected or NotAvailable outcomes\", async () => {\n const session = makeSession({\n requestResourceAllocation: async () =>\n ok([\n { tag: \"Rejected\" as const, value: undefined },\n { tag: \"NotAvailable\" as const, value: undefined },\n ]) as StubReturn,\n });\n\n const adapter = fakeAdapter(\"p\");\n await requestResourceAllocation(session, adapter, [\n { tag: \"BulletInAllowance\", value: undefined },\n { tag: \"StatementStoreAllowance\", value: undefined },\n ]);\n\n expect(\n await getCachedAllocation(adapter, { tag: \"BulletInAllowance\", value: undefined }),\n ).toBeNull();\n expect(\n await getCachedAllocation(adapter, {\n tag: \"StatementStoreAllowance\",\n value: undefined,\n }),\n ).toBeNull();\n });\n\n test(\"mixed cached + uncached request stays on Ignore (conservative)\", async () => {\n // Bulletin cached, SSS not. Auto-pick must NOT send Increase\n // — the wire takes one policy for the whole request, and\n // Increase against an uncached resource is at-best a no-op\n // and at-worst a mis-allocation.\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n // Length-match the request: 1 or 2 outcomes as needed.\n return ok(\n req.resources.map((_r, i) =>\n i === 0\n ? {\n tag: \"Allocated\" as const,\n value: {\n tag: \"BulletInAllowance\" as const,\n value: { slotAccountKey: new Uint8Array([1]) },\n },\n }\n : { tag: \"Rejected\" as const, value: undefined },\n ),\n ) as StubReturn;\n },\n });\n\n const adapter = fakeAdapter(\"p\");\n // Populate Bulletin in cache.\n await requestResourceAllocation(session, adapter, [\n { tag: \"BulletInAllowance\", value: undefined },\n ]);\n\n // Now request both Bulletin (cached) and SSS (uncached).\n await requestResourceAllocation(session, adapter, [\n { tag: \"BulletInAllowance\", value: undefined },\n { tag: \"StatementStoreAllowance\", value: undefined },\n ]);\n\n expect(captured[captured.length - 1].onExisting).toBe(\"Ignore\");\n });\n\n test(\"AutoSigning never auto-Increases even when cached\", async () => {\n // AutoSigning isn't slot-additive; auto-pick must stay on Ignore.\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return ok([\n {\n tag: \"Allocated\" as const,\n value: {\n tag: \"AutoSigning\" as const,\n value: {\n productDerivationSecret: \"secret\",\n productRootPrivateKey: new Uint8Array([0xab]),\n },\n },\n },\n ]) as StubReturn;\n },\n });\n\n const adapter = fakeAdapter(\"p\");\n const req: AllocatableResource[] = [{ tag: \"AutoSigning\", value: undefined }];\n\n await requestResourceAllocation(session, adapter, req);\n await requestResourceAllocation(session, adapter, req);\n\n expect(captured.map((c) => c.onExisting)).toEqual([\"Ignore\", \"Ignore\"]);\n });\n\n test(\"parallel calls for different resources both persist their keys (no last-writer-wins)\", async () => {\n // Regression test for the load → wire → save race: without\n // serialization, both calls snapshot an empty cache, do their\n // wire round-trip, then save — the second save clobbers the\n // first. Both keys must end up in the cache.\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n // Force interleaving: each call yields before responding.\n await new Promise((r) => setTimeout(r, 5));\n const tag = req.resources[0]?.tag;\n return ok([\n {\n tag: \"Allocated\" as const,\n value:\n tag === \"BulletInAllowance\"\n ? {\n tag: \"BulletInAllowance\" as const,\n value: { slotAccountKey: new Uint8Array([0xaa]) },\n }\n : {\n tag: \"StatementStoreAllowance\" as const,\n value: { slotAccountKey: new Uint8Array([0xbb]) },\n },\n },\n ]) as StubReturn;\n },\n });\n const adapter = fakeAdapter(\"p\");\n\n await Promise.all([\n requestResourceAllocation(session, adapter, [\n { tag: \"BulletInAllowance\", value: undefined },\n ]),\n requestResourceAllocation(session, adapter, [\n { tag: \"StatementStoreAllowance\", value: undefined },\n ]),\n ]);\n\n expect(\n await getCachedAllocation(adapter, {\n tag: \"BulletInAllowance\",\n value: undefined,\n }),\n ).not.toBeNull();\n expect(\n await getCachedAllocation(adapter, {\n tag: \"StatementStoreAllowance\",\n value: undefined,\n }),\n ).not.toBeNull();\n });\n\n test(\"two appIds maintain independent caches in the same storageDir\", async () => {\n const session = makeSession({\n requestResourceAllocation: async () => bulletinAllocated(new Uint8Array([1, 2, 3])),\n });\n\n const adapterA = fakeAdapter(\"app-a\");\n const adapterB = fakeAdapter(\"app-b\");\n\n await requestResourceAllocation(session, adapterA, [\n { tag: \"BulletInAllowance\", value: undefined },\n ]);\n\n // app-b's cache is empty even though app-a's is populated.\n expect(\n await getCachedAllocation(adapterB, {\n tag: \"BulletInAllowance\",\n value: undefined,\n }),\n ).toBeNull();\n expect(\n await getCachedAllocation(adapterA, {\n tag: \"BulletInAllowance\",\n value: undefined,\n }),\n ).not.toBeNull();\n });\n });\n\n describe(\"getCachedAllocation\", () => {\n test(\"returns null when nothing is cached\", async () => {\n const adapter = fakeAdapter(\"p\");\n const cached = await getCachedAllocation(adapter, {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n expect(cached).toBeNull();\n });\n\n test(\"disambiguates SmartContractAllowance entries by dest\", async () => {\n const session = makeSession({\n requestResourceAllocation: async () =>\n ok([\n {\n tag: \"Allocated\" as const,\n value: {\n tag: \"SmartContractAllowance\" as const,\n value: undefined,\n },\n },\n ]) as StubReturn,\n });\n const adapter = fakeAdapter(\"p\");\n await requestResourceAllocation(session, adapter, [\n { tag: \"SmartContractAllowance\", value: 5 },\n ]);\n\n expect(\n await getCachedAllocation(adapter, { tag: \"SmartContractAllowance\", value: 5 }),\n ).toEqual({ tag: \"SmartContractAllowance\", dest: 5 });\n // Different dest doesn't match.\n expect(\n await getCachedAllocation(adapter, { tag: \"SmartContractAllowance\", value: 6 }),\n ).toBeNull();\n });\n });\n\n describe(\"ensureSlotAccountSigner\", () => {\n // Deterministic mini-secret so the signer is constructable.\n const slotKey = mnemonicToMiniSecret(DEV_PHRASE);\n\n function bulletinAllocatedResponse() {\n return ok([\n {\n tag: \"Allocated\" as const,\n value: {\n tag: \"BulletInAllowance\" as const,\n value: { slotAccountKey: slotKey },\n },\n },\n ]) as StubReturn;\n }\n\n test(\"cache hit returns a signer without invoking the AP wire\", async () => {\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return bulletinAllocatedResponse();\n },\n });\n const adapter = fakeAdapter(\"p\");\n\n // Populate cache.\n await requestResourceAllocation(session, adapter, [\n { tag: \"BulletInAllowance\", value: undefined },\n ]);\n expect(captured).toHaveLength(1);\n\n // ensureSlotAccountSigner should find the cached key and NOT\n // call the wire again.\n const signer = await ensureSlotAccountSigner(session, adapter, {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n expect(signer.publicKey).toBeInstanceOf(Uint8Array);\n expect(captured).toHaveLength(1); // still 1 — no extra round-trip\n });\n\n test(\"cache miss triggers allocation and returns a fresh signer\", async () => {\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return bulletinAllocatedResponse();\n },\n });\n const adapter = fakeAdapter(\"p\");\n\n // No prior call. ensureSlotAccountSigner should round-trip.\n const signer = await ensureSlotAccountSigner(session, adapter, {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n expect(signer.publicKey).toBeInstanceOf(Uint8Array);\n expect(captured).toHaveLength(1);\n // Implicit allocation requests exactly one resource.\n expect(captured[0].resources.map((r) => r.tag)).toEqual([\"BulletInAllowance\"]);\n });\n\n test(\"Rejected outcome surfaces as a throw — no signer to return\", async () => {\n const session = makeSession({\n requestResourceAllocation: async () =>\n ok([{ tag: \"Rejected\" as const, value: undefined }]) as StubReturn,\n });\n\n await expect(\n ensureSlotAccountSigner(session, fakeAdapter(\"p\"), {\n tag: \"BulletInAllowance\",\n value: undefined,\n }),\n ).rejects.toThrow(/allocation Rejected for BulletInAllowance/);\n });\n\n test(\"empty outcomes array from the wire surfaces as a length-mismatch throw\", async () => {\n // mergeOutcomes enforces the length contract and throws before\n // the downstream `outcomes[0]` defensive guard is reached.\n const session = makeSession({\n requestResourceAllocation: async () => ok([]) as StubReturn,\n });\n\n await expect(\n ensureSlotAccountSigner(session, fakeAdapter(\"p\"), {\n tag: \"BulletInAllowance\",\n value: undefined,\n }),\n ).rejects.toThrow(/length mismatch — requested 1, got 0/);\n });\n\n test(\"NotAvailable outcome surfaces as a throw\", async () => {\n const session = makeSession({\n requestResourceAllocation: async () =>\n ok([{ tag: \"NotAvailable\" as const, value: undefined }]) as StubReturn,\n });\n\n await expect(\n ensureSlotAccountSigner(session, fakeAdapter(\"p\"), {\n tag: \"StatementStoreAllowance\",\n value: undefined,\n }),\n ).rejects.toThrow(/allocation NotAvailable for StatementStoreAllowance/);\n });\n\n test(\"transport error propagates from the underlying allocation call\", async () => {\n const session = makeSession({\n requestResourceAllocation: async () =>\n err(new Error(\"mobile timed out\")) as StubReturn,\n });\n\n await expect(\n ensureSlotAccountSigner(session, fakeAdapter(\"p\"), {\n tag: \"BulletInAllowance\",\n value: undefined,\n }),\n ).rejects.toThrow(/mobile timed out/);\n });\n\n test(\"populates the cache so a later call is a hit\", async () => {\n const session = makeSession({\n requestResourceAllocation: async () => bulletinAllocatedResponse(),\n });\n const adapter = fakeAdapter(\"p\");\n\n await ensureSlotAccountSigner(session, adapter, {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n\n // After the implicit allocation, getCachedAllocation should see the key.\n const cached = await getCachedAllocation(adapter, {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n expect(cached?.tag).toBe(\"BulletInAllowance\");\n });\n\n test(\"parallel calls for the SAME resource share one allocation (no double-prompt)\", async () => {\n // Regression: previously the cache-hit check ran outside the\n // lock, so two concurrent same-resource calls both passed it,\n // then both hit the wire under the serialized lock — burning\n // two slots and prompting the wallet twice. Both calls must\n // resolve from a single allocation.\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n // Force a yield so the second caller has a chance to\n // race past the cache check if the lock isn't around it.\n await new Promise((r) => setTimeout(r, 5));\n return bulletinAllocatedResponse();\n },\n });\n const adapter = fakeAdapter(\"p\");\n\n const [signerA, signerB] = await Promise.all([\n ensureSlotAccountSigner(session, adapter, {\n tag: \"BulletInAllowance\",\n value: undefined,\n }),\n ensureSlotAccountSigner(session, adapter, {\n tag: \"BulletInAllowance\",\n value: undefined,\n }),\n ]);\n\n // Exactly one wire round-trip — the second caller must observe\n // the freshly-cached key, not start a second allocation.\n expect(captured).toHaveLength(1);\n // Both calls return functioning signers backed by the same key.\n expect(signerA.publicKey).toBeInstanceOf(Uint8Array);\n expect(signerB.publicKey).toBeInstanceOf(Uint8Array);\n expect(toHex(signerA.publicKey)).toBe(toHex(signerB.publicKey));\n });\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Persistent allowance-key cache. One JSON file per `appId`, 0o600.\n *\n * Cache key is the variant tag, except `SmartContractAllowance::{dest}`\n * which disambiguates per-derivation-index PGAS pre-warming.\n *\n * @internal\n */\nimport { createLogger } from \"@parity/product-sdk-logger\";\nimport { toHex } from \"@polkadot-api/utils\";\nimport { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nimport type { AllocatableResource, ApAllocationOutcome } from \"./host.js\";\n\nconst log = createLogger(\"terminal\");\n\nconst DEFAULT_STORAGE_DIR = join(homedir(), \".polkadot-apps\");\nconst CACHE_FILE_MODE = 0o600;\n\n/** One cached allowance entry. Hex strings are 0x-prefixed. */\nexport type CachedAllocation =\n | { tag: \"BulletInAllowance\"; slotAccountKey: string }\n | { tag: \"StatementStoreAllowance\"; slotAccountKey: string }\n | { tag: \"SmartContractAllowance\"; dest: number }\n | {\n tag: \"AutoSigning\";\n productDerivationSecret: string;\n productRootPrivateKey: string;\n };\n\ninterface AllowanceCacheV1 {\n version: 1;\n entries: Record<string, CachedAllocation>;\n}\n\nfunction sanitizeAppId(appId: string): string {\n return appId.replace(/[^a-zA-Z0-9_.-]/g, \"_\");\n}\n\nfunction cachePath(appId: string, storageDir?: string): string {\n return join(storageDir ?? DEFAULT_STORAGE_DIR, `${sanitizeAppId(appId)}_AllowanceKeys.json`);\n}\n\nfunction emptyCache(): AllowanceCacheV1 {\n return { version: 1, entries: {} };\n}\n\nexport async function loadCache(appId: string, storageDir?: string): Promise<AllowanceCacheV1> {\n const path = cachePath(appId, storageDir);\n let raw: string;\n try {\n raw = await readFile(path, \"utf-8\");\n } catch (e: unknown) {\n if ((e as NodeJS.ErrnoException)?.code === \"ENOENT\") return emptyCache();\n throw e;\n }\n try {\n const parsed = JSON.parse(raw) as AllowanceCacheV1;\n if (\n parsed?.version !== 1 ||\n typeof parsed.entries !== \"object\" ||\n parsed.entries === null\n ) {\n log.warn(\"allowance cache schema mismatch; starting fresh\", { appId, path });\n return emptyCache();\n }\n return parsed;\n } catch (e) {\n log.warn(\"allowance cache parse failed; starting fresh\", { appId, path, error: String(e) });\n return emptyCache();\n }\n}\n\nexport async function saveCache(\n appId: string,\n cache: AllowanceCacheV1,\n storageDir?: string,\n): Promise<void> {\n const path = cachePath(appId, storageDir);\n await mkdir(dirname(path), { recursive: true });\n // Temp + rename so a mid-write crash can't leave a half-written file.\n // Concurrent Hosts: the Account Holder serializes per (user, product,\n // resource) and returns the same key to all callers, so racing writes\n // converge on identical bytes.\n const tmp = `${path}.tmp`;\n await writeFile(tmp, JSON.stringify(cache, null, 2), { mode: CACHE_FILE_MODE });\n await rename(tmp, path);\n}\n\nexport function cacheKey(resource: AllocatableResource): string {\n if (resource.tag === \"SmartContractAllowance\") {\n return `${resource.tag}::${resource.value}`;\n }\n return resource.tag;\n}\n\n/**\n * Pick the wire `onExisting` policy from current cache state. `Ignore`\n * unless every requested resource is a slot-table variant (Bulletin /\n * SSS) AND already cached, in which case `Increase`. SC and AutoSigning\n * aren't slot-additive so they always force `Ignore`.\n */\nexport function pickOnExistingPolicy(\n cache: AllowanceCacheV1,\n resources: AllocatableResource[],\n): \"Ignore\" | \"Increase\" {\n if (resources.length === 0) return \"Ignore\";\n const allSlotTableAndCached = resources.every((r) => {\n if (r.tag !== \"BulletInAllowance\" && r.tag !== \"StatementStoreAllowance\") return false;\n return Object.hasOwn(cache.entries, cacheKey(r));\n });\n return allSlotTableAndCached ? \"Increase\" : \"Ignore\";\n}\n\n/**\n * Merge `Allocated` outcomes into the cache. Non-`Allocated` outcomes\n * leave the cache unchanged. Returns the input `cache` reference rather\n * than a copy when no entry was added, so callers can short-circuit a\n * disk write via reference equality.\n *\n * Throws on length mismatch — `outcomes` must be length-matched with\n * `requested`. A divergence is a wire contract violation, not data to\n * silently truncate.\n */\nexport function mergeOutcomes(\n cache: AllowanceCacheV1,\n requested: AllocatableResource[],\n outcomes: ApAllocationOutcome[],\n): AllowanceCacheV1 {\n if (requested.length !== outcomes.length) {\n throw new Error(\n `mergeOutcomes: length mismatch — requested ${requested.length}, got ${outcomes.length}`,\n );\n }\n const entries = { ...cache.entries };\n let mutated = false;\n for (let i = 0; i < outcomes.length; i++) {\n const req = requested[i];\n const out = outcomes[i];\n if (out.tag !== \"Allocated\") continue;\n const inner = out.value;\n // outcomes must align positionally AND by variant with the request.\n // A divergence is a wire contract violation: throw rather than\n // silently miscaching a key under the wrong cache slot.\n if (inner.tag !== req.tag) {\n throw new Error(\n `mergeOutcomes: variant mismatch at index ${i} — requested ${req.tag}, got Allocated<${inner.tag}>`,\n );\n }\n const key = cacheKey(req);\n switch (inner.tag) {\n case \"BulletInAllowance\":\n case \"StatementStoreAllowance\":\n entries[key] = {\n tag: inner.tag,\n slotAccountKey: toHex(inner.value.slotAccountKey),\n };\n mutated = true;\n break;\n case \"SmartContractAllowance\":\n // dest comes from the request; the response payload is undefined.\n // Variant alignment is enforced above, so req.tag is guaranteed\n // to be SmartContractAllowance here — the cast narrows it.\n if (req.tag === \"SmartContractAllowance\") {\n entries[key] = { tag: \"SmartContractAllowance\", dest: req.value };\n mutated = true;\n }\n break;\n case \"AutoSigning\":\n entries[key] = {\n tag: \"AutoSigning\",\n productDerivationSecret: inner.value.productDerivationSecret,\n productRootPrivateKey: toHex(inner.value.productRootPrivateKey),\n };\n mutated = true;\n break;\n }\n }\n return mutated ? { version: 1, entries } : cache;\n}\n\n/** Look up a single cached allocation, or `null` if absent. */\nexport function readCacheEntry(\n cache: AllowanceCacheV1,\n resource: AllocatableResource,\n): CachedAllocation | null {\n return cache.entries[cacheKey(resource)] ?? null;\n}\n\n/**\n * Serialize load/merge/save sequences for the same cache file within a\n * single process. Without this, parallel `requestResourceAllocation`\n * calls for *different* resources can race: each snapshots the cache\n * before the other writes, last writer wins, the loser's key is lost.\n *\n * Cross-process races are out of scope here — the Account Holder\n * serializes per user/product/resource and returns identical bytes\n * to concurrent callers for the same resource, so two CLI processes\n * writing the same key converge.\n */\nconst cacheLocks = new Map<string, Promise<unknown>>();\n\nexport function withCacheLock<T>(\n appId: string,\n storageDir: string | undefined,\n fn: () => Promise<T>,\n): Promise<T> {\n const key = cachePath(appId, storageDir);\n const prev = cacheLocks.get(key) ?? Promise.resolve();\n // Neutralize a prior rejection so one failure doesn't block subsequent waiters.\n const next = prev.catch(() => {}).then(fn);\n cacheLocks.set(\n key,\n next.catch(() => {}),\n );\n return next;\n}\n\nif (import.meta.vitest) {\n const { describe, test, expect, beforeEach } = import.meta.vitest;\n const { mkdtempSync, rmSync } = await import(\"node:fs\");\n const { tmpdir } = await import(\"node:os\");\n\n let storageDir: string;\n beforeEach(() => {\n storageDir = mkdtempSync(join(tmpdir(), \"host-cache-test-\"));\n return () => rmSync(storageDir, { recursive: true, force: true });\n });\n\n describe(\"cacheKey\", () => {\n test(\"returns the variant tag for slot-table resources and AutoSigning\", () => {\n expect(cacheKey({ tag: \"BulletInAllowance\", value: undefined })).toBe(\n \"BulletInAllowance\",\n );\n expect(cacheKey({ tag: \"StatementStoreAllowance\", value: undefined })).toBe(\n \"StatementStoreAllowance\",\n );\n expect(cacheKey({ tag: \"AutoSigning\", value: undefined })).toBe(\"AutoSigning\");\n });\n\n test(\"disambiguates SmartContractAllowance by dest\", () => {\n expect(cacheKey({ tag: \"SmartContractAllowance\", value: 0 })).toBe(\n \"SmartContractAllowance::0\",\n );\n expect(cacheKey({ tag: \"SmartContractAllowance\", value: 7 })).toBe(\n \"SmartContractAllowance::7\",\n );\n });\n });\n\n describe(\"loadCache / saveCache round-trip\", () => {\n test(\"loadCache on missing file returns empty cache, not an error\", async () => {\n const cache = await loadCache(\"my-app\", storageDir);\n expect(cache).toEqual({ version: 1, entries: {} });\n });\n\n test(\"saveCache then loadCache returns the same content\", async () => {\n const original: AllowanceCacheV1 = {\n version: 1,\n entries: {\n BulletInAllowance: { tag: \"BulletInAllowance\", slotAccountKey: \"0xdeadbeef\" },\n },\n };\n await saveCache(\"my-app\", original, storageDir);\n const reloaded = await loadCache(\"my-app\", storageDir);\n expect(reloaded).toEqual(original);\n });\n\n test(\"two appIds in the same storageDir don't collide\", async () => {\n const a: AllowanceCacheV1 = {\n version: 1,\n entries: {\n BulletInAllowance: { tag: \"BulletInAllowance\", slotAccountKey: \"0x01\" },\n },\n };\n const b: AllowanceCacheV1 = {\n version: 1,\n entries: {\n BulletInAllowance: { tag: \"BulletInAllowance\", slotAccountKey: \"0x02\" },\n },\n };\n await saveCache(\"app-a\", a, storageDir);\n await saveCache(\"app-b\", b, storageDir);\n expect((await loadCache(\"app-a\", storageDir)).entries.BulletInAllowance).toEqual(\n a.entries.BulletInAllowance,\n );\n expect((await loadCache(\"app-b\", storageDir)).entries.BulletInAllowance).toEqual(\n b.entries.BulletInAllowance,\n );\n });\n\n test(\"appId with non-alphanumeric characters sanitizes to a safe path\", async () => {\n // Verifies a path-traversal-style appId can't escape storageDir.\n const cache: AllowanceCacheV1 = { version: 1, entries: {} };\n await expect(saveCache(\"../escape\", cache, storageDir)).resolves.toBeUndefined();\n const reloaded = await loadCache(\"../escape\", storageDir);\n expect(reloaded).toEqual(cache);\n });\n\n test(\"malformed cache file is treated as empty, not thrown\", async () => {\n await writeFile(cachePath(\"my-app\", storageDir), \"{ not valid json\", \"utf-8\");\n const cache = await loadCache(\"my-app\", storageDir);\n expect(cache).toEqual({ version: 1, entries: {} });\n });\n\n test(\"version mismatch is treated as empty, not thrown\", async () => {\n await writeFile(\n cachePath(\"my-app\", storageDir),\n JSON.stringify({ version: 99, entries: {} }),\n \"utf-8\",\n );\n const cache = await loadCache(\"my-app\", storageDir);\n expect(cache).toEqual({ version: 1, entries: {} });\n });\n\n test(\"file is written with 0o600 permissions (defense-in-depth)\", async () => {\n const { stat } = await import(\"node:fs/promises\");\n const cache: AllowanceCacheV1 = { version: 1, entries: {} };\n await saveCache(\"my-app\", cache, storageDir);\n const s = await stat(cachePath(\"my-app\", storageDir));\n // Mask to permission bits only — file type bits are above 0o777.\n expect(s.mode & 0o777).toBe(0o600);\n });\n });\n\n describe(\"pickOnExistingPolicy\", () => {\n test(\"empty request resolves to Ignore\", () => {\n const cache = emptyCache();\n expect(pickOnExistingPolicy(cache, [])).toBe(\"Ignore\");\n });\n\n test(\"no cached entries → Ignore\", () => {\n const cache = emptyCache();\n expect(\n pickOnExistingPolicy(cache, [{ tag: \"BulletInAllowance\", value: undefined }]),\n ).toBe(\"Ignore\");\n });\n\n test(\"all slot-table resources cached → Increase\", () => {\n const cache: AllowanceCacheV1 = {\n version: 1,\n entries: {\n BulletInAllowance: { tag: \"BulletInAllowance\", slotAccountKey: \"0x01\" },\n StatementStoreAllowance: {\n tag: \"StatementStoreAllowance\",\n slotAccountKey: \"0x02\",\n },\n },\n };\n expect(\n pickOnExistingPolicy(cache, [\n { tag: \"BulletInAllowance\", value: undefined },\n { tag: \"StatementStoreAllowance\", value: undefined },\n ]),\n ).toBe(\"Increase\");\n });\n\n test(\"mixed cached + uncached → Ignore (conservative)\", () => {\n const cache: AllowanceCacheV1 = {\n version: 1,\n entries: {\n BulletInAllowance: { tag: \"BulletInAllowance\", slotAccountKey: \"0x01\" },\n },\n };\n expect(\n pickOnExistingPolicy(cache, [\n { tag: \"BulletInAllowance\", value: undefined },\n { tag: \"StatementStoreAllowance\", value: undefined },\n ]),\n ).toBe(\"Ignore\");\n });\n\n test(\"AutoSigning in the request → Ignore even if cached (not slot-additive)\", () => {\n const cache: AllowanceCacheV1 = {\n version: 1,\n entries: {\n AutoSigning: {\n tag: \"AutoSigning\",\n productDerivationSecret: \"x\",\n productRootPrivateKey: \"0xaa\",\n },\n },\n };\n expect(pickOnExistingPolicy(cache, [{ tag: \"AutoSigning\", value: undefined }])).toBe(\n \"Ignore\",\n );\n });\n\n test(\"SC in the request → Ignore (not slot-additive; PGAS claim is per-call)\", () => {\n const cache: AllowanceCacheV1 = {\n version: 1,\n entries: {\n \"SmartContractAllowance::5\": { tag: \"SmartContractAllowance\", dest: 5 },\n },\n };\n expect(pickOnExistingPolicy(cache, [{ tag: \"SmartContractAllowance\", value: 5 }])).toBe(\n \"Ignore\",\n );\n });\n });\n\n describe(\"mergeOutcomes\", () => {\n test(\"ignores Rejected and NotAvailable\", () => {\n const cache = emptyCache();\n const requested: AllocatableResource[] = [\n { tag: \"BulletInAllowance\", value: undefined },\n { tag: \"StatementStoreAllowance\", value: undefined },\n ];\n const outcomes: ApAllocationOutcome[] = [\n { tag: \"Rejected\", value: undefined },\n { tag: \"NotAvailable\", value: undefined },\n ];\n const merged = mergeOutcomes(cache, requested, outcomes);\n expect(merged.entries).toEqual({});\n });\n\n test(\"stores Allocated Bulletin/SSS keys with hex encoding\", () => {\n const cache = emptyCache();\n const requested: AllocatableResource[] = [\n { tag: \"BulletInAllowance\", value: undefined },\n ];\n const outcomes: ApAllocationOutcome[] = [\n {\n tag: \"Allocated\",\n value: {\n tag: \"BulletInAllowance\",\n value: { slotAccountKey: new Uint8Array([0xde, 0xad, 0xbe, 0xef]) },\n },\n },\n ];\n const merged = mergeOutcomes(cache, requested, outcomes);\n expect(merged.entries.BulletInAllowance).toEqual({\n tag: \"BulletInAllowance\",\n slotAccountKey: \"0xdeadbeef\",\n });\n });\n\n test(\"stores SmartContractAllowance with dest from the request\", () => {\n const cache = emptyCache();\n const requested: AllocatableResource[] = [{ tag: \"SmartContractAllowance\", value: 7 }];\n const outcomes: ApAllocationOutcome[] = [\n {\n tag: \"Allocated\",\n value: { tag: \"SmartContractAllowance\", value: undefined },\n },\n ];\n const merged = mergeOutcomes(cache, requested, outcomes);\n expect(merged.entries[\"SmartContractAllowance::7\"]).toEqual({\n tag: \"SmartContractAllowance\",\n dest: 7,\n });\n });\n\n test(\"stores AutoSigning subtree key + secret\", () => {\n const cache = emptyCache();\n const requested: AllocatableResource[] = [{ tag: \"AutoSigning\", value: undefined }];\n const outcomes: ApAllocationOutcome[] = [\n {\n tag: \"Allocated\",\n value: {\n tag: \"AutoSigning\",\n value: {\n productDerivationSecret: \"secret-hex\",\n productRootPrivateKey: new Uint8Array([0xab, 0xcd]),\n },\n },\n },\n ];\n const merged = mergeOutcomes(cache, requested, outcomes);\n expect(merged.entries.AutoSigning).toEqual({\n tag: \"AutoSigning\",\n productDerivationSecret: \"secret-hex\",\n productRootPrivateKey: \"0xabcd\",\n });\n });\n\n test(\"returns the same cache reference when no Allocated outcomes (no-op)\", () => {\n // Lets callers gate disk writes on reference equality.\n const cache: AllowanceCacheV1 = {\n version: 1,\n entries: {\n BulletInAllowance: { tag: \"BulletInAllowance\", slotAccountKey: \"0xaa\" },\n },\n };\n const requested: AllocatableResource[] = [\n { tag: \"BulletInAllowance\", value: undefined },\n { tag: \"StatementStoreAllowance\", value: undefined },\n ];\n const outcomes: ApAllocationOutcome[] = [\n { tag: \"Rejected\", value: undefined },\n { tag: \"NotAvailable\", value: undefined },\n ];\n expect(mergeOutcomes(cache, requested, outcomes)).toBe(cache);\n });\n\n test(\"empty inputs return the same cache reference\", () => {\n const cache = emptyCache();\n expect(mergeOutcomes(cache, [], [])).toBe(cache);\n });\n\n test(\"returns a new object reference when at least one entry is added\", () => {\n const cache = emptyCache();\n const requested: AllocatableResource[] = [\n { tag: \"BulletInAllowance\", value: undefined },\n ];\n const outcomes: ApAllocationOutcome[] = [\n {\n tag: \"Allocated\",\n value: {\n tag: \"BulletInAllowance\",\n value: { slotAccountKey: new Uint8Array([1]) },\n },\n },\n ];\n expect(mergeOutcomes(cache, requested, outcomes)).not.toBe(cache);\n });\n\n test(\"throws when outcomes do not length-match requested\", () => {\n const cache = emptyCache();\n const requested: AllocatableResource[] = [\n { tag: \"BulletInAllowance\", value: undefined },\n { tag: \"StatementStoreAllowance\", value: undefined },\n ];\n const outcomes: ApAllocationOutcome[] = [{ tag: \"Rejected\", value: undefined }];\n expect(() => mergeOutcomes(cache, requested, outcomes)).toThrow(/length mismatch/);\n });\n\n test(\"throws when an Allocated outcome's variant does not match the requested variant\", () => {\n // outcomes must align positionally AND by variant with the\n // request. Without this guard, a Bulletin key would get stored\n // under the StatementStoreAllowance cache slot.\n const cache = emptyCache();\n const requested: AllocatableResource[] = [\n { tag: \"StatementStoreAllowance\", value: undefined },\n ];\n const outcomes: ApAllocationOutcome[] = [\n {\n tag: \"Allocated\",\n value: {\n tag: \"BulletInAllowance\",\n value: { slotAccountKey: new Uint8Array([0xaa]) },\n },\n },\n ];\n expect(() => mergeOutcomes(cache, requested, outcomes)).toThrow(\n /variant mismatch at index 0 — requested StatementStoreAllowance, got Allocated<BulletInAllowance>/,\n );\n });\n\n test(\"preserves existing entries when merging new ones\", () => {\n const cache: AllowanceCacheV1 = {\n version: 1,\n entries: {\n StatementStoreAllowance: {\n tag: \"StatementStoreAllowance\",\n slotAccountKey: \"0xaa\",\n },\n },\n };\n const requested: AllocatableResource[] = [\n { tag: \"BulletInAllowance\", value: undefined },\n ];\n const outcomes: ApAllocationOutcome[] = [\n {\n tag: \"Allocated\",\n value: {\n tag: \"BulletInAllowance\",\n value: { slotAccountKey: new Uint8Array([0xbb]) },\n },\n },\n ];\n const merged = mergeOutcomes(cache, requested, outcomes);\n expect(Object.keys(merged.entries).sort()).toEqual([\n \"BulletInAllowance\",\n \"StatementStoreAllowance\",\n ]);\n expect(merged.entries.StatementStoreAllowance).toEqual({\n tag: \"StatementStoreAllowance\",\n slotAccountKey: \"0xaa\",\n });\n });\n });\n\n describe(\"readCacheEntry\", () => {\n test(\"returns the cached entry by resource discriminator\", () => {\n const cache: AllowanceCacheV1 = {\n version: 1,\n entries: {\n \"SmartContractAllowance::5\": {\n tag: \"SmartContractAllowance\",\n dest: 5,\n },\n },\n };\n expect(readCacheEntry(cache, { tag: \"SmartContractAllowance\", value: 5 })).toEqual({\n tag: \"SmartContractAllowance\",\n dest: 5,\n });\n // Different dest doesn't match.\n expect(readCacheEntry(cache, { tag: \"SmartContractAllowance\", value: 6 })).toBeNull();\n });\n\n test(\"returns null when nothing cached\", () => {\n const cache = emptyCache();\n expect(\n readCacheEntry(cache, { tag: \"BulletInAllowance\", value: undefined }),\n ).toBeNull();\n });\n });\n\n describe(\"withCacheLock\", () => {\n test(\"serializes calls for the same (appId, storageDir)\", async () => {\n const order: string[] = [];\n const a = withCacheLock(\"app\", storageDir, async () => {\n order.push(\"a-start\");\n await new Promise((r) => setTimeout(r, 10));\n order.push(\"a-end\");\n });\n const b = withCacheLock(\"app\", storageDir, async () => {\n order.push(\"b-start\");\n order.push(\"b-end\");\n });\n await Promise.all([a, b]);\n expect(order).toEqual([\"a-start\", \"a-end\", \"b-start\", \"b-end\"]);\n });\n\n test(\"different (appId, storageDir) keys do not block each other\", async () => {\n let bRan = false;\n const a = withCacheLock(\"app-a\", storageDir, async () => {\n // b should run while a is still inside the await\n await new Promise((r) => setTimeout(r, 20));\n expect(bRan).toBe(true);\n });\n const b = withCacheLock(\"app-b\", storageDir, async () => {\n bRan = true;\n });\n await Promise.all([a, b]);\n });\n\n test(\"a rejection in one call does not block subsequent calls on the same key\", async () => {\n const a = withCacheLock(\"app\", storageDir, async () => {\n throw new Error(\"boom\");\n });\n await expect(a).rejects.toThrow(\"boom\");\n const b = await withCacheLock(\"app\", storageDir, async () => \"ok\");\n expect(b).toBe(\"ok\");\n });\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Slot-account signer: reads the cached Bulletin/SSS allowance key and\n * returns a `PolkadotSigner` that signs locally with sr25519. SC and\n * AutoSigning are not slot-table variants and throw.\n *\n * @module\n */\nimport { fromHex } from \"@polkadot-api/utils\";\nimport { createDerive, sr25519, sr25519Derive } from \"@polkadot-labs/hdkd-helpers\";\nimport type { PolkadotSigner } from \"polkadot-api\";\nimport { getPolkadotSigner } from \"polkadot-api/signer\";\n\nimport type { TerminalAdapter } from \"./adapter.js\";\nimport { type CachedAllocation, loadCache, readCacheEntry } from \"./host-cache.js\";\nimport type { AllocatableResource } from \"./host.js\";\n\n// Wire encoding for slotAccountKey isn't pinned: mobile may send a\n// 32-byte mini-secret or a 64-byte expanded secret. Detect length and\n// route; any other length throws.\nfunction buildKeypair(secret: Uint8Array): {\n publicKey: Uint8Array;\n sign: (data: Uint8Array) => Uint8Array;\n} {\n if (secret.length === 32) {\n const derive = createDerive({ seed: secret, curve: sr25519, derive: sr25519Derive });\n const keypair = derive(\"\");\n return { publicKey: keypair.publicKey, sign: (data) => keypair.sign(data) };\n }\n if (secret.length === 64) {\n return {\n publicKey: sr25519.getPublicKey(secret),\n sign: (data) => sr25519.sign(data, secret),\n };\n }\n throw new Error(\n `createSlotAccountSigner: unexpected slotAccountKey length ${secret.length}. Expected 32 (mini-secret) or 64 (expanded secret).`,\n );\n}\n\n/**\n * Build a `PolkadotSigner` from an already-loaded cache entry. Throws\n * for SC and AutoSigning — they don't carry a slot account key. See\n * the module docstring.\n *\n * Useful when the caller already holds the cache in memory, e.g. inside\n * a `withCacheLock` block, and wants to avoid re-reading disk.\n */\nexport function buildSignerFromEntry(entry: CachedAllocation): PolkadotSigner {\n if (entry.tag !== \"BulletInAllowance\" && entry.tag !== \"StatementStoreAllowance\") {\n throw new Error(\n `createSlotAccountSigner: ${entry.tag} does not carry a slot account key. Slot-table signing is defined for BulletInAllowance and StatementStoreAllowance only.`,\n );\n }\n const secret = fromHex(entry.slotAccountKey);\n const { publicKey, sign } = buildKeypair(secret);\n return getPolkadotSigner(publicKey, \"Sr25519\", async (data) => sign(data));\n}\n\n/**\n * `PolkadotSigner` backed by the cached slot account key. Returns `null`\n * when nothing's cached for `(adapter.appId, resource)`. Throws for SC\n * and AutoSigning (no slot account key — see module docstring).\n *\n * Signs locally with sr25519 — no wallet round-trip on the hot path.\n *\n * @example\n * ```ts\n * const signer = await createSlotAccountSigner(adapter, {\n * tag: \"BulletInAllowance\", value: undefined,\n * });\n * if (signer) await tx.submitAndWatch(signer);\n * ```\n */\nexport async function createSlotAccountSigner(\n adapter: TerminalAdapter,\n resource: AllocatableResource,\n): Promise<PolkadotSigner | null> {\n const cache = await loadCache(adapter.appId, adapter.storageDir);\n const entry = readCacheEntry(cache, resource);\n if (!entry) return null;\n return buildSignerFromEntry(entry);\n}\n\nif (import.meta.vitest) {\n const { describe, test, expect, beforeEach } = import.meta.vitest;\n const { mkdtempSync, rmSync } = await import(\"node:fs\");\n const { tmpdir } = await import(\"node:os\");\n const { join: pathJoin } = await import(\"node:path\");\n const { saveCache } = await import(\"./host-cache.js\");\n const { DEV_PHRASE, mnemonicToMiniSecret } = await import(\"@polkadot-labs/hdkd-helpers\");\n const { toHex } = await import(\"@polkadot-api/utils\");\n\n let storageDir: string;\n beforeEach(() => {\n storageDir = mkdtempSync(pathJoin(tmpdir(), \"host-signer-test-\"));\n return () => rmSync(storageDir, { recursive: true, force: true });\n });\n\n function fakeAdapter(appId: string): TerminalAdapter {\n return { appId, storageDir } as unknown as TerminalAdapter;\n }\n\n function knownSecret(): {\n secret: Uint8Array;\n publicKey: Uint8Array;\n hex: string;\n sign: (msg: Uint8Array) => Uint8Array;\n } {\n // Deterministic across runs — uses the standard substrate dev\n // mnemonic. `mnemonicToMiniSecret` returns the 32-byte mini-secret\n // form (one of the two shapes `createSlotAccountSigner` accepts).\n const secret = mnemonicToMiniSecret(DEV_PHRASE);\n const derive = createDerive({ seed: secret, curve: sr25519, derive: sr25519Derive });\n const keypair = derive(\"\");\n return {\n secret,\n publicKey: keypair.publicKey,\n hex: toHex(secret),\n sign: (msg) => keypair.sign(msg),\n };\n }\n\n describe(\"createSlotAccountSigner\", () => {\n test(\"returns null when no allocation is cached\", async () => {\n const adapter = fakeAdapter(\"p\");\n const signer = await createSlotAccountSigner(adapter, {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n expect(signer).toBeNull();\n });\n\n test(\"returns a signer with the sr25519 public key derived from the cached secret\", async () => {\n const { hex, publicKey } = knownSecret();\n await saveCache(\n \"p\",\n {\n version: 1,\n entries: {\n BulletInAllowance: { tag: \"BulletInAllowance\", slotAccountKey: hex },\n },\n },\n storageDir,\n );\n\n const signer = await createSlotAccountSigner(fakeAdapter(\"p\"), {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n expect(signer).not.toBeNull();\n expect(signer?.publicKey).toEqual(publicKey);\n });\n\n test(\"signs locally and the signature verifies against the cached account\", async () => {\n const { hex, publicKey } = knownSecret();\n await saveCache(\n \"p\",\n {\n version: 1,\n entries: {\n BulletInAllowance: { tag: \"BulletInAllowance\", slotAccountKey: hex },\n },\n },\n storageDir,\n );\n\n const signer = await createSlotAccountSigner(fakeAdapter(\"p\"), {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n // `signBytes` wraps with `<Bytes>...</Bytes>` like all PAPI\n // PolkadotSigners do; for direct payload signing PAPI calls\n // `signTx` which routes through the same `sign` callback we\n // installed. Test that callback directly by reaching for the\n // typed surface: PAPI exposes signTx but the easiest direct\n // verification is `signBytes` which is wired through our sign.\n const payload = new TextEncoder().encode(\"hello slot\");\n // signBytes wraps in <Bytes>...</Bytes> before calling our sign.\n // To verify, reconstruct the wrap manually.\n const wrapped = new Uint8Array([\n ...new TextEncoder().encode(\"<Bytes>\"),\n ...payload,\n ...new TextEncoder().encode(\"</Bytes>\"),\n ]);\n const signature = await signer!.signBytes(payload);\n expect(sr25519.verify(signature, wrapped, publicKey)).toBe(true);\n });\n\n test(\"64-byte expanded-secret form produces a valid signer\", async () => {\n // Pre-computed: @scure/sr25519's secretFromSeed(mnemonicToMiniSecret(DEV_PHRASE)).\n // Public key must match the 32-byte path's derived key.\n const SECRET_64_HEX =\n \"0x28b0ae221c6bb06856b287f60d7ea0d98552ea5a16db16956849aa371db3eb51fd190cce74df356432b410bd64682309d6dedb27c76845daf388557cbac3ca34\";\n\n await saveCache(\n \"p\",\n {\n version: 1,\n entries: {\n BulletInAllowance: {\n tag: \"BulletInAllowance\",\n slotAccountKey: SECRET_64_HEX,\n },\n },\n },\n storageDir,\n );\n\n const signer = await createSlotAccountSigner(fakeAdapter(\"p\"), {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n expect(signer).not.toBeNull();\n\n // Public key from the 64-byte path matches the 32-byte path's\n // — same logical key, two encodings.\n const { publicKey: pubFrom32Bytes } = knownSecret();\n expect(signer?.publicKey).toEqual(pubFrom32Bytes);\n\n // Signature from the 64-byte branch verifies against the same\n // public key.\n const payload = new TextEncoder().encode(\"hello 64-byte path\");\n const wrapped = new Uint8Array([\n ...new TextEncoder().encode(\"<Bytes>\"),\n ...payload,\n ...new TextEncoder().encode(\"</Bytes>\"),\n ]);\n const sig = await signer!.signBytes(payload);\n expect(sr25519.verify(sig, wrapped, pubFrom32Bytes)).toBe(true);\n });\n\n test(\"works for StatementStoreAllowance the same way\", async () => {\n const { hex, publicKey } = knownSecret();\n await saveCache(\n \"p\",\n {\n version: 1,\n entries: {\n StatementStoreAllowance: {\n tag: \"StatementStoreAllowance\",\n slotAccountKey: hex,\n },\n },\n },\n storageDir,\n );\n\n const signer = await createSlotAccountSigner(fakeAdapter(\"p\"), {\n tag: \"StatementStoreAllowance\",\n value: undefined,\n });\n expect(signer?.publicKey).toEqual(publicKey);\n });\n\n test(\"throws for SmartContractAllowance (no slot account key)\", async () => {\n await saveCache(\n \"p\",\n {\n version: 1,\n entries: {\n \"SmartContractAllowance::5\": { tag: \"SmartContractAllowance\", dest: 5 },\n },\n },\n storageDir,\n );\n\n await expect(\n createSlotAccountSigner(fakeAdapter(\"p\"), {\n tag: \"SmartContractAllowance\",\n value: 5,\n }),\n ).rejects.toThrow(/SmartContractAllowance does not carry a slot account key/);\n });\n\n test(\"throws for AutoSigning (subtree key, not slot account)\", async () => {\n await saveCache(\n \"p\",\n {\n version: 1,\n entries: {\n AutoSigning: {\n tag: \"AutoSigning\",\n productDerivationSecret: \"secret\",\n productRootPrivateKey: \"0xabcd\",\n },\n },\n },\n storageDir,\n );\n\n await expect(\n createSlotAccountSigner(fakeAdapter(\"p\"), {\n tag: \"AutoSigning\",\n value: undefined,\n }),\n ).rejects.toThrow(/AutoSigning does not carry a slot account key/);\n });\n\n test(\"different appIds yield different signers from disjoint caches\", async () => {\n const a = knownSecret();\n // Distinct mini-secret → distinct derived keypair.\n const otherMiniSecret = new Uint8Array(32).fill(0x07);\n const otherKeypair = createDerive({\n seed: otherMiniSecret,\n curve: sr25519,\n derive: sr25519Derive,\n })(\"\");\n\n await saveCache(\n \"app-a\",\n {\n version: 1,\n entries: {\n BulletInAllowance: { tag: \"BulletInAllowance\", slotAccountKey: a.hex },\n },\n },\n storageDir,\n );\n await saveCache(\n \"app-b\",\n {\n version: 1,\n entries: {\n BulletInAllowance: {\n tag: \"BulletInAllowance\",\n slotAccountKey: toHex(otherMiniSecret),\n },\n },\n },\n storageDir,\n );\n\n const signerA = await createSlotAccountSigner(fakeAdapter(\"app-a\"), {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n const signerB = await createSlotAccountSigner(fakeAdapter(\"app-b\"), {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n expect(signerA?.publicKey).toEqual(a.publicKey);\n expect(signerB?.publicKey).toEqual(otherKeypair.publicKey);\n expect(signerA?.publicKey).not.toEqual(signerB?.publicKey);\n });\n\n test(\"rejects an unexpected slotAccountKey length\", async () => {\n // Variable-length codec on the wire, but our keypair builder\n // only recognizes the standard 32 / 64 byte forms.\n await saveCache(\n \"p\",\n {\n version: 1,\n entries: {\n BulletInAllowance: {\n tag: \"BulletInAllowance\",\n slotAccountKey: toHex(new Uint8Array(48).fill(1)),\n },\n },\n },\n storageDir,\n );\n\n await expect(\n createSlotAccountSigner(fakeAdapter(\"p\"), {\n tag: \"BulletInAllowance\",\n value: undefined,\n }),\n ).rejects.toThrow(/unexpected slotAccountKey length 48/);\n });\n });\n}\n"],"mappings":";AAWA,SAAS,gBAAAA,qBAAoB;;;ACD7B,SAAS,oBAAoB;AAC7B,SAAS,aAAa;AACtB,SAAS,OAAO,UAAU,QAAQ,iBAAiB;AACnD,SAAS,eAAe;AACxB,SAAS,SAAS,YAAY;AAI9B,IAAM,MAAM,aAAa,UAAU;AAEnC,IAAM,sBAAsB,KAAK,QAAQ,GAAG,gBAAgB;AAC5D,IAAM,kBAAkB;AAkBxB,SAAS,cAAc,OAAuB;AAC1C,SAAO,MAAM,QAAQ,oBAAoB,GAAG;AAChD;AAEA,SAAS,UAAU,OAAe,YAA6B;AAC3D,SAAO,KAAK,cAAc,qBAAqB,GAAG,cAAc,KAAK,CAAC,qBAAqB;AAC/F;AAEA,SAAS,aAA+B;AACpC,SAAO,EAAE,SAAS,GAAG,SAAS,CAAC,EAAE;AACrC;AAEA,eAAsB,UAAU,OAAe,YAAgD;AAC3F,QAAM,OAAO,UAAU,OAAO,UAAU;AACxC,MAAI;AACJ,MAAI;AACA,UAAM,MAAM,SAAS,MAAM,OAAO;AAAA,EACtC,SAAS,GAAY;AACjB,QAAK,GAA6B,SAAS,SAAU,QAAO,WAAW;AACvE,UAAM;AAAA,EACV;AACA,MAAI;AACA,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QACI,QAAQ,YAAY,KACpB,OAAO,OAAO,YAAY,YAC1B,OAAO,YAAY,MACrB;AACE,UAAI,KAAK,mDAAmD,EAAE,OAAO,KAAK,CAAC;AAC3E,aAAO,WAAW;AAAA,IACtB;AACA,WAAO;AAAA,EACX,SAAS,GAAG;AACR,QAAI,KAAK,gDAAgD,EAAE,OAAO,MAAM,OAAO,OAAO,CAAC,EAAE,CAAC;AAC1F,WAAO,WAAW;AAAA,EACtB;AACJ;AAEA,eAAsB,UAClB,OACA,OACA,YACa;AACb,QAAM,OAAO,UAAU,OAAO,UAAU;AACxC,QAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAK9C,QAAM,MAAM,GAAG,IAAI;AACnB,QAAM,UAAU,KAAK,KAAK,UAAU,OAAO,MAAM,CAAC,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAC9E,QAAM,OAAO,KAAK,IAAI;AAC1B;AAEO,SAAS,SAAS,UAAuC;AAC5D,MAAI,SAAS,QAAQ,0BAA0B;AAC3C,WAAO,GAAG,SAAS,GAAG,KAAK,SAAS,KAAK;AAAA,EAC7C;AACA,SAAO,SAAS;AACpB;AAQO,SAAS,qBACZ,OACA,WACqB;AACrB,MAAI,UAAU,WAAW,EAAG,QAAO;AACnC,QAAM,wBAAwB,UAAU,MAAM,CAAC,MAAM;AACjD,QAAI,EAAE,QAAQ,uBAAuB,EAAE,QAAQ,0BAA2B,QAAO;AACjF,WAAO,OAAO,OAAO,MAAM,SAAS,SAAS,CAAC,CAAC;AAAA,EACnD,CAAC;AACD,SAAO,wBAAwB,aAAa;AAChD;AAYO,SAAS,cACZ,OACA,WACA,UACgB;AAChB,MAAI,UAAU,WAAW,SAAS,QAAQ;AACtC,UAAM,IAAI;AAAA,MACN,mDAA8C,UAAU,MAAM,SAAS,SAAS,MAAM;AAAA,IAC1F;AAAA,EACJ;AACA,QAAM,UAAU,EAAE,GAAG,MAAM,QAAQ;AACnC,MAAI,UAAU;AACd,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACtC,UAAM,MAAM,UAAU,CAAC;AACvB,UAAM,MAAM,SAAS,CAAC;AACtB,QAAI,IAAI,QAAQ,YAAa;AAC7B,UAAM,QAAQ,IAAI;AAIlB,QAAI,MAAM,QAAQ,IAAI,KAAK;AACvB,YAAM,IAAI;AAAA,QACN,4CAA4C,CAAC,qBAAgB,IAAI,GAAG,mBAAmB,MAAM,GAAG;AAAA,MACpG;AAAA,IACJ;AACA,UAAM,MAAM,SAAS,GAAG;AACxB,YAAQ,MAAM,KAAK;AAAA,MACf,KAAK;AAAA,MACL,KAAK;AACD,gBAAQ,GAAG,IAAI;AAAA,UACX,KAAK,MAAM;AAAA,UACX,gBAAgB,MAAM,MAAM,MAAM,cAAc;AAAA,QACpD;AACA,kBAAU;AACV;AAAA,MACJ,KAAK;AAID,YAAI,IAAI,QAAQ,0BAA0B;AACtC,kBAAQ,GAAG,IAAI,EAAE,KAAK,0BAA0B,MAAM,IAAI,MAAM;AAChE,oBAAU;AAAA,QACd;AACA;AAAA,MACJ,KAAK;AACD,gBAAQ,GAAG,IAAI;AAAA,UACX,KAAK;AAAA,UACL,yBAAyB,MAAM,MAAM;AAAA,UACrC,uBAAuB,MAAM,MAAM,MAAM,qBAAqB;AAAA,QAClE;AACA,kBAAU;AACV;AAAA,IACR;AAAA,EACJ;AACA,SAAO,UAAU,EAAE,SAAS,GAAG,QAAQ,IAAI;AAC/C;AAGO,SAAS,eACZ,OACA,UACuB;AACvB,SAAO,MAAM,QAAQ,SAAS,QAAQ,CAAC,KAAK;AAChD;AAaA,IAAM,aAAa,oBAAI,IAA8B;AAE9C,SAAS,cACZ,OACA,YACA,IACU;AACV,QAAM,MAAM,UAAU,OAAO,UAAU;AACvC,QAAM,OAAO,WAAW,IAAI,GAAG,KAAK,QAAQ,QAAQ;AAEpD,QAAM,OAAO,KAAK,MAAM,MAAM;AAAA,EAAC,CAAC,EAAE,KAAK,EAAE;AACzC,aAAW;AAAA,IACP;AAAA,IACA,KAAK,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACvB;AACA,SAAO;AACX;AAEA,IAAI,QAAoB;AACpB,QAAM,EAAE,UAAU,MAAM,QAAQ,WAAW,IAAI;AAC/C,QAAM,EAAE,aAAa,OAAO,IAAI,MAAa;AAC7C,QAAM,EAAE,OAAO,IAAI,MAAa;AAEhC,MAAI;AACJ,aAAW,MAAM;AACb,iBAAa,YAAY,KAAK,OAAO,GAAG,kBAAkB,CAAC;AAC3D,WAAO,MAAM,OAAO,YAAY,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EACpE,CAAC;AAED,WAAS,YAAY,MAAM;AACvB,SAAK,oEAAoE,MAAM;AAC3E,aAAO,SAAS,EAAE,KAAK,qBAAqB,OAAO,OAAU,CAAC,CAAC,EAAE;AAAA,QAC7D;AAAA,MACJ;AACA,aAAO,SAAS,EAAE,KAAK,2BAA2B,OAAO,OAAU,CAAC,CAAC,EAAE;AAAA,QACnE;AAAA,MACJ;AACA,aAAO,SAAS,EAAE,KAAK,eAAe,OAAO,OAAU,CAAC,CAAC,EAAE,KAAK,aAAa;AAAA,IACjF,CAAC;AAED,SAAK,gDAAgD,MAAM;AACvD,aAAO,SAAS,EAAE,KAAK,0BAA0B,OAAO,EAAE,CAAC,CAAC,EAAE;AAAA,QAC1D;AAAA,MACJ;AACA,aAAO,SAAS,EAAE,KAAK,0BAA0B,OAAO,EAAE,CAAC,CAAC,EAAE;AAAA,QAC1D;AAAA,MACJ;AAAA,IACJ,CAAC;AAAA,EACL,CAAC;AAED,WAAS,oCAAoC,MAAM;AAC/C,SAAK,+DAA+D,YAAY;AAC5E,YAAM,QAAQ,MAAM,UAAU,UAAU,UAAU;AAClD,aAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,GAAG,SAAS,CAAC,EAAE,CAAC;AAAA,IACrD,CAAC;AAED,SAAK,qDAAqD,YAAY;AAClE,YAAM,WAA6B;AAAA,QAC/B,SAAS;AAAA,QACT,SAAS;AAAA,UACL,mBAAmB,EAAE,KAAK,qBAAqB,gBAAgB,aAAa;AAAA,QAChF;AAAA,MACJ;AACA,YAAM,UAAU,UAAU,UAAU,UAAU;AAC9C,YAAM,WAAW,MAAM,UAAU,UAAU,UAAU;AACrD,aAAO,QAAQ,EAAE,QAAQ,QAAQ;AAAA,IACrC,CAAC;AAED,SAAK,mDAAmD,YAAY;AAChE,YAAM,IAAsB;AAAA,QACxB,SAAS;AAAA,QACT,SAAS;AAAA,UACL,mBAAmB,EAAE,KAAK,qBAAqB,gBAAgB,OAAO;AAAA,QAC1E;AAAA,MACJ;AACA,YAAM,IAAsB;AAAA,QACxB,SAAS;AAAA,QACT,SAAS;AAAA,UACL,mBAAmB,EAAE,KAAK,qBAAqB,gBAAgB,OAAO;AAAA,QAC1E;AAAA,MACJ;AACA,YAAM,UAAU,SAAS,GAAG,UAAU;AACtC,YAAM,UAAU,SAAS,GAAG,UAAU;AACtC,cAAQ,MAAM,UAAU,SAAS,UAAU,GAAG,QAAQ,iBAAiB,EAAE;AAAA,QACrE,EAAE,QAAQ;AAAA,MACd;AACA,cAAQ,MAAM,UAAU,SAAS,UAAU,GAAG,QAAQ,iBAAiB,EAAE;AAAA,QACrE,EAAE,QAAQ;AAAA,MACd;AAAA,IACJ,CAAC;AAED,SAAK,mEAAmE,YAAY;AAEhF,YAAM,QAA0B,EAAE,SAAS,GAAG,SAAS,CAAC,EAAE;AAC1D,YAAM,OAAO,UAAU,aAAa,OAAO,UAAU,CAAC,EAAE,SAAS,cAAc;AAC/E,YAAM,WAAW,MAAM,UAAU,aAAa,UAAU;AACxD,aAAO,QAAQ,EAAE,QAAQ,KAAK;AAAA,IAClC,CAAC;AAED,SAAK,wDAAwD,YAAY;AACrE,YAAM,UAAU,UAAU,UAAU,UAAU,GAAG,oBAAoB,OAAO;AAC5E,YAAM,QAAQ,MAAM,UAAU,UAAU,UAAU;AAClD,aAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,GAAG,SAAS,CAAC,EAAE,CAAC;AAAA,IACrD,CAAC;AAED,SAAK,oDAAoD,YAAY;AACjE,YAAM;AAAA,QACF,UAAU,UAAU,UAAU;AAAA,QAC9B,KAAK,UAAU,EAAE,SAAS,IAAI,SAAS,CAAC,EAAE,CAAC;AAAA,QAC3C;AAAA,MACJ;AACA,YAAM,QAAQ,MAAM,UAAU,UAAU,UAAU;AAClD,aAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,GAAG,SAAS,CAAC,EAAE,CAAC;AAAA,IACrD,CAAC;AAED,SAAK,6DAA6D,YAAY;AAC1E,YAAM,EAAE,KAAK,IAAI,MAAa;AAC9B,YAAM,QAA0B,EAAE,SAAS,GAAG,SAAS,CAAC,EAAE;AAC1D,YAAM,UAAU,UAAU,OAAO,UAAU;AAC3C,YAAM,IAAI,MAAM,KAAK,UAAU,UAAU,UAAU,CAAC;AAEpD,aAAO,EAAE,OAAO,GAAK,EAAE,KAAK,GAAK;AAAA,IACrC,CAAC;AAAA,EACL,CAAC;AAED,WAAS,wBAAwB,MAAM;AACnC,SAAK,oCAAoC,MAAM;AAC3C,YAAM,QAAQ,WAAW;AACzB,aAAO,qBAAqB,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,QAAQ;AAAA,IACzD,CAAC;AAED,SAAK,mCAA8B,MAAM;AACrC,YAAM,QAAQ,WAAW;AACzB;AAAA,QACI,qBAAqB,OAAO,CAAC,EAAE,KAAK,qBAAqB,OAAO,OAAU,CAAC,CAAC;AAAA,MAChF,EAAE,KAAK,QAAQ;AAAA,IACnB,CAAC;AAED,SAAK,mDAA8C,MAAM;AACrD,YAAM,QAA0B;AAAA,QAC5B,SAAS;AAAA,QACT,SAAS;AAAA,UACL,mBAAmB,EAAE,KAAK,qBAAqB,gBAAgB,OAAO;AAAA,UACtE,yBAAyB;AAAA,YACrB,KAAK;AAAA,YACL,gBAAgB;AAAA,UACpB;AAAA,QACJ;AAAA,MACJ;AACA;AAAA,QACI,qBAAqB,OAAO;AAAA,UACxB,EAAE,KAAK,qBAAqB,OAAO,OAAU;AAAA,UAC7C,EAAE,KAAK,2BAA2B,OAAO,OAAU;AAAA,QACvD,CAAC;AAAA,MACL,EAAE,KAAK,UAAU;AAAA,IACrB,CAAC;AAED,SAAK,wDAAmD,MAAM;AAC1D,YAAM,QAA0B;AAAA,QAC5B,SAAS;AAAA,QACT,SAAS;AAAA,UACL,mBAAmB,EAAE,KAAK,qBAAqB,gBAAgB,OAAO;AAAA,QAC1E;AAAA,MACJ;AACA;AAAA,QACI,qBAAqB,OAAO;AAAA,UACxB,EAAE,KAAK,qBAAqB,OAAO,OAAU;AAAA,UAC7C,EAAE,KAAK,2BAA2B,OAAO,OAAU;AAAA,QACvD,CAAC;AAAA,MACL,EAAE,KAAK,QAAQ;AAAA,IACnB,CAAC;AAED,SAAK,+EAA0E,MAAM;AACjF,YAAM,QAA0B;AAAA,QAC5B,SAAS;AAAA,QACT,SAAS;AAAA,UACL,aAAa;AAAA,YACT,KAAK;AAAA,YACL,yBAAyB;AAAA,YACzB,uBAAuB;AAAA,UAC3B;AAAA,QACJ;AAAA,MACJ;AACA,aAAO,qBAAqB,OAAO,CAAC,EAAE,KAAK,eAAe,OAAO,OAAU,CAAC,CAAC,CAAC,EAAE;AAAA,QAC5E;AAAA,MACJ;AAAA,IACJ,CAAC;AAED,SAAK,+EAA0E,MAAM;AACjF,YAAM,QAA0B;AAAA,QAC5B,SAAS;AAAA,QACT,SAAS;AAAA,UACL,6BAA6B,EAAE,KAAK,0BAA0B,MAAM,EAAE;AAAA,QAC1E;AAAA,MACJ;AACA,aAAO,qBAAqB,OAAO,CAAC,EAAE,KAAK,0BAA0B,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE;AAAA,QAC/E;AAAA,MACJ;AAAA,IACJ,CAAC;AAAA,EACL,CAAC;AAED,WAAS,iBAAiB,MAAM;AAC5B,SAAK,qCAAqC,MAAM;AAC5C,YAAM,QAAQ,WAAW;AACzB,YAAM,YAAmC;AAAA,QACrC,EAAE,KAAK,qBAAqB,OAAO,OAAU;AAAA,QAC7C,EAAE,KAAK,2BAA2B,OAAO,OAAU;AAAA,MACvD;AACA,YAAM,WAAkC;AAAA,QACpC,EAAE,KAAK,YAAY,OAAO,OAAU;AAAA,QACpC,EAAE,KAAK,gBAAgB,OAAO,OAAU;AAAA,MAC5C;AACA,YAAM,SAAS,cAAc,OAAO,WAAW,QAAQ;AACvD,aAAO,OAAO,OAAO,EAAE,QAAQ,CAAC,CAAC;AAAA,IACrC,CAAC;AAED,SAAK,wDAAwD,MAAM;AAC/D,YAAM,QAAQ,WAAW;AACzB,YAAM,YAAmC;AAAA,QACrC,EAAE,KAAK,qBAAqB,OAAO,OAAU;AAAA,MACjD;AACA,YAAM,WAAkC;AAAA,QACpC;AAAA,UACI,KAAK;AAAA,UACL,OAAO;AAAA,YACH,KAAK;AAAA,YACL,OAAO,EAAE,gBAAgB,IAAI,WAAW,CAAC,KAAM,KAAM,KAAM,GAAI,CAAC,EAAE;AAAA,UACtE;AAAA,QACJ;AAAA,MACJ;AACA,YAAM,SAAS,cAAc,OAAO,WAAW,QAAQ;AACvD,aAAO,OAAO,QAAQ,iBAAiB,EAAE,QAAQ;AAAA,QAC7C,KAAK;AAAA,QACL,gBAAgB;AAAA,MACpB,CAAC;AAAA,IACL,CAAC;AAED,SAAK,4DAA4D,MAAM;AACnE,YAAM,QAAQ,WAAW;AACzB,YAAM,YAAmC,CAAC,EAAE,KAAK,0BAA0B,OAAO,EAAE,CAAC;AACrF,YAAM,WAAkC;AAAA,QACpC;AAAA,UACI,KAAK;AAAA,UACL,OAAO,EAAE,KAAK,0BAA0B,OAAO,OAAU;AAAA,QAC7D;AAAA,MACJ;AACA,YAAM,SAAS,cAAc,OAAO,WAAW,QAAQ;AACvD,aAAO,OAAO,QAAQ,2BAA2B,CAAC,EAAE,QAAQ;AAAA,QACxD,KAAK;AAAA,QACL,MAAM;AAAA,MACV,CAAC;AAAA,IACL,CAAC;AAED,SAAK,2CAA2C,MAAM;AAClD,YAAM,QAAQ,WAAW;AACzB,YAAM,YAAmC,CAAC,EAAE,KAAK,eAAe,OAAO,OAAU,CAAC;AAClF,YAAM,WAAkC;AAAA,QACpC;AAAA,UACI,KAAK;AAAA,UACL,OAAO;AAAA,YACH,KAAK;AAAA,YACL,OAAO;AAAA,cACH,yBAAyB;AAAA,cACzB,uBAAuB,IAAI,WAAW,CAAC,KAAM,GAAI,CAAC;AAAA,YACtD;AAAA,UACJ;AAAA,QACJ;AAAA,MACJ;AACA,YAAM,SAAS,cAAc,OAAO,WAAW,QAAQ;AACvD,aAAO,OAAO,QAAQ,WAAW,EAAE,QAAQ;AAAA,QACvC,KAAK;AAAA,QACL,yBAAyB;AAAA,QACzB,uBAAuB;AAAA,MAC3B,CAAC;AAAA,IACL,CAAC;AAED,SAAK,uEAAuE,MAAM;AAE9E,YAAM,QAA0B;AAAA,QAC5B,SAAS;AAAA,QACT,SAAS;AAAA,UACL,mBAAmB,EAAE,KAAK,qBAAqB,gBAAgB,OAAO;AAAA,QAC1E;AAAA,MACJ;AACA,YAAM,YAAmC;AAAA,QACrC,EAAE,KAAK,qBAAqB,OAAO,OAAU;AAAA,QAC7C,EAAE,KAAK,2BAA2B,OAAO,OAAU;AAAA,MACvD;AACA,YAAM,WAAkC;AAAA,QACpC,EAAE,KAAK,YAAY,OAAO,OAAU;AAAA,QACpC,EAAE,KAAK,gBAAgB,OAAO,OAAU;AAAA,MAC5C;AACA,aAAO,cAAc,OAAO,WAAW,QAAQ,CAAC,EAAE,KAAK,KAAK;AAAA,IAChE,CAAC;AAED,SAAK,gDAAgD,MAAM;AACvD,YAAM,QAAQ,WAAW;AACzB,aAAO,cAAc,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK;AAAA,IACnD,CAAC;AAED,SAAK,mEAAmE,MAAM;AAC1E,YAAM,QAAQ,WAAW;AACzB,YAAM,YAAmC;AAAA,QACrC,EAAE,KAAK,qBAAqB,OAAO,OAAU;AAAA,MACjD;AACA,YAAM,WAAkC;AAAA,QACpC;AAAA,UACI,KAAK;AAAA,UACL,OAAO;AAAA,YACH,KAAK;AAAA,YACL,OAAO,EAAE,gBAAgB,IAAI,WAAW,CAAC,CAAC,CAAC,EAAE;AAAA,UACjD;AAAA,QACJ;AAAA,MACJ;AACA,aAAO,cAAc,OAAO,WAAW,QAAQ,CAAC,EAAE,IAAI,KAAK,KAAK;AAAA,IACpE,CAAC;AAED,SAAK,sDAAsD,MAAM;AAC7D,YAAM,QAAQ,WAAW;AACzB,YAAM,YAAmC;AAAA,QACrC,EAAE,KAAK,qBAAqB,OAAO,OAAU;AAAA,QAC7C,EAAE,KAAK,2BAA2B,OAAO,OAAU;AAAA,MACvD;AACA,YAAM,WAAkC,CAAC,EAAE,KAAK,YAAY,OAAO,OAAU,CAAC;AAC9E,aAAO,MAAM,cAAc,OAAO,WAAW,QAAQ,CAAC,EAAE,QAAQ,iBAAiB;AAAA,IACrF,CAAC;AAED,SAAK,mFAAmF,MAAM;AAI1F,YAAM,QAAQ,WAAW;AACzB,YAAM,YAAmC;AAAA,QACrC,EAAE,KAAK,2BAA2B,OAAO,OAAU;AAAA,MACvD;AACA,YAAM,WAAkC;AAAA,QACpC;AAAA,UACI,KAAK;AAAA,UACL,OAAO;AAAA,YACH,KAAK;AAAA,YACL,OAAO,EAAE,gBAAgB,IAAI,WAAW,CAAC,GAAI,CAAC,EAAE;AAAA,UACpD;AAAA,QACJ;AAAA,MACJ;AACA,aAAO,MAAM,cAAc,OAAO,WAAW,QAAQ,CAAC,EAAE;AAAA,QACpD;AAAA,MACJ;AAAA,IACJ,CAAC;AAED,SAAK,oDAAoD,MAAM;AAC3D,YAAM,QAA0B;AAAA,QAC5B,SAAS;AAAA,QACT,SAAS;AAAA,UACL,yBAAyB;AAAA,YACrB,KAAK;AAAA,YACL,gBAAgB;AAAA,UACpB;AAAA,QACJ;AAAA,MACJ;AACA,YAAM,YAAmC;AAAA,QACrC,EAAE,KAAK,qBAAqB,OAAO,OAAU;AAAA,MACjD;AACA,YAAM,WAAkC;AAAA,QACpC;AAAA,UACI,KAAK;AAAA,UACL,OAAO;AAAA,YACH,KAAK;AAAA,YACL,OAAO,EAAE,gBAAgB,IAAI,WAAW,CAAC,GAAI,CAAC,EAAE;AAAA,UACpD;AAAA,QACJ;AAAA,MACJ;AACA,YAAM,SAAS,cAAc,OAAO,WAAW,QAAQ;AACvD,aAAO,OAAO,KAAK,OAAO,OAAO,EAAE,KAAK,CAAC,EAAE,QAAQ;AAAA,QAC/C;AAAA,QACA;AAAA,MACJ,CAAC;AACD,aAAO,OAAO,QAAQ,uBAAuB,EAAE,QAAQ;AAAA,QACnD,KAAK;AAAA,QACL,gBAAgB;AAAA,MACpB,CAAC;AAAA,IACL,CAAC;AAAA,EACL,CAAC;AAED,WAAS,kBAAkB,MAAM;AAC7B,SAAK,sDAAsD,MAAM;AAC7D,YAAM,QAA0B;AAAA,QAC5B,SAAS;AAAA,QACT,SAAS;AAAA,UACL,6BAA6B;AAAA,YACzB,KAAK;AAAA,YACL,MAAM;AAAA,UACV;AAAA,QACJ;AAAA,MACJ;AACA,aAAO,eAAe,OAAO,EAAE,KAAK,0BAA0B,OAAO,EAAE,CAAC,CAAC,EAAE,QAAQ;AAAA,QAC/E,KAAK;AAAA,QACL,MAAM;AAAA,MACV,CAAC;AAED,aAAO,eAAe,OAAO,EAAE,KAAK,0BAA0B,OAAO,EAAE,CAAC,CAAC,EAAE,SAAS;AAAA,IACxF,CAAC;AAED,SAAK,oCAAoC,MAAM;AAC3C,YAAM,QAAQ,WAAW;AACzB;AAAA,QACI,eAAe,OAAO,EAAE,KAAK,qBAAqB,OAAO,OAAU,CAAC;AAAA,MACxE,EAAE,SAAS;AAAA,IACf,CAAC;AAAA,EACL,CAAC;AAED,WAAS,iBAAiB,MAAM;AAC5B,SAAK,qDAAqD,YAAY;AAClE,YAAM,QAAkB,CAAC;AACzB,YAAM,IAAI,cAAc,OAAO,YAAY,YAAY;AACnD,cAAM,KAAK,SAAS;AACpB,cAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAC1C,cAAM,KAAK,OAAO;AAAA,MACtB,CAAC;AACD,YAAM,IAAI,cAAc,OAAO,YAAY,YAAY;AACnD,cAAM,KAAK,SAAS;AACpB,cAAM,KAAK,OAAO;AAAA,MACtB,CAAC;AACD,YAAM,QAAQ,IAAI,CAAC,GAAG,CAAC,CAAC;AACxB,aAAO,KAAK,EAAE,QAAQ,CAAC,WAAW,SAAS,WAAW,OAAO,CAAC;AAAA,IAClE,CAAC;AAED,SAAK,8DAA8D,YAAY;AAC3E,UAAI,OAAO;AACX,YAAM,IAAI,cAAc,SAAS,YAAY,YAAY;AAErD,cAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAC1C,eAAO,IAAI,EAAE,KAAK,IAAI;AAAA,MAC1B,CAAC;AACD,YAAM,IAAI,cAAc,SAAS,YAAY,YAAY;AACrD,eAAO;AAAA,MACX,CAAC;AACD,YAAM,QAAQ,IAAI,CAAC,GAAG,CAAC,CAAC;AAAA,IAC5B,CAAC;AAED,SAAK,2EAA2E,YAAY;AACxF,YAAM,IAAI,cAAc,OAAO,YAAY,YAAY;AACnD,cAAM,IAAI,MAAM,MAAM;AAAA,MAC1B,CAAC;AACD,YAAM,OAAO,CAAC,EAAE,QAAQ,QAAQ,MAAM;AACtC,YAAM,IAAI,MAAM,cAAc,OAAO,YAAY,YAAY,IAAI;AACjE,aAAO,CAAC,EAAE,KAAK,IAAI;AAAA,IACvB,CAAC;AAAA,EACL,CAAC;AACL;;;ACnoBA,SAAS,eAAe;AACxB,SAAS,cAAc,SAAS,qBAAqB;AAErD,SAAS,yBAAyB;AASlC,SAAS,aAAa,QAGpB;AACE,MAAI,OAAO,WAAW,IAAI;AACtB,UAAM,SAAS,aAAa,EAAE,MAAM,QAAQ,OAAO,SAAS,QAAQ,cAAc,CAAC;AACnF,UAAM,UAAU,OAAO,EAAE;AACzB,WAAO,EAAE,WAAW,QAAQ,WAAW,MAAM,CAAC,SAAS,QAAQ,KAAK,IAAI,EAAE;AAAA,EAC9E;AACA,MAAI,OAAO,WAAW,IAAI;AACtB,WAAO;AAAA,MACH,WAAW,QAAQ,aAAa,MAAM;AAAA,MACtC,MAAM,CAAC,SAAS,QAAQ,KAAK,MAAM,MAAM;AAAA,IAC7C;AAAA,EACJ;AACA,QAAM,IAAI;AAAA,IACN,6DAA6D,OAAO,MAAM;AAAA,EAC9E;AACJ;AAUO,SAAS,qBAAqB,OAAyC;AAC1E,MAAI,MAAM,QAAQ,uBAAuB,MAAM,QAAQ,2BAA2B;AAC9E,UAAM,IAAI;AAAA,MACN,4BAA4B,MAAM,GAAG;AAAA,IACzC;AAAA,EACJ;AACA,QAAM,SAAS,QAAQ,MAAM,cAAc;AAC3C,QAAM,EAAE,WAAW,KAAK,IAAI,aAAa,MAAM;AAC/C,SAAO,kBAAkB,WAAW,WAAW,OAAO,SAAS,KAAK,IAAI,CAAC;AAC7E;AAiBA,eAAsB,wBAClB,SACA,UAC8B;AAC9B,QAAM,QAAQ,MAAM,UAAU,QAAQ,OAAO,QAAQ,UAAU;AAC/D,QAAM,QAAQ,eAAe,OAAO,QAAQ;AAC5C,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,qBAAqB,KAAK;AACrC;AAEA,IAAI,QAAoB;AAepB,MAAS,cAAT,SAAqB,OAAgC;AACjD,WAAO,EAAE,OAAO,WAAW;AAAA,EAC/B,GAES,cAAT,WAKE;AAIE,UAAM,SAAS,qBAAqB,UAAU;AAC9C,UAAM,SAAS,aAAa,EAAE,MAAM,QAAQ,OAAO,SAAS,QAAQ,cAAc,CAAC;AACnF,UAAM,UAAU,OAAO,EAAE;AACzB,WAAO;AAAA,MACH;AAAA,MACA,WAAW,QAAQ;AAAA,MACnB,KAAKC,OAAM,MAAM;AAAA,MACjB,MAAM,CAAC,QAAQ,QAAQ,KAAK,GAAG;AAAA,IACnC;AAAA,EACJ;AAtBS,EAAAC,eAAA,aAIAC,eAAA;AAlBT,QAAM,EAAE,UAAU,MAAM,QAAQ,WAAW,IAAI;AAC/C,QAAM,EAAE,aAAa,OAAO,IAAI,MAAa;AAC7C,QAAM,EAAE,OAAO,IAAI,MAAa;AAChC,QAAM,EAAE,MAAM,SAAS,IAAI,MAAa;AACxC,QAAM,EAAE,WAAAC,WAAU,IAAI,MAAa;AACnC,QAAM,EAAE,YAAY,qBAAqB,IAAI,MAAa;AAC1D,QAAM,EAAE,OAAAH,OAAM,IAAI,MAAa;AAE/B,MAAI;AACJ,aAAW,MAAM;AACb,iBAAa,YAAY,SAAS,OAAO,GAAG,mBAAmB,CAAC;AAChE,WAAO,MAAM,OAAO,YAAY,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EACpE,CAAC;AA0BD,WAAS,2BAA2B,MAAM;AACtC,SAAK,6CAA6C,YAAY;AAC1D,YAAM,UAAU,YAAY,GAAG;AAC/B,YAAM,SAAS,MAAM,wBAAwB,SAAS;AAAA,QAClD,KAAK;AAAA,QACL,OAAO;AAAA,MACX,CAAC;AACD,aAAO,MAAM,EAAE,SAAS;AAAA,IAC5B,CAAC;AAED,SAAK,+EAA+E,YAAY;AAC5F,YAAM,EAAE,KAAK,UAAU,IAAI,YAAY;AACvC,YAAMG;AAAA,QACF;AAAA,QACA;AAAA,UACI,SAAS;AAAA,UACT,SAAS;AAAA,YACL,mBAAmB,EAAE,KAAK,qBAAqB,gBAAgB,IAAI;AAAA,UACvE;AAAA,QACJ;AAAA,QACA;AAAA,MACJ;AAEA,YAAM,SAAS,MAAM,wBAAwB,YAAY,GAAG,GAAG;AAAA,QAC3D,KAAK;AAAA,QACL,OAAO;AAAA,MACX,CAAC;AACD,aAAO,MAAM,EAAE,IAAI,SAAS;AAC5B,aAAO,QAAQ,SAAS,EAAE,QAAQ,SAAS;AAAA,IAC/C,CAAC;AAED,SAAK,uEAAuE,YAAY;AACpF,YAAM,EAAE,KAAK,UAAU,IAAI,YAAY;AACvC,YAAMA;AAAA,QACF;AAAA,QACA;AAAA,UACI,SAAS;AAAA,UACT,SAAS;AAAA,YACL,mBAAmB,EAAE,KAAK,qBAAqB,gBAAgB,IAAI;AAAA,UACvE;AAAA,QACJ;AAAA,QACA;AAAA,MACJ;AAEA,YAAM,SAAS,MAAM,wBAAwB,YAAY,GAAG,GAAG;AAAA,QAC3D,KAAK;AAAA,QACL,OAAO;AAAA,MACX,CAAC;AAOD,YAAM,UAAU,IAAI,YAAY,EAAE,OAAO,YAAY;AAGrD,YAAM,UAAU,IAAI,WAAW;AAAA,QAC3B,GAAG,IAAI,YAAY,EAAE,OAAO,SAAS;AAAA,QACrC,GAAG;AAAA,QACH,GAAG,IAAI,YAAY,EAAE,OAAO,UAAU;AAAA,MAC1C,CAAC;AACD,YAAM,YAAY,MAAM,OAAQ,UAAU,OAAO;AACjD,aAAO,QAAQ,OAAO,WAAW,SAAS,SAAS,CAAC,EAAE,KAAK,IAAI;AAAA,IACnE,CAAC;AAED,SAAK,wDAAwD,YAAY;AAGrE,YAAM,gBACF;AAEJ,YAAMA;AAAA,QACF;AAAA,QACA;AAAA,UACI,SAAS;AAAA,UACT,SAAS;AAAA,YACL,mBAAmB;AAAA,cACf,KAAK;AAAA,cACL,gBAAgB;AAAA,YACpB;AAAA,UACJ;AAAA,QACJ;AAAA,QACA;AAAA,MACJ;AAEA,YAAM,SAAS,MAAM,wBAAwB,YAAY,GAAG,GAAG;AAAA,QAC3D,KAAK;AAAA,QACL,OAAO;AAAA,MACX,CAAC;AACD,aAAO,MAAM,EAAE,IAAI,SAAS;AAI5B,YAAM,EAAE,WAAW,eAAe,IAAI,YAAY;AAClD,aAAO,QAAQ,SAAS,EAAE,QAAQ,cAAc;AAIhD,YAAM,UAAU,IAAI,YAAY,EAAE,OAAO,oBAAoB;AAC7D,YAAM,UAAU,IAAI,WAAW;AAAA,QAC3B,GAAG,IAAI,YAAY,EAAE,OAAO,SAAS;AAAA,QACrC,GAAG;AAAA,QACH,GAAG,IAAI,YAAY,EAAE,OAAO,UAAU;AAAA,MAC1C,CAAC;AACD,YAAM,MAAM,MAAM,OAAQ,UAAU,OAAO;AAC3C,aAAO,QAAQ,OAAO,KAAK,SAAS,cAAc,CAAC,EAAE,KAAK,IAAI;AAAA,IAClE,CAAC;AAED,SAAK,kDAAkD,YAAY;AAC/D,YAAM,EAAE,KAAK,UAAU,IAAI,YAAY;AACvC,YAAMA;AAAA,QACF;AAAA,QACA;AAAA,UACI,SAAS;AAAA,UACT,SAAS;AAAA,YACL,yBAAyB;AAAA,cACrB,KAAK;AAAA,cACL,gBAAgB;AAAA,YACpB;AAAA,UACJ;AAAA,QACJ;AAAA,QACA;AAAA,MACJ;AAEA,YAAM,SAAS,MAAM,wBAAwB,YAAY,GAAG,GAAG;AAAA,QAC3D,KAAK;AAAA,QACL,OAAO;AAAA,MACX,CAAC;AACD,aAAO,QAAQ,SAAS,EAAE,QAAQ,SAAS;AAAA,IAC/C,CAAC;AAED,SAAK,2DAA2D,YAAY;AACxE,YAAMA;AAAA,QACF;AAAA,QACA;AAAA,UACI,SAAS;AAAA,UACT,SAAS;AAAA,YACL,6BAA6B,EAAE,KAAK,0BAA0B,MAAM,EAAE;AAAA,UAC1E;AAAA,QACJ;AAAA,QACA;AAAA,MACJ;AAEA,YAAM;AAAA,QACF,wBAAwB,YAAY,GAAG,GAAG;AAAA,UACtC,KAAK;AAAA,UACL,OAAO;AAAA,QACX,CAAC;AAAA,MACL,EAAE,QAAQ,QAAQ,0DAA0D;AAAA,IAChF,CAAC;AAED,SAAK,0DAA0D,YAAY;AACvE,YAAMA;AAAA,QACF;AAAA,QACA;AAAA,UACI,SAAS;AAAA,UACT,SAAS;AAAA,YACL,aAAa;AAAA,cACT,KAAK;AAAA,cACL,yBAAyB;AAAA,cACzB,uBAAuB;AAAA,YAC3B;AAAA,UACJ;AAAA,QACJ;AAAA,QACA;AAAA,MACJ;AAEA,YAAM;AAAA,QACF,wBAAwB,YAAY,GAAG,GAAG;AAAA,UACtC,KAAK;AAAA,UACL,OAAO;AAAA,QACX,CAAC;AAAA,MACL,EAAE,QAAQ,QAAQ,+CAA+C;AAAA,IACrE,CAAC;AAED,SAAK,iEAAiE,YAAY;AAC9E,YAAM,IAAI,YAAY;AAEtB,YAAM,kBAAkB,IAAI,WAAW,EAAE,EAAE,KAAK,CAAI;AACpD,YAAM,eAAe,aAAa;AAAA,QAC9B,MAAM;AAAA,QACN,OAAO;AAAA,QACP,QAAQ;AAAA,MACZ,CAAC,EAAE,EAAE;AAEL,YAAMA;AAAA,QACF;AAAA,QACA;AAAA,UACI,SAAS;AAAA,UACT,SAAS;AAAA,YACL,mBAAmB,EAAE,KAAK,qBAAqB,gBAAgB,EAAE,IAAI;AAAA,UACzE;AAAA,QACJ;AAAA,QACA;AAAA,MACJ;AACA,YAAMA;AAAA,QACF;AAAA,QACA;AAAA,UACI,SAAS;AAAA,UACT,SAAS;AAAA,YACL,mBAAmB;AAAA,cACf,KAAK;AAAA,cACL,gBAAgBH,OAAM,eAAe;AAAA,YACzC;AAAA,UACJ;AAAA,QACJ;AAAA,QACA;AAAA,MACJ;AAEA,YAAM,UAAU,MAAM,wBAAwB,YAAY,OAAO,GAAG;AAAA,QAChE,KAAK;AAAA,QACL,OAAO;AAAA,MACX,CAAC;AACD,YAAM,UAAU,MAAM,wBAAwB,YAAY,OAAO,GAAG;AAAA,QAChE,KAAK;AAAA,QACL,OAAO;AAAA,MACX,CAAC;AACD,aAAO,SAAS,SAAS,EAAE,QAAQ,EAAE,SAAS;AAC9C,aAAO,SAAS,SAAS,EAAE,QAAQ,aAAa,SAAS;AACzD,aAAO,SAAS,SAAS,EAAE,IAAI,QAAQ,SAAS,SAAS;AAAA,IAC7D,CAAC;AAED,SAAK,+CAA+C,YAAY;AAG5D,YAAMG;AAAA,QACF;AAAA,QACA;AAAA,UACI,SAAS;AAAA,UACT,SAAS;AAAA,YACL,mBAAmB;AAAA,cACf,KAAK;AAAA,cACL,gBAAgBH,OAAM,IAAI,WAAW,EAAE,EAAE,KAAK,CAAC,CAAC;AAAA,YACpD;AAAA,UACJ;AAAA,QACJ;AAAA,QACA;AAAA,MACJ;AAEA,YAAM;AAAA,QACF,wBAAwB,YAAY,GAAG,GAAG;AAAA,UACtC,KAAK;AAAA,UACL,OAAO;AAAA,QACX,CAAC;AAAA,MACL,EAAE,QAAQ,QAAQ,qCAAqC;AAAA,IAC3D,CAAC;AAAA,EACL,CAAC;AACL;AAhRa,IAAAC;AAIA,IAAAC;;;AF5Eb,IAAME,OAAMC,cAAa,UAAU;AA6DnC,eAAsB,0BAClB,SACA,SACA,WACA,UAA4C,CAAC,GACf;AAC9B,SAAO,cAAc,QAAQ,OAAO,QAAQ,YAAY,YAAY;AAChE,UAAM,QAAQ,MAAM,UAAU,QAAQ,OAAO,QAAQ,UAAU;AAC/D,UAAM,aAAa,QAAQ,cAAc,qBAAqB,OAAO,SAAS;AAC9E,IAAAC,KAAI,MAAM,6BAA6B;AAAA,MACnC,WAAW,QAAQ;AAAA,MACnB,WAAW,UAAU,IAAI,CAAC,MAAM,EAAE,GAAG;AAAA,MACrC;AAAA,MACA,YAAY,QAAQ,eAAe;AAAA,IACvC,CAAC;AAED,UAAM,SAAS,MAAM,QAAQ,0BAA0B;AAAA,MACnD,kBAAkB,QAAQ;AAAA,MAC1B;AAAA,MACA;AAAA,IACJ,CAAC;AAED,QAAI,OAAO,MAAM,GAAG;AAChB,YAAM,IAAI,MAAM,qCAAqC,OAAO,MAAM,OAAO,EAAE;AAAA,IAC/E;AAEA,UAAM,OAAO,cAAc,OAAO,WAAW,OAAO,KAAK;AACzD,QAAI,SAAS,OAAO;AAChB,YAAM,UAAU,QAAQ,OAAO,MAAM,QAAQ,UAAU;AAAA,IAC3D;AAEA,WAAO,OAAO;AAAA,EAClB,CAAC;AACL;AAQA,eAAsB,oBAClB,SACA,UACgC;AAChC,QAAM,QAAQ,MAAM,UAAU,QAAQ,OAAO,QAAQ,UAAU;AAC/D,SAAO,eAAe,OAAO,QAAQ;AACzC;AAeA,eAAsB,wBAClB,SACA,SACA,UACuB;AAKvB,SAAO,cAAc,QAAQ,OAAO,QAAQ,YAAY,YAAY;AAKhE,UAAM,QAAQ,MAAM,UAAU,QAAQ,OAAO,QAAQ,UAAU;AAC/D,UAAM,MAAM,eAAe,OAAO,QAAQ;AAC1C,QAAI,IAAK,QAAO,qBAAqB,GAAG;AAExC,IAAAA,KAAI,MAAM,mDAAmD,EAAE,UAAU,SAAS,IAAI,CAAC;AAMvF,UAAM,aAAa,qBAAqB,OAAO,CAAC,QAAQ,CAAC;AACzD,UAAM,SAAS,MAAM,QAAQ,0BAA0B;AAAA,MACnD,kBAAkB,QAAQ;AAAA,MAC1B,WAAW,CAAC,QAAQ;AAAA,MACpB;AAAA,IACJ,CAAC;AACD,QAAI,OAAO,MAAM,GAAG;AAChB,YAAM,IAAI,MAAM,qCAAqC,OAAO,MAAM,OAAO,EAAE;AAAA,IAC/E;AACA,UAAM,WAAW,OAAO;AACxB,UAAM,OAAO,cAAc,OAAO,CAAC,QAAQ,GAAG,QAAQ;AACtD,QAAI,SAAS,OAAO;AAChB,YAAM,UAAU,QAAQ,OAAO,MAAM,QAAQ,UAAU;AAAA,IAC3D;AAEA,UAAM,UAAU,SAAS,CAAC;AAC1B,QAAI,CAAC,SAAS;AAGV,YAAM,IAAI;AAAA,QACN,yEAAoE,SAAS,GAAG;AAAA,MACpF;AAAA,IACJ;AACA,QAAI,QAAQ,QAAQ,aAAa;AAC7B,YAAM,IAAI;AAAA,QACN,uCAAuC,QAAQ,GAAG,QAAQ,SAAS,GAAG;AAAA,MAC1E;AAAA,IACJ;AAIA,UAAM,aAAa,eAAe,MAAM,QAAQ;AAChD,QAAI,CAAC,YAAY;AAGb,YAAM,IAAI;AAAA,QACN,oFAAoF,SAAS,GAAG;AAAA,MACpG;AAAA,IACJ;AACA,WAAO,qBAAqB,UAAU;AAAA,EAC1C,CAAC;AACL;AAEA,IAAI,QAAoB;AAqBpB,MAAS,cAAT,SAAqB,OAAgC;AAEjD,WAAO,EAAE,OAAO,YAAY,eAAe;AAAA,EAC/C,GAKS,cAAT,SAAqB,MAEL;AACZ,WAAO;AAAA,MACH,2BAA2B,GAAG;AAAA,QAC1B,KAAK,8BACA,YAAY;AACT,gBAAM,IAAI,MAAM,oDAAoD;AAAA,QACxE;AAAA,MACR;AAAA,IACJ;AAAA,EACJ;AAnBS,EAAAC,eAAA,aAQAC,eAAA;AA5BT,QAAM,EAAE,UAAU,MAAM,QAAQ,IAAI,WAAW,IAAI;AACnD,QAAM,EAAE,IAAI,IAAI,IAAI,MAAa;AACjC,QAAM,EAAE,aAAa,OAAO,IAAI,MAAa;AAC7C,QAAM,EAAE,OAAO,IAAI,MAAa;AAChC,QAAM,EAAE,MAAM,SAAS,IAAI,MAAa;AAIxC,QAAM,EAAE,sBAAsB,WAAW,IAAI,MAAa;AAC1D,QAAM,EAAE,OAAAC,OAAM,IAAI,MAAa;AAE/B,MAAI;AACJ,aAAW,MAAM;AAIb,qBAAiB,YAAY,SAAS,OAAO,GAAG,YAAY,CAAC;AAC7D,WAAO,MAAM,OAAO,gBAAgB,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EACxE,CAAC;AAuBD,WAAS,6BAA6B,MAAM;AACxC,SAAK,6DAA6D,YAAY;AAC1E,YAAM,WAA6B,CAAC;AACpC,YAAM,UAAU;AAAA,QACZ;AAAA,UACI,KAAK;AAAA,UACL,OAAO;AAAA,YACH,KAAK;AAAA,YACL,OAAO,EAAE,gBAAgB,IAAI,WAAW,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE;AAAA,UACvD;AAAA,QACJ;AAAA,QACA,EAAE,KAAK,YAAqB,OAAO,OAAU;AAAA,MACjD;AACA,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,OAAO,QAAQ;AACtC,mBAAS,KAAK,GAAG;AACjB,iBAAO,GAAG,OAAO;AAAA,QACrB;AAAA,MACJ,CAAC;AAED,YAAM,WAAW,MAAM,0BAA0B,SAAS,YAAY,QAAQ,GAAG;AAAA,QAC7E,EAAE,KAAK,qBAAqB,OAAO,OAAU;AAAA,QAC7C,EAAE,KAAK,2BAA2B,OAAO,OAAU;AAAA,MACvD,CAAC;AAED,aAAO,QAAQ,EAAE,aAAa,CAAC;AAC/B,aAAO,SAAS,CAAC,EAAE,gBAAgB,EAAE,KAAK,QAAQ;AAClD,aAAO,SAAS,CAAC,EAAE,UAAU,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ;AAAA,QACpD;AAAA,QACA;AAAA,MACJ,CAAC;AACD,aAAO,QAAQ,EAAE,QAAQ,OAAO;AAAA,IACpC,CAAC;AAED,SAAK,0CAA0C,YAAY;AACvD,YAAM,WAA6B,CAAC;AACpC,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,OAAO,QAAQ;AACtC,mBAAS,KAAK,GAAG;AACjB,iBAAO,GAAG,CAAC,CAAC;AAAA,QAChB;AAAA,MACJ,CAAC;AAED,YAAM,0BAA0B,SAAS,YAAY,iBAAiB,GAAG,CAAC,CAAC;AAE3E,aAAO,SAAS,CAAC,EAAE,gBAAgB,EAAE,KAAK,iBAAiB;AAAA,IAC/D,CAAC;AAED,SAAK,kDAAkD,YAAY;AAC/D,YAAM,WAA6B,CAAC;AACpC,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,OAAO,QAAQ;AACtC,mBAAS,KAAK,GAAG;AACjB,iBAAO,GAAG,CAAC,CAAC;AAAA,QAChB;AAAA,MACJ,CAAC;AAED,YAAM,0BAA0B,SAAS,YAAY,GAAG,GAAG,CAAC,CAAC;AAE7D,aAAO,SAAS,CAAC,EAAE,UAAU,EAAE,KAAK,QAAQ;AAAA,IAChD,CAAC;AAED,SAAK,yDAAyD,YAAY;AACtE,YAAM,WAA6B,CAAC;AACpC,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,OAAO,QAAQ;AACtC,mBAAS,KAAK,GAAG;AACjB,iBAAO,GAAG,CAAC,EAAE,KAAK,YAAqB,OAAO,OAAU,CAAC,CAAC;AAAA,QAC9D;AAAA,MACJ,CAAC;AAED,YAAM;AAAA,QACF;AAAA,QACA,YAAY,GAAG;AAAA,QACf,CAAC,EAAE,KAAK,qBAAqB,OAAO,OAAU,CAAC;AAAA,QAC/C,EAAE,YAAY,WAAW;AAAA,MAC7B;AAEA,aAAO,SAAS,CAAC,EAAE,UAAU,EAAE,KAAK,UAAU;AAAA,IAClD,CAAC;AAED,SAAK,oDAAoD,YAAY;AACjE,YAAM,WAA6B,CAAC;AACpC,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,OAAO,QAAQ;AACtC,mBAAS,KAAK,GAAG;AACjB,iBAAO,GAAG,CAAC,EAAE,KAAK,YAAqB,OAAO,OAAU,CAAC,CAAC;AAAA,QAC9D;AAAA,MACJ,CAAC;AAED,YAAM,0BAA0B,SAAS,YAAY,GAAG,GAAG;AAAA,QACvD,EAAE,KAAK,0BAA0B,OAAO,EAAE;AAAA,MAC9C,CAAC;AAED,YAAM,OAAO,SAAS,CAAC,EAAE,UAAU,CAAC;AACpC,aAAO,KAAK,GAAG,EAAE,KAAK,wBAAwB;AAC9C,UAAI,KAAK,QAAQ,0BAA0B;AACvC,eAAO,KAAK,KAAK,EAAE,KAAK,CAAC;AAAA,MAC7B;AAAA,IACJ,CAAC;AAED,SAAK,oDAAoD,YAAY;AACjE,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,YACvB,IAAI,IAAI,MAAM,qBAAqB,CAAC;AAAA,MAC5C,CAAC;AAED,YAAM;AAAA,QACF,0BAA0B,SAAS,YAAY,GAAG,GAAG;AAAA,UACjD,EAAE,KAAK,qBAAqB,OAAO,OAAU;AAAA,QACjD,CAAC;AAAA,MACL,EAAE,QAAQ,QAAQ,uDAAuD;AAAA,IAC7E,CAAC;AAED,SAAK,gFAAgF,YAAY;AAQ7F,YAAM,WAA6B,CAAC;AACpC,YAAM,UAAU,CAAC,EAAE,KAAK,gBAAyB,OAAO,OAAU,CAAC;AACnE,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,OAAO,QAAQ;AACtC,mBAAS,KAAK,GAAG;AACjB,iBAAO,GAAG,OAAO;AAAA,QACrB;AAAA,MACJ,CAAC;AAED,YAAM,WAAW,MAAM,0BAA0B,SAAS,YAAY,GAAG,GAAG;AAAA,QACxE,EAAE,KAAK,eAAe,OAAO,OAAU;AAAA,MAC3C,CAAC;AAED,YAAM,OAAO,SAAS,CAAC,EAAE,UAAU,CAAC;AACpC,aAAO,KAAK,GAAG,EAAE,KAAK,aAAa;AACnC,aAAO,QAAQ,EAAE,QAAQ,OAAO;AAAA,IACpC,CAAC;AAED,SAAK,sEAAsE,YAAY;AAInF,YAAM,WAA6B,CAAC;AACpC,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,OAAO,QAAQ;AACtC,mBAAS,KAAK,GAAG;AACjB,iBAAO,GAAG,CAAC,CAAC;AAAA,QAChB;AAAA,MACJ,CAAC;AAED,YAAM,WAAW,MAAM,0BAA0B,SAAS,YAAY,GAAG,GAAG,CAAC,CAAC;AAE9E,aAAO,QAAQ,EAAE,aAAa,CAAC;AAC/B,aAAO,SAAS,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AACxC,aAAO,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAAA,IAC/B,CAAC;AAAA,EACL,CAAC;AAED,WAAS,sDAAiD,MAAM;AAC5D,aAAS,kBAAkB,SAAiC;AACxD,aAAO,GAAG;AAAA,QACN;AAAA,UACI,KAAK;AAAA,UACL,OAAO;AAAA,YACH,KAAK;AAAA,YACL,OAAO,EAAE,gBAAgB,QAAQ;AAAA,UACrC;AAAA,QACJ;AAAA,MACJ,CAAC;AAAA,IACL;AAEA,SAAK,+CAA+C,YAAY;AAC5D,YAAM,WAA6B,CAAC;AACpC,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,OAAO,QAAQ;AACtC,mBAAS,KAAK,GAAG;AACjB,iBAAO,kBAAkB,IAAI,WAAW,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;AAAA,QACtD;AAAA,MACJ,CAAC;AAED,YAAM,0BAA0B,SAAS,YAAY,GAAG,GAAG;AAAA,QACvD,EAAE,KAAK,qBAAqB,OAAO,OAAU;AAAA,MACjD,CAAC;AAED,aAAO,SAAS,CAAC,EAAE,UAAU,EAAE,KAAK,QAAQ;AAAA,IAChD,CAAC;AAED,SAAK,gEAAgE,YAAY;AAC7E,YAAM,WAA6B,CAAC;AACpC,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,OAAOC,SAAQ;AACtC,mBAAS,KAAKA,IAAG;AACjB,iBAAO,kBAAkB,IAAI,WAAW,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;AAAA,QACtD;AAAA,MACJ,CAAC;AAED,YAAM,UAAU,YAAY,GAAG;AAC/B,YAAM,MAA6B,CAAC,EAAE,KAAK,qBAAqB,OAAO,OAAU,CAAC;AAElF,YAAM,0BAA0B,SAAS,SAAS,GAAG;AACrD,YAAM,0BAA0B,SAAS,SAAS,GAAG;AAErD,aAAO,SAAS,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,QAAQ,CAAC,UAAU,UAAU,CAAC;AAAA,IAC5E,CAAC;AAED,SAAK,uDAAuD,YAAY;AACpE,YAAM,WAA6B,CAAC;AACpC,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,OAAOA,SAAQ;AACtC,mBAAS,KAAKA,IAAG;AACjB,iBAAO,kBAAkB,IAAI,WAAW,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;AAAA,QACtD;AAAA,MACJ,CAAC;AAED,YAAM,UAAU,YAAY,GAAG;AAC/B,YAAM,MAA6B,CAAC,EAAE,KAAK,qBAAqB,OAAO,OAAU,CAAC;AAMlF,YAAM,0BAA0B,SAAS,SAAS,GAAG;AACrD,YAAM,0BAA0B,SAAS,SAAS,KAAK,EAAE,YAAY,SAAS,CAAC;AAE/E,aAAO,SAAS,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,QAAQ,CAAC,UAAU,QAAQ,CAAC;AAAA,IAC1E,CAAC;AAED,SAAK,mEAAmE,YAAY;AAChF,YAAM,UAAU,IAAI,WAAW,CAAC,KAAM,KAAM,KAAM,GAAI,CAAC;AACvD,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,YAAY,kBAAkB,OAAO;AAAA,MACpE,CAAC;AAED,YAAM,UAAU,YAAY,GAAG;AAC/B,YAAM,0BAA0B,SAAS,SAAS;AAAA,QAC9C,EAAE,KAAK,qBAAqB,OAAO,OAAU;AAAA,MACjD,CAAC;AAGD,YAAM,SAAS,MAAM,oBAAoB,SAAS;AAAA,QAC9C,KAAK;AAAA,QACL,OAAO;AAAA,MACX,CAAC;AACD,aAAO,MAAM,EAAE,QAAQ;AAAA,QACnB,KAAK;AAAA,QACL,gBAAgB;AAAA,MACpB,CAAC;AAAA,IACL,CAAC;AAED,SAAK,oDAAoD,YAAY;AACjE,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,YACvB,GAAG;AAAA,UACC,EAAE,KAAK,YAAqB,OAAO,OAAU;AAAA,UAC7C,EAAE,KAAK,gBAAyB,OAAO,OAAU;AAAA,QACrD,CAAC;AAAA,MACT,CAAC;AAED,YAAM,UAAU,YAAY,GAAG;AAC/B,YAAM,0BAA0B,SAAS,SAAS;AAAA,QAC9C,EAAE,KAAK,qBAAqB,OAAO,OAAU;AAAA,QAC7C,EAAE,KAAK,2BAA2B,OAAO,OAAU;AAAA,MACvD,CAAC;AAED;AAAA,QACI,MAAM,oBAAoB,SAAS,EAAE,KAAK,qBAAqB,OAAO,OAAU,CAAC;AAAA,MACrF,EAAE,SAAS;AACX;AAAA,QACI,MAAM,oBAAoB,SAAS;AAAA,UAC/B,KAAK;AAAA,UACL,OAAO;AAAA,QACX,CAAC;AAAA,MACL,EAAE,SAAS;AAAA,IACf,CAAC;AAED,SAAK,kEAAkE,YAAY;AAK/E,YAAM,WAA6B,CAAC;AACpC,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,OAAO,QAAQ;AACtC,mBAAS,KAAK,GAAG;AAEjB,iBAAO;AAAA,YACH,IAAI,UAAU;AAAA,cAAI,CAAC,IAAI,MACnB,MAAM,IACA;AAAA,gBACI,KAAK;AAAA,gBACL,OAAO;AAAA,kBACH,KAAK;AAAA,kBACL,OAAO,EAAE,gBAAgB,IAAI,WAAW,CAAC,CAAC,CAAC,EAAE;AAAA,gBACjD;AAAA,cACJ,IACA,EAAE,KAAK,YAAqB,OAAO,OAAU;AAAA,YACvD;AAAA,UACJ;AAAA,QACJ;AAAA,MACJ,CAAC;AAED,YAAM,UAAU,YAAY,GAAG;AAE/B,YAAM,0BAA0B,SAAS,SAAS;AAAA,QAC9C,EAAE,KAAK,qBAAqB,OAAO,OAAU;AAAA,MACjD,CAAC;AAGD,YAAM,0BAA0B,SAAS,SAAS;AAAA,QAC9C,EAAE,KAAK,qBAAqB,OAAO,OAAU;AAAA,QAC7C,EAAE,KAAK,2BAA2B,OAAO,OAAU;AAAA,MACvD,CAAC;AAED,aAAO,SAAS,SAAS,SAAS,CAAC,EAAE,UAAU,EAAE,KAAK,QAAQ;AAAA,IAClE,CAAC;AAED,SAAK,qDAAqD,YAAY;AAElE,YAAM,WAA6B,CAAC;AACpC,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,OAAOA,SAAQ;AACtC,mBAAS,KAAKA,IAAG;AACjB,iBAAO,GAAG;AAAA,YACN;AAAA,cACI,KAAK;AAAA,cACL,OAAO;AAAA,gBACH,KAAK;AAAA,gBACL,OAAO;AAAA,kBACH,yBAAyB;AAAA,kBACzB,uBAAuB,IAAI,WAAW,CAAC,GAAI,CAAC;AAAA,gBAChD;AAAA,cACJ;AAAA,YACJ;AAAA,UACJ,CAAC;AAAA,QACL;AAAA,MACJ,CAAC;AAED,YAAM,UAAU,YAAY,GAAG;AAC/B,YAAM,MAA6B,CAAC,EAAE,KAAK,eAAe,OAAO,OAAU,CAAC;AAE5E,YAAM,0BAA0B,SAAS,SAAS,GAAG;AACrD,YAAM,0BAA0B,SAAS,SAAS,GAAG;AAErD,aAAO,SAAS,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,QAAQ,CAAC,UAAU,QAAQ,CAAC;AAAA,IAC1E,CAAC;AAED,SAAK,wFAAwF,YAAY;AAKrG,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,OAAO,QAAQ;AAEtC,gBAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AACzC,gBAAM,MAAM,IAAI,UAAU,CAAC,GAAG;AAC9B,iBAAO,GAAG;AAAA,YACN;AAAA,cACI,KAAK;AAAA,cACL,OACI,QAAQ,sBACF;AAAA,gBACI,KAAK;AAAA,gBACL,OAAO,EAAE,gBAAgB,IAAI,WAAW,CAAC,GAAI,CAAC,EAAE;AAAA,cACpD,IACA;AAAA,gBACI,KAAK;AAAA,gBACL,OAAO,EAAE,gBAAgB,IAAI,WAAW,CAAC,GAAI,CAAC,EAAE;AAAA,cACpD;AAAA,YACd;AAAA,UACJ,CAAC;AAAA,QACL;AAAA,MACJ,CAAC;AACD,YAAM,UAAU,YAAY,GAAG;AAE/B,YAAM,QAAQ,IAAI;AAAA,QACd,0BAA0B,SAAS,SAAS;AAAA,UACxC,EAAE,KAAK,qBAAqB,OAAO,OAAU;AAAA,QACjD,CAAC;AAAA,QACD,0BAA0B,SAAS,SAAS;AAAA,UACxC,EAAE,KAAK,2BAA2B,OAAO,OAAU;AAAA,QACvD,CAAC;AAAA,MACL,CAAC;AAED;AAAA,QACI,MAAM,oBAAoB,SAAS;AAAA,UAC/B,KAAK;AAAA,UACL,OAAO;AAAA,QACX,CAAC;AAAA,MACL,EAAE,IAAI,SAAS;AACf;AAAA,QACI,MAAM,oBAAoB,SAAS;AAAA,UAC/B,KAAK;AAAA,UACL,OAAO;AAAA,QACX,CAAC;AAAA,MACL,EAAE,IAAI,SAAS;AAAA,IACnB,CAAC;AAED,SAAK,iEAAiE,YAAY;AAC9E,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,YAAY,kBAAkB,IAAI,WAAW,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;AAAA,MACtF,CAAC;AAED,YAAM,WAAW,YAAY,OAAO;AACpC,YAAM,WAAW,YAAY,OAAO;AAEpC,YAAM,0BAA0B,SAAS,UAAU;AAAA,QAC/C,EAAE,KAAK,qBAAqB,OAAO,OAAU;AAAA,MACjD,CAAC;AAGD;AAAA,QACI,MAAM,oBAAoB,UAAU;AAAA,UAChC,KAAK;AAAA,UACL,OAAO;AAAA,QACX,CAAC;AAAA,MACL,EAAE,SAAS;AACX;AAAA,QACI,MAAM,oBAAoB,UAAU;AAAA,UAChC,KAAK;AAAA,UACL,OAAO;AAAA,QACX,CAAC;AAAA,MACL,EAAE,IAAI,SAAS;AAAA,IACnB,CAAC;AAAA,EACL,CAAC;AAED,WAAS,uBAAuB,MAAM;AAClC,SAAK,uCAAuC,YAAY;AACpD,YAAM,UAAU,YAAY,GAAG;AAC/B,YAAM,SAAS,MAAM,oBAAoB,SAAS;AAAA,QAC9C,KAAK;AAAA,QACL,OAAO;AAAA,MACX,CAAC;AACD,aAAO,MAAM,EAAE,SAAS;AAAA,IAC5B,CAAC;AAED,SAAK,wDAAwD,YAAY;AACrE,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,YACvB,GAAG;AAAA,UACC;AAAA,YACI,KAAK;AAAA,YACL,OAAO;AAAA,cACH,KAAK;AAAA,cACL,OAAO;AAAA,YACX;AAAA,UACJ;AAAA,QACJ,CAAC;AAAA,MACT,CAAC;AACD,YAAM,UAAU,YAAY,GAAG;AAC/B,YAAM,0BAA0B,SAAS,SAAS;AAAA,QAC9C,EAAE,KAAK,0BAA0B,OAAO,EAAE;AAAA,MAC9C,CAAC;AAED;AAAA,QACI,MAAM,oBAAoB,SAAS,EAAE,KAAK,0BAA0B,OAAO,EAAE,CAAC;AAAA,MAClF,EAAE,QAAQ,EAAE,KAAK,0BAA0B,MAAM,EAAE,CAAC;AAEpD;AAAA,QACI,MAAM,oBAAoB,SAAS,EAAE,KAAK,0BAA0B,OAAO,EAAE,CAAC;AAAA,MAClF,EAAE,SAAS;AAAA,IACf,CAAC;AAAA,EACL,CAAC;AAED,WAAS,2BAA2B,MAAM;AAEtC,UAAM,UAAU,qBAAqB,UAAU;AAE/C,aAAS,4BAA4B;AACjC,aAAO,GAAG;AAAA,QACN;AAAA,UACI,KAAK;AAAA,UACL,OAAO;AAAA,YACH,KAAK;AAAA,YACL,OAAO,EAAE,gBAAgB,QAAQ;AAAA,UACrC;AAAA,QACJ;AAAA,MACJ,CAAC;AAAA,IACL;AAEA,SAAK,2DAA2D,YAAY;AACxE,YAAM,WAA6B,CAAC;AACpC,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,OAAO,QAAQ;AACtC,mBAAS,KAAK,GAAG;AACjB,iBAAO,0BAA0B;AAAA,QACrC;AAAA,MACJ,CAAC;AACD,YAAM,UAAU,YAAY,GAAG;AAG/B,YAAM,0BAA0B,SAAS,SAAS;AAAA,QAC9C,EAAE,KAAK,qBAAqB,OAAO,OAAU;AAAA,MACjD,CAAC;AACD,aAAO,QAAQ,EAAE,aAAa,CAAC;AAI/B,YAAM,SAAS,MAAM,wBAAwB,SAAS,SAAS;AAAA,QAC3D,KAAK;AAAA,QACL,OAAO;AAAA,MACX,CAAC;AACD,aAAO,OAAO,SAAS,EAAE,eAAe,UAAU;AAClD,aAAO,QAAQ,EAAE,aAAa,CAAC;AAAA,IACnC,CAAC;AAED,SAAK,6DAA6D,YAAY;AAC1E,YAAM,WAA6B,CAAC;AACpC,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,OAAO,QAAQ;AACtC,mBAAS,KAAK,GAAG;AACjB,iBAAO,0BAA0B;AAAA,QACrC;AAAA,MACJ,CAAC;AACD,YAAM,UAAU,YAAY,GAAG;AAG/B,YAAM,SAAS,MAAM,wBAAwB,SAAS,SAAS;AAAA,QAC3D,KAAK;AAAA,QACL,OAAO;AAAA,MACX,CAAC;AACD,aAAO,OAAO,SAAS,EAAE,eAAe,UAAU;AAClD,aAAO,QAAQ,EAAE,aAAa,CAAC;AAE/B,aAAO,SAAS,CAAC,EAAE,UAAU,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,CAAC,mBAAmB,CAAC;AAAA,IACjF,CAAC;AAED,SAAK,mEAA8D,YAAY;AAC3E,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,YACvB,GAAG,CAAC,EAAE,KAAK,YAAqB,OAAO,OAAU,CAAC,CAAC;AAAA,MAC3D,CAAC;AAED,YAAM;AAAA,QACF,wBAAwB,SAAS,YAAY,GAAG,GAAG;AAAA,UAC/C,KAAK;AAAA,UACL,OAAO;AAAA,QACX,CAAC;AAAA,MACL,EAAE,QAAQ,QAAQ,2CAA2C;AAAA,IACjE,CAAC;AAED,SAAK,0EAA0E,YAAY;AAGvF,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,YAAY,GAAG,CAAC,CAAC;AAAA,MAChD,CAAC;AAED,YAAM;AAAA,QACF,wBAAwB,SAAS,YAAY,GAAG,GAAG;AAAA,UAC/C,KAAK;AAAA,UACL,OAAO;AAAA,QACX,CAAC;AAAA,MACL,EAAE,QAAQ,QAAQ,sCAAsC;AAAA,IAC5D,CAAC;AAED,SAAK,4CAA4C,YAAY;AACzD,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,YACvB,GAAG,CAAC,EAAE,KAAK,gBAAyB,OAAO,OAAU,CAAC,CAAC;AAAA,MAC/D,CAAC;AAED,YAAM;AAAA,QACF,wBAAwB,SAAS,YAAY,GAAG,GAAG;AAAA,UAC/C,KAAK;AAAA,UACL,OAAO;AAAA,QACX,CAAC;AAAA,MACL,EAAE,QAAQ,QAAQ,qDAAqD;AAAA,IAC3E,CAAC;AAED,SAAK,kEAAkE,YAAY;AAC/E,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,YACvB,IAAI,IAAI,MAAM,kBAAkB,CAAC;AAAA,MACzC,CAAC;AAED,YAAM;AAAA,QACF,wBAAwB,SAAS,YAAY,GAAG,GAAG;AAAA,UAC/C,KAAK;AAAA,UACL,OAAO;AAAA,QACX,CAAC;AAAA,MACL,EAAE,QAAQ,QAAQ,kBAAkB;AAAA,IACxC,CAAC;AAED,SAAK,gDAAgD,YAAY;AAC7D,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,YAAY,0BAA0B;AAAA,MACrE,CAAC;AACD,YAAM,UAAU,YAAY,GAAG;AAE/B,YAAM,wBAAwB,SAAS,SAAS;AAAA,QAC5C,KAAK;AAAA,QACL,OAAO;AAAA,MACX,CAAC;AAGD,YAAM,SAAS,MAAM,oBAAoB,SAAS;AAAA,QAC9C,KAAK;AAAA,QACL,OAAO;AAAA,MACX,CAAC;AACD,aAAO,QAAQ,GAAG,EAAE,KAAK,mBAAmB;AAAA,IAChD,CAAC;AAED,SAAK,gFAAgF,YAAY;AAM7F,YAAM,WAA6B,CAAC;AACpC,YAAM,UAAU,YAAY;AAAA,QACxB,2BAA2B,OAAO,QAAQ;AACtC,mBAAS,KAAK,GAAG;AAGjB,gBAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AACzC,iBAAO,0BAA0B;AAAA,QACrC;AAAA,MACJ,CAAC;AACD,YAAM,UAAU,YAAY,GAAG;AAE/B,YAAM,CAAC,SAAS,OAAO,IAAI,MAAM,QAAQ,IAAI;AAAA,QACzC,wBAAwB,SAAS,SAAS;AAAA,UACtC,KAAK;AAAA,UACL,OAAO;AAAA,QACX,CAAC;AAAA,QACD,wBAAwB,SAAS,SAAS;AAAA,UACtC,KAAK;AAAA,UACL,OAAO;AAAA,QACX,CAAC;AAAA,MACL,CAAC;AAID,aAAO,QAAQ,EAAE,aAAa,CAAC;AAE/B,aAAO,QAAQ,SAAS,EAAE,eAAe,UAAU;AACnD,aAAO,QAAQ,SAAS,EAAE,eAAe,UAAU;AACnD,aAAOD,OAAM,QAAQ,SAAS,CAAC,EAAE,KAAKA,OAAM,QAAQ,SAAS,CAAC;AAAA,IAClE,CAAC;AAAA,EACL,CAAC;AACL;AAxpBa,IAAAF;AAQA,IAAAC;","names":["createLogger","toHex","fakeAdapter","knownSecret","saveCache","log","createLogger","log","fakeAdapter","makeSession","toHex","req"]}
|
|
1
|
+
{"version":3,"sources":["../src/host.ts","../src/host-cache.ts","../src/host-signer.ts"],"sourcesContent":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Host-runner facet of `@parity/product-sdk-terminal`. A CLI using this\n * package plays the Host role per RFC-10; this module implements the\n * three §Stakeholders Host responsibilities (AP client, cache, signer)\n * over `@novasamatech/host-papp@0.7.7`'s `UserSession`.\n *\n * @module\n */\nimport type { UserSession } from \"@novasamatech/host-papp\";\nimport { createLogger } from \"@parity/product-sdk-logger\";\nimport type { ResultAsync } from \"neverthrow\";\nimport type { PolkadotSigner } from \"polkadot-api\";\n\nimport type { TerminalAdapter } from \"./adapter.js\";\nimport {\n type CachedAllocation,\n loadCache,\n mergeOutcomes,\n pickOnExistingPolicy,\n readCacheEntry,\n saveCache,\n withCacheLock,\n} from \"./host-cache.js\";\nimport { buildSignerFromEntry, createSlotAccountSigner } from \"./host-signer.js\";\n\n// `terminal` namespace matches adapter.ts / node-storage.ts.\nconst log = createLogger(\"terminal\");\n\n// Public re-exports — internal helpers (loadCache, mergeOutcomes, etc.)\n// stay private. CachedAllocation must be exported because it's the return\n// type of getCachedAllocation.\nexport type { CachedAllocation };\nexport { createSlotAccountSigner };\n\n// Types derived from `UserSession['requestResourceAllocation']` so upstream\n// codec changes surface as compile errors here, not runtime decode failures.\n// host-papp doesn't re-export these codec types from its root.\ntype ResourceAllocationRequest = Parameters<UserSession[\"requestResourceAllocation\"]>[0];\n\n/**\n * One resource a Host can request from the Account Holder. AutoSigning\n * currently returns `NotAvailable` on both Android and iOS wallets.\n */\nexport type AllocatableResource = ResourceAllocationRequest[\"resources\"][number];\n\n/**\n * `\"Ignore\"`: return existing keys if any, else allocate one slot.\n * `\"Increase\"`: add one slot to an existing allowance account.\n */\nexport type OnExistingAllowancePolicy = ResourceAllocationRequest[\"onExisting\"];\n\ntype ExtractOk<R> = R extends ResultAsync<infer T, unknown> ? T : never;\n\n/**\n * Per-resource outcome. `Allocated.value` carries the materialized payload\n * (slot account key for Bulletin/SSS; subtree key + secret for AutoSigning;\n * undefined for SC). Each entry is independent — no rollback on partial\n * success.\n */\nexport type ApAllocationOutcome = ExtractOk<\n ReturnType<UserSession[\"requestResourceAllocation\"]>\n>[number];\n\nexport interface RequestResourceAllocationOptions {\n /**\n * Override the auto-picked `onExisting`. Default: `Ignore` unless every\n * requested resource is a cached slot-table variant, then `Increase`.\n */\n onExisting?: OnExistingAllowancePolicy;\n}\n\n/**\n * Send the AP `request_resource_allocation` message over the paired\n * session; block on the user's mobile dialog; return outcomes in request\n * order. Granted key material is cached on disk so subsequent calls skip\n * the wallet prompt.\n *\n * @throws If the session call fails (transport, timeout, AH protocol error).\n *\n * @example\n * ```ts\n * const [session] = adapter.sessions.sessions.read();\n * const outcomes = await requestResourceAllocation(session, adapter, [\n * { tag: \"BulletInAllowance\", value: undefined },\n * ]);\n * ```\n */\nexport async function requestResourceAllocation(\n session: UserSession,\n adapter: TerminalAdapter,\n resources: AllocatableResource[],\n options: RequestResourceAllocationOptions = {},\n): Promise<ApAllocationOutcome[]> {\n return withCacheLock(adapter.appId, adapter.storageDir, async () => {\n const cache = await loadCache(adapter.appId, adapter.storageDir);\n const onExisting = options.onExisting ?? pickOnExistingPolicy(cache, resources);\n log.debug(\"requestResourceAllocation\", {\n productId: adapter.appId,\n resources: resources.map((r) => r.tag),\n onExisting,\n autoPolicy: options.onExisting === undefined,\n });\n\n const result = await session.requestResourceAllocation({\n callingProductId: adapter.appId,\n resources,\n onExisting,\n });\n\n if (result.isErr()) {\n throw new Error(`requestResourceAllocation failed: ${result.error.message}`);\n }\n\n const next = mergeOutcomes(cache, resources, result.value);\n if (next !== cache) {\n await saveCache(adapter.appId, next, adapter.storageDir);\n }\n\n return result.value;\n });\n}\n\n/**\n * Read a cached allowance entry without going over the wire. Returns\n * `null` if nothing's cached for `(adapter.appId, resource)`. For\n * `SmartContractAllowance`, the same `dest` must be passed — entries\n * are keyed per-dest.\n */\nexport async function getCachedAllocation(\n adapter: TerminalAdapter,\n resource: AllocatableResource,\n): Promise<CachedAllocation | null> {\n const cache = await loadCache(adapter.appId, adapter.storageDir);\n return readCacheEntry(cache, resource);\n}\n\n/**\n * Cache hit → return signer. Cache miss → call\n * {@link requestResourceAllocation} for `[resource]`, then build the signer.\n * Throws on `Rejected` / `NotAvailable`.\n *\n * @example\n * ```ts\n * const signer = await ensureSlotAccountSigner(session, adapter, {\n * tag: \"BulletInAllowance\", value: undefined,\n * });\n * await someBulletinTx.submitAndWatch(signer);\n * ```\n */\nexport async function ensureSlotAccountSigner(\n session: UserSession,\n adapter: TerminalAdapter,\n resource: AllocatableResource,\n): Promise<PolkadotSigner> {\n // The cache-hit check and the allocation path must share the same\n // critical section: otherwise two concurrent calls for the same\n // resource both see an empty cache, then both serialize through the\n // lock but each issues its own wallet prompt and burns a slot.\n return withCacheLock(adapter.appId, adapter.storageDir, async () => {\n // Single loadCache for the whole critical section — we hold the\n // lock, so no other writer can change disk under us. The fast\n // cache-hit path and the slow allocate-then-build path both\n // reuse this in-memory `cache` reference.\n const cache = await loadCache(adapter.appId, adapter.storageDir);\n const hit = readCacheEntry(cache, resource);\n if (hit) return buildSignerFromEntry(hit);\n\n log.debug(\"ensureSlotAccountSigner: cache miss, allocating\", { resource: resource.tag });\n\n // Inline the wire → merge → save sequence rather than calling the\n // public `requestResourceAllocation` wrapper. `withCacheLock` is\n // non-reentrant, so calling the wrapper from inside would deadlock\n // on the second lock acquisition.\n const onExisting = pickOnExistingPolicy(cache, [resource]);\n const result = await session.requestResourceAllocation({\n callingProductId: adapter.appId,\n resources: [resource],\n onExisting,\n });\n if (result.isErr()) {\n throw new Error(`requestResourceAllocation failed: ${result.error.message}`);\n }\n const outcomes = result.value;\n const next = mergeOutcomes(cache, [resource], outcomes);\n if (next !== cache) {\n await saveCache(adapter.appId, next, adapter.storageDir);\n }\n\n const outcome = outcomes[0];\n if (!outcome) {\n // Unreachable: mergeOutcomes throws on length mismatch first.\n // Kept as a typed-non-null guard.\n throw new Error(\n `ensureSlotAccountSigner: protocol violation — empty outcomes for ${resource.tag}`,\n );\n }\n if (outcome.tag !== \"Allocated\") {\n throw new Error(\n `ensureSlotAccountSigner: allocation ${outcome.tag} for ${resource.tag}`,\n );\n }\n\n // Build the signer from the in-memory post-merge cache — no third\n // disk read.\n const freshEntry = readCacheEntry(next, resource);\n if (!freshEntry) {\n // Unreachable: the entry was just merged in.\n // Kept as a typed-non-null guard against mergeOutcomes regressions.\n throw new Error(\n `ensureSlotAccountSigner: allocation succeeded but cache lookup returned null for ${resource.tag}`,\n );\n }\n return buildSignerFromEntry(freshEntry);\n });\n}\n\nif (import.meta.vitest) {\n const { describe, test, expect, vi, beforeEach } = import.meta.vitest;\n const { ok, err } = await import(\"neverthrow\");\n const { mkdtempSync, rmSync } = await import(\"node:fs\");\n const { tmpdir } = await import(\"node:os\");\n const { join: pathJoin } = await import(\"node:path\");\n // Needed by the ensureSlotAccountSigner suite. Lifted up here because\n // top-level await is only allowed at module top-level (or inside this\n // top-level vitest block), not inside `describe`.\n const { mnemonicToMiniSecret, DEV_PHRASE } = await import(\"@polkadot-labs/hdkd-helpers\");\n const { toHex } = await import(\"@polkadot-api/utils\");\n\n let testStorageDir: string;\n beforeEach(() => {\n // Per-test temp dir so cache writes don't leak between tests or\n // touch the real default storage directory. The wrapper inherits\n // `storageDir` from `adapter.storageDir`; we pin it here.\n testStorageDir = mkdtempSync(pathJoin(tmpdir(), \"host-test-\"));\n return () => rmSync(testStorageDir, { recursive: true, force: true });\n });\n\n function fakeAdapter(appId: string): TerminalAdapter {\n // appId + storageDir are the only fields the wrapper touches.\n return { appId, storageDir: testStorageDir } as unknown as TerminalAdapter;\n }\n\n type RequestCapture = ResourceAllocationRequest;\n type StubReturn = Awaited<ReturnType<UserSession[\"requestResourceAllocation\"]>>;\n\n function makeSession(opts: {\n requestResourceAllocation?: (req: RequestCapture) => Promise<StubReturn>;\n }): UserSession {\n return {\n requestResourceAllocation: vi.fn(\n opts.requestResourceAllocation ??\n (async () => {\n throw new Error(\"requestResourceAllocation not stubbed in this test\");\n }),\n ),\n } as unknown as UserSession;\n }\n\n describe(\"requestResourceAllocation\", () => {\n test(\"forwards resources verbatim and returns outcomes in order\", async () => {\n const captured: RequestCapture[] = [];\n const stubbed = [\n {\n tag: \"Allocated\" as const,\n value: {\n tag: \"BulletInAllowance\" as const,\n value: { slotAccountKey: new Uint8Array([1, 2, 3]) },\n },\n },\n { tag: \"Rejected\" as const, value: undefined },\n ];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return ok(stubbed) as StubReturn;\n },\n });\n\n const outcomes = await requestResourceAllocation(session, fakeAdapter(\"my-app\"), [\n { tag: \"BulletInAllowance\", value: undefined },\n { tag: \"StatementStoreAllowance\", value: undefined },\n ]);\n\n expect(captured).toHaveLength(1);\n expect(captured[0].callingProductId).toBe(\"my-app\");\n expect(captured[0].resources.map((r) => r.tag)).toEqual([\n \"BulletInAllowance\",\n \"StatementStoreAllowance\",\n ]);\n expect(outcomes).toEqual(stubbed);\n });\n\n test(\"uses adapter.appId as callingProductId\", async () => {\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return ok([]) as StubReturn;\n },\n });\n\n await requestResourceAllocation(session, fakeAdapter(\"alt-product.dot\"), []);\n\n expect(captured[0].callingProductId).toBe(\"alt-product.dot\");\n });\n\n test(\"auto-picks Ignore for an empty resources array\", async () => {\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return ok([]) as StubReturn;\n },\n });\n\n await requestResourceAllocation(session, fakeAdapter(\"p\"), []);\n\n expect(captured[0].onExisting).toBe(\"Ignore\");\n });\n\n test(\"forwards onExisting when explicitly set to 'Increase'\", async () => {\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return ok([{ tag: \"Rejected\" as const, value: undefined }]) as StubReturn;\n },\n });\n\n await requestResourceAllocation(\n session,\n fakeAdapter(\"p\"),\n [{ tag: \"BulletInAllowance\", value: undefined }],\n { onExisting: \"Increase\" },\n );\n\n expect(captured[0].onExisting).toBe(\"Increase\");\n });\n\n test(\"forwards SmartContractAllowance derivation index\", async () => {\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return ok([{ tag: \"Rejected\" as const, value: undefined }]) as StubReturn;\n },\n });\n\n await requestResourceAllocation(session, fakeAdapter(\"p\"), [\n { tag: \"SmartContractAllowance\", value: 7 },\n ]);\n\n const sent = captured[0].resources[0];\n expect(sent.tag).toBe(\"SmartContractAllowance\");\n if (sent.tag === \"SmartContractAllowance\") {\n expect(sent.value).toBe(7);\n }\n });\n\n test(\"throws a clear error when the session call fails\", async () => {\n const session = makeSession({\n requestResourceAllocation: async () =>\n err(new Error(\"mobile disconnected\")) as StubReturn,\n });\n\n await expect(\n requestResourceAllocation(session, fakeAdapter(\"p\"), [\n { tag: \"BulletInAllowance\", value: undefined },\n ]),\n ).rejects.toThrow(\"requestResourceAllocation failed: mobile disconnected\");\n });\n\n test(\"forwards AutoSigning and returns NotAvailable verbatim (mobile-default path)\", async () => {\n // Both Android (paritytech/polkadot-app-android-v2#563) and iOS\n // (paritytech/polkadot-app-ios-v2#759) currently return NotAvailable\n // for any AutoSigning request. The wrapper must round-trip both\n // the request variant and the NotAvailable outcome without\n // filtering, transforming, or throwing — that's the only state\n // real users see for one of the four AllocatableResource variants\n // today.\n const captured: RequestCapture[] = [];\n const stubbed = [{ tag: \"NotAvailable\" as const, value: undefined }];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return ok(stubbed) as StubReturn;\n },\n });\n\n const outcomes = await requestResourceAllocation(session, fakeAdapter(\"p\"), [\n { tag: \"AutoSigning\", value: undefined },\n ]);\n\n const sent = captured[0].resources[0];\n expect(sent.tag).toBe(\"AutoSigning\");\n expect(outcomes).toEqual(stubbed);\n });\n\n test(\"passes empty resources through (no-op request still hits the wire)\", async () => {\n // Wrappers shouldn't second-guess the caller: an empty resource\n // list is a legitimate (if degenerate) request and the wire\n // accepts it. The Account Holder may use it as a probe.\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return ok([]) as StubReturn;\n },\n });\n\n const outcomes = await requestResourceAllocation(session, fakeAdapter(\"p\"), []);\n\n expect(captured).toHaveLength(1);\n expect(captured[0].resources).toEqual([]);\n expect(outcomes).toEqual([]);\n });\n });\n\n describe(\"requestResourceAllocation — cache integration\", () => {\n function bulletinAllocated(slotKey: Uint8Array): StubReturn {\n return ok([\n {\n tag: \"Allocated\" as const,\n value: {\n tag: \"BulletInAllowance\" as const,\n value: { slotAccountKey: slotKey },\n },\n },\n ]) as StubReturn;\n }\n\n test(\"first call sends Ignore when cache is empty\", async () => {\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return bulletinAllocated(new Uint8Array([1, 2, 3]));\n },\n });\n\n await requestResourceAllocation(session, fakeAdapter(\"p\"), [\n { tag: \"BulletInAllowance\", value: undefined },\n ]);\n\n expect(captured[0].onExisting).toBe(\"Ignore\");\n });\n\n test(\"second call for same slot-table resource auto-picks Increase\", async () => {\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return bulletinAllocated(new Uint8Array([1, 2, 3]));\n },\n });\n\n const adapter = fakeAdapter(\"p\");\n const req: AllocatableResource[] = [{ tag: \"BulletInAllowance\", value: undefined }];\n\n await requestResourceAllocation(session, adapter, req);\n await requestResourceAllocation(session, adapter, req);\n\n expect(captured.map((c) => c.onExisting)).toEqual([\"Ignore\", \"Increase\"]);\n });\n\n test(\"explicit options.onExisting overrides the auto-pick\", async () => {\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return bulletinAllocated(new Uint8Array([1, 2, 3]));\n },\n });\n\n const adapter = fakeAdapter(\"p\");\n const req: AllocatableResource[] = [{ tag: \"BulletInAllowance\", value: undefined }];\n\n // Even after a cache-populating first call, passing Ignore\n // explicitly forces Ignore — useful for callers that want\n // idempotent \"ensure I have allowance\" semantics rather than\n // the additive-scaling default.\n await requestResourceAllocation(session, adapter, req);\n await requestResourceAllocation(session, adapter, req, { onExisting: \"Ignore\" });\n\n expect(captured.map((c) => c.onExisting)).toEqual([\"Ignore\", \"Ignore\"]);\n });\n\n test(\"populates cache on Allocated and survives across separate calls\", async () => {\n const slotKey = new Uint8Array([0xde, 0xad, 0xbe, 0xef]);\n const session = makeSession({\n requestResourceAllocation: async () => bulletinAllocated(slotKey),\n });\n\n const adapter = fakeAdapter(\"p\");\n await requestResourceAllocation(session, adapter, [\n { tag: \"BulletInAllowance\", value: undefined },\n ]);\n\n // Second call reads cache via the public lookup, no wire.\n const cached = await getCachedAllocation(adapter, {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n expect(cached).toEqual({\n tag: \"BulletInAllowance\",\n slotAccountKey: \"0xdeadbeef\",\n });\n });\n\n test(\"does not cache Rejected or NotAvailable outcomes\", async () => {\n const session = makeSession({\n requestResourceAllocation: async () =>\n ok([\n { tag: \"Rejected\" as const, value: undefined },\n { tag: \"NotAvailable\" as const, value: undefined },\n ]) as StubReturn,\n });\n\n const adapter = fakeAdapter(\"p\");\n await requestResourceAllocation(session, adapter, [\n { tag: \"BulletInAllowance\", value: undefined },\n { tag: \"StatementStoreAllowance\", value: undefined },\n ]);\n\n expect(\n await getCachedAllocation(adapter, { tag: \"BulletInAllowance\", value: undefined }),\n ).toBeNull();\n expect(\n await getCachedAllocation(adapter, {\n tag: \"StatementStoreAllowance\",\n value: undefined,\n }),\n ).toBeNull();\n });\n\n test(\"mixed cached + uncached request stays on Ignore (conservative)\", async () => {\n // Bulletin cached, SSS not. Auto-pick must NOT send Increase\n // — the wire takes one policy for the whole request, and\n // Increase against an uncached resource is at-best a no-op\n // and at-worst a mis-allocation.\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n // Length-match the request: 1 or 2 outcomes as needed.\n return ok(\n req.resources.map((_r, i) =>\n i === 0\n ? {\n tag: \"Allocated\" as const,\n value: {\n tag: \"BulletInAllowance\" as const,\n value: { slotAccountKey: new Uint8Array([1]) },\n },\n }\n : { tag: \"Rejected\" as const, value: undefined },\n ),\n ) as StubReturn;\n },\n });\n\n const adapter = fakeAdapter(\"p\");\n // Populate Bulletin in cache.\n await requestResourceAllocation(session, adapter, [\n { tag: \"BulletInAllowance\", value: undefined },\n ]);\n\n // Now request both Bulletin (cached) and SSS (uncached).\n await requestResourceAllocation(session, adapter, [\n { tag: \"BulletInAllowance\", value: undefined },\n { tag: \"StatementStoreAllowance\", value: undefined },\n ]);\n\n expect(captured[captured.length - 1].onExisting).toBe(\"Ignore\");\n });\n\n test(\"AutoSigning never auto-Increases even when cached\", async () => {\n // AutoSigning isn't slot-additive; auto-pick must stay on Ignore.\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return ok([\n {\n tag: \"Allocated\" as const,\n value: {\n tag: \"AutoSigning\" as const,\n value: {\n productDerivationSecret: \"secret\",\n productRootPrivateKey: new Uint8Array([0xab]),\n },\n },\n },\n ]) as StubReturn;\n },\n });\n\n const adapter = fakeAdapter(\"p\");\n const req: AllocatableResource[] = [{ tag: \"AutoSigning\", value: undefined }];\n\n await requestResourceAllocation(session, adapter, req);\n await requestResourceAllocation(session, adapter, req);\n\n expect(captured.map((c) => c.onExisting)).toEqual([\"Ignore\", \"Ignore\"]);\n });\n\n test(\"parallel calls for different resources both persist their keys (no last-writer-wins)\", async () => {\n // Regression test for the load → wire → save race: without\n // serialization, both calls snapshot an empty cache, do their\n // wire round-trip, then save — the second save clobbers the\n // first. Both keys must end up in the cache.\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n // Force interleaving: each call yields before responding.\n await new Promise((r) => setTimeout(r, 5));\n const tag = req.resources[0]?.tag;\n return ok([\n {\n tag: \"Allocated\" as const,\n value:\n tag === \"BulletInAllowance\"\n ? {\n tag: \"BulletInAllowance\" as const,\n value: { slotAccountKey: new Uint8Array([0xaa]) },\n }\n : {\n tag: \"StatementStoreAllowance\" as const,\n value: { slotAccountKey: new Uint8Array([0xbb]) },\n },\n },\n ]) as StubReturn;\n },\n });\n const adapter = fakeAdapter(\"p\");\n\n await Promise.all([\n requestResourceAllocation(session, adapter, [\n { tag: \"BulletInAllowance\", value: undefined },\n ]),\n requestResourceAllocation(session, adapter, [\n { tag: \"StatementStoreAllowance\", value: undefined },\n ]),\n ]);\n\n expect(\n await getCachedAllocation(adapter, {\n tag: \"BulletInAllowance\",\n value: undefined,\n }),\n ).not.toBeNull();\n expect(\n await getCachedAllocation(adapter, {\n tag: \"StatementStoreAllowance\",\n value: undefined,\n }),\n ).not.toBeNull();\n });\n\n test(\"two appIds maintain independent caches in the same storageDir\", async () => {\n const session = makeSession({\n requestResourceAllocation: async () => bulletinAllocated(new Uint8Array([1, 2, 3])),\n });\n\n const adapterA = fakeAdapter(\"app-a\");\n const adapterB = fakeAdapter(\"app-b\");\n\n await requestResourceAllocation(session, adapterA, [\n { tag: \"BulletInAllowance\", value: undefined },\n ]);\n\n // app-b's cache is empty even though app-a's is populated.\n expect(\n await getCachedAllocation(adapterB, {\n tag: \"BulletInAllowance\",\n value: undefined,\n }),\n ).toBeNull();\n expect(\n await getCachedAllocation(adapterA, {\n tag: \"BulletInAllowance\",\n value: undefined,\n }),\n ).not.toBeNull();\n });\n });\n\n describe(\"getCachedAllocation\", () => {\n test(\"returns null when nothing is cached\", async () => {\n const adapter = fakeAdapter(\"p\");\n const cached = await getCachedAllocation(adapter, {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n expect(cached).toBeNull();\n });\n\n test(\"disambiguates SmartContractAllowance entries by dest\", async () => {\n const session = makeSession({\n requestResourceAllocation: async () =>\n ok([\n {\n tag: \"Allocated\" as const,\n value: {\n tag: \"SmartContractAllowance\" as const,\n value: undefined,\n },\n },\n ]) as StubReturn,\n });\n const adapter = fakeAdapter(\"p\");\n await requestResourceAllocation(session, adapter, [\n { tag: \"SmartContractAllowance\", value: 5 },\n ]);\n\n expect(\n await getCachedAllocation(adapter, { tag: \"SmartContractAllowance\", value: 5 }),\n ).toEqual({ tag: \"SmartContractAllowance\", dest: 5 });\n // Different dest doesn't match.\n expect(\n await getCachedAllocation(adapter, { tag: \"SmartContractAllowance\", value: 6 }),\n ).toBeNull();\n });\n });\n\n describe(\"ensureSlotAccountSigner\", () => {\n // Deterministic mini-secret so the signer is constructable.\n const slotKey = mnemonicToMiniSecret(DEV_PHRASE);\n\n function bulletinAllocatedResponse() {\n return ok([\n {\n tag: \"Allocated\" as const,\n value: {\n tag: \"BulletInAllowance\" as const,\n value: { slotAccountKey: slotKey },\n },\n },\n ]) as StubReturn;\n }\n\n test(\"cache hit returns a signer without invoking the AP wire\", async () => {\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return bulletinAllocatedResponse();\n },\n });\n const adapter = fakeAdapter(\"p\");\n\n // Populate cache.\n await requestResourceAllocation(session, adapter, [\n { tag: \"BulletInAllowance\", value: undefined },\n ]);\n expect(captured).toHaveLength(1);\n\n // ensureSlotAccountSigner should find the cached key and NOT\n // call the wire again.\n const signer = await ensureSlotAccountSigner(session, adapter, {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n expect(signer.publicKey).toBeInstanceOf(Uint8Array);\n expect(captured).toHaveLength(1); // still 1 — no extra round-trip\n });\n\n test(\"cache miss triggers allocation and returns a fresh signer\", async () => {\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n return bulletinAllocatedResponse();\n },\n });\n const adapter = fakeAdapter(\"p\");\n\n // No prior call. ensureSlotAccountSigner should round-trip.\n const signer = await ensureSlotAccountSigner(session, adapter, {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n expect(signer.publicKey).toBeInstanceOf(Uint8Array);\n expect(captured).toHaveLength(1);\n // Implicit allocation requests exactly one resource.\n expect(captured[0].resources.map((r) => r.tag)).toEqual([\"BulletInAllowance\"]);\n });\n\n test(\"Rejected outcome surfaces as a throw — no signer to return\", async () => {\n const session = makeSession({\n requestResourceAllocation: async () =>\n ok([{ tag: \"Rejected\" as const, value: undefined }]) as StubReturn,\n });\n\n await expect(\n ensureSlotAccountSigner(session, fakeAdapter(\"p\"), {\n tag: \"BulletInAllowance\",\n value: undefined,\n }),\n ).rejects.toThrow(/allocation Rejected for BulletInAllowance/);\n });\n\n test(\"empty outcomes array from the wire surfaces as a length-mismatch throw\", async () => {\n // mergeOutcomes enforces the length contract and throws before\n // the downstream `outcomes[0]` defensive guard is reached.\n const session = makeSession({\n requestResourceAllocation: async () => ok([]) as StubReturn,\n });\n\n await expect(\n ensureSlotAccountSigner(session, fakeAdapter(\"p\"), {\n tag: \"BulletInAllowance\",\n value: undefined,\n }),\n ).rejects.toThrow(/length mismatch — requested 1, got 0/);\n });\n\n test(\"NotAvailable outcome surfaces as a throw\", async () => {\n const session = makeSession({\n requestResourceAllocation: async () =>\n ok([{ tag: \"NotAvailable\" as const, value: undefined }]) as StubReturn,\n });\n\n await expect(\n ensureSlotAccountSigner(session, fakeAdapter(\"p\"), {\n tag: \"StatementStoreAllowance\",\n value: undefined,\n }),\n ).rejects.toThrow(/allocation NotAvailable for StatementStoreAllowance/);\n });\n\n test(\"transport error propagates from the underlying allocation call\", async () => {\n const session = makeSession({\n requestResourceAllocation: async () =>\n err(new Error(\"mobile timed out\")) as StubReturn,\n });\n\n await expect(\n ensureSlotAccountSigner(session, fakeAdapter(\"p\"), {\n tag: \"BulletInAllowance\",\n value: undefined,\n }),\n ).rejects.toThrow(/mobile timed out/);\n });\n\n test(\"populates the cache so a later call is a hit\", async () => {\n const session = makeSession({\n requestResourceAllocation: async () => bulletinAllocatedResponse(),\n });\n const adapter = fakeAdapter(\"p\");\n\n await ensureSlotAccountSigner(session, adapter, {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n\n // After the implicit allocation, getCachedAllocation should see the key.\n const cached = await getCachedAllocation(adapter, {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n expect(cached?.tag).toBe(\"BulletInAllowance\");\n });\n\n test(\"parallel calls for the SAME resource share one allocation (no double-prompt)\", async () => {\n // Regression: previously the cache-hit check ran outside the\n // lock, so two concurrent same-resource calls both passed it,\n // then both hit the wire under the serialized lock — burning\n // two slots and prompting the wallet twice. Both calls must\n // resolve from a single allocation.\n const captured: RequestCapture[] = [];\n const session = makeSession({\n requestResourceAllocation: async (req) => {\n captured.push(req);\n // Force a yield so the second caller has a chance to\n // race past the cache check if the lock isn't around it.\n await new Promise((r) => setTimeout(r, 5));\n return bulletinAllocatedResponse();\n },\n });\n const adapter = fakeAdapter(\"p\");\n\n const [signerA, signerB] = await Promise.all([\n ensureSlotAccountSigner(session, adapter, {\n tag: \"BulletInAllowance\",\n value: undefined,\n }),\n ensureSlotAccountSigner(session, adapter, {\n tag: \"BulletInAllowance\",\n value: undefined,\n }),\n ]);\n\n // Exactly one wire round-trip — the second caller must observe\n // the freshly-cached key, not start a second allocation.\n expect(captured).toHaveLength(1);\n // Both calls return functioning signers backed by the same key.\n expect(signerA.publicKey).toBeInstanceOf(Uint8Array);\n expect(signerB.publicKey).toBeInstanceOf(Uint8Array);\n expect(toHex(signerA.publicKey)).toBe(toHex(signerB.publicKey));\n });\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Persistent allowance-key cache. One JSON file per `appId`, 0o600.\n *\n * Cache key is the variant tag, except `SmartContractAllowance::{dest}`\n * which disambiguates per-derivation-index PGAS pre-warming.\n *\n * @internal\n */\nimport { createLogger } from \"@parity/product-sdk-logger\";\nimport { toHex } from \"@polkadot-api/utils\";\nimport { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nimport type { AllocatableResource, ApAllocationOutcome } from \"./host.js\";\n\nconst log = createLogger(\"terminal\");\n\nconst DEFAULT_STORAGE_DIR = join(homedir(), \".polkadot-apps\");\nconst CACHE_FILE_MODE = 0o600;\n\n/** One cached allowance entry. Hex strings are 0x-prefixed. */\nexport type CachedAllocation =\n | { tag: \"BulletInAllowance\"; slotAccountKey: string }\n | { tag: \"StatementStoreAllowance\"; slotAccountKey: string }\n | { tag: \"SmartContractAllowance\"; dest: number }\n | {\n tag: \"AutoSigning\";\n productDerivationSecret: string;\n productRootPrivateKey: string;\n };\n\ninterface AllowanceCacheV1 {\n version: 1;\n entries: Record<string, CachedAllocation>;\n}\n\nfunction sanitizeAppId(appId: string): string {\n return appId.replace(/[^a-zA-Z0-9_.-]/g, \"_\");\n}\n\nfunction cachePath(appId: string, storageDir?: string): string {\n return join(storageDir ?? DEFAULT_STORAGE_DIR, `${sanitizeAppId(appId)}_AllowanceKeys.json`);\n}\n\nfunction emptyCache(): AllowanceCacheV1 {\n return { version: 1, entries: {} };\n}\n\nexport async function loadCache(appId: string, storageDir?: string): Promise<AllowanceCacheV1> {\n const path = cachePath(appId, storageDir);\n let raw: string;\n try {\n raw = await readFile(path, \"utf-8\");\n } catch (e: unknown) {\n if ((e as NodeJS.ErrnoException)?.code === \"ENOENT\") return emptyCache();\n throw e;\n }\n try {\n const parsed = JSON.parse(raw) as AllowanceCacheV1;\n if (\n parsed?.version !== 1 ||\n typeof parsed.entries !== \"object\" ||\n parsed.entries === null\n ) {\n log.warn(\"allowance cache schema mismatch; starting fresh\", { appId, path });\n return emptyCache();\n }\n return parsed;\n } catch (e) {\n log.warn(\"allowance cache parse failed; starting fresh\", { appId, path, error: String(e) });\n return emptyCache();\n }\n}\n\nexport async function saveCache(\n appId: string,\n cache: AllowanceCacheV1,\n storageDir?: string,\n): Promise<void> {\n const path = cachePath(appId, storageDir);\n await mkdir(dirname(path), { recursive: true });\n // Temp + rename so a mid-write crash can't leave a half-written file.\n // Concurrent Hosts: the Account Holder serializes per (user, product,\n // resource) and returns the same key to all callers, so racing writes\n // converge on identical bytes.\n const tmp = `${path}.tmp`;\n await writeFile(tmp, JSON.stringify(cache, null, 2), { mode: CACHE_FILE_MODE });\n await rename(tmp, path);\n}\n\nexport function cacheKey(resource: AllocatableResource): string {\n if (resource.tag === \"SmartContractAllowance\") {\n return `${resource.tag}::${resource.value}`;\n }\n return resource.tag;\n}\n\n/**\n * Pick the wire `onExisting` policy from current cache state. `Ignore`\n * unless every requested resource is a slot-table variant (Bulletin /\n * SSS) AND already cached, in which case `Increase`. SC and AutoSigning\n * aren't slot-additive so they always force `Ignore`.\n */\nexport function pickOnExistingPolicy(\n cache: AllowanceCacheV1,\n resources: AllocatableResource[],\n): \"Ignore\" | \"Increase\" {\n if (resources.length === 0) return \"Ignore\";\n const allSlotTableAndCached = resources.every((r) => {\n if (r.tag !== \"BulletInAllowance\" && r.tag !== \"StatementStoreAllowance\") return false;\n return Object.hasOwn(cache.entries, cacheKey(r));\n });\n return allSlotTableAndCached ? \"Increase\" : \"Ignore\";\n}\n\n/**\n * Merge `Allocated` outcomes into the cache. Non-`Allocated` outcomes\n * leave the cache unchanged. Returns the input `cache` reference rather\n * than a copy when no entry was added, so callers can short-circuit a\n * disk write via reference equality.\n *\n * Throws on length mismatch — `outcomes` must be length-matched with\n * `requested`. A divergence is a wire contract violation, not data to\n * silently truncate.\n */\nexport function mergeOutcomes(\n cache: AllowanceCacheV1,\n requested: AllocatableResource[],\n outcomes: ApAllocationOutcome[],\n): AllowanceCacheV1 {\n if (requested.length !== outcomes.length) {\n throw new Error(\n `mergeOutcomes: length mismatch — requested ${requested.length}, got ${outcomes.length}`,\n );\n }\n const entries = { ...cache.entries };\n let mutated = false;\n for (let i = 0; i < outcomes.length; i++) {\n const req = requested[i];\n const out = outcomes[i];\n if (out.tag !== \"Allocated\") continue;\n const inner = out.value;\n // outcomes must align positionally AND by variant with the request.\n // A divergence is a wire contract violation: throw rather than\n // silently miscaching a key under the wrong cache slot.\n if (inner.tag !== req.tag) {\n throw new Error(\n `mergeOutcomes: variant mismatch at index ${i} — requested ${req.tag}, got Allocated<${inner.tag}>`,\n );\n }\n const key = cacheKey(req);\n switch (inner.tag) {\n case \"BulletInAllowance\":\n case \"StatementStoreAllowance\":\n entries[key] = {\n tag: inner.tag,\n slotAccountKey: toHex(inner.value.slotAccountKey),\n };\n mutated = true;\n break;\n case \"SmartContractAllowance\":\n // dest comes from the request; the response payload is undefined.\n // Variant alignment is enforced above, so req.tag is guaranteed\n // to be SmartContractAllowance here — the cast narrows it.\n if (req.tag === \"SmartContractAllowance\") {\n entries[key] = { tag: \"SmartContractAllowance\", dest: req.value };\n mutated = true;\n }\n break;\n case \"AutoSigning\":\n entries[key] = {\n tag: \"AutoSigning\",\n productDerivationSecret: inner.value.productDerivationSecret,\n productRootPrivateKey: toHex(inner.value.productRootPrivateKey),\n };\n mutated = true;\n break;\n }\n }\n return mutated ? { version: 1, entries } : cache;\n}\n\n/** Look up a single cached allocation, or `null` if absent. */\nexport function readCacheEntry(\n cache: AllowanceCacheV1,\n resource: AllocatableResource,\n): CachedAllocation | null {\n return cache.entries[cacheKey(resource)] ?? null;\n}\n\n/**\n * Serialize load/merge/save sequences for the same cache file within a\n * single process. Without this, parallel `requestResourceAllocation`\n * calls for *different* resources can race: each snapshots the cache\n * before the other writes, last writer wins, the loser's key is lost.\n *\n * Cross-process races are out of scope here — the Account Holder\n * serializes per user/product/resource and returns identical bytes\n * to concurrent callers for the same resource, so two CLI processes\n * writing the same key converge.\n */\nconst cacheLocks = new Map<string, Promise<unknown>>();\n\nexport function withCacheLock<T>(\n appId: string,\n storageDir: string | undefined,\n fn: () => Promise<T>,\n): Promise<T> {\n const key = cachePath(appId, storageDir);\n const prev = cacheLocks.get(key) ?? Promise.resolve();\n // Neutralize a prior rejection so one failure doesn't block subsequent waiters.\n const next = prev.catch(() => {}).then(fn);\n cacheLocks.set(\n key,\n next.catch(() => {}),\n );\n return next;\n}\n\nif (import.meta.vitest) {\n const { describe, test, expect, beforeEach } = import.meta.vitest;\n const { mkdtempSync, rmSync } = await import(\"node:fs\");\n const { tmpdir } = await import(\"node:os\");\n\n let storageDir: string;\n beforeEach(() => {\n storageDir = mkdtempSync(join(tmpdir(), \"host-cache-test-\"));\n return () => rmSync(storageDir, { recursive: true, force: true });\n });\n\n describe(\"cacheKey\", () => {\n test(\"returns the variant tag for slot-table resources and AutoSigning\", () => {\n expect(cacheKey({ tag: \"BulletInAllowance\", value: undefined })).toBe(\n \"BulletInAllowance\",\n );\n expect(cacheKey({ tag: \"StatementStoreAllowance\", value: undefined })).toBe(\n \"StatementStoreAllowance\",\n );\n expect(cacheKey({ tag: \"AutoSigning\", value: undefined })).toBe(\"AutoSigning\");\n });\n\n test(\"disambiguates SmartContractAllowance by dest\", () => {\n expect(cacheKey({ tag: \"SmartContractAllowance\", value: 0 })).toBe(\n \"SmartContractAllowance::0\",\n );\n expect(cacheKey({ tag: \"SmartContractAllowance\", value: 7 })).toBe(\n \"SmartContractAllowance::7\",\n );\n });\n });\n\n describe(\"loadCache / saveCache round-trip\", () => {\n test(\"loadCache on missing file returns empty cache, not an error\", async () => {\n const cache = await loadCache(\"my-app\", storageDir);\n expect(cache).toEqual({ version: 1, entries: {} });\n });\n\n test(\"saveCache then loadCache returns the same content\", async () => {\n const original: AllowanceCacheV1 = {\n version: 1,\n entries: {\n BulletInAllowance: { tag: \"BulletInAllowance\", slotAccountKey: \"0xdeadbeef\" },\n },\n };\n await saveCache(\"my-app\", original, storageDir);\n const reloaded = await loadCache(\"my-app\", storageDir);\n expect(reloaded).toEqual(original);\n });\n\n test(\"two appIds in the same storageDir don't collide\", async () => {\n const a: AllowanceCacheV1 = {\n version: 1,\n entries: {\n BulletInAllowance: { tag: \"BulletInAllowance\", slotAccountKey: \"0x01\" },\n },\n };\n const b: AllowanceCacheV1 = {\n version: 1,\n entries: {\n BulletInAllowance: { tag: \"BulletInAllowance\", slotAccountKey: \"0x02\" },\n },\n };\n await saveCache(\"app-a\", a, storageDir);\n await saveCache(\"app-b\", b, storageDir);\n expect((await loadCache(\"app-a\", storageDir)).entries.BulletInAllowance).toEqual(\n a.entries.BulletInAllowance,\n );\n expect((await loadCache(\"app-b\", storageDir)).entries.BulletInAllowance).toEqual(\n b.entries.BulletInAllowance,\n );\n });\n\n test(\"appId with non-alphanumeric characters sanitizes to a safe path\", async () => {\n // Verifies a path-traversal-style appId can't escape storageDir.\n const cache: AllowanceCacheV1 = { version: 1, entries: {} };\n await expect(saveCache(\"../escape\", cache, storageDir)).resolves.toBeUndefined();\n const reloaded = await loadCache(\"../escape\", storageDir);\n expect(reloaded).toEqual(cache);\n });\n\n test(\"malformed cache file is treated as empty, not thrown\", async () => {\n await writeFile(cachePath(\"my-app\", storageDir), \"{ not valid json\", \"utf-8\");\n const cache = await loadCache(\"my-app\", storageDir);\n expect(cache).toEqual({ version: 1, entries: {} });\n });\n\n test(\"version mismatch is treated as empty, not thrown\", async () => {\n await writeFile(\n cachePath(\"my-app\", storageDir),\n JSON.stringify({ version: 99, entries: {} }),\n \"utf-8\",\n );\n const cache = await loadCache(\"my-app\", storageDir);\n expect(cache).toEqual({ version: 1, entries: {} });\n });\n\n test(\"file is written with 0o600 permissions (defense-in-depth)\", async () => {\n const { stat } = await import(\"node:fs/promises\");\n const cache: AllowanceCacheV1 = { version: 1, entries: {} };\n await saveCache(\"my-app\", cache, storageDir);\n const s = await stat(cachePath(\"my-app\", storageDir));\n // Mask to permission bits only — file type bits are above 0o777.\n expect(s.mode & 0o777).toBe(0o600);\n });\n });\n\n describe(\"pickOnExistingPolicy\", () => {\n test(\"empty request resolves to Ignore\", () => {\n const cache = emptyCache();\n expect(pickOnExistingPolicy(cache, [])).toBe(\"Ignore\");\n });\n\n test(\"no cached entries → Ignore\", () => {\n const cache = emptyCache();\n expect(\n pickOnExistingPolicy(cache, [{ tag: \"BulletInAllowance\", value: undefined }]),\n ).toBe(\"Ignore\");\n });\n\n test(\"all slot-table resources cached → Increase\", () => {\n const cache: AllowanceCacheV1 = {\n version: 1,\n entries: {\n BulletInAllowance: { tag: \"BulletInAllowance\", slotAccountKey: \"0x01\" },\n StatementStoreAllowance: {\n tag: \"StatementStoreAllowance\",\n slotAccountKey: \"0x02\",\n },\n },\n };\n expect(\n pickOnExistingPolicy(cache, [\n { tag: \"BulletInAllowance\", value: undefined },\n { tag: \"StatementStoreAllowance\", value: undefined },\n ]),\n ).toBe(\"Increase\");\n });\n\n test(\"mixed cached + uncached → Ignore (conservative)\", () => {\n const cache: AllowanceCacheV1 = {\n version: 1,\n entries: {\n BulletInAllowance: { tag: \"BulletInAllowance\", slotAccountKey: \"0x01\" },\n },\n };\n expect(\n pickOnExistingPolicy(cache, [\n { tag: \"BulletInAllowance\", value: undefined },\n { tag: \"StatementStoreAllowance\", value: undefined },\n ]),\n ).toBe(\"Ignore\");\n });\n\n test(\"AutoSigning in the request → Ignore even if cached (not slot-additive)\", () => {\n const cache: AllowanceCacheV1 = {\n version: 1,\n entries: {\n AutoSigning: {\n tag: \"AutoSigning\",\n productDerivationSecret: \"x\",\n productRootPrivateKey: \"0xaa\",\n },\n },\n };\n expect(pickOnExistingPolicy(cache, [{ tag: \"AutoSigning\", value: undefined }])).toBe(\n \"Ignore\",\n );\n });\n\n test(\"SC in the request → Ignore (not slot-additive; PGAS claim is per-call)\", () => {\n const cache: AllowanceCacheV1 = {\n version: 1,\n entries: {\n \"SmartContractAllowance::5\": { tag: \"SmartContractAllowance\", dest: 5 },\n },\n };\n expect(pickOnExistingPolicy(cache, [{ tag: \"SmartContractAllowance\", value: 5 }])).toBe(\n \"Ignore\",\n );\n });\n });\n\n describe(\"mergeOutcomes\", () => {\n test(\"ignores Rejected and NotAvailable\", () => {\n const cache = emptyCache();\n const requested: AllocatableResource[] = [\n { tag: \"BulletInAllowance\", value: undefined },\n { tag: \"StatementStoreAllowance\", value: undefined },\n ];\n const outcomes: ApAllocationOutcome[] = [\n { tag: \"Rejected\", value: undefined },\n { tag: \"NotAvailable\", value: undefined },\n ];\n const merged = mergeOutcomes(cache, requested, outcomes);\n expect(merged.entries).toEqual({});\n });\n\n test(\"stores Allocated Bulletin/SSS keys with hex encoding\", () => {\n const cache = emptyCache();\n const requested: AllocatableResource[] = [\n { tag: \"BulletInAllowance\", value: undefined },\n ];\n const outcomes: ApAllocationOutcome[] = [\n {\n tag: \"Allocated\",\n value: {\n tag: \"BulletInAllowance\",\n value: { slotAccountKey: new Uint8Array([0xde, 0xad, 0xbe, 0xef]) },\n },\n },\n ];\n const merged = mergeOutcomes(cache, requested, outcomes);\n expect(merged.entries.BulletInAllowance).toEqual({\n tag: \"BulletInAllowance\",\n slotAccountKey: \"0xdeadbeef\",\n });\n });\n\n test(\"stores SmartContractAllowance with dest from the request\", () => {\n const cache = emptyCache();\n const requested: AllocatableResource[] = [{ tag: \"SmartContractAllowance\", value: 7 }];\n const outcomes: ApAllocationOutcome[] = [\n {\n tag: \"Allocated\",\n value: { tag: \"SmartContractAllowance\", value: undefined },\n },\n ];\n const merged = mergeOutcomes(cache, requested, outcomes);\n expect(merged.entries[\"SmartContractAllowance::7\"]).toEqual({\n tag: \"SmartContractAllowance\",\n dest: 7,\n });\n });\n\n test(\"stores AutoSigning subtree key + secret\", () => {\n const cache = emptyCache();\n const requested: AllocatableResource[] = [{ tag: \"AutoSigning\", value: undefined }];\n const outcomes: ApAllocationOutcome[] = [\n {\n tag: \"Allocated\",\n value: {\n tag: \"AutoSigning\",\n value: {\n productDerivationSecret: \"secret-hex\",\n productRootPrivateKey: new Uint8Array([0xab, 0xcd]),\n },\n },\n },\n ];\n const merged = mergeOutcomes(cache, requested, outcomes);\n expect(merged.entries.AutoSigning).toEqual({\n tag: \"AutoSigning\",\n productDerivationSecret: \"secret-hex\",\n productRootPrivateKey: \"0xabcd\",\n });\n });\n\n test(\"returns the same cache reference when no Allocated outcomes (no-op)\", () => {\n // Lets callers gate disk writes on reference equality.\n const cache: AllowanceCacheV1 = {\n version: 1,\n entries: {\n BulletInAllowance: { tag: \"BulletInAllowance\", slotAccountKey: \"0xaa\" },\n },\n };\n const requested: AllocatableResource[] = [\n { tag: \"BulletInAllowance\", value: undefined },\n { tag: \"StatementStoreAllowance\", value: undefined },\n ];\n const outcomes: ApAllocationOutcome[] = [\n { tag: \"Rejected\", value: undefined },\n { tag: \"NotAvailable\", value: undefined },\n ];\n expect(mergeOutcomes(cache, requested, outcomes)).toBe(cache);\n });\n\n test(\"empty inputs return the same cache reference\", () => {\n const cache = emptyCache();\n expect(mergeOutcomes(cache, [], [])).toBe(cache);\n });\n\n test(\"returns a new object reference when at least one entry is added\", () => {\n const cache = emptyCache();\n const requested: AllocatableResource[] = [\n { tag: \"BulletInAllowance\", value: undefined },\n ];\n const outcomes: ApAllocationOutcome[] = [\n {\n tag: \"Allocated\",\n value: {\n tag: \"BulletInAllowance\",\n value: { slotAccountKey: new Uint8Array([1]) },\n },\n },\n ];\n expect(mergeOutcomes(cache, requested, outcomes)).not.toBe(cache);\n });\n\n test(\"throws when outcomes do not length-match requested\", () => {\n const cache = emptyCache();\n const requested: AllocatableResource[] = [\n { tag: \"BulletInAllowance\", value: undefined },\n { tag: \"StatementStoreAllowance\", value: undefined },\n ];\n const outcomes: ApAllocationOutcome[] = [{ tag: \"Rejected\", value: undefined }];\n expect(() => mergeOutcomes(cache, requested, outcomes)).toThrow(/length mismatch/);\n });\n\n test(\"throws when an Allocated outcome's variant does not match the requested variant\", () => {\n // outcomes must align positionally AND by variant with the\n // request. Without this guard, a Bulletin key would get stored\n // under the StatementStoreAllowance cache slot.\n const cache = emptyCache();\n const requested: AllocatableResource[] = [\n { tag: \"StatementStoreAllowance\", value: undefined },\n ];\n const outcomes: ApAllocationOutcome[] = [\n {\n tag: \"Allocated\",\n value: {\n tag: \"BulletInAllowance\",\n value: { slotAccountKey: new Uint8Array([0xaa]) },\n },\n },\n ];\n expect(() => mergeOutcomes(cache, requested, outcomes)).toThrow(\n /variant mismatch at index 0 — requested StatementStoreAllowance, got Allocated<BulletInAllowance>/,\n );\n });\n\n test(\"preserves existing entries when merging new ones\", () => {\n const cache: AllowanceCacheV1 = {\n version: 1,\n entries: {\n StatementStoreAllowance: {\n tag: \"StatementStoreAllowance\",\n slotAccountKey: \"0xaa\",\n },\n },\n };\n const requested: AllocatableResource[] = [\n { tag: \"BulletInAllowance\", value: undefined },\n ];\n const outcomes: ApAllocationOutcome[] = [\n {\n tag: \"Allocated\",\n value: {\n tag: \"BulletInAllowance\",\n value: { slotAccountKey: new Uint8Array([0xbb]) },\n },\n },\n ];\n const merged = mergeOutcomes(cache, requested, outcomes);\n expect(Object.keys(merged.entries).sort()).toEqual([\n \"BulletInAllowance\",\n \"StatementStoreAllowance\",\n ]);\n expect(merged.entries.StatementStoreAllowance).toEqual({\n tag: \"StatementStoreAllowance\",\n slotAccountKey: \"0xaa\",\n });\n });\n });\n\n describe(\"readCacheEntry\", () => {\n test(\"returns the cached entry by resource discriminator\", () => {\n const cache: AllowanceCacheV1 = {\n version: 1,\n entries: {\n \"SmartContractAllowance::5\": {\n tag: \"SmartContractAllowance\",\n dest: 5,\n },\n },\n };\n expect(readCacheEntry(cache, { tag: \"SmartContractAllowance\", value: 5 })).toEqual({\n tag: \"SmartContractAllowance\",\n dest: 5,\n });\n // Different dest doesn't match.\n expect(readCacheEntry(cache, { tag: \"SmartContractAllowance\", value: 6 })).toBeNull();\n });\n\n test(\"returns null when nothing cached\", () => {\n const cache = emptyCache();\n expect(\n readCacheEntry(cache, { tag: \"BulletInAllowance\", value: undefined }),\n ).toBeNull();\n });\n });\n\n describe(\"withCacheLock\", () => {\n test(\"serializes calls for the same (appId, storageDir)\", async () => {\n const order: string[] = [];\n const a = withCacheLock(\"app\", storageDir, async () => {\n order.push(\"a-start\");\n await new Promise((r) => setTimeout(r, 10));\n order.push(\"a-end\");\n });\n const b = withCacheLock(\"app\", storageDir, async () => {\n order.push(\"b-start\");\n order.push(\"b-end\");\n });\n await Promise.all([a, b]);\n expect(order).toEqual([\"a-start\", \"a-end\", \"b-start\", \"b-end\"]);\n });\n\n test(\"different (appId, storageDir) keys do not block each other\", async () => {\n let bRan = false;\n const a = withCacheLock(\"app-a\", storageDir, async () => {\n // b should run while a is still inside the await\n await new Promise((r) => setTimeout(r, 20));\n expect(bRan).toBe(true);\n });\n const b = withCacheLock(\"app-b\", storageDir, async () => {\n bRan = true;\n });\n await Promise.all([a, b]);\n });\n\n test(\"a rejection in one call does not block subsequent calls on the same key\", async () => {\n const a = withCacheLock(\"app\", storageDir, async () => {\n throw new Error(\"boom\");\n });\n await expect(a).rejects.toThrow(\"boom\");\n const b = await withCacheLock(\"app\", storageDir, async () => \"ok\");\n expect(b).toBe(\"ok\");\n });\n });\n}\n","// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Slot-account signer: reads the cached Bulletin/SSS allowance key and\n * returns a `PolkadotSigner` that signs locally with sr25519. SC and\n * AutoSigning are not slot-table variants and throw.\n *\n * @module\n */\nimport { bytesToNumberLE, numberToBytesLE } from \"@noble/curves/utils.js\";\nimport { fromHex } from \"@polkadot-api/utils\";\nimport { createDerive, sr25519, sr25519Derive } from \"@polkadot-labs/hdkd-helpers\";\nimport type { PolkadotSigner } from \"polkadot-api\";\nimport { getPolkadotSigner } from \"polkadot-api/signer\";\n\nimport type { TerminalAdapter } from \"./adapter.js\";\nimport { type CachedAllocation, loadCache, readCacheEntry } from \"./host-cache.js\";\nimport type { AllocatableResource } from \"./host.js\";\n\n// schnorrkel to_bytes() (canonical scalar) -> to_ed25519_bytes() form @scure expects (×8 cofactor).\nfunction canonicalSr25519SecretToEd25519Bytes(secret: Uint8Array): Uint8Array {\n const ed25519Scalar = numberToBytesLE(bytesToNumberLE(secret.subarray(0, 32)) << 3n, 32);\n const out = new Uint8Array(64);\n out.set(ed25519Scalar, 0);\n out.set(secret.subarray(32, 64), 32);\n return out;\n}\n\n// Wire encoding for slotAccountKey isn't pinned: mobile may send a\n// 32-byte mini-secret or a 64-byte expanded secret. Detect length and\n// route; any other length throws.\nfunction buildKeypair(secret: Uint8Array): {\n publicKey: Uint8Array;\n sign: (data: Uint8Array) => Uint8Array;\n} {\n if (secret.length === 32) {\n const derive = createDerive({ seed: secret, curve: sr25519, derive: sr25519Derive });\n const keypair = derive(\"\");\n return { publicKey: keypair.publicKey, sign: (data) => keypair.sign(data) };\n }\n if (secret.length === 64) {\n const ed25519Secret = canonicalSr25519SecretToEd25519Bytes(secret);\n return {\n publicKey: sr25519.getPublicKey(ed25519Secret),\n sign: (data) => sr25519.sign(data, ed25519Secret),\n };\n }\n throw new Error(\n `createSlotAccountSigner: unexpected slotAccountKey length ${secret.length}. Expected 32 (mini-secret) or 64 (expanded secret).`,\n );\n}\n\n/**\n * Build a `PolkadotSigner` from an already-loaded cache entry. Throws\n * for SC and AutoSigning — they don't carry a slot account key. See\n * the module docstring.\n *\n * Useful when the caller already holds the cache in memory, e.g. inside\n * a `withCacheLock` block, and wants to avoid re-reading disk.\n */\nexport function buildSignerFromEntry(entry: CachedAllocation): PolkadotSigner {\n if (entry.tag !== \"BulletInAllowance\" && entry.tag !== \"StatementStoreAllowance\") {\n throw new Error(\n `createSlotAccountSigner: ${entry.tag} does not carry a slot account key. Slot-table signing is defined for BulletInAllowance and StatementStoreAllowance only.`,\n );\n }\n const secret = fromHex(entry.slotAccountKey);\n const { publicKey, sign } = buildKeypair(secret);\n return getPolkadotSigner(publicKey, \"Sr25519\", async (data) => sign(data));\n}\n\n/**\n * `PolkadotSigner` backed by the cached slot account key. Returns `null`\n * when nothing's cached for `(adapter.appId, resource)`. Throws for SC\n * and AutoSigning (no slot account key — see module docstring).\n *\n * Signs locally with sr25519 — no wallet round-trip on the hot path.\n *\n * @example\n * ```ts\n * const signer = await createSlotAccountSigner(adapter, {\n * tag: \"BulletInAllowance\", value: undefined,\n * });\n * if (signer) await tx.submitAndWatch(signer);\n * ```\n */\nexport async function createSlotAccountSigner(\n adapter: TerminalAdapter,\n resource: AllocatableResource,\n): Promise<PolkadotSigner | null> {\n const cache = await loadCache(adapter.appId, adapter.storageDir);\n const entry = readCacheEntry(cache, resource);\n if (!entry) return null;\n return buildSignerFromEntry(entry);\n}\n\nif (import.meta.vitest) {\n const { describe, test, expect, beforeEach } = import.meta.vitest;\n const { mkdtempSync, rmSync } = await import(\"node:fs\");\n const { tmpdir } = await import(\"node:os\");\n const { join: pathJoin } = await import(\"node:path\");\n const { saveCache } = await import(\"./host-cache.js\");\n const { DEV_PHRASE, mnemonicToMiniSecret } = await import(\"@polkadot-labs/hdkd-helpers\");\n const { toHex } = await import(\"@polkadot-api/utils\");\n\n let storageDir: string;\n beforeEach(() => {\n storageDir = mkdtempSync(pathJoin(tmpdir(), \"host-signer-test-\"));\n return () => rmSync(storageDir, { recursive: true, force: true });\n });\n\n function fakeAdapter(appId: string): TerminalAdapter {\n return { appId, storageDir } as unknown as TerminalAdapter;\n }\n\n function knownSecret(): {\n secret: Uint8Array;\n publicKey: Uint8Array;\n hex: string;\n sign: (msg: Uint8Array) => Uint8Array;\n } {\n // Deterministic across runs — uses the standard substrate dev\n // mnemonic. `mnemonicToMiniSecret` returns the 32-byte mini-secret\n // form (one of the two shapes `createSlotAccountSigner` accepts).\n const secret = mnemonicToMiniSecret(DEV_PHRASE);\n const derive = createDerive({ seed: secret, curve: sr25519, derive: sr25519Derive });\n const keypair = derive(\"\");\n return {\n secret,\n publicKey: keypair.publicKey,\n hex: toHex(secret),\n sign: (msg) => keypair.sign(msg),\n };\n }\n\n describe(\"createSlotAccountSigner\", () => {\n test(\"returns null when no allocation is cached\", async () => {\n const adapter = fakeAdapter(\"p\");\n const signer = await createSlotAccountSigner(adapter, {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n expect(signer).toBeNull();\n });\n\n test(\"returns a signer with the sr25519 public key derived from the cached secret\", async () => {\n const { hex, publicKey } = knownSecret();\n await saveCache(\n \"p\",\n {\n version: 1,\n entries: {\n BulletInAllowance: { tag: \"BulletInAllowance\", slotAccountKey: hex },\n },\n },\n storageDir,\n );\n\n const signer = await createSlotAccountSigner(fakeAdapter(\"p\"), {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n expect(signer).not.toBeNull();\n expect(signer?.publicKey).toEqual(publicKey);\n });\n\n test(\"signs locally and the signature verifies against the cached account\", async () => {\n const { hex, publicKey } = knownSecret();\n await saveCache(\n \"p\",\n {\n version: 1,\n entries: {\n BulletInAllowance: { tag: \"BulletInAllowance\", slotAccountKey: hex },\n },\n },\n storageDir,\n );\n\n const signer = await createSlotAccountSigner(fakeAdapter(\"p\"), {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n // `signBytes` wraps with `<Bytes>...</Bytes>` like all PAPI\n // PolkadotSigners do; for direct payload signing PAPI calls\n // `signTx` which routes through the same `sign` callback we\n // installed. Test that callback directly by reaching for the\n // typed surface: PAPI exposes signTx but the easiest direct\n // verification is `signBytes` which is wired through our sign.\n const payload = new TextEncoder().encode(\"hello slot\");\n // signBytes wraps in <Bytes>...</Bytes> before calling our sign.\n // To verify, reconstruct the wrap manually.\n const wrapped = new Uint8Array([\n ...new TextEncoder().encode(\"<Bytes>\"),\n ...payload,\n ...new TextEncoder().encode(\"</Bytes>\"),\n ]);\n const signature = await signer!.signBytes(payload);\n expect(sr25519.verify(signature, wrapped, publicKey)).toBe(true);\n });\n\n test(\"64-byte expanded-secret form produces a valid signer\", async () => {\n // Canonical schnorrkel to_bytes() for DEV_PHRASE (scalar + nonce), the mobile wire form.\n const SECRET_64_HEX =\n \"0x05d65584630d16cd4af6d0bec10f34bb504a5dcb62dba2122d49f5a663763d0afd190cce74df356432b410bd64682309d6dedb27c76845daf388557cbac3ca34\";\n\n await saveCache(\n \"p\",\n {\n version: 1,\n entries: {\n BulletInAllowance: {\n tag: \"BulletInAllowance\",\n slotAccountKey: SECRET_64_HEX,\n },\n },\n },\n storageDir,\n );\n\n const signer = await createSlotAccountSigner(fakeAdapter(\"p\"), {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n expect(signer).not.toBeNull();\n\n // Public key from the 64-byte path matches the 32-byte path's\n // — same logical key, two encodings.\n const { publicKey: pubFrom32Bytes } = knownSecret();\n expect(signer?.publicKey).toEqual(pubFrom32Bytes);\n\n // Signature from the 64-byte branch verifies against the same\n // public key.\n const payload = new TextEncoder().encode(\"hello 64-byte path\");\n const wrapped = new Uint8Array([\n ...new TextEncoder().encode(\"<Bytes>\"),\n ...payload,\n ...new TextEncoder().encode(\"</Bytes>\"),\n ]);\n const sig = await signer!.signBytes(payload);\n expect(sr25519.verify(sig, wrapped, pubFrom32Bytes)).toBe(true);\n });\n\n test(\"works for StatementStoreAllowance the same way\", async () => {\n const { hex, publicKey } = knownSecret();\n await saveCache(\n \"p\",\n {\n version: 1,\n entries: {\n StatementStoreAllowance: {\n tag: \"StatementStoreAllowance\",\n slotAccountKey: hex,\n },\n },\n },\n storageDir,\n );\n\n const signer = await createSlotAccountSigner(fakeAdapter(\"p\"), {\n tag: \"StatementStoreAllowance\",\n value: undefined,\n });\n expect(signer?.publicKey).toEqual(publicKey);\n });\n\n test(\"throws for SmartContractAllowance (no slot account key)\", async () => {\n await saveCache(\n \"p\",\n {\n version: 1,\n entries: {\n \"SmartContractAllowance::5\": { tag: \"SmartContractAllowance\", dest: 5 },\n },\n },\n storageDir,\n );\n\n await expect(\n createSlotAccountSigner(fakeAdapter(\"p\"), {\n tag: \"SmartContractAllowance\",\n value: 5,\n }),\n ).rejects.toThrow(/SmartContractAllowance does not carry a slot account key/);\n });\n\n test(\"throws for AutoSigning (subtree key, not slot account)\", async () => {\n await saveCache(\n \"p\",\n {\n version: 1,\n entries: {\n AutoSigning: {\n tag: \"AutoSigning\",\n productDerivationSecret: \"secret\",\n productRootPrivateKey: \"0xabcd\",\n },\n },\n },\n storageDir,\n );\n\n await expect(\n createSlotAccountSigner(fakeAdapter(\"p\"), {\n tag: \"AutoSigning\",\n value: undefined,\n }),\n ).rejects.toThrow(/AutoSigning does not carry a slot account key/);\n });\n\n test(\"different appIds yield different signers from disjoint caches\", async () => {\n const a = knownSecret();\n // Distinct mini-secret → distinct derived keypair.\n const otherMiniSecret = new Uint8Array(32).fill(0x07);\n const otherKeypair = createDerive({\n seed: otherMiniSecret,\n curve: sr25519,\n derive: sr25519Derive,\n })(\"\");\n\n await saveCache(\n \"app-a\",\n {\n version: 1,\n entries: {\n BulletInAllowance: { tag: \"BulletInAllowance\", slotAccountKey: a.hex },\n },\n },\n storageDir,\n );\n await saveCache(\n \"app-b\",\n {\n version: 1,\n entries: {\n BulletInAllowance: {\n tag: \"BulletInAllowance\",\n slotAccountKey: toHex(otherMiniSecret),\n },\n },\n },\n storageDir,\n );\n\n const signerA = await createSlotAccountSigner(fakeAdapter(\"app-a\"), {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n const signerB = await createSlotAccountSigner(fakeAdapter(\"app-b\"), {\n tag: \"BulletInAllowance\",\n value: undefined,\n });\n expect(signerA?.publicKey).toEqual(a.publicKey);\n expect(signerB?.publicKey).toEqual(otherKeypair.publicKey);\n expect(signerA?.publicKey).not.toEqual(signerB?.publicKey);\n });\n\n test(\"rejects an unexpected slotAccountKey length\", async () => {\n // Variable-length codec on the wire, but our keypair builder\n // only recognizes the standard 32 / 64 byte forms.\n await saveCache(\n \"p\",\n {\n version: 1,\n entries: {\n BulletInAllowance: {\n tag: \"BulletInAllowance\",\n slotAccountKey: toHex(new Uint8Array(48).fill(1)),\n },\n },\n },\n storageDir,\n );\n\n await expect(\n createSlotAccountSigner(fakeAdapter(\"p\"), {\n tag: \"BulletInAllowance\",\n value: undefined,\n }),\n ).rejects.toThrow(/unexpected slotAccountKey length 48/);\n });\n });\n}\n"],"mappings":"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","names":["createLogger","toHex","fakeAdapter","knownSecret","saveCache","log","createLogger","log","fakeAdapter","makeSession","toHex","req"]}
|
package/dist/testing.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ interface CreateTestSessionOptions {
|
|
|
18
18
|
/** Stable session id. Default: random nanoid(12). */
|
|
19
19
|
sessionId?: string;
|
|
20
20
|
/**
|
|
21
|
-
* Whether to write the encrypted `
|
|
21
|
+
* Whether to write the encrypted `UserSecretsV2_<id>` file. Default: `true`.
|
|
22
22
|
* Set to `false` to exercise recovery paths where a session exists on disk
|
|
23
23
|
* but its secrets are missing.
|
|
24
24
|
*/
|
|
@@ -65,9 +65,9 @@ interface TestSession {
|
|
|
65
65
|
* tracked via on-chain attestation state. See above for how expiry-path
|
|
66
66
|
* tests still work in practice.
|
|
67
67
|
* - **Corrupted-session cases** don't need a helper — write garbage to
|
|
68
|
-
* `<storageDir>/<appId>
|
|
68
|
+
* `<storageDir>/<appId>_SsoSessionsV2.json` with `fs.writeFile` directly.
|
|
69
69
|
* - **Repeated calls replace the session list.** Each call writes a fresh
|
|
70
|
-
* single-entry `
|
|
70
|
+
* single-entry `SsoSessionsV2` file, so calling twice on the same
|
|
71
71
|
* `storageDir`+`appId` leaves only the second session on disk. Use a
|
|
72
72
|
* fresh `mkdtempSync` per test to keep cases isolated.
|
|
73
73
|
*
|
package/dist/testing.js
CHANGED
|
@@ -25,20 +25,22 @@ import {
|
|
|
25
25
|
import { mkdir, writeFile } from "fs/promises";
|
|
26
26
|
import { join } from "path";
|
|
27
27
|
import { nanoid } from "nanoid";
|
|
28
|
-
import { Bytes,
|
|
28
|
+
import { Bytes, Struct, Vector, str } from "scale-ts";
|
|
29
29
|
var storedUserSessionCodec = Struct({
|
|
30
30
|
id: str,
|
|
31
31
|
localAccount: LocalSessionAccountCodec,
|
|
32
32
|
remoteAccount: RemoteSessionAccountCodec,
|
|
33
33
|
rootAccountId: AccountIdCodec,
|
|
34
|
-
identityAccountId:
|
|
35
|
-
identityChatPublicKey:
|
|
34
|
+
identityAccountId: AccountIdCodec,
|
|
35
|
+
identityChatPublicKey: Bytes(65),
|
|
36
|
+
ssoEncPubKey: Bytes(65),
|
|
37
|
+
rootEntropySource: Bytes(32)
|
|
36
38
|
});
|
|
37
39
|
var sessionsCodec = Vector(storedUserSessionCodec);
|
|
38
40
|
var storedUserSecretsCodec = Struct({
|
|
39
41
|
ssSecret: Bytes(),
|
|
40
42
|
encrSecret: Bytes(),
|
|
41
|
-
|
|
43
|
+
identityChatPrivateKey: Bytes(32)
|
|
42
44
|
});
|
|
43
45
|
function encryptSecrets(appId, plaintext) {
|
|
44
46
|
const key = blake2b(new TextEncoder().encode(appId), { dkLen: 16 });
|
|
@@ -74,13 +76,14 @@ async function createTestSession(options) {
|
|
|
74
76
|
sharedSecret,
|
|
75
77
|
void 0
|
|
76
78
|
),
|
|
77
|
-
// synthesized session: the remote (wallet) account doubles as root.
|
|
78
79
|
rootAccountId: createAccountId(remotePublicKey),
|
|
79
|
-
identityAccountId:
|
|
80
|
-
identityChatPublicKey:
|
|
80
|
+
identityAccountId: createAccountId(remotePublicKey),
|
|
81
|
+
identityChatPublicKey: remoteEncrPublicKey,
|
|
82
|
+
ssoEncPubKey: remoteEncrPublicKey,
|
|
83
|
+
rootEntropySource: remoteEntropy
|
|
81
84
|
};
|
|
82
85
|
await writeFile(
|
|
83
|
-
join(options.storageDir, `${sanitizeKey(options.appId, "
|
|
86
|
+
join(options.storageDir, `${sanitizeKey(options.appId, "SsoSessionsV2")}.json`),
|
|
84
87
|
toHex(sessionsCodec.enc([session])),
|
|
85
88
|
"utf-8"
|
|
86
89
|
);
|
|
@@ -89,12 +92,12 @@ async function createTestSession(options) {
|
|
|
89
92
|
const encoded = storedUserSecretsCodec.enc({
|
|
90
93
|
ssSecret: localSecret,
|
|
91
94
|
encrSecret: localEncrSecret,
|
|
92
|
-
|
|
95
|
+
identityChatPrivateKey: localEncrSecret
|
|
93
96
|
});
|
|
94
97
|
await writeFile(
|
|
95
98
|
join(
|
|
96
99
|
options.storageDir,
|
|
97
|
-
`${sanitizeKey(options.appId, `
|
|
100
|
+
`${sanitizeKey(options.appId, `UserSecretsV2_${sessionId}`)}.json`
|
|
98
101
|
),
|
|
99
102
|
encryptSecrets(options.appId, encoded),
|
|
100
103
|
"utf-8"
|
|
@@ -128,22 +131,22 @@ if (void 0) {
|
|
|
128
131
|
}
|
|
129
132
|
});
|
|
130
133
|
describe("createTestSession", () => {
|
|
131
|
-
test("writes both
|
|
134
|
+
test("writes both SsoSessionsV2 and UserSecretsV2 files by default", async () => {
|
|
132
135
|
const result = await createTestSession({
|
|
133
136
|
appId: "my-app",
|
|
134
137
|
storageDir,
|
|
135
138
|
localMnemonic: LOCAL_MNEMONIC,
|
|
136
139
|
remoteMnemonic: REMOTE_MNEMONIC
|
|
137
140
|
});
|
|
138
|
-
const sessions = await readFile(join(storageDir, "my-
|
|
141
|
+
const sessions = await readFile(join(storageDir, "my-app_SsoSessionsV2.json"), "utf-8");
|
|
139
142
|
expect(sessions).toMatch(/^0x[0-9a-f]+$/);
|
|
140
143
|
const secrets = await readFile(
|
|
141
|
-
join(storageDir, `my-
|
|
144
|
+
join(storageDir, `my-app_UserSecretsV2_${result.sessionId}.json`),
|
|
142
145
|
"utf-8"
|
|
143
146
|
);
|
|
144
147
|
expect(secrets).toMatch(/^0x[0-9a-f]+$/);
|
|
145
148
|
});
|
|
146
|
-
test("omits
|
|
149
|
+
test("omits UserSecretsV2 file when includeSecrets is false", async () => {
|
|
147
150
|
const result = await createTestSession({
|
|
148
151
|
appId: "my-app",
|
|
149
152
|
storageDir,
|
|
@@ -152,13 +155,16 @@ if (void 0) {
|
|
|
152
155
|
includeSecrets: false
|
|
153
156
|
});
|
|
154
157
|
await expect(
|
|
155
|
-
readFile(join(storageDir, "my-
|
|
158
|
+
readFile(join(storageDir, "my-app_SsoSessionsV2.json"), "utf-8")
|
|
156
159
|
).resolves.toMatch(/^0x/);
|
|
157
160
|
await expect(
|
|
158
|
-
readFile(
|
|
161
|
+
readFile(
|
|
162
|
+
join(storageDir, `my-app_UserSecretsV2_${result.sessionId}.json`),
|
|
163
|
+
"utf-8"
|
|
164
|
+
)
|
|
159
165
|
).rejects.toThrow(/ENOENT/);
|
|
160
166
|
});
|
|
161
|
-
test("
|
|
167
|
+
test("SsoSessionsV2 file decodes with the host-papp session codec shape", async () => {
|
|
162
168
|
const result = await createTestSession({
|
|
163
169
|
appId: "my-app",
|
|
164
170
|
storageDir,
|
|
@@ -166,7 +172,7 @@ if (void 0) {
|
|
|
166
172
|
remoteMnemonic: REMOTE_MNEMONIC,
|
|
167
173
|
sessionId: "stable-test-id"
|
|
168
174
|
});
|
|
169
|
-
const hex = await readFile(join(storageDir, "my-
|
|
175
|
+
const hex = await readFile(join(storageDir, "my-app_SsoSessionsV2.json"), "utf-8");
|
|
170
176
|
const decoded = sessionsCodec.dec(fromHex(hex));
|
|
171
177
|
expect(decoded).toHaveLength(1);
|
|
172
178
|
expect(decoded[0].id).toBe("stable-test-id");
|
|
@@ -189,7 +195,7 @@ if (void 0) {
|
|
|
189
195
|
expect(result.localAccountId).toEqual(expected);
|
|
190
196
|
expect(result.localAccountId).toHaveLength(32);
|
|
191
197
|
});
|
|
192
|
-
test("
|
|
198
|
+
test("UserSecretsV2 file decrypts and decodes with the host-papp secret codec shape", async () => {
|
|
193
199
|
const appId = "my-app";
|
|
194
200
|
const result = await createTestSession({
|
|
195
201
|
appId,
|
|
@@ -198,25 +204,25 @@ if (void 0) {
|
|
|
198
204
|
remoteMnemonic: REMOTE_MNEMONIC
|
|
199
205
|
});
|
|
200
206
|
const hex = await readFile(
|
|
201
|
-
join(storageDir, `${appId}
|
|
207
|
+
join(storageDir, `${appId}_UserSecretsV2_${result.sessionId}.json`),
|
|
202
208
|
"utf-8"
|
|
203
209
|
);
|
|
204
210
|
const key = blake2b(new TextEncoder().encode(appId), { dkLen: 16 });
|
|
205
211
|
const nonce = blake2b(new TextEncoder().encode("nonce"), { dkLen: 32 });
|
|
206
212
|
const decrypted = gcm(key, nonce).decrypt(fromHex(hex));
|
|
207
213
|
const decoded = storedUserSecretsCodec.dec(decrypted);
|
|
208
|
-
expect(decoded.entropy).toEqual(mnemonicToEntropy(LOCAL_MNEMONIC));
|
|
209
214
|
expect(decoded.ssSecret).toHaveLength(64);
|
|
210
215
|
expect(decoded.encrSecret).toHaveLength(32);
|
|
216
|
+
expect(decoded.identityChatPrivateKey).toHaveLength(32);
|
|
211
217
|
});
|
|
212
218
|
test("different appIds produce files under different prefixes", async () => {
|
|
213
219
|
await createTestSession({ appId: "app-a", storageDir, sessionId: "id" });
|
|
214
220
|
await createTestSession({ appId: "app-b", storageDir, sessionId: "id" });
|
|
215
221
|
await expect(
|
|
216
|
-
readFile(join(storageDir, "app-
|
|
222
|
+
readFile(join(storageDir, "app-a_SsoSessionsV2.json"), "utf-8")
|
|
217
223
|
).resolves.toMatch(/^0x/);
|
|
218
224
|
await expect(
|
|
219
|
-
readFile(join(storageDir, "app-
|
|
225
|
+
readFile(join(storageDir, "app-b_SsoSessionsV2.json"), "utf-8")
|
|
220
226
|
).resolves.toMatch(/^0x/);
|
|
221
227
|
});
|
|
222
228
|
test("sanitizes appIds with special characters", async () => {
|
|
@@ -226,14 +232,14 @@ if (void 0) {
|
|
|
226
232
|
sessionId: "id"
|
|
227
233
|
});
|
|
228
234
|
await expect(
|
|
229
|
-
readFile(join(storageDir, "
|
|
235
|
+
readFile(join(storageDir, "app_with_spaces_SsoSessionsV2.json"), "utf-8")
|
|
230
236
|
).resolves.toMatch(/^0x/);
|
|
231
237
|
});
|
|
232
238
|
test("creates storageDir when it does not yet exist", async () => {
|
|
233
239
|
const nested = join(storageDir, "does", "not", "exist");
|
|
234
240
|
await createTestSession({ appId: "my-app", storageDir: nested });
|
|
235
241
|
await expect(
|
|
236
|
-
readFile(join(nested, "my-
|
|
242
|
+
readFile(join(nested, "my-app_SsoSessionsV2.json"), "utf-8")
|
|
237
243
|
).resolves.toMatch(/^0x/);
|
|
238
244
|
});
|
|
239
245
|
test("generates fresh mnemonics when none are supplied", async () => {
|
|
@@ -251,7 +257,7 @@ if (void 0) {
|
|
|
251
257
|
});
|
|
252
258
|
expect(result.sessionId).toBe("pinned-id");
|
|
253
259
|
await expect(
|
|
254
|
-
readFile(join(storageDir, "my-
|
|
260
|
+
readFile(join(storageDir, "my-app_UserSecretsV2_pinned-id.json"), "utf-8")
|
|
255
261
|
).resolves.toMatch(/^0x/);
|
|
256
262
|
});
|
|
257
263
|
test("respects a custom localDerivation", async () => {
|
|
@@ -279,15 +285,18 @@ if (void 0) {
|
|
|
279
285
|
storageDir,
|
|
280
286
|
sessionId: "second"
|
|
281
287
|
});
|
|
282
|
-
const hex = await readFile(join(storageDir, "my-
|
|
288
|
+
const hex = await readFile(join(storageDir, "my-app_SsoSessionsV2.json"), "utf-8");
|
|
283
289
|
const decoded = sessionsCodec.dec(fromHex(hex));
|
|
284
290
|
expect(decoded).toHaveLength(1);
|
|
285
291
|
expect(decoded[0].id).toBe("second");
|
|
286
292
|
await expect(
|
|
287
|
-
readFile(join(storageDir, `my-
|
|
293
|
+
readFile(join(storageDir, `my-app_UserSecretsV2_${first.sessionId}.json`), "utf-8")
|
|
288
294
|
).resolves.toMatch(/^0x/);
|
|
289
295
|
await expect(
|
|
290
|
-
readFile(
|
|
296
|
+
readFile(
|
|
297
|
+
join(storageDir, `my-app_UserSecretsV2_${second.sessionId}.json`),
|
|
298
|
+
"utf-8"
|
|
299
|
+
)
|
|
291
300
|
).resolves.toMatch(/^0x/);
|
|
292
301
|
});
|
|
293
302
|
test("AccountIdCodec is Bytes(32) \u2014 keep this invariant if upstream changes", () => {
|
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/testing.ts"],"sourcesContent":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Test helpers for synthesizing persisted sessions.\n *\n * Production sessions are persisted to the storage directory by\n * `createNodeStorageAdapter` in the SCALE-encoded / AES-GCM-encrypted\n * format defined by `@novasamatech/host-papp`'s internal repositories.\n * There is no public API on the adapter to write a session without going\n * through the QR pairing + on-chain attestation flow, which makes E2E\n * testing of session-dependent CLI flows impossible without a real phone.\n *\n * This module mirrors that format using only public primitives, so E2E\n * tests can inject a known-good session file. The `testing.test.ts`\n * integration test reads the synthesized files back through the real\n * `ssoSessionRepository` + `userSecretRepository` from `@novasamatech/host-papp`\n * and fails if the upstream format ever drifts from our reproduction.\n */\nimport { gcm } from \"@noble/ciphers/aes.js\";\nimport { p256 } from \"@noble/curves/nist.js\";\nimport { blake2b } from \"@noble/hashes/blake2.js\";\nimport {\n AccountIdCodec,\n LocalSessionAccountCodec,\n RemoteSessionAccountCodec,\n createAccountId,\n createLocalSessionAccount,\n createRemoteSessionAccount,\n createSr25519Secret,\n deriveSr25519PublicKey,\n} from \"@novasamatech/statement-store\";\nimport { toHex } from \"@polkadot-api/utils\";\nimport {\n entropyToMiniSecret,\n generateMnemonic,\n mnemonicToEntropy,\n} from \"@polkadot-labs/hdkd-helpers\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { nanoid } from \"nanoid\";\nimport { Bytes, type Codec, Option, Struct, Vector, str } from \"scale-ts\";\nimport type { StoredUserSession } from \"@novasamatech/host-papp\";\n\nimport { sanitizeKey } from \"./node-storage.js\";\n\n// Mirror of host-papp's internal stored-session codec (not exported — only the\n// `StoredUserSession` type is). `satisfies` guards drift at build time, the\n// interop test at runtime; field order and the `Bytes(65)` chat key must match.\nconst storedUserSessionCodec = Struct({\n id: str,\n localAccount: LocalSessionAccountCodec,\n remoteAccount: RemoteSessionAccountCodec,\n rootAccountId: AccountIdCodec,\n identityAccountId: Option(AccountIdCodec),\n identityChatPublicKey: Option(Bytes(65)),\n}) satisfies Codec<StoredUserSession>;\nconst sessionsCodec = Vector(storedUserSessionCodec);\n\n// Mirrors the internal StoredUserSecretsCodec in host-papp's userSecretRepository.\nconst storedUserSecretsCodec = Struct({\n ssSecret: Bytes(),\n encrSecret: Bytes(),\n entropy: Bytes(),\n});\n\n// Mirrors host-papp's userSecretRepository AES-GCM wrapper: the encryption\n// key is blake2b(appId, 16) and the nonce is blake2b(\"nonce\", 32). The appId\n// doubles as the salt, so sessions written by this helper are only readable\n// by a TerminalAdapter configured with the same appId.\nfunction encryptSecrets(appId: string, plaintext: Uint8Array): string {\n const key = blake2b(new TextEncoder().encode(appId), { dkLen: 16 });\n const nonce = blake2b(new TextEncoder().encode(\"nonce\"), { dkLen: 32 });\n return toHex(gcm(key, nonce).encrypt(plaintext));\n}\n\n/** Mirrors host-papp's createEncrSecret: pad the mini-secret to 48 bytes and feed it to P256 keygen. */\nfunction p256SecretFromEntropy(entropy: Uint8Array): Uint8Array {\n const seed = new Uint8Array(48);\n seed.set(entropyToMiniSecret(entropy));\n return p256.keygen(seed).secretKey;\n}\n\nexport interface CreateTestSessionOptions {\n /** Unique app identifier. Must match the one passed to `createTerminalAdapter`. */\n appId: string;\n /**\n * Directory where session files are written. Must match the one the\n * CLI reads from — pass the same value to `createTerminalAdapter`'s\n * `storageDir` option.\n */\n storageDir: string;\n /** BIP39 mnemonic for the local (host-side) account. Default: freshly generated. */\n localMnemonic?: string;\n /** Derivation path for the local account. Default: `\"//wallet//sso\"` (matches production). */\n localDerivation?: string;\n /** BIP39 mnemonic for the remote (phone) account. Default: freshly generated. */\n remoteMnemonic?: string;\n /** Derivation path for the remote account. Default: `\"\"`. */\n remoteDerivation?: string;\n /** Stable session id. Default: random nanoid(12). */\n sessionId?: string;\n /**\n * Whether to write the encrypted `UserSecrets_<id>` file. Default: `true`.\n * Set to `false` to exercise recovery paths where a session exists on disk\n * but its secrets are missing.\n */\n includeSecrets?: boolean;\n}\n\nexport interface TestSession {\n sessionId: string;\n /** Sr25519 public key of the local (host) account, 32 bytes. */\n localAccountId: Uint8Array;\n /** Sr25519 public key of the remote (phone) account, 32 bytes. */\n remoteAccountId: Uint8Array;\n localMnemonic: string;\n localDerivation: string;\n remoteMnemonic: string;\n remoteDerivation: string;\n}\n\n/**\n * Write a valid persisted session to `storageDir`, as if the user had\n * completed QR pairing + attestation. A `TerminalAdapter` created with the\n * same `appId` + `storageDir` will emit the synthesized session from\n * `adapter.sessions.sessions`.\n *\n * @remarks\n * **Dev utility — not a stable contract.** This helper hand-rolls SCALE codecs\n * that mirror an internal on-disk format used by `@novasamatech/host-papp`'s\n * session repositories. The format is not part of host-papp's documented public\n * API and may change on minor version bumps. The `testing.interop.test.ts`\n * round-trip test fails loudly when the format drifts. Use this only in test code.\n *\n * ## Limits\n *\n * - **Signing does not round-trip.** Sending a request via `session.signRaw`\n * still goes out over the statement store and expects a real phone to\n * respond. Use this helper for flows that test session discovery,\n * persistence, and logout — not end-to-end signing.\n * - **Signing attempts surface `NoAllowanceError`.** The synthesized local\n * account was never registered on the People chain, so the first write\n * to the statement store fails with `NoAllowanceError`. That's the same\n * error path the CLI sees when a previously valid session's on-chain\n * attestation has expired, so tests that assert \"CLI handles an expired\n * session\" *can* be written against a synthesized session even though\n * there's no `expiresAt` knob to turn.\n * - **No `expiresAt`.** The on-disk codec has no expiry field; validity is\n * tracked via on-chain attestation state. See above for how expiry-path\n * tests still work in practice.\n * - **Corrupted-session cases** don't need a helper — write garbage to\n * `<storageDir>/<appId>_SsoSessions.json` with `fs.writeFile` directly.\n * - **Repeated calls replace the session list.** Each call writes a fresh\n * single-entry `SsoSessions` file, so calling twice on the same\n * `storageDir`+`appId` leaves only the second session on disk. Use a\n * fresh `mkdtempSync` per test to keep cases isolated.\n *\n * @example\n * ```ts\n * import { mkdtempSync } from \"node:fs\";\n * import { tmpdir } from \"node:os\";\n * import { join } from \"node:path\";\n * import { createTestSession } from \"@parity/product-sdk-terminal/testing\";\n * import { createTerminalAdapter, waitForSessions } from \"@parity/product-sdk-terminal\";\n *\n * const storageDir = mkdtempSync(join(tmpdir(), \"e2e-\"));\n * const { sessionId } = await createTestSession({ appId: \"dot-cli\", storageDir });\n *\n * const adapter = createTerminalAdapter({ appId: \"dot-cli\", storageDir });\n * const sessions = await waitForSessions(adapter);\n * // sessions[0].id === sessionId\n * ```\n */\nexport async function createTestSession(options: CreateTestSessionOptions): Promise<TestSession> {\n await mkdir(options.storageDir, { recursive: true });\n\n const localMnemonic = options.localMnemonic ?? generateMnemonic();\n const localDerivation = options.localDerivation ?? \"//wallet//sso\";\n const localEntropy = mnemonicToEntropy(localMnemonic);\n const localSecret = createSr25519Secret(localEntropy, localDerivation);\n const localPublicKey = deriveSr25519PublicKey(localSecret);\n const localEncrSecret = p256SecretFromEntropy(localEntropy);\n\n const remoteMnemonic = options.remoteMnemonic ?? generateMnemonic();\n const remoteDerivation = options.remoteDerivation ?? \"\";\n const remoteEntropy = mnemonicToEntropy(remoteMnemonic);\n const remoteSecret = createSr25519Secret(remoteEntropy, remoteDerivation);\n const remotePublicKey = deriveSr25519PublicKey(remoteSecret);\n const remoteEncrPublicKey = p256.getPublicKey(p256SecretFromEntropy(remoteEntropy), false);\n\n // In production, `remoteAccount.publicKey` is the ECDH shared secret\n // between the host's P256 encryption key and the phone's P256 encryption\n // key. We compute the same thing from two mnemonic-derived P256 keys so\n // the synthesized session is cryptographically well-formed.\n const sharedSecret = p256.getSharedSecret(localEncrSecret, remoteEncrPublicKey).slice(1, 33);\n\n const sessionId = options.sessionId ?? nanoid(12);\n\n const session = {\n id: sessionId,\n localAccount: createLocalSessionAccount(createAccountId(localPublicKey), undefined),\n remoteAccount: createRemoteSessionAccount(\n createAccountId(remotePublicKey),\n sharedSecret,\n undefined,\n ),\n // synthesized session: the remote (wallet) account doubles as root.\n rootAccountId: createAccountId(remotePublicKey),\n identityAccountId: undefined,\n identityChatPublicKey: undefined,\n };\n\n await writeFile(\n join(options.storageDir, `${sanitizeKey(options.appId, \"SsoSessions\")}.json`),\n toHex(sessionsCodec.enc([session])),\n \"utf-8\",\n );\n\n const includeSecrets = options.includeSecrets ?? true;\n if (includeSecrets) {\n const encoded = storedUserSecretsCodec.enc({\n ssSecret: localSecret,\n encrSecret: localEncrSecret,\n entropy: localEntropy,\n });\n await writeFile(\n join(\n options.storageDir,\n `${sanitizeKey(options.appId, `UserSecrets_${sessionId}`)}.json`,\n ),\n encryptSecrets(options.appId, encoded),\n \"utf-8\",\n );\n }\n\n return {\n sessionId,\n localAccountId: localPublicKey,\n remoteAccountId: remotePublicKey,\n localMnemonic,\n localDerivation,\n remoteMnemonic,\n remoteDerivation,\n };\n}\n\nif (import.meta.vitest) {\n const { describe, test, expect, beforeEach, afterAll } = import.meta.vitest;\n const { mkdtemp, readFile, rm } = await import(\"node:fs/promises\");\n const { tmpdir } = await import(\"node:os\");\n const { fromHex } = await import(\"@polkadot-api/utils\");\n\n // Stable dev mnemonics — deterministic inputs keep the tests hermetic.\n // The first is the well-known Substrate dev root (the source of `//Alice`\n // et al.); the second is the BIP39 all-abandon test vector.\n const LOCAL_MNEMONIC = \"bottom drive obey lake curtain smoke basket hold race lonely fit walk\";\n const REMOTE_MNEMONIC =\n \"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about\";\n\n let storageDir: string;\n\n beforeEach(async () => {\n storageDir = await mkdtemp(join(tmpdir(), \"terminal-testing-\"));\n });\n\n afterAll(async () => {\n try {\n await rm(storageDir, { recursive: true });\n } catch {\n /* ignore */\n }\n });\n\n describe(\"createTestSession\", () => {\n test(\"writes both SsoSessions and UserSecrets files by default\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n });\n\n const sessions = await readFile(join(storageDir, \"my-app_SsoSessions.json\"), \"utf-8\");\n expect(sessions).toMatch(/^0x[0-9a-f]+$/);\n\n const secrets = await readFile(\n join(storageDir, `my-app_UserSecrets_${result.sessionId}.json`),\n \"utf-8\",\n );\n expect(secrets).toMatch(/^0x[0-9a-f]+$/);\n });\n\n test(\"omits UserSecrets file when includeSecrets is false\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n includeSecrets: false,\n });\n\n await expect(\n readFile(join(storageDir, \"my-app_SsoSessions.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n\n await expect(\n readFile(join(storageDir, `my-app_UserSecrets_${result.sessionId}.json`), \"utf-8\"),\n ).rejects.toThrow(/ENOENT/);\n });\n\n test(\"SsoSessions file decodes with the host-papp session codec shape\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n sessionId: \"stable-test-id\",\n });\n\n const hex = await readFile(join(storageDir, \"my-app_SsoSessions.json\"), \"utf-8\");\n const decoded = sessionsCodec.dec(fromHex(hex));\n\n expect(decoded).toHaveLength(1);\n expect(decoded[0].id).toBe(\"stable-test-id\");\n expect(decoded[0].localAccount.accountId).toEqual(result.localAccountId);\n expect(decoded[0].localAccount.pin).toBeUndefined();\n expect(decoded[0].remoteAccount.accountId).toEqual(result.remoteAccountId);\n expect(decoded[0].remoteAccount.publicKey).toHaveLength(32);\n expect(decoded[0].remoteAccount.pin).toBeUndefined();\n });\n\n test(\"localAccountId matches the Sr25519 public key derived from the mnemonic\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n });\n\n const expected = deriveSr25519PublicKey(\n createSr25519Secret(mnemonicToEntropy(LOCAL_MNEMONIC), \"//wallet//sso\"),\n );\n expect(result.localAccountId).toEqual(expected);\n expect(result.localAccountId).toHaveLength(32);\n });\n\n test(\"UserSecrets file decrypts and decodes with the host-papp secret codec shape\", async () => {\n const appId = \"my-app\";\n const result = await createTestSession({\n appId,\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n });\n\n const hex = await readFile(\n join(storageDir, `${appId}_UserSecrets_${result.sessionId}.json`),\n \"utf-8\",\n );\n const key = blake2b(new TextEncoder().encode(appId), { dkLen: 16 });\n const nonce = blake2b(new TextEncoder().encode(\"nonce\"), { dkLen: 32 });\n const decrypted = gcm(key, nonce).decrypt(fromHex(hex));\n const decoded = storedUserSecretsCodec.dec(decrypted);\n\n expect(decoded.entropy).toEqual(mnemonicToEntropy(LOCAL_MNEMONIC));\n // Sr25519 secret is 64 bytes (32-byte secret + 32-byte nonce).\n expect(decoded.ssSecret).toHaveLength(64);\n // P256 secret is 32 bytes.\n expect(decoded.encrSecret).toHaveLength(32);\n });\n\n test(\"different appIds produce files under different prefixes\", async () => {\n await createTestSession({ appId: \"app-a\", storageDir, sessionId: \"id\" });\n await createTestSession({ appId: \"app-b\", storageDir, sessionId: \"id\" });\n\n await expect(\n readFile(join(storageDir, \"app-a_SsoSessions.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n await expect(\n readFile(join(storageDir, \"app-b_SsoSessions.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"sanitizes appIds with special characters\", async () => {\n await createTestSession({\n appId: \"app/with spaces\",\n storageDir,\n sessionId: \"id\",\n });\n await expect(\n readFile(join(storageDir, \"app_with_spaces_SsoSessions.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"creates storageDir when it does not yet exist\", async () => {\n const nested = join(storageDir, \"does\", \"not\", \"exist\");\n await createTestSession({ appId: \"my-app\", storageDir: nested });\n await expect(\n readFile(join(nested, \"my-app_SsoSessions.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"generates fresh mnemonics when none are supplied\", async () => {\n const a = await createTestSession({ appId: \"a\", storageDir });\n const b = await createTestSession({ appId: \"b\", storageDir });\n expect(a.localMnemonic).not.toBe(b.localMnemonic);\n expect(a.remoteMnemonic).not.toBe(b.remoteMnemonic);\n expect(a.sessionId).not.toBe(b.sessionId);\n });\n\n test(\"respects an explicit sessionId\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n sessionId: \"pinned-id\",\n });\n expect(result.sessionId).toBe(\"pinned-id\");\n await expect(\n readFile(join(storageDir, \"my-app_UserSecrets_pinned-id.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"respects a custom localDerivation\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n localDerivation: \"//custom//path\",\n });\n expect(result.localDerivation).toBe(\"//custom//path\");\n const expected = deriveSr25519PublicKey(\n createSr25519Secret(mnemonicToEntropy(LOCAL_MNEMONIC), \"//custom//path\"),\n );\n expect(result.localAccountId).toEqual(expected);\n });\n\n test(\"repeated calls replace the session list\", async () => {\n // Documented behavior — second call leaves only the second session\n // on disk. Callers who want multiple sessions should use separate\n // storage dirs.\n const first = await createTestSession({\n appId: \"my-app\",\n storageDir,\n sessionId: \"first\",\n });\n const second = await createTestSession({\n appId: \"my-app\",\n storageDir,\n sessionId: \"second\",\n });\n\n const hex = await readFile(join(storageDir, \"my-app_SsoSessions.json\"), \"utf-8\");\n const decoded = sessionsCodec.dec(fromHex(hex));\n expect(decoded).toHaveLength(1);\n expect(decoded[0].id).toBe(\"second\");\n // The first session's UserSecrets file is left behind (not cleaned).\n // This matches a real logout flow as well, so we don't try to hide it.\n await expect(\n readFile(join(storageDir, `my-app_UserSecrets_${first.sessionId}.json`), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n await expect(\n readFile(join(storageDir, `my-app_UserSecrets_${second.sessionId}.json`), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"AccountIdCodec is Bytes(32) — keep this invariant if upstream changes\", () => {\n // Guards against silent upstream changes to the session-account\n // codec shape that would make our synthesized files unreadable.\n const encoded = AccountIdCodec.enc(createAccountId(new Uint8Array(32).fill(7)));\n expect(encoded).toHaveLength(32);\n });\n });\n}\n"],"mappings":";;;;;AAkBA,SAAS,WAAW;AACpB,SAAS,YAAY;AACrB,SAAS,eAAe;AACxB;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;AACP,SAAS,aAAa;AACtB;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,OACG;AACP,SAAS,OAAO,iBAAiB;AACjC,SAAS,YAAY;AACrB,SAAS,cAAc;AACvB,SAAS,OAAmB,QAAQ,QAAQ,QAAQ,WAAW;AAQ/D,IAAM,yBAAyB,OAAO;AAAA,EAClC,IAAI;AAAA,EACJ,cAAc;AAAA,EACd,eAAe;AAAA,EACf,eAAe;AAAA,EACf,mBAAmB,OAAO,cAAc;AAAA,EACxC,uBAAuB,OAAO,MAAM,EAAE,CAAC;AAC3C,CAAC;AACD,IAAM,gBAAgB,OAAO,sBAAsB;AAGnD,IAAM,yBAAyB,OAAO;AAAA,EAClC,UAAU,MAAM;AAAA,EAChB,YAAY,MAAM;AAAA,EAClB,SAAS,MAAM;AACnB,CAAC;AAMD,SAAS,eAAe,OAAe,WAA+B;AAClE,QAAM,MAAM,QAAQ,IAAI,YAAY,EAAE,OAAO,KAAK,GAAG,EAAE,OAAO,GAAG,CAAC;AAClE,QAAM,QAAQ,QAAQ,IAAI,YAAY,EAAE,OAAO,OAAO,GAAG,EAAE,OAAO,GAAG,CAAC;AACtE,SAAO,MAAM,IAAI,KAAK,KAAK,EAAE,QAAQ,SAAS,CAAC;AACnD;AAGA,SAAS,sBAAsB,SAAiC;AAC5D,QAAM,OAAO,IAAI,WAAW,EAAE;AAC9B,OAAK,IAAI,oBAAoB,OAAO,CAAC;AACrC,SAAO,KAAK,OAAO,IAAI,EAAE;AAC7B;AA6FA,eAAsB,kBAAkB,SAAyD;AAC7F,QAAM,MAAM,QAAQ,YAAY,EAAE,WAAW,KAAK,CAAC;AAEnD,QAAM,gBAAgB,QAAQ,iBAAiB,iBAAiB;AAChE,QAAM,kBAAkB,QAAQ,mBAAmB;AACnD,QAAM,eAAe,kBAAkB,aAAa;AACpD,QAAM,cAAc,oBAAoB,cAAc,eAAe;AACrE,QAAM,iBAAiB,uBAAuB,WAAW;AACzD,QAAM,kBAAkB,sBAAsB,YAAY;AAE1D,QAAM,iBAAiB,QAAQ,kBAAkB,iBAAiB;AAClE,QAAM,mBAAmB,QAAQ,oBAAoB;AACrD,QAAM,gBAAgB,kBAAkB,cAAc;AACtD,QAAM,eAAe,oBAAoB,eAAe,gBAAgB;AACxE,QAAM,kBAAkB,uBAAuB,YAAY;AAC3D,QAAM,sBAAsB,KAAK,aAAa,sBAAsB,aAAa,GAAG,KAAK;AAMzF,QAAM,eAAe,KAAK,gBAAgB,iBAAiB,mBAAmB,EAAE,MAAM,GAAG,EAAE;AAE3F,QAAM,YAAY,QAAQ,aAAa,OAAO,EAAE;AAEhD,QAAM,UAAU;AAAA,IACZ,IAAI;AAAA,IACJ,cAAc,0BAA0B,gBAAgB,cAAc,GAAG,MAAS;AAAA,IAClF,eAAe;AAAA,MACX,gBAAgB,eAAe;AAAA,MAC/B;AAAA,MACA;AAAA,IACJ;AAAA;AAAA,IAEA,eAAe,gBAAgB,eAAe;AAAA,IAC9C,mBAAmB;AAAA,IACnB,uBAAuB;AAAA,EAC3B;AAEA,QAAM;AAAA,IACF,KAAK,QAAQ,YAAY,GAAG,YAAY,QAAQ,OAAO,aAAa,CAAC,OAAO;AAAA,IAC5E,MAAM,cAAc,IAAI,CAAC,OAAO,CAAC,CAAC;AAAA,IAClC;AAAA,EACJ;AAEA,QAAM,iBAAiB,QAAQ,kBAAkB;AACjD,MAAI,gBAAgB;AAChB,UAAM,UAAU,uBAAuB,IAAI;AAAA,MACvC,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,SAAS;AAAA,IACb,CAAC;AACD,UAAM;AAAA,MACF;AAAA,QACI,QAAQ;AAAA,QACR,GAAG,YAAY,QAAQ,OAAO,eAAe,SAAS,EAAE,CAAC;AAAA,MAC7D;AAAA,MACA,eAAe,QAAQ,OAAO,OAAO;AAAA,MACrC;AAAA,IACJ;AAAA,EACJ;AAEA,SAAO;AAAA,IACH;AAAA,IACA,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACJ;AACJ;AAEA,IAAI,QAAoB;AACpB,QAAM,EAAE,UAAU,MAAM,QAAQ,YAAY,SAAS,IAAI;AACzD,QAAM,EAAE,SAAS,UAAU,GAAG,IAAI,MAAa;AAC/C,QAAM,EAAE,OAAO,IAAI,MAAa;AAChC,QAAM,EAAE,QAAQ,IAAI,MAAa;AAKjC,QAAM,iBAAiB;AACvB,QAAM,kBACF;AAEJ,MAAI;AAEJ,aAAW,YAAY;AACnB,iBAAa,MAAM,QAAQ,KAAK,OAAO,GAAG,mBAAmB,CAAC;AAAA,EAClE,CAAC;AAED,WAAS,YAAY;AACjB,QAAI;AACA,YAAM,GAAG,YAAY,EAAE,WAAW,KAAK,CAAC;AAAA,IAC5C,QAAQ;AAAA,IAER;AAAA,EACJ,CAAC;AAED,WAAS,qBAAqB,MAAM;AAChC,SAAK,4DAA4D,YAAY;AACzE,YAAM,SAAS,MAAM,kBAAkB;AAAA,QACnC,OAAO;AAAA,QACP;AAAA,QACA,eAAe;AAAA,QACf,gBAAgB;AAAA,MACpB,CAAC;AAED,YAAM,WAAW,MAAM,SAAS,KAAK,YAAY,yBAAyB,GAAG,OAAO;AACpF,aAAO,QAAQ,EAAE,QAAQ,eAAe;AAExC,YAAM,UAAU,MAAM;AAAA,QAClB,KAAK,YAAY,sBAAsB,OAAO,SAAS,OAAO;AAAA,QAC9D;AAAA,MACJ;AACA,aAAO,OAAO,EAAE,QAAQ,eAAe;AAAA,IAC3C,CAAC;AAED,SAAK,uDAAuD,YAAY;AACpE,YAAM,SAAS,MAAM,kBAAkB;AAAA,QACnC,OAAO;AAAA,QACP;AAAA,QACA,eAAe;AAAA,QACf,gBAAgB;AAAA,QAChB,gBAAgB;AAAA,MACpB,CAAC;AAED,YAAM;AAAA,QACF,SAAS,KAAK,YAAY,yBAAyB,GAAG,OAAO;AAAA,MACjE,EAAE,SAAS,QAAQ,KAAK;AAExB,YAAM;AAAA,QACF,SAAS,KAAK,YAAY,sBAAsB,OAAO,SAAS,OAAO,GAAG,OAAO;AAAA,MACrF,EAAE,QAAQ,QAAQ,QAAQ;AAAA,IAC9B,CAAC;AAED,SAAK,mEAAmE,YAAY;AAChF,YAAM,SAAS,MAAM,kBAAkB;AAAA,QACnC,OAAO;AAAA,QACP;AAAA,QACA,eAAe;AAAA,QACf,gBAAgB;AAAA,QAChB,WAAW;AAAA,MACf,CAAC;AAED,YAAM,MAAM,MAAM,SAAS,KAAK,YAAY,yBAAyB,GAAG,OAAO;AAC/E,YAAM,UAAU,cAAc,IAAI,QAAQ,GAAG,CAAC;AAE9C,aAAO,OAAO,EAAE,aAAa,CAAC;AAC9B,aAAO,QAAQ,CAAC,EAAE,EAAE,EAAE,KAAK,gBAAgB;AAC3C,aAAO,QAAQ,CAAC,EAAE,aAAa,SAAS,EAAE,QAAQ,OAAO,cAAc;AACvE,aAAO,QAAQ,CAAC,EAAE,aAAa,GAAG,EAAE,cAAc;AAClD,aAAO,QAAQ,CAAC,EAAE,cAAc,SAAS,EAAE,QAAQ,OAAO,eAAe;AACzE,aAAO,QAAQ,CAAC,EAAE,cAAc,SAAS,EAAE,aAAa,EAAE;AAC1D,aAAO,QAAQ,CAAC,EAAE,cAAc,GAAG,EAAE,cAAc;AAAA,IACvD,CAAC;AAED,SAAK,2EAA2E,YAAY;AACxF,YAAM,SAAS,MAAM,kBAAkB;AAAA,QACnC,OAAO;AAAA,QACP;AAAA,QACA,eAAe;AAAA,QACf,gBAAgB;AAAA,MACpB,CAAC;AAED,YAAM,WAAW;AAAA,QACb,oBAAoB,kBAAkB,cAAc,GAAG,eAAe;AAAA,MAC1E;AACA,aAAO,OAAO,cAAc,EAAE,QAAQ,QAAQ;AAC9C,aAAO,OAAO,cAAc,EAAE,aAAa,EAAE;AAAA,IACjD,CAAC;AAED,SAAK,+EAA+E,YAAY;AAC5F,YAAM,QAAQ;AACd,YAAM,SAAS,MAAM,kBAAkB;AAAA,QACnC;AAAA,QACA;AAAA,QACA,eAAe;AAAA,QACf,gBAAgB;AAAA,MACpB,CAAC;AAED,YAAM,MAAM,MAAM;AAAA,QACd,KAAK,YAAY,GAAG,KAAK,gBAAgB,OAAO,SAAS,OAAO;AAAA,QAChE;AAAA,MACJ;AACA,YAAM,MAAM,QAAQ,IAAI,YAAY,EAAE,OAAO,KAAK,GAAG,EAAE,OAAO,GAAG,CAAC;AAClE,YAAM,QAAQ,QAAQ,IAAI,YAAY,EAAE,OAAO,OAAO,GAAG,EAAE,OAAO,GAAG,CAAC;AACtE,YAAM,YAAY,IAAI,KAAK,KAAK,EAAE,QAAQ,QAAQ,GAAG,CAAC;AACtD,YAAM,UAAU,uBAAuB,IAAI,SAAS;AAEpD,aAAO,QAAQ,OAAO,EAAE,QAAQ,kBAAkB,cAAc,CAAC;AAEjE,aAAO,QAAQ,QAAQ,EAAE,aAAa,EAAE;AAExC,aAAO,QAAQ,UAAU,EAAE,aAAa,EAAE;AAAA,IAC9C,CAAC;AAED,SAAK,2DAA2D,YAAY;AACxE,YAAM,kBAAkB,EAAE,OAAO,SAAS,YAAY,WAAW,KAAK,CAAC;AACvE,YAAM,kBAAkB,EAAE,OAAO,SAAS,YAAY,WAAW,KAAK,CAAC;AAEvE,YAAM;AAAA,QACF,SAAS,KAAK,YAAY,wBAAwB,GAAG,OAAO;AAAA,MAChE,EAAE,SAAS,QAAQ,KAAK;AACxB,YAAM;AAAA,QACF,SAAS,KAAK,YAAY,wBAAwB,GAAG,OAAO;AAAA,MAChE,EAAE,SAAS,QAAQ,KAAK;AAAA,IAC5B,CAAC;AAED,SAAK,4CAA4C,YAAY;AACzD,YAAM,kBAAkB;AAAA,QACpB,OAAO;AAAA,QACP;AAAA,QACA,WAAW;AAAA,MACf,CAAC;AACD,YAAM;AAAA,QACF,SAAS,KAAK,YAAY,kCAAkC,GAAG,OAAO;AAAA,MAC1E,EAAE,SAAS,QAAQ,KAAK;AAAA,IAC5B,CAAC;AAED,SAAK,iDAAiD,YAAY;AAC9D,YAAM,SAAS,KAAK,YAAY,QAAQ,OAAO,OAAO;AACtD,YAAM,kBAAkB,EAAE,OAAO,UAAU,YAAY,OAAO,CAAC;AAC/D,YAAM;AAAA,QACF,SAAS,KAAK,QAAQ,yBAAyB,GAAG,OAAO;AAAA,MAC7D,EAAE,SAAS,QAAQ,KAAK;AAAA,IAC5B,CAAC;AAED,SAAK,oDAAoD,YAAY;AACjE,YAAM,IAAI,MAAM,kBAAkB,EAAE,OAAO,KAAK,WAAW,CAAC;AAC5D,YAAM,IAAI,MAAM,kBAAkB,EAAE,OAAO,KAAK,WAAW,CAAC;AAC5D,aAAO,EAAE,aAAa,EAAE,IAAI,KAAK,EAAE,aAAa;AAChD,aAAO,EAAE,cAAc,EAAE,IAAI,KAAK,EAAE,cAAc;AAClD,aAAO,EAAE,SAAS,EAAE,IAAI,KAAK,EAAE,SAAS;AAAA,IAC5C,CAAC;AAED,SAAK,kCAAkC,YAAY;AAC/C,YAAM,SAAS,MAAM,kBAAkB;AAAA,QACnC,OAAO;AAAA,QACP;AAAA,QACA,WAAW;AAAA,MACf,CAAC;AACD,aAAO,OAAO,SAAS,EAAE,KAAK,WAAW;AACzC,YAAM;AAAA,QACF,SAAS,KAAK,YAAY,mCAAmC,GAAG,OAAO;AAAA,MAC3E,EAAE,SAAS,QAAQ,KAAK;AAAA,IAC5B,CAAC;AAED,SAAK,qCAAqC,YAAY;AAClD,YAAM,SAAS,MAAM,kBAAkB;AAAA,QACnC,OAAO;AAAA,QACP;AAAA,QACA,eAAe;AAAA,QACf,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,MACrB,CAAC;AACD,aAAO,OAAO,eAAe,EAAE,KAAK,gBAAgB;AACpD,YAAM,WAAW;AAAA,QACb,oBAAoB,kBAAkB,cAAc,GAAG,gBAAgB;AAAA,MAC3E;AACA,aAAO,OAAO,cAAc,EAAE,QAAQ,QAAQ;AAAA,IAClD,CAAC;AAED,SAAK,2CAA2C,YAAY;AAIxD,YAAM,QAAQ,MAAM,kBAAkB;AAAA,QAClC,OAAO;AAAA,QACP;AAAA,QACA,WAAW;AAAA,MACf,CAAC;AACD,YAAM,SAAS,MAAM,kBAAkB;AAAA,QACnC,OAAO;AAAA,QACP;AAAA,QACA,WAAW;AAAA,MACf,CAAC;AAED,YAAM,MAAM,MAAM,SAAS,KAAK,YAAY,yBAAyB,GAAG,OAAO;AAC/E,YAAM,UAAU,cAAc,IAAI,QAAQ,GAAG,CAAC;AAC9C,aAAO,OAAO,EAAE,aAAa,CAAC;AAC9B,aAAO,QAAQ,CAAC,EAAE,EAAE,EAAE,KAAK,QAAQ;AAGnC,YAAM;AAAA,QACF,SAAS,KAAK,YAAY,sBAAsB,MAAM,SAAS,OAAO,GAAG,OAAO;AAAA,MACpF,EAAE,SAAS,QAAQ,KAAK;AACxB,YAAM;AAAA,QACF,SAAS,KAAK,YAAY,sBAAsB,OAAO,SAAS,OAAO,GAAG,OAAO;AAAA,MACrF,EAAE,SAAS,QAAQ,KAAK;AAAA,IAC5B,CAAC;AAED,SAAK,8EAAyE,MAAM;AAGhF,YAAM,UAAU,eAAe,IAAI,gBAAgB,IAAI,WAAW,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC;AAC9E,aAAO,OAAO,EAAE,aAAa,EAAE;AAAA,IACnC,CAAC;AAAA,EACL,CAAC;AACL;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/testing.ts"],"sourcesContent":["// Copyright 2026 Parity Technologies (UK) Ltd.\n// SPDX-License-Identifier: Apache-2.0\n/**\n * Test helpers for synthesizing persisted sessions.\n *\n * Production sessions are persisted to the storage directory by\n * `createNodeStorageAdapter` in the SCALE-encoded / AES-GCM-encrypted\n * format defined by `@novasamatech/host-papp`'s internal repositories.\n * There is no public API on the adapter to write a session without going\n * through the QR pairing + on-chain attestation flow, which makes E2E\n * testing of session-dependent CLI flows impossible without a real phone.\n *\n * This module mirrors that format using only public primitives, so E2E\n * tests can inject a known-good session file. The `testing.test.ts`\n * integration test reads the synthesized files back through the real\n * `ssoSessionRepository` + `userSecretRepository` from `@novasamatech/host-papp`\n * and fails if the upstream format ever drifts from our reproduction.\n */\nimport { gcm } from \"@noble/ciphers/aes.js\";\nimport { p256 } from \"@noble/curves/nist.js\";\nimport { blake2b } from \"@noble/hashes/blake2.js\";\nimport {\n AccountIdCodec,\n LocalSessionAccountCodec,\n RemoteSessionAccountCodec,\n createAccountId,\n createLocalSessionAccount,\n createRemoteSessionAccount,\n createSr25519Secret,\n deriveSr25519PublicKey,\n} from \"@novasamatech/statement-store\";\nimport { toHex } from \"@polkadot-api/utils\";\nimport {\n entropyToMiniSecret,\n generateMnemonic,\n mnemonicToEntropy,\n} from \"@polkadot-labs/hdkd-helpers\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { nanoid } from \"nanoid\";\nimport { Bytes, type Codec, Struct, Vector, str } from \"scale-ts\";\nimport type { StoredUserSession } from \"@novasamatech/host-papp\";\n\nimport { sanitizeKey } from \"./node-storage.js\";\n\n// Mirror of host-papp's internal stored-session codec (not exported — only the\n// `StoredUserSession` type is). `satisfies` guards drift at build time, the\n// interop test at runtime; field order and the fixed-width fields must match.\n//\n// host-papp 0.8.6 (RFC-0007, PR #205) dropped the `Option` wrapper on\n// `identityAccountId`, `identityChatPublicKey`, and `ssoEncPubKey` — all\n// three are now required — and appended `rootEntropySource: Bytes(32)`,\n// the layer-1 entropy the host consumes via `deriveProductEntropyFromSource`.\nconst storedUserSessionCodec = Struct({\n id: str,\n localAccount: LocalSessionAccountCodec,\n remoteAccount: RemoteSessionAccountCodec,\n rootAccountId: AccountIdCodec,\n identityAccountId: AccountIdCodec,\n identityChatPublicKey: Bytes(65),\n ssoEncPubKey: Bytes(65),\n rootEntropySource: Bytes(32),\n}) satisfies Codec<StoredUserSession>;\nconst sessionsCodec = Vector(storedUserSessionCodec);\n\n// Mirrors the internal StoredUserSecretsCodec in host-papp's userSecretRepository.\n//\n// host-papp 0.8.6 dropped `entropy` (root entropy moved onto the session as\n// `rootEntropySource`) and added the V2 `identityChatPrivateKey` field.\n// The storage key was also renamed `UserSecrets` → `UserSecretsV2`.\nconst storedUserSecretsCodec = Struct({\n ssSecret: Bytes(),\n encrSecret: Bytes(),\n identityChatPrivateKey: Bytes(32),\n});\n\n// Mirrors host-papp's userSecretRepository AES-GCM wrapper: the encryption\n// key is blake2b(appId, 16) and the nonce is blake2b(\"nonce\", 32). The appId\n// doubles as the salt, so sessions written by this helper are only readable\n// by a TerminalAdapter configured with the same appId.\nfunction encryptSecrets(appId: string, plaintext: Uint8Array): string {\n const key = blake2b(new TextEncoder().encode(appId), { dkLen: 16 });\n const nonce = blake2b(new TextEncoder().encode(\"nonce\"), { dkLen: 32 });\n return toHex(gcm(key, nonce).encrypt(plaintext));\n}\n\n/** Mirrors host-papp's createEncrSecret: pad the mini-secret to 48 bytes and feed it to P256 keygen. */\nfunction p256SecretFromEntropy(entropy: Uint8Array): Uint8Array {\n const seed = new Uint8Array(48);\n seed.set(entropyToMiniSecret(entropy));\n return p256.keygen(seed).secretKey;\n}\n\nexport interface CreateTestSessionOptions {\n /** Unique app identifier. Must match the one passed to `createTerminalAdapter`. */\n appId: string;\n /**\n * Directory where session files are written. Must match the one the\n * CLI reads from — pass the same value to `createTerminalAdapter`'s\n * `storageDir` option.\n */\n storageDir: string;\n /** BIP39 mnemonic for the local (host-side) account. Default: freshly generated. */\n localMnemonic?: string;\n /** Derivation path for the local account. Default: `\"//wallet//sso\"` (matches production). */\n localDerivation?: string;\n /** BIP39 mnemonic for the remote (phone) account. Default: freshly generated. */\n remoteMnemonic?: string;\n /** Derivation path for the remote account. Default: `\"\"`. */\n remoteDerivation?: string;\n /** Stable session id. Default: random nanoid(12). */\n sessionId?: string;\n /**\n * Whether to write the encrypted `UserSecretsV2_<id>` file. Default: `true`.\n * Set to `false` to exercise recovery paths where a session exists on disk\n * but its secrets are missing.\n */\n includeSecrets?: boolean;\n}\n\nexport interface TestSession {\n sessionId: string;\n /** Sr25519 public key of the local (host) account, 32 bytes. */\n localAccountId: Uint8Array;\n /** Sr25519 public key of the remote (phone) account, 32 bytes. */\n remoteAccountId: Uint8Array;\n localMnemonic: string;\n localDerivation: string;\n remoteMnemonic: string;\n remoteDerivation: string;\n}\n\n/**\n * Write a valid persisted session to `storageDir`, as if the user had\n * completed QR pairing + attestation. A `TerminalAdapter` created with the\n * same `appId` + `storageDir` will emit the synthesized session from\n * `adapter.sessions.sessions`.\n *\n * @remarks\n * **Dev utility — not a stable contract.** This helper hand-rolls SCALE codecs\n * that mirror an internal on-disk format used by `@novasamatech/host-papp`'s\n * session repositories. The format is not part of host-papp's documented public\n * API and may change on minor version bumps. The `testing.interop.test.ts`\n * round-trip test fails loudly when the format drifts. Use this only in test code.\n *\n * ## Limits\n *\n * - **Signing does not round-trip.** Sending a request via `session.signRaw`\n * still goes out over the statement store and expects a real phone to\n * respond. Use this helper for flows that test session discovery,\n * persistence, and logout — not end-to-end signing.\n * - **Signing attempts surface `NoAllowanceError`.** The synthesized local\n * account was never registered on the People chain, so the first write\n * to the statement store fails with `NoAllowanceError`. That's the same\n * error path the CLI sees when a previously valid session's on-chain\n * attestation has expired, so tests that assert \"CLI handles an expired\n * session\" *can* be written against a synthesized session even though\n * there's no `expiresAt` knob to turn.\n * - **No `expiresAt`.** The on-disk codec has no expiry field; validity is\n * tracked via on-chain attestation state. See above for how expiry-path\n * tests still work in practice.\n * - **Corrupted-session cases** don't need a helper — write garbage to\n * `<storageDir>/<appId>_SsoSessionsV2.json` with `fs.writeFile` directly.\n * - **Repeated calls replace the session list.** Each call writes a fresh\n * single-entry `SsoSessionsV2` file, so calling twice on the same\n * `storageDir`+`appId` leaves only the second session on disk. Use a\n * fresh `mkdtempSync` per test to keep cases isolated.\n *\n * @example\n * ```ts\n * import { mkdtempSync } from \"node:fs\";\n * import { tmpdir } from \"node:os\";\n * import { join } from \"node:path\";\n * import { createTestSession } from \"@parity/product-sdk-terminal/testing\";\n * import { createTerminalAdapter, waitForSessions } from \"@parity/product-sdk-terminal\";\n *\n * const storageDir = mkdtempSync(join(tmpdir(), \"e2e-\"));\n * const { sessionId } = await createTestSession({ appId: \"dot-cli\", storageDir });\n *\n * const adapter = createTerminalAdapter({ appId: \"dot-cli\", storageDir });\n * const sessions = await waitForSessions(adapter);\n * // sessions[0].id === sessionId\n * ```\n */\nexport async function createTestSession(options: CreateTestSessionOptions): Promise<TestSession> {\n await mkdir(options.storageDir, { recursive: true });\n\n const localMnemonic = options.localMnemonic ?? generateMnemonic();\n const localDerivation = options.localDerivation ?? \"//wallet//sso\";\n const localEntropy = mnemonicToEntropy(localMnemonic);\n const localSecret = createSr25519Secret(localEntropy, localDerivation);\n const localPublicKey = deriveSr25519PublicKey(localSecret);\n const localEncrSecret = p256SecretFromEntropy(localEntropy);\n\n const remoteMnemonic = options.remoteMnemonic ?? generateMnemonic();\n const remoteDerivation = options.remoteDerivation ?? \"\";\n const remoteEntropy = mnemonicToEntropy(remoteMnemonic);\n const remoteSecret = createSr25519Secret(remoteEntropy, remoteDerivation);\n const remotePublicKey = deriveSr25519PublicKey(remoteSecret);\n const remoteEncrPublicKey = p256.getPublicKey(p256SecretFromEntropy(remoteEntropy), false);\n\n // In production, `remoteAccount.publicKey` is the ECDH shared secret\n // between the host's P256 encryption key and the phone's P256 encryption\n // key. We compute the same thing from two mnemonic-derived P256 keys so\n // the synthesized session is cryptographically well-formed.\n const sharedSecret = p256.getSharedSecret(localEncrSecret, remoteEncrPublicKey).slice(1, 33);\n\n const sessionId = options.sessionId ?? nanoid(12);\n\n // host-papp 0.8.6 (RFC-0007) requires all of identityAccountId,\n // identityChatPublicKey, ssoEncPubKey, and rootEntropySource on the\n // persisted session. For a synthesized test session we collapse the\n // identity-side keys onto the remote account (the wallet doubles as\n // identity), reuse the peer's P-256 encryption pubkey for both chat\n // and SSO transports, and use the remote entropy as the RFC-0007\n // root entropy source so the value is reproducible.\n const session = {\n id: sessionId,\n localAccount: createLocalSessionAccount(createAccountId(localPublicKey), undefined),\n remoteAccount: createRemoteSessionAccount(\n createAccountId(remotePublicKey),\n sharedSecret,\n undefined,\n ),\n rootAccountId: createAccountId(remotePublicKey),\n identityAccountId: createAccountId(remotePublicKey),\n identityChatPublicKey: remoteEncrPublicKey,\n ssoEncPubKey: remoteEncrPublicKey,\n rootEntropySource: remoteEntropy,\n };\n\n await writeFile(\n join(options.storageDir, `${sanitizeKey(options.appId, \"SsoSessionsV2\")}.json`),\n toHex(sessionsCodec.enc([session])),\n \"utf-8\",\n );\n\n const includeSecrets = options.includeSecrets ?? true;\n if (includeSecrets) {\n // host-papp 0.8.6 dropped `entropy` (now lives on the session as\n // `rootEntropySource`) and requires a 32-byte `identityChatPrivateKey`\n // (V2 — P-256 raw scalar). Reuse the local P-256 encryption secret\n // for both — same domain (P-256, 32-byte scalar).\n const encoded = storedUserSecretsCodec.enc({\n ssSecret: localSecret,\n encrSecret: localEncrSecret,\n identityChatPrivateKey: localEncrSecret,\n });\n await writeFile(\n join(\n options.storageDir,\n `${sanitizeKey(options.appId, `UserSecretsV2_${sessionId}`)}.json`,\n ),\n encryptSecrets(options.appId, encoded),\n \"utf-8\",\n );\n }\n\n return {\n sessionId,\n localAccountId: localPublicKey,\n remoteAccountId: remotePublicKey,\n localMnemonic,\n localDerivation,\n remoteMnemonic,\n remoteDerivation,\n };\n}\n\nif (import.meta.vitest) {\n const { describe, test, expect, beforeEach, afterAll } = import.meta.vitest;\n const { mkdtemp, readFile, rm } = await import(\"node:fs/promises\");\n const { tmpdir } = await import(\"node:os\");\n const { fromHex } = await import(\"@polkadot-api/utils\");\n\n // Stable dev mnemonics — deterministic inputs keep the tests hermetic.\n // The first is the well-known Substrate dev root (the source of `//Alice`\n // et al.); the second is the BIP39 all-abandon test vector.\n const LOCAL_MNEMONIC = \"bottom drive obey lake curtain smoke basket hold race lonely fit walk\";\n const REMOTE_MNEMONIC =\n \"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about\";\n\n let storageDir: string;\n\n beforeEach(async () => {\n storageDir = await mkdtemp(join(tmpdir(), \"terminal-testing-\"));\n });\n\n afterAll(async () => {\n try {\n await rm(storageDir, { recursive: true });\n } catch {\n /* ignore */\n }\n });\n\n describe(\"createTestSession\", () => {\n test(\"writes both SsoSessionsV2 and UserSecretsV2 files by default\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n });\n\n const sessions = await readFile(join(storageDir, \"my-app_SsoSessionsV2.json\"), \"utf-8\");\n expect(sessions).toMatch(/^0x[0-9a-f]+$/);\n\n const secrets = await readFile(\n join(storageDir, `my-app_UserSecretsV2_${result.sessionId}.json`),\n \"utf-8\",\n );\n expect(secrets).toMatch(/^0x[0-9a-f]+$/);\n });\n\n test(\"omits UserSecretsV2 file when includeSecrets is false\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n includeSecrets: false,\n });\n\n await expect(\n readFile(join(storageDir, \"my-app_SsoSessionsV2.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n\n await expect(\n readFile(\n join(storageDir, `my-app_UserSecretsV2_${result.sessionId}.json`),\n \"utf-8\",\n ),\n ).rejects.toThrow(/ENOENT/);\n });\n\n test(\"SsoSessionsV2 file decodes with the host-papp session codec shape\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n sessionId: \"stable-test-id\",\n });\n\n const hex = await readFile(join(storageDir, \"my-app_SsoSessionsV2.json\"), \"utf-8\");\n const decoded = sessionsCodec.dec(fromHex(hex));\n\n expect(decoded).toHaveLength(1);\n expect(decoded[0].id).toBe(\"stable-test-id\");\n expect(decoded[0].localAccount.accountId).toEqual(result.localAccountId);\n expect(decoded[0].localAccount.pin).toBeUndefined();\n expect(decoded[0].remoteAccount.accountId).toEqual(result.remoteAccountId);\n expect(decoded[0].remoteAccount.publicKey).toHaveLength(32);\n expect(decoded[0].remoteAccount.pin).toBeUndefined();\n });\n\n test(\"localAccountId matches the Sr25519 public key derived from the mnemonic\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n });\n\n const expected = deriveSr25519PublicKey(\n createSr25519Secret(mnemonicToEntropy(LOCAL_MNEMONIC), \"//wallet//sso\"),\n );\n expect(result.localAccountId).toEqual(expected);\n expect(result.localAccountId).toHaveLength(32);\n });\n\n test(\"UserSecretsV2 file decrypts and decodes with the host-papp secret codec shape\", async () => {\n const appId = \"my-app\";\n const result = await createTestSession({\n appId,\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n });\n\n const hex = await readFile(\n join(storageDir, `${appId}_UserSecretsV2_${result.sessionId}.json`),\n \"utf-8\",\n );\n const key = blake2b(new TextEncoder().encode(appId), { dkLen: 16 });\n const nonce = blake2b(new TextEncoder().encode(\"nonce\"), { dkLen: 32 });\n const decrypted = gcm(key, nonce).decrypt(fromHex(hex));\n const decoded = storedUserSecretsCodec.dec(decrypted);\n\n // Sr25519 secret is 64 bytes (32-byte secret + 32-byte nonce).\n expect(decoded.ssSecret).toHaveLength(64);\n // P256 secret is 32 bytes.\n expect(decoded.encrSecret).toHaveLength(32);\n // V2 chat private key (P-256 raw scalar) is 32 bytes.\n expect(decoded.identityChatPrivateKey).toHaveLength(32);\n });\n\n test(\"different appIds produce files under different prefixes\", async () => {\n await createTestSession({ appId: \"app-a\", storageDir, sessionId: \"id\" });\n await createTestSession({ appId: \"app-b\", storageDir, sessionId: \"id\" });\n\n await expect(\n readFile(join(storageDir, \"app-a_SsoSessionsV2.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n await expect(\n readFile(join(storageDir, \"app-b_SsoSessionsV2.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"sanitizes appIds with special characters\", async () => {\n await createTestSession({\n appId: \"app/with spaces\",\n storageDir,\n sessionId: \"id\",\n });\n await expect(\n readFile(join(storageDir, \"app_with_spaces_SsoSessionsV2.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"creates storageDir when it does not yet exist\", async () => {\n const nested = join(storageDir, \"does\", \"not\", \"exist\");\n await createTestSession({ appId: \"my-app\", storageDir: nested });\n await expect(\n readFile(join(nested, \"my-app_SsoSessionsV2.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"generates fresh mnemonics when none are supplied\", async () => {\n const a = await createTestSession({ appId: \"a\", storageDir });\n const b = await createTestSession({ appId: \"b\", storageDir });\n expect(a.localMnemonic).not.toBe(b.localMnemonic);\n expect(a.remoteMnemonic).not.toBe(b.remoteMnemonic);\n expect(a.sessionId).not.toBe(b.sessionId);\n });\n\n test(\"respects an explicit sessionId\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n sessionId: \"pinned-id\",\n });\n expect(result.sessionId).toBe(\"pinned-id\");\n await expect(\n readFile(join(storageDir, \"my-app_UserSecretsV2_pinned-id.json\"), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"respects a custom localDerivation\", async () => {\n const result = await createTestSession({\n appId: \"my-app\",\n storageDir,\n localMnemonic: LOCAL_MNEMONIC,\n remoteMnemonic: REMOTE_MNEMONIC,\n localDerivation: \"//custom//path\",\n });\n expect(result.localDerivation).toBe(\"//custom//path\");\n const expected = deriveSr25519PublicKey(\n createSr25519Secret(mnemonicToEntropy(LOCAL_MNEMONIC), \"//custom//path\"),\n );\n expect(result.localAccountId).toEqual(expected);\n });\n\n test(\"repeated calls replace the session list\", async () => {\n // Documented behavior — second call leaves only the second session\n // on disk. Callers who want multiple sessions should use separate\n // storage dirs.\n const first = await createTestSession({\n appId: \"my-app\",\n storageDir,\n sessionId: \"first\",\n });\n const second = await createTestSession({\n appId: \"my-app\",\n storageDir,\n sessionId: \"second\",\n });\n\n const hex = await readFile(join(storageDir, \"my-app_SsoSessionsV2.json\"), \"utf-8\");\n const decoded = sessionsCodec.dec(fromHex(hex));\n expect(decoded).toHaveLength(1);\n expect(decoded[0].id).toBe(\"second\");\n // The first session's UserSecretsV2 file is left behind (not cleaned).\n // This matches a real logout flow as well, so we don't try to hide it.\n await expect(\n readFile(join(storageDir, `my-app_UserSecretsV2_${first.sessionId}.json`), \"utf-8\"),\n ).resolves.toMatch(/^0x/);\n await expect(\n readFile(\n join(storageDir, `my-app_UserSecretsV2_${second.sessionId}.json`),\n \"utf-8\",\n ),\n ).resolves.toMatch(/^0x/);\n });\n\n test(\"AccountIdCodec is Bytes(32) — keep this invariant if upstream changes\", () => {\n // Guards against silent upstream changes to the session-account\n // codec shape that would make our synthesized files unreadable.\n const encoded = AccountIdCodec.enc(createAccountId(new Uint8Array(32).fill(7)));\n expect(encoded).toHaveLength(32);\n });\n });\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parity/product-sdk-terminal",
|
|
3
3
|
"description": "QR code login and signing for CLI/terminal apps via Polkadot mobile wallet pairing",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"@noble/ciphers": "^2.1.0",
|
|
32
32
|
"@noble/curves": "^2.0.1",
|
|
33
33
|
"@noble/hashes": "^2.2.0",
|
|
34
|
-
"@novasamatech/host-papp": "^0.8.
|
|
35
|
-
"@novasamatech/statement-store": "^0.8.
|
|
36
|
-
"@novasamatech/storage-adapter": "^0.8.
|
|
34
|
+
"@novasamatech/host-papp": "^0.8.6",
|
|
35
|
+
"@novasamatech/statement-store": "^0.8.6",
|
|
36
|
+
"@novasamatech/storage-adapter": "^0.8.6",
|
|
37
37
|
"@polkadot-api/substrate-bindings": "^0.20.2",
|
|
38
38
|
"@polkadot-api/utils": "^0.4.0",
|
|
39
39
|
"@polkadot-api/ws-provider": "^0.9.0",
|