@parity/product-sdk-terminal 0.3.1 → 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/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 `UserSecrets_<id>` file. Default: `true`.
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>_SsoSessions.json` with `fs.writeFile` directly.
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 `SsoSessions` file, so calling twice on the same
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, Option, Struct, Vector, str } from "scale-ts";
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: Option(AccountIdCodec),
35
- identityChatPublicKey: Option(Bytes(65))
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
- entropy: Bytes()
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: void 0,
80
- identityChatPublicKey: void 0
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, "SsoSessions")}.json`),
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
- entropy: localEntropy
95
+ identityChatPrivateKey: localEncrSecret
93
96
  });
94
97
  await writeFile(
95
98
  join(
96
99
  options.storageDir,
97
- `${sanitizeKey(options.appId, `UserSecrets_${sessionId}`)}.json`
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 SsoSessions and UserSecrets files by default", async () => {
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-app_SsoSessions.json"), "utf-8");
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-app_UserSecrets_${result.sessionId}.json`),
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 UserSecrets file when includeSecrets is false", async () => {
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-app_SsoSessions.json"), "utf-8")
158
+ readFile(join(storageDir, "my-app_SsoSessionsV2.json"), "utf-8")
156
159
  ).resolves.toMatch(/^0x/);
157
160
  await expect(
158
- readFile(join(storageDir, `my-app_UserSecrets_${result.sessionId}.json`), "utf-8")
161
+ readFile(
162
+ join(storageDir, `my-app_UserSecretsV2_${result.sessionId}.json`),
163
+ "utf-8"
164
+ )
159
165
  ).rejects.toThrow(/ENOENT/);
160
166
  });
161
- test("SsoSessions file decodes with the host-papp session codec shape", async () => {
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-app_SsoSessions.json"), "utf-8");
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("UserSecrets file decrypts and decodes with the host-papp secret codec shape", async () => {
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}_UserSecrets_${result.sessionId}.json`),
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-a_SsoSessions.json"), "utf-8")
222
+ readFile(join(storageDir, "app-a_SsoSessionsV2.json"), "utf-8")
217
223
  ).resolves.toMatch(/^0x/);
218
224
  await expect(
219
- readFile(join(storageDir, "app-b_SsoSessions.json"), "utf-8")
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, "app_with_spaces_SsoSessions.json"), "utf-8")
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-app_SsoSessions.json"), "utf-8")
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-app_UserSecrets_pinned-id.json"), "utf-8")
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-app_SsoSessions.json"), "utf-8");
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-app_UserSecrets_${first.sessionId}.json`), "utf-8")
293
+ readFile(join(storageDir, `my-app_UserSecretsV2_${first.sessionId}.json`), "utf-8")
288
294
  ).resolves.toMatch(/^0x/);
289
295
  await expect(
290
- readFile(join(storageDir, `my-app_UserSecrets_${second.sessionId}.json`), "utf-8")
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", () => {
@@ -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":"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","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.1",
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.1",
35
- "@novasamatech/statement-store": "^0.8.1",
36
- "@novasamatech/storage-adapter": "^0.8.1",
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",