@parity/product-sdk-terminal 0.0.0-dev.421.0 → 0.0.0-dev.457.0

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/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { T as TerminalAdapter } from './host-_G9M0isV.js';
2
- export { A as AllocatableResource, a as ApAllocationOutcome, O as OnExistingAllowancePolicy, R as RequestResourceAllocationOptions, b as TerminalAdapterOptions, c as createTerminalAdapter, i as isBenignTeardownError, r as requestResourceAllocation, s as sendResourceAllocation } from './host-_G9M0isV.js';
1
+ import { T as TerminalAdapter } from './host-Dk5G2hVc.js';
2
+ export { A as AllocatableResource, a as ApAllocationOutcome, O as OnExistingAllowancePolicy, R as RequestResourceAllocationOptions, b as TerminalAdapterOptions, c as createTerminalAdapter, i as isBenignTeardownError, r as requestResourceAllocation, s as sendResourceAllocation } from './host-Dk5G2hVc.js';
3
3
  import { UserSession } from '@novasamatech/host-papp';
4
4
  export { AllowanceError, AllowanceErrorReason, AllowanceService, HostMetadata, PairingStatus, PappAdapter, SS_STABLE_STAGE_ENDPOINTS, SigningPayloadRequest, SigningPayloadResponse, SigningRawRequest, StoredUserSession, UserSession } from '@novasamatech/host-papp';
5
- import { PolkadotSigner } from 'polkadot-api';
5
+ import { CommonSignerTxCreator } from '@polkadot-api/signers-common';
6
6
  export { AllowanceExpiredError, SignerError } from '@parity/product-sdk-signer/errors';
7
7
  import { StatementProver } from '@novasamatech/statement-store';
8
8
  import { StorageAdapter } from '@novasamatech/storage-adapter';
@@ -46,7 +46,7 @@ interface ProductSubtreeOptions {
46
46
  declare function getProductSubtreePublicKey(session: UserSession, productId: string, options?: ProductSubtreeOptions): Promise<Uint8Array>;
47
47
 
48
48
  /**
49
- * Create a PolkadotSigner from a QR-paired session.
49
+ * Create a PAPI 3 transaction creator from a QR-paired session.
50
50
  *
51
51
  * Routes **transaction signing** through `session.createTransaction` — the
52
52
  * host-papp `CreateTransactionRequest`/`CreateTransactionResponse` SSO
@@ -112,7 +112,15 @@ interface ProductAccountRef {
112
112
  publicKey?: Uint8Array;
113
113
  }
114
114
  interface SessionSignerOptions extends ProductSubtreeOptions {
115
- /** Extension version of the supplied signTx bytes. Defaults to 0; does not change encoding. */
115
+ /**
116
+ * Pipeline version to prefer when the transaction names none. Defaults to 0.
117
+ *
118
+ * When that pipeline does not declare every extension the caller supplied,
119
+ * for example in `customSignedExtensions`, the signer uses the lowest version
120
+ * that does, and refuses a transaction no pipeline can carry. A wrapper that
121
+ * writes its own extensions, such as `withAsPerson`, sets the version on the
122
+ * transaction itself, and that always wins.
123
+ */
116
124
  txExtVersion?: number;
117
125
  }
118
126
  declare const INCOMPLETE_SESSION_MESSAGE = "Stored login session is missing the root account public key. Run \"logout\" and then \"login\" to pair again.";
@@ -134,7 +142,7 @@ declare function sessionRootPublicKey(session: UserSession): Uint8Array;
134
142
  */
135
143
  declare function deriveProductPublicKey(session: UserSession, ref: ProductAccountRef, options?: ProductSubtreeOptions): Promise<Uint8Array>;
136
144
  /**
137
- * Create a `PolkadotSigner` backed by a QR-paired mobile wallet session,
145
+ * Create a PAPI 3 transaction creator backed by a QR-paired mobile wallet session,
138
146
  * using the session's **default account** (`derivationIndex: 0`).
139
147
  *
140
148
  * For non-default sub-accounts, use {@link createSessionSignerForAccount}.
@@ -146,9 +154,9 @@ declare function deriveProductPublicKey(session: UserSession, ref: ProductAccoun
146
154
  * `[adapter.appId, 0]`. Optional — when omitted it's fetched and derived.
147
155
  * See {@link ProductAccountRef.publicKey}.
148
156
  */
149
- declare function createSessionSigner(session: UserSession, adapter: TerminalAdapter, publicKey?: Uint8Array, options?: SessionSignerOptions): Promise<PolkadotSigner>;
157
+ declare function createSessionSigner(session: UserSession, adapter: TerminalAdapter, publicKey?: Uint8Array, options?: SessionSignerOptions): Promise<CommonSignerTxCreator>;
150
158
  /**
151
- * Create a `PolkadotSigner` for a specific sub-account of a paired session.
159
+ * Create a PAPI 3 transaction creator for a specific sub-account of a paired session.
152
160
  *
153
161
  * Use this when you need a derivation index other than `0`, or a `productId`
154
162
  * different from the adapter's `appId`. For the common default-account case,
@@ -157,13 +165,13 @@ declare function createSessionSigner(session: UserSession, adapter: TerminalAdap
157
165
  * @param session The paired user session.
158
166
  * @param ref The product account to sign as: `{ productId, derivationIndex }`.
159
167
  */
160
- declare function createSessionSignerForAccount(session: UserSession, ref: ProductAccountRef, options?: SessionSignerOptions): Promise<PolkadotSigner>;
168
+ declare function createSessionSignerForAccount(session: UserSession, ref: ProductAccountRef, options?: SessionSignerOptions): Promise<CommonSignerTxCreator>;
161
169
 
162
170
  /**
163
- * Get a `PolkadotSigner` for a Bulletin allowance slot.
171
+ * Get a PAPI 3 transaction creator for a Bulletin allowance slot.
164
172
  *
165
173
  * Allocates an allowance slot via the paired wallet (or returns the cached
166
- * one), derives the slot-account keypair, and returns a `PolkadotSigner`
174
+ * one), derives the slot-account keypair, and returns a PAPI 3 transaction creator
167
175
  * that signs Bulletin extrinsics with it. Replaces the manual
168
176
  * `requestResourceAllocation` + `createSlotAccountSigner` two-step for the
169
177
  * common case.
@@ -184,10 +192,10 @@ declare function createSessionSignerForAccount(session: UserSession, ref: Produc
184
192
  * const adapter = createTerminalAdapter({ appId: "my-cli" });
185
193
  * // ... QR pair, wait for session ...
186
194
  * const signer = await getBulletinSigner(adapter, "my-cli.dot");
187
- * await client.bulletin.tx.TransactionStorage.store({ data }).signAndSubmit(signer);
195
+ * await client.bulletin.tx.TransactionStorage.store({ data }).createAndSubmit(signer);
188
196
  * ```
189
197
  */
190
- declare function getBulletinSigner(adapter: TerminalAdapter, productId: string, sessionId?: string): Promise<PolkadotSigner>;
198
+ declare function getBulletinSigner(adapter: TerminalAdapter, productId: string, sessionId?: string): Promise<CommonSignerTxCreator>;
191
199
  /**
192
200
  * Get a `StatementProver` for a statement-store allowance slot.
193
201
  *
@@ -250,6 +258,99 @@ declare function hasStatementStoreAllowance(adapter: TerminalAdapter, productId:
250
258
  */
251
259
  declare function waitForSessions(adapter: TerminalAdapter, timeoutMs?: number): Promise<UserSession[]>;
252
260
 
261
+ /**
262
+ * Client-side RFC-0007 product-entropy derivation for terminal (QR/SSO) sessions.
263
+ *
264
+ * Specified normatively by host-spec §C.8 "RFC-0007 product entropy"
265
+ * ({@link https://github.com/paritytech/host-spec/blob/adb3989208ae1c2107dbf0159611353e6989422c/spec/C-account-derivation.md?plain=1#L129-L147 | spec/C-account-derivation.md}):
266
+ *
267
+ * layer1 = blake2b256_keyed(key = utf8("product-entropy-derivation"), msg = rootAccountSecret)
268
+ * layer2 = blake2b256_keyed(key = blake2b256(utf8(productId)), msg = layer1)
269
+ * result = blake2b256_keyed(key = callerKey, msg = layer2)
270
+ *
271
+ * The paired {@link UserSession} already carries layer 1 as `rootEntropySource`,
272
+ * so this module computes only layers 2 and 3 — no host round-trip. The output
273
+ * matches what an in-container app gets from `@parity/product-sdk-host`'s
274
+ * `deriveEntropy` for the same wallet + product + key, so entropy-derived keys
275
+ * interoperate across web (in-container) and terminal (QR/SSO) clients.
276
+ *
277
+ * Layer 1's input is the raw BIP-39 entropy of the root account — 16 or 32
278
+ * bytes, *not* the 64-byte PBKDF2 seed. Terminal never sees it: the host applies
279
+ * layer 1 during the SSO handshake and sends only the 32-byte result.
280
+ *
281
+ * The scheme has parallel implementations in five languages — host-container
282
+ * (TS), host-encoding (Rust, canonical), sdk-swift, sdk-android and
283
+ * brevity-flutter (Dart) — so the shared artifact is the spec rather than a
284
+ * package: Swift, Kotlin and Dart cannot import a TS one. The safety net is the
285
+ * cross-language conformance suite, which the tests below pin against verbatim.
286
+ * Cite the spec, not any one implementation: mirrors drift.
287
+ *
288
+ * A follow-up lifts the two halves into their canonical homes without changing
289
+ * behaviour: `blake2b256Keyed` into `@parity/product-sdk-crypto` (the missing
290
+ * sibling of the `blake2b256` already there) and the pure derivation into
291
+ * `@parity/product-sdk-keys`, beside `product-account.ts`. The session wrapper
292
+ * below stays exactly as it is.
293
+ *
294
+ * @module
295
+ */
296
+
297
+ /**
298
+ * Derive 32 bytes of deterministic entropy for a terminal session, scoped to the
299
+ * calling product and a caller key (RFC-0007 layers 2 + 3).
300
+ *
301
+ * Same wallet + product + key ⇒ same bytes; any difference ⇒ uncorrelated
302
+ * entropy. Because it derives from the wallet (not the device), the entropy is
303
+ * recreatable after device loss as long as the wallet is recoverable.
304
+ *
305
+ * @param session - A QR-paired {@link UserSession}. Must carry
306
+ * `rootEntropySource` (present since host-papp 0.8.6 / RFC-0007).
307
+ * @param productId - The calling product's identifier, used verbatim as the
308
+ * layer-2 scope. Per host-spec §C.7 that is `<label>.dot` for a dotNS product
309
+ * (e.g. `"my-app.dot"`), or `localhost:<port>` — no `.dot` suffix — for local
310
+ * dev. It is case-sensitive and is **not** normalized here: `"My-App.dot"`,
311
+ * `"my-app.dot "` and `"my-app.dot"` derive three uncorrelated entropies.
312
+ *
313
+ * This must be the identifier the *in-container* deployment is served under —
314
+ * the same value you pass as `productId` to `getBulletinSigner` and as
315
+ * `options.productId` to `requestResourceAllocation` — because that is the
316
+ * scope the host derives against. It is **not** `adapter.appId`: the adapter
317
+ * id is a local namespace (cache keys, storage dir) and the two are
318
+ * routinely different. The package's own example pairs
319
+ * `createTerminalAdapter({ appId: "my-cli" })` with
320
+ * `getBulletinSigner(adapter, "my-cli.dot")`. Note that
321
+ * `requestResourceAllocation` falls back to `adapter.appId` when
322
+ * `options.productId` is omitted, so a call relying on that default is *not*
323
+ * a reliable reference point for this argument.
324
+ *
325
+ * The host takes the scope from the iframe's deployment label rather than
326
+ * from the caller, so it differs per deployment: production, each PR preview
327
+ * and local dev all scope to different entropy. A CLI that hardcodes the
328
+ * production id derives valid-but-different bytes against a preview, with no
329
+ * error anywhere.
330
+ *
331
+ * Required and positional **by design** — do not give this a default. Unlike
332
+ * an allocation request, a wrong product scope here fails silently: it yields
333
+ * well-formed entropy that simply cannot decrypt anything.
334
+ * @param key - Caller key, 1..32 bytes (the layer-3 BLAKE2b key). Both
335
+ * bounds are normative: the spec requires zero-length and >32-byte keys to be
336
+ * rejected, because some BLAKE2b implementations silently degrade an empty MAC
337
+ * key to unkeyed mode.
338
+ * @returns 32 bytes of derived entropy.
339
+ * @throws if the session lacks `rootEntropySource` or carries one of the wrong
340
+ * width, if `productId` is not a non-empty string, or if `key` is not 1..32
341
+ * bytes.
342
+ *
343
+ * @remarks
344
+ * This reads nothing account-identifying, and a terminal may hold several paired
345
+ * sessions (see `waitForSessions`). Passing the wrong one yields valid entropy
346
+ * for a *different* wallet: the intended user cannot decrypt, and the other
347
+ * wallet's holder can. Pin the identity with `sessionRootPublicKey` before
348
+ * deriving long-lived keys.
349
+ *
350
+ * The return value is raw key material — do not log it or persist it unwrapped.
351
+ */
352
+ declare function deriveEntropy(session: UserSession, productId: string, key: Uint8Array): Uint8Array;
353
+
253
354
  /**
254
355
  * Terminal QR code rendering using Unicode half-block characters.
255
356
  *
@@ -286,4 +387,4 @@ declare function renderQrCode(data: string, options?: QrRenderOptions): Promise<
286
387
  */
287
388
  declare function createNodeStorageAdapter(appId: string, storageDir?: string): StorageAdapter;
288
389
 
289
- export { INCOMPLETE_SESSION_MESSAGE, type ProductAccountRef, type ProductSubtreeOptions, type QrRenderOptions, type SessionSignerOptions, type StatementStoreEnvironment, StatementStoreNetworks, TerminalAdapter, createNodeStorageAdapter, createSessionSigner, createSessionSignerForAccount, deriveProductPublicKey, getBulletinSigner, getProductSubtreePublicKey, getStatementStoreProver, hasBulletinAllowance, hasStatementStoreAllowance, renderQrCode, sessionRootPublicKey, waitForSessions };
390
+ export { INCOMPLETE_SESSION_MESSAGE, type ProductAccountRef, type ProductSubtreeOptions, type QrRenderOptions, type SessionSignerOptions, type StatementStoreEnvironment, StatementStoreNetworks, TerminalAdapter, createNodeStorageAdapter, createSessionSigner, createSessionSignerForAccount, deriveEntropy, deriveProductPublicKey, getBulletinSigner, getProductSubtreePublicKey, getStatementStoreProver, hasBulletinAllowance, hasStatementStoreAllowance, renderQrCode, sessionRootPublicKey, waitForSessions };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { cacheFilePath, withFileLock, loadJsonCache, saveJsonCache } from './chunk-EORPX5XL.js';
2
- export { requestResourceAllocation, sendResourceAllocation } from './chunk-EORPX5XL.js';
1
+ import { cacheFilePath, withFileLock, loadJsonCache, saveJsonCache } from './chunk-L75TTEEA.js';
2
+ export { requestResourceAllocation, sendResourceAllocation } from './chunk-L75TTEEA.js';
3
3
  import { createNodeStorageAdapter, sanitizeKey } from './chunk-GHOFKVUG.js';
4
4
  export { createNodeStorageAdapter } from './chunk-GHOFKVUG.js';
5
5
  import { SS_PREVIEW_STAGE_ENDPOINTS, createPappAdapter, AllowanceError } from '@novasamatech/host-papp';
@@ -10,12 +10,14 @@ import { getWsProvider } from '@polkadot-api/ws-provider';
10
10
  import { deriveProductAccountPublicKey } from '@parity/product-sdk-keys';
11
11
  import { AllowanceExpiredError } from '@parity/product-sdk-signer/errors';
12
12
  export { AllowanceExpiredError, SignerError } from '@parity/product-sdk-signer/errors';
13
+ import { createHostTxCreator } from '@parity/product-sdk-host/tx-creator';
13
14
  import { toHex, fromHex } from '@polkadot-api/utils';
14
15
  import { gcm } from '@noble/ciphers/aes.js';
15
16
  import { blake2b } from '@noble/hashes/blake2.js';
16
17
  import { readFile } from 'fs/promises';
17
18
  import { join } from 'path';
18
19
  import { Enum, _void, Struct, str, Bytes, Vector } from 'scale-ts';
20
+ import '@noble/hashes/utils.js';
19
21
 
20
22
  var StatementStoreNetworks = {
21
23
  // host-papp has no constant for this host; its `SS_PASEO_STABLE_STAGE_ENDPOINTS`
@@ -196,30 +198,20 @@ function toAllowanceExpiredError(error) {
196
198
  function toProductAccountId(ref) {
197
199
  return [ref.productId, { tag: "Index", value: ref.derivationIndex }];
198
200
  }
199
- function buildCreateTransactionRequest(productAccountId, callData, signedExtensions, txExtVersion) {
200
- const checkGenesis = signedExtensions.CheckGenesis;
201
- if (!checkGenesis) {
202
- throw new Error(
203
- "Cannot build transaction: chain did not provide the CheckGenesis signed extension"
204
- );
205
- }
201
+ function buildCreateTransactionRequest(productAccountId, request) {
206
202
  return {
207
203
  payload: {
208
204
  tag: "v1",
209
205
  value: {
210
206
  signer: productAccountId,
211
- // CheckGenesis carries the genesis hash as its additionalSigned (implicit) data.
212
- // `toHex` is typed `string`; the codec wants the 0x literal type.
213
- genesisHash: toHex(checkGenesis.additionalSigned),
214
- callData,
215
- extensions: Object.values(signedExtensions).map(
216
- ({ identifier, value, additionalSigned }) => ({
217
- id: identifier,
218
- extra: value,
219
- additionalSigned
220
- })
221
- ),
222
- txExtVersion
207
+ genesisHash: request.genesisHash,
208
+ callData: fromHex(request.callData),
209
+ extensions: request.extensions.map(({ id, extra, additionalSigned }) => ({
210
+ id,
211
+ extra: fromHex(extra),
212
+ additionalSigned: fromHex(additionalSigned)
213
+ })),
214
+ txExtVersion: request.txExtVersion
223
215
  }
224
216
  }
225
217
  };
@@ -233,17 +225,6 @@ async function requestSignedTransaction(session, request) {
233
225
  }
234
226
  return result.value;
235
227
  }
236
- function makeTxSignTx(session, productAccountId, txExtVersion) {
237
- return async (callData, signedExtensions) => requestSignedTransaction(
238
- session,
239
- buildCreateTransactionRequest(
240
- productAccountId,
241
- callData,
242
- signedExtensions,
243
- txExtVersion
244
- )
245
- );
246
- }
247
228
  function makeRawBytesSignCallback(session, productAccountId) {
248
229
  return async (data) => {
249
230
  const result = await session.signRaw({
@@ -274,17 +255,17 @@ async function deriveProductPublicKey(session, ref, options) {
274
255
  async function buildSessionSigner(session, ref, options) {
275
256
  const productAccountId = toProductAccountId(ref);
276
257
  const publicKey = ref.publicKey ?? await deriveProductPublicKey(session, ref, options);
277
- return {
258
+ return createHostTxCreator(
278
259
  publicKey,
279
- // Transaction signing → host-papp `createTransaction`: the wallet builds
280
- // and signs the extrinsic, so it can show a decoded tx (no `<Bytes>`
281
- // envelope) and unknown signed extensions (e.g. `AsPgas`) survive.
282
- signTx: makeTxSignTx(session, productAccountId, options?.txExtVersion ?? 0),
283
- // Arbitrary-byte signing keeps the `Bytes` tag so the wallet applies the
284
- // `<Bytes>...</Bytes>` anti-phishing envelope exactly once on its side
285
- // (correct for non-extrinsic user data).
286
- signBytes: makeRawBytesSignCallback(session, productAccountId)
287
- };
260
+ async (request) => toHex(
261
+ await requestSignedTransaction(
262
+ session,
263
+ buildCreateTransactionRequest(productAccountId, request)
264
+ )
265
+ ),
266
+ makeRawBytesSignCallback(session, productAccountId),
267
+ options?.txExtVersion ?? 0
268
+ );
288
269
  }
289
270
  function createSessionSigner(session, adapter, publicKey, options) {
290
271
  return buildSessionSigner(
@@ -346,6 +327,8 @@ function resolveSessionId(adapter, sessionId) {
346
327
  async function getBulletinSigner(adapter, productId, sessionId) {
347
328
  const resolvedSessionId = resolveSessionId(adapter, sessionId);
348
329
  return adapter.allowance.getBulletinSigner(resolvedSessionId, productId).match(
330
+ // host-papp 0.12 builds this with `getTxCreator`, so it carries
331
+ // `publicKey` and `signBytes`, but its declared type is plain `TxCreator`.
349
332
  (signer) => signer,
350
333
  (err) => {
351
334
  throw err;
@@ -402,6 +385,33 @@ function waitForSessions(adapter, timeoutMs = 3e3) {
402
385
  });
403
386
  });
404
387
  }
388
+ var ROOT_ENTROPY_SOURCE_LEN = 32;
389
+ var MAX_KEY_LEN = 32;
390
+ var textEncoder = new TextEncoder();
391
+ var b2 = (message, key) => key ? blake2b(message, { dkLen: 32, key }) : blake2b(message, { dkLen: 32 });
392
+ function deriveEntropy(session, productId, key) {
393
+ const rootEntropySource = session.rootEntropySource;
394
+ if (!rootEntropySource) {
395
+ throw new Error(
396
+ 'deriveEntropy: stored login session is missing rootEntropySource. Run "logout" and then "login" to pair again with an RFC-0007 host.'
397
+ );
398
+ }
399
+ if (rootEntropySource.length !== ROOT_ENTROPY_SOURCE_LEN) {
400
+ throw new Error(
401
+ `deriveEntropy: rootEntropySource must be ${ROOT_ENTROPY_SOURCE_LEN} bytes, got ${rootEntropySource.length}. The session is corrupt or the host emitted a non-conforming width; re-pairing will not help if the host is at fault.`
402
+ );
403
+ }
404
+ if (typeof productId !== "string" || productId.length === 0) {
405
+ throw new Error(
406
+ `deriveEntropy: productId must be a non-empty string, got ${typeof productId === "string" ? "an empty string" : String(productId)}`
407
+ );
408
+ }
409
+ if (key.length === 0 || key.length > MAX_KEY_LEN) {
410
+ throw new Error(`deriveEntropy: key must be 1..${MAX_KEY_LEN} bytes, got ${key.length}`);
411
+ }
412
+ const perProduct = b2(rootEntropySource, b2(textEncoder.encode(productId)));
413
+ return b2(perProduct, key);
414
+ }
405
415
 
406
416
  // src/qr-encode.ts
407
417
  async function renderQrCode(data, options) {
@@ -414,6 +424,6 @@ async function renderQrCode(data, options) {
414
424
  return result;
415
425
  }
416
426
 
417
- export { INCOMPLETE_SESSION_MESSAGE, StatementStoreNetworks, createSessionSigner, createSessionSignerForAccount, createTerminalAdapter, deriveProductPublicKey, getBulletinSigner, getProductSubtreePublicKey, getStatementStoreProver, hasBulletinAllowance, hasStatementStoreAllowance, isBenignTeardownError, renderQrCode, sessionRootPublicKey, waitForSessions };
427
+ export { INCOMPLETE_SESSION_MESSAGE, StatementStoreNetworks, createSessionSigner, createSessionSignerForAccount, createTerminalAdapter, deriveEntropy, deriveProductPublicKey, getBulletinSigner, getProductSubtreePublicKey, getStatementStoreProver, hasBulletinAllowance, hasStatementStoreAllowance, isBenignTeardownError, renderQrCode, sessionRootPublicKey, waitForSessions };
418
428
  //# sourceMappingURL=index.js.map
419
429
  //# sourceMappingURL=index.js.map