@novasamatech/host-papp 0.9.2 → 0.9.4

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/README.md CHANGED
@@ -218,34 +218,74 @@ vrf.match(
218
218
  This is the non-`AutoSigning` path only: when `AutoSigning` covers the account the host signs locally and never
219
219
  round-trips to the wallet.
220
220
 
221
- ## Ring VRF proofs and aliases
221
+ ## Ring VRF keys, proofs and aliases
222
222
 
223
- A `UserSession` can ask the paired device for a privacy-preserving contextual alias, or a ring VRF proof, for a
224
- product-scoped `context` and a `ring` location. The device selects the member key for the ring; `callingProductId` names
225
- the product the host is acting for. Both take the same `(context, ring)` so the alias in the proof matches
226
- `getRingVrfAlias`.
223
+ Ring VRF member keys are **explicit and product-owned** (RFC-0024): a product registers the keys it owns against the
224
+ rings it intends them for, other products discover those registrations by an anonymized handle, and the handle is passed
225
+ to every call that uses the key. The paired device is the authoritative registry it needs the complete set to serve
226
+ slot assignment and PGAS claims, and to show the user what their keys are used for.
227
227
 
228
228
  ```ts
229
- // [productId, suffix]. The suffix is the wire `Index(u32) | Raw([u8; 32])` selector
230
- // (RFC 0022): `Index` for a plain index, `Raw` for a raw 32-byte index. It
231
- // expands to the same 32-byte value as a product account's derivation index.
232
- const context = ['product.dot', { tag: 'Index', value: 0 }];
233
229
  const ring = {
234
230
  chainId: '0x…', // 32-byte chain genesis hash
235
231
  junctions: [{ tag: 'PalletInstance', value: 42 }],
236
232
  };
237
233
 
238
- const alias = await currentSession.getRingVrfAlias('caller.dot', context, ring);
234
+ // Register a key owned by `peopl.dot` at index 0 for that ring. Consent-free and
235
+ // idempotent — re-registering an index for another ring extends the entry.
236
+ const publicKey = await currentSession.registerRingVrfKey('peopl.dot', { tag: 'Index', value: 0 }, ring);
237
+
238
+ // Discover another product's keys. `'PublicKey'` disclosure additionally returns
239
+ // the member public key, which is linkable across every ring it appears in and so
240
+ // is permissioned cross-product.
241
+ const entries = await currentSession.listRingVrfKeys('game.dot', 'peopl.dot', 'Anonymized');
242
+ ```
243
+
244
+ A `UserSession` can then ask the paired device for a privacy-preserving contextual alias, or a ring VRF proof, for a
245
+ given `keyHandle`, product-scoped `context` and `ring` location. `callingProductId` names the product the host is acting
246
+ for — it is what the owner's allowlist is checked against when the handle is foreign. Both take the same
247
+ `(keyHandle, context, ring)` so the alias in the proof matches `getRingVrfAlias`.
239
248
 
240
- const proof = await currentSession.createRingVrfProof('caller.dot', context, ring, new Uint8Array([0x48, 0x69]));
249
+ ```ts
250
+ // The key handle names a slot in the owner's ring VRF domain. Select it from
251
+ // `listRingVrfKeys` by declared ring and pass it through opaquely — never
252
+ // hardcode another product's index.
253
+ const keyHandle = ['peopl.dot', { tag: 'Index', value: 0 }];
254
+
255
+ // [productId, suffix]. The suffix is the wire `Index(u32) | Raw([u8; 32])` selector
256
+ // (RFC 0022): `Index` for a plain index, `Raw` for a raw 32-byte index. It
257
+ // expands to the same 32-byte value as a product account's derivation index.
258
+ const context = ['product.dot', { tag: 'Index', value: 0 }];
259
+
260
+ const alias = await currentSession.getRingVrfAlias('caller.dot', keyHandle, context, ring);
261
+
262
+ const proof = await currentSession.createRingVrfProof(
263
+ 'caller.dot',
264
+ keyHandle,
265
+ context,
266
+ ring,
267
+ new Uint8Array([0x48, 0x69]),
268
+ );
241
269
  proof.match(
242
270
  ({ proof, contextualAlias, ringIndex, ringRevision }) =>
243
271
  console.log('proof at ring', ringIndex, 'revision', ringRevision),
244
- // failures decode to a structured `RingVrfError` (RingNotFound / NotMember / Rejected / Unknown)
272
+ // failures decode to a structured `CreateProofErr` RingNotFound / NotMember /
273
+ // KeyNotRegistered / KeyNotInRing / NotAllowlisted / Rejected / Unknown
245
274
  error => console.error('proof failed:', error),
246
275
  );
276
+
277
+ // `ringVrfSign` signs with the member key itself instead of proving membership
278
+ // anonymously. No context and no ring — nothing for either to scope — and the
279
+ // result is verified against the member public key, so it is linkable to every
280
+ // other use of that key.
281
+ const signature = await currentSession.ringVrfSign('caller.dot', keyHandle, new Uint8Array([0x48, 0x69]));
247
282
  ```
248
283
 
284
+ Producing a proof or a signature with a **foreign** key handle is gated on the key's owning product having allowlisted
285
+ the caller in its manifest, and there is deliberately no user-prompt fallback: `message` is opaque, so consenting to it
286
+ is not meaningful consent, and only the owner is positioned to evaluate the risk. Reading an alias authorizes nothing
287
+ and stays on the ordinary grant-or-prompt path.
288
+
249
289
  ## Product subtree public keys
250
290
 
251
291
  Product accounts live at `//product//{productId}/{index}` (RFC 0022). The product junction is **hard**, so the user's
@@ -261,9 +301,16 @@ subtreeKey.match(
261
301
  );
262
302
  ```
263
303
 
264
- The request is consent-free — the response carries no secret material. Only `AutoSigning` does: its payload is now the
265
- product-subtree secret key alone (`productRootPrivateKey`, 64-byte expanded sr25519 secret), which exposes exactly that
266
- product's subtree. The former `productDerivationSecret` is gone.
304
+ The request is consent-free — the response carries no secret material. Only `AutoSigning` does: its payload is the
305
+ product-subtree secret key (`productRootPrivateKey`, 64-byte expanded sr25519 secret), which exposes exactly that
306
+ product's subtree, plus `ringVrfDomainEntropy` (RFC-0024) — the entropy of the `//{productId}` node of the disjoint ring
307
+ VRF tree, which lets the host derive the member secret of a **registered** key locally. The former
308
+ `productDerivationSecret` is gone.
309
+
310
+ Bundling the entropy widens the grant: "sign transactions without prompting me" and "produce personhood proofs offline"
311
+ become one decision. And because derivation from the entropy is unconditional arithmetic, the registry is what
312
+ distinguishes a meaningful index from a meaningless one — a host MUST NOT derive a member secret for a
313
+ `(product, index)` pair absent from its registry.
267
314
 
268
315
  ## Identity lookups
269
316
 
package/dist/index.d.ts CHANGED
@@ -13,7 +13,8 @@ export type { Credibility, Identity, IdentityAdapter, IdentityRepository } from
13
13
  export { createIdentityRepository } from './identity/impl.js';
14
14
  export { createIdentityRpcAdapter } from './identity/rpcAdapter.js';
15
15
  export type { SignRawLegacyRequest, SignRawLegacyResponse, SigningPayloadRequest, SigningPayloadResponse, SigningRawRequest, SigningRequest, } from './sso/sessionManager/scale/signing.js';
16
- export type { RingVrfAliasRequest, RingVrfAliasResponse, RingVrfProofRequest, RingVrfProofResponse, } from './sso/sessionManager/scale/ringVrf.js';
16
+ export type { RingVrfAliasRequest, RingVrfAliasResponse, RingVrfProofRequest, RingVrfProofResponse, RingVrfSignRequest, RingVrfSignResponse, } from './sso/sessionManager/scale/ringVrf.js';
17
+ export type { ListRingVrfKeysRequest, ListRingVrfKeysResponse, RegisterRingVrfKeyRequest, RegisterRingVrfKeyResponse, } from './sso/sessionManager/scale/ringVrfKeys.js';
17
18
  export type { SignVrfErr, SignVrfRequest, SignVrfResponse } from './sso/sessionManager/scale/signVrf.js';
18
19
  export type { CreateTransactionLegacyRequest, CreateTransactionRequest, CreateTransactionResponse, } from './sso/sessionManager/scale/createTransaction.js';
19
20
  export type { ProductSubtreeRequest, ProductSubtreeResponse } from './sso/sessionManager/scale/productSubtree.js';
@@ -76,5 +76,14 @@ export declare function createAuth({ hostMetadata, deviceIdentity, deviceIdentit
76
76
  };
77
77
  authenticate(): ResultAsync<StoredUserSession | null, Error>;
78
78
  abortAuthentication(): void;
79
+ /**
80
+ * Discard the persisted device keypair and the processed-handshake marker
81
+ * stored with it, so the next pairing runs on a new topic with a new key —
82
+ * which makes any cached on-chain HandshakeSuccess undecryptable.
83
+ *
84
+ * No-op for hosts supplying their own `deviceIdentity` factory, and a
85
+ * pairing already in flight keeps the old identity.
86
+ */
87
+ resetDeviceIdentity(): ResultAsync<void, Error>;
79
88
  };
80
89
  export {};
@@ -146,6 +146,17 @@ export function createAuth({ hostMetadata, deviceIdentity, deviceIdentityStore,
146
146
  authResult = null;
147
147
  pairingStatus.reset();
148
148
  },
149
+ /**
150
+ * Discard the persisted device keypair and the processed-handshake marker
151
+ * stored with it, so the next pairing runs on a new topic with a new key —
152
+ * which makes any cached on-chain HandshakeSuccess undecryptable.
153
+ *
154
+ * No-op for hosts supplying their own `deviceIdentity` factory, and a
155
+ * pairing already in flight keeps the old identity.
156
+ */
157
+ resetDeviceIdentity() {
158
+ return deviceIdentityStore.reset();
159
+ },
149
160
  };
150
161
  function persistAndNotify(identity, success, _flowId) {
151
162
  const localAccount = createLocalSessionAccount(createAccountId(identity.statementAccountPublicKey));
@@ -9,6 +9,7 @@ export type DeviceIdentityStore = {
9
9
  loadOrCreate(): ResultAsync<DeviceIdentity, Error>;
10
10
  readLastProcessedHandshakeStatement(): ResultAsync<string | null, Error>;
11
11
  writeLastProcessedHandshakeStatement(hex: string): ResultAsync<void, Error>;
12
+ reset(): ResultAsync<void, Error>;
12
13
  };
13
14
  export declare function createDeviceIdentityStore(salt: string, storage: StorageAdapter): DeviceIdentityStore;
14
15
  export declare const awaitDeviceIdentity: (store: DeviceIdentityStore) => Promise<DeviceIdentity>;
@@ -66,6 +66,9 @@ export function createDeviceIdentityStore(salt, storage) {
66
66
  return write({ ...existing, lastProcessedHandshakeStatement: hex });
67
67
  });
68
68
  },
69
+ reset() {
70
+ return storage.clear(KEY);
71
+ },
69
72
  };
70
73
  }
71
74
  // Re-export the awaitable form for convenience, since most call sites already
@@ -1,5 +1,6 @@
1
1
  import type { StatementStoreAdapter } from '@novasamatech/statement-store';
2
2
  import type { StorageAdapter } from '@novasamatech/storage-adapter';
3
+ import { ResultAsync } from 'neverthrow';
3
4
  import type { IdentityRepository } from '../../identity/types.js';
4
5
  import type { Callback } from '../../types.js';
5
6
  import type { AllowanceRepository } from '../allowance/index.js';
@@ -20,7 +21,11 @@ export declare function createSsoSessionManager({ ssoSessionRepository, userSecr
20
21
  read: () => UserSession[];
21
22
  subscribe: (callback: Callback<UserSession[]>) => () => void;
22
23
  };
23
- disconnect(userSession: StoredUserSession): import("neverthrow").ResultAsync<void, Error>;
24
+ disconnect(userSession: StoredUserSession): ResultAsync<undefined, Error>;
25
+ /**
26
+ * Teardown by id alone, without notifying the peer.
27
+ * */
28
+ forget(sessionId: string): ResultAsync<undefined, Error>;
24
29
  dispose(): void;
25
30
  };
26
31
  export {};
@@ -1,9 +1,9 @@
1
1
  import { createEncryption } from '@novasamatech/statement-store';
2
- import { okAsync } from 'neverthrow';
2
+ import { ResultAsync, okAsync } from 'neverthrow';
3
3
  import { emitHostPappDebugMessage } from '../../debugBus.js';
4
4
  import { createState } from '../../helpers/state.js';
5
5
  import { createSsoStatementProver } from '../ssoSessionProver.js';
6
- import { createUserSession } from './userSession.js';
6
+ import { createUserSession, processedMessagesKey } from './userSession.js';
7
7
  export function createSsoSessionManager({ ssoSessionRepository, userSecretRepository, allowanceRepository, identityRepository, statementStore, storage, }) {
8
8
  const localSessions = createState({});
9
9
  const sessionUnsubscribes = new Map();
@@ -11,9 +11,16 @@ export function createSsoSessionManager({ ssoSessionRepository, userSecretReposi
11
11
  sessionUnsubscribes.get(id)?.();
12
12
  sessionUnsubscribes.delete(id);
13
13
  };
14
- const disconnect = (session) => {
15
- return ssoSessionRepository.filter(s => s.id !== session.id).map(() => undefined);
16
- };
14
+ // The session-list write goes first: it is what notifies the subscription
15
+ // above to unsubscribe and dispose the live session.
16
+ const purgeSession = (sessionId) => ssoSessionRepository
17
+ .filter(s => s.id !== sessionId)
18
+ .andThen(() => ResultAsync.combine([
19
+ userSecretRepository.clear(sessionId),
20
+ allowanceRepository.clearSession(sessionId),
21
+ storage.clear(processedMessagesKey(sessionId)),
22
+ ]))
23
+ .map(() => undefined);
17
24
  ssoSessionRepository.subscribe(userSessions => {
18
25
  const activeSessions = localSessions.read();
19
26
  const toRemove = new Set(Object.keys(activeSessions));
@@ -36,7 +43,7 @@ export function createSsoSessionManager({ ssoSessionRepository, userSecretReposi
36
43
  case 'v1': {
37
44
  switch (message.data.value.tag) {
38
45
  case 'Disconnected':
39
- return disconnect(userSession).map(() => true);
46
+ return purgeSession(userSession.id).map(() => false);
40
47
  }
41
48
  }
42
49
  }
@@ -76,9 +83,18 @@ export function createSsoSessionManager({ ssoSessionRepository, userSecretReposi
76
83
  const session = createSession(userSession, statementStore, storage, userSecretRepository, allowanceRepository, identityRepository);
77
84
  return session
78
85
  .sendDisconnectMessage()
79
- .andThen(() => disconnect(userSession))
80
- .andThen(() => userSecretRepository.clear(userSession.id))
81
- .andThen(() => allowanceRepository.clearSession(userSession.id));
86
+ .orElse(error => {
87
+ console.warn('[host-papp] disconnect: peer notification failed, tearing down locally anyway', error);
88
+ return okAsync(undefined);
89
+ })
90
+ .andTee(() => session.dispose())
91
+ .andThen(() => purgeSession(userSession.id));
92
+ },
93
+ /**
94
+ * Teardown by id alone, without notifying the peer.
95
+ * */
96
+ forget(sessionId) {
97
+ return purgeSession(sessionId);
82
98
  },
83
99
  dispose() {
84
100
  for (const session of Object.values(localSessions.read())) {
@@ -67,6 +67,13 @@ export declare const RemoteMessageCodec: import("scale-ts").Codec<{
67
67
  tag: "RingVrfAliasRequest";
68
68
  value: {
69
69
  callingProductId: string;
70
+ keyHandle: [string, {
71
+ tag: "Index";
72
+ value: number;
73
+ } | {
74
+ tag: "Raw";
75
+ value: Uint8Array<ArrayBufferLike>;
76
+ }];
70
77
  context: [string, {
71
78
  tag: "Index";
72
79
  value: number;
@@ -94,7 +101,7 @@ export declare const RemoteMessageCodec: import("scale-ts").Codec<{
94
101
  alias: Uint8Array<ArrayBufferLike>;
95
102
  }, import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::Rejected"> | import("@novasamatech/scale").CodecError<{
96
103
  reason: string;
97
- }, "GetAliasErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::RingNotFound"> | import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::NotMember">>;
104
+ }, "GetAliasErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::RingNotFound"> | import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::NotMember"> | import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::KeyNotRegistered"> | import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::KeyNotInRing">>;
98
105
  };
99
106
  } | {
100
107
  tag: "ResourceAllocationRequest";
@@ -142,6 +149,7 @@ export declare const RemoteMessageCodec: import("scale-ts").Codec<{
142
149
  tag: "AutoSigning";
143
150
  value: {
144
151
  productRootPrivateKey: Uint8Array<ArrayBufferLike>;
152
+ ringVrfDomainEntropy: Uint8Array<ArrayBufferLike>;
145
153
  };
146
154
  } | {
147
155
  tag: "BulletInAllowance";
@@ -224,6 +232,13 @@ export declare const RemoteMessageCodec: import("scale-ts").Codec<{
224
232
  tag: "RingVrfProofRequest";
225
233
  value: {
226
234
  callingProductId: string;
235
+ keyHandle: [string, {
236
+ tag: "Index";
237
+ value: number;
238
+ } | {
239
+ tag: "Raw";
240
+ value: Uint8Array<ArrayBufferLike>;
241
+ }];
227
242
  context: [string, {
228
243
  tag: "Index";
229
244
  value: number;
@@ -257,7 +272,7 @@ export declare const RemoteMessageCodec: import("scale-ts").Codec<{
257
272
  ringRevision: number;
258
273
  }, import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::Rejected"> | import("@novasamatech/scale").CodecError<{
259
274
  reason: string;
260
- }, "CreateProofErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::RingNotFound"> | import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::NotMember">>;
275
+ }, "CreateProofErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::RingNotFound"> | import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::NotMember"> | import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::KeyNotRegistered"> | import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::KeyNotInRing"> | import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::NotAllowlisted">>;
261
276
  };
262
277
  } | {
263
278
  tag: "SignVrfRequest";
@@ -300,6 +315,91 @@ export declare const RemoteMessageCodec: import("scale-ts").Codec<{
300
315
  productPublicKey: Uint8Array<ArrayBufferLike>;
301
316
  }, string>;
302
317
  };
318
+ } | {
319
+ tag: "RegisterRingVrfKeyRequest";
320
+ value: {
321
+ callingProductId: string;
322
+ index: {
323
+ tag: "Index";
324
+ value: number;
325
+ } | {
326
+ tag: "Raw";
327
+ value: Uint8Array<ArrayBufferLike>;
328
+ };
329
+ ring: {
330
+ chainId: `0x${string}`;
331
+ junctions: ({
332
+ tag: "PalletInstance";
333
+ value: number;
334
+ } | {
335
+ tag: "CollectionId";
336
+ value: Uint8Array<ArrayBufferLike>;
337
+ })[];
338
+ };
339
+ };
340
+ } | {
341
+ tag: "RegisterRingVrfKeyResponse";
342
+ value: {
343
+ respondingTo: string;
344
+ payload: import("scale-ts").ResultPayload<Uint8Array<ArrayBufferLike>, import("@novasamatech/scale").CodecError<undefined, "RegisterRingVrfKeyErr::NotConnected"> | import("@novasamatech/scale").CodecError<undefined, "RegisterRingVrfKeyErr::Rejected"> | import("@novasamatech/scale").CodecError<{
345
+ reason: string;
346
+ }, "RegisterRingVrfKeyErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "RegisterRingVrfKeyErr::RingNotFound">>;
347
+ };
348
+ } | {
349
+ tag: "ListRingVrfKeysRequest";
350
+ value: {
351
+ callingProductId: string;
352
+ owner: string;
353
+ disclosure: "Anonymized" | "PublicKey";
354
+ };
355
+ } | {
356
+ tag: "ListRingVrfKeysResponse";
357
+ value: {
358
+ respondingTo: string;
359
+ payload: import("scale-ts").ResultPayload<{
360
+ handle: [string, {
361
+ tag: "Index";
362
+ value: number;
363
+ } | {
364
+ tag: "Raw";
365
+ value: Uint8Array<ArrayBufferLike>;
366
+ }];
367
+ rings: {
368
+ chainId: `0x${string}`;
369
+ junctions: ({
370
+ tag: "PalletInstance";
371
+ value: number;
372
+ } | {
373
+ tag: "CollectionId";
374
+ value: Uint8Array<ArrayBufferLike>;
375
+ })[];
376
+ }[];
377
+ publicKey: Uint8Array<ArrayBufferLike> | undefined;
378
+ }[], import("@novasamatech/scale").CodecError<undefined, "ListRingVrfKeysErr::NotConnected"> | import("@novasamatech/scale").CodecError<undefined, "ListRingVrfKeysErr::Rejected"> | import("@novasamatech/scale").CodecError<{
379
+ reason: string;
380
+ }, "ListRingVrfKeysErr::Unknown">>;
381
+ };
382
+ } | {
383
+ tag: "RingVrfSignRequest";
384
+ value: {
385
+ callingProductId: string;
386
+ keyHandle: [string, {
387
+ tag: "Index";
388
+ value: number;
389
+ } | {
390
+ tag: "Raw";
391
+ value: Uint8Array<ArrayBufferLike>;
392
+ }];
393
+ message: Uint8Array<ArrayBufferLike>;
394
+ };
395
+ } | {
396
+ tag: "RingVrfSignResponse";
397
+ value: {
398
+ respondingTo: string;
399
+ payload: import("scale-ts").ResultPayload<Uint8Array<ArrayBufferLike>, import("@novasamatech/scale").CodecError<undefined, "RingVrfSignErr::NotConnected"> | import("@novasamatech/scale").CodecError<undefined, "RingVrfSignErr::Rejected"> | import("@novasamatech/scale").CodecError<{
400
+ reason: string;
401
+ }, "RingVrfSignErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "RingVrfSignErr::KeyNotRegistered"> | import("@novasamatech/scale").CodecError<undefined, "RingVrfSignErr::NotAllowlisted">>;
402
+ };
303
403
  };
304
404
  };
305
405
  }>;
@@ -3,7 +3,8 @@ import { Struct, _void, str } from 'scale-ts';
3
3
  import { CreateTransactionLegacyRequestCodec, CreateTransactionRequestCodec, CreateTransactionResponseCodec, } from './createTransaction.js';
4
4
  import { ProductSubtreeRequestCodec, ProductSubtreeResponseCodec } from './productSubtree.js';
5
5
  import { ResourceAllocationRequestCodec, ResourceAllocationResponseCodec } from './resourceAllocation.js';
6
- import { RingVrfAliasRequestCodec, RingVrfAliasResponseCodec, RingVrfProofRequestCodec, RingVrfProofResponseCodec, } from './ringVrf.js';
6
+ import { RingVrfAliasRequestCodec, RingVrfAliasResponseCodec, RingVrfProofRequestCodec, RingVrfProofResponseCodec, RingVrfSignRequestCodec, RingVrfSignResponseCodec, } from './ringVrf.js';
7
+ import { ListRingVrfKeysRequestCodec, ListRingVrfKeysResponseCodec, RegisterRingVrfKeyRequestCodec, RegisterRingVrfKeyResponseCodec, } from './ringVrfKeys.js';
7
8
  import { SignVrfRequestCodec, SignVrfResponseCodec } from './signVrf.js';
8
9
  import { SignRawLegacyRequestCodec, SignRawLegacyResponseCodec, SigningRequestCodec, SigningResponseCodec, } from './signing.js';
9
10
  export const RemoteMessageCodec = Struct({
@@ -31,6 +32,15 @@ export const RemoteMessageCodec = Struct({
31
32
  // RFC-0022 additions — appended so existing variant indexes stay stable.
32
33
  ProductSubtreeRequest: ProductSubtreeRequestCodec,
33
34
  ProductSubtreeResponse: ProductSubtreeResponseCodec,
35
+ // RFC-0024 additions — likewise appended. `RingVrfAliasRequest` and
36
+ // `RingVrfProofRequest` above gained a `keyHandle` field in place rather
37
+ // than being re-added here, since a handle is now mandatory on both.
38
+ RegisterRingVrfKeyRequest: RegisterRingVrfKeyRequestCodec,
39
+ RegisterRingVrfKeyResponse: RegisterRingVrfKeyResponseCodec,
40
+ ListRingVrfKeysRequest: ListRingVrfKeysRequestCodec,
41
+ ListRingVrfKeysResponse: ListRingVrfKeysResponseCodec,
42
+ RingVrfSignRequest: RingVrfSignRequestCodec,
43
+ RingVrfSignResponse: RingVrfSignResponseCodec,
34
44
  }),
35
45
  }),
36
46
  });
@@ -32,6 +32,7 @@ export declare const ApAllocatedResourceCodec: import("scale-ts").Codec<{
32
32
  tag: "AutoSigning";
33
33
  value: {
34
34
  productRootPrivateKey: Uint8Array<ArrayBufferLike>;
35
+ ringVrfDomainEntropy: Uint8Array<ArrayBufferLike>;
35
36
  };
36
37
  } | {
37
38
  tag: "BulletInAllowance";
@@ -57,6 +58,7 @@ export declare const ApAllocationOutcomeCodec: import("scale-ts").Codec<{
57
58
  tag: "AutoSigning";
58
59
  value: {
59
60
  productRootPrivateKey: Uint8Array<ArrayBufferLike>;
61
+ ringVrfDomainEntropy: Uint8Array<ArrayBufferLike>;
60
62
  };
61
63
  } | {
62
64
  tag: "BulletInAllowance";
@@ -113,6 +115,7 @@ export declare const ResourceAllocationResponseCodec: import("scale-ts").Codec<{
113
115
  tag: "AutoSigning";
114
116
  value: {
115
117
  productRootPrivateKey: Uint8Array<ArrayBufferLike>;
118
+ ringVrfDomainEntropy: Uint8Array<ArrayBufferLike>;
116
119
  };
117
120
  } | {
118
121
  tag: "BulletInAllowance";
@@ -23,6 +23,17 @@ export const ApAllocatedResourceCodec = Enum({
23
23
  // `Sr25519PrivateKey ++ Sr25519Nonce` (64 bytes): the full expanded secret
24
24
  // needed to sign and soft-derive `/{index}` below the product root.
25
25
  productRootPrivateKey: Bytes(),
26
+ // RFC-0024: entropy of the `//{productId}` node of the ring VRF tree, which
27
+ // is disjoint from the sr25519 product-account tree above. It lets the Host
28
+ // derive the member secret of a *registered* key locally — the second
29
+ // motivation being that a headless Account Holder execution context may not
30
+ // fit a ring VRF proof inside its ~30 s / ~24 MB budget.
31
+ //
32
+ // Derivation from this entropy is unconditional arithmetic: nothing about
33
+ // holding it distinguishes a meaningful index from a meaningless one, so the
34
+ // registry supplies that distinction. A Host MUST NOT derive a member secret
35
+ // for a `(product, index)` pair absent from its registry.
36
+ ringVrfDomainEntropy: Bytes(),
26
37
  }),
27
38
  });
28
39
  export const ApAllocationOutcomeCodec = Enum({
@@ -2,6 +2,13 @@ import type { CodecType } from 'scale-ts';
2
2
  export type RingVrfAliasRequest = CodecType<typeof RingVrfAliasRequestCodec>;
3
3
  export declare const RingVrfAliasRequestCodec: import("scale-ts").Codec<{
4
4
  callingProductId: string;
5
+ keyHandle: [string, {
6
+ tag: "Index";
7
+ value: number;
8
+ } | {
9
+ tag: "Raw";
10
+ value: Uint8Array<ArrayBufferLike>;
11
+ }];
5
12
  context: [string, {
6
13
  tag: "Index";
7
14
  value: number;
@@ -28,11 +35,18 @@ export declare const RingVrfAliasResponseCodec: import("scale-ts").Codec<{
28
35
  alias: Uint8Array<ArrayBufferLike>;
29
36
  }, import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::Rejected"> | import("@novasamatech/scale").CodecError<{
30
37
  reason: string;
31
- }, "GetAliasErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::RingNotFound"> | import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::NotMember">>;
38
+ }, "GetAliasErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::RingNotFound"> | import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::NotMember"> | import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::KeyNotRegistered"> | import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::KeyNotInRing">>;
32
39
  }>;
33
40
  export type RingVrfProofRequest = CodecType<typeof RingVrfProofRequestCodec>;
34
41
  export declare const RingVrfProofRequestCodec: import("scale-ts").Codec<{
35
42
  callingProductId: string;
43
+ keyHandle: [string, {
44
+ tag: "Index";
45
+ value: number;
46
+ } | {
47
+ tag: "Raw";
48
+ value: Uint8Array<ArrayBufferLike>;
49
+ }];
36
50
  context: [string, {
37
51
  tag: "Index";
38
52
  value: number;
@@ -65,5 +79,32 @@ export declare const RingVrfProofResponseCodec: import("scale-ts").Codec<{
65
79
  ringRevision: number;
66
80
  }, import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::Rejected"> | import("@novasamatech/scale").CodecError<{
67
81
  reason: string;
68
- }, "CreateProofErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::RingNotFound"> | import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::NotMember">>;
82
+ }, "CreateProofErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::RingNotFound"> | import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::NotMember"> | import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::KeyNotRegistered"> | import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::KeyNotInRing"> | import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::NotAllowlisted">>;
83
+ }>;
84
+ /**
85
+ * Host → Account Holder request for a plain signature by the ring VRF member key
86
+ * itself (RFC-0024 `ring_vrf_sign`), rather than an anonymous ring proof.
87
+ *
88
+ * Carries no context and no ring: the signature derives no alias and proves no
89
+ * membership, so there is nothing for either to scope. It is verified against
90
+ * the member public key and is therefore linkable to every other use of the key.
91
+ */
92
+ export type RingVrfSignRequest = CodecType<typeof RingVrfSignRequestCodec>;
93
+ export declare const RingVrfSignRequestCodec: import("scale-ts").Codec<{
94
+ callingProductId: string;
95
+ keyHandle: [string, {
96
+ tag: "Index";
97
+ value: number;
98
+ } | {
99
+ tag: "Raw";
100
+ value: Uint8Array<ArrayBufferLike>;
101
+ }];
102
+ message: Uint8Array<ArrayBufferLike>;
103
+ }>;
104
+ export type RingVrfSignResponse = CodecType<typeof RingVrfSignResponseCodec>;
105
+ export declare const RingVrfSignResponseCodec: import("scale-ts").Codec<{
106
+ respondingTo: string;
107
+ payload: import("scale-ts").ResultPayload<Uint8Array<ArrayBufferLike>, import("@novasamatech/scale").CodecError<undefined, "RingVrfSignErr::NotConnected"> | import("@novasamatech/scale").CodecError<undefined, "RingVrfSignErr::Rejected"> | import("@novasamatech/scale").CodecError<{
108
+ reason: string;
109
+ }, "RingVrfSignErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "RingVrfSignErr::KeyNotRegistered"> | import("@novasamatech/scale").CodecError<undefined, "RingVrfSignErr::NotAllowlisted">>;
69
110
  }>;
@@ -1,8 +1,9 @@
1
- import { ContextualAlias, CreateProofErr, DotNsIdentifier, GetAliasErr, ProductProofContext, RingLocation, RingVrfProof, } from '@novasamatech/host-api';
1
+ import { ContextualAlias, CreateProofErr, DotNsIdentifier, GetAliasErr, ProductProofContext, RingLocation, RingVrfKeyHandle, RingVrfProof, RingVrfSignErr, } from '@novasamatech/host-api';
2
2
  import { Bytes } from '@novasamatech/scale';
3
3
  import { Result, Struct, str } from 'scale-ts';
4
4
  export const RingVrfAliasRequestCodec = Struct({
5
5
  callingProductId: DotNsIdentifier,
6
+ keyHandle: RingVrfKeyHandle,
6
7
  context: ProductProofContext,
7
8
  ring: RingLocation,
8
9
  });
@@ -12,6 +13,7 @@ export const RingVrfAliasResponseCodec = Struct({
12
13
  });
13
14
  export const RingVrfProofRequestCodec = Struct({
14
15
  callingProductId: DotNsIdentifier,
16
+ keyHandle: RingVrfKeyHandle,
15
17
  context: ProductProofContext,
16
18
  ring: RingLocation,
17
19
  message: Bytes(),
@@ -20,3 +22,12 @@ export const RingVrfProofResponseCodec = Struct({
20
22
  respondingTo: str,
21
23
  payload: Result(RingVrfProof, CreateProofErr),
22
24
  });
25
+ export const RingVrfSignRequestCodec = Struct({
26
+ callingProductId: DotNsIdentifier,
27
+ keyHandle: RingVrfKeyHandle,
28
+ message: Bytes(),
29
+ });
30
+ export const RingVrfSignResponseCodec = Struct({
31
+ respondingTo: str,
32
+ payload: Result(Bytes(), RingVrfSignErr),
33
+ });
@@ -0,0 +1,85 @@
1
+ import type { CodecType } from 'scale-ts';
2
+ /**
3
+ * Host → Account Holder registration of a key the calling product owns.
4
+ *
5
+ * Ownership is `callingProductId` and is never chosen by the caller, so this is
6
+ * consent-free. Registration is idempotent: re-registering an `index` for an
7
+ * additional `ring` extends the existing entry rather than creating a second
8
+ * one, so re-notifying the phone about an entry it already has costs nothing.
9
+ *
10
+ * A Host holding the product's ring VRF domain entropy (see `AutoSigning` in
11
+ * `resourceAllocation.ts`) answers immediately and mirrors this fire-and-forget;
12
+ * without the entropy it issues the request and waits.
13
+ *
14
+ * > A Host MUST NOT derive a member secret for a `(product, index)` pair absent
15
+ * > from its registry. Domain entropy makes derivation unconditional — only
16
+ * > registration, which always reaches the phone, brings a key into existence.
17
+ */
18
+ export type RegisterRingVrfKeyRequest = CodecType<typeof RegisterRingVrfKeyRequestCodec>;
19
+ export declare const RegisterRingVrfKeyRequestCodec: import("scale-ts").Codec<{
20
+ callingProductId: string;
21
+ index: {
22
+ tag: "Index";
23
+ value: number;
24
+ } | {
25
+ tag: "Raw";
26
+ value: Uint8Array<ArrayBufferLike>;
27
+ };
28
+ ring: {
29
+ chainId: `0x${string}`;
30
+ junctions: ({
31
+ tag: "PalletInstance";
32
+ value: number;
33
+ } | {
34
+ tag: "CollectionId";
35
+ value: Uint8Array<ArrayBufferLike>;
36
+ })[];
37
+ };
38
+ }>;
39
+ export type RegisterRingVrfKeyResponse = CodecType<typeof RegisterRingVrfKeyResponseCodec>;
40
+ export declare const RegisterRingVrfKeyResponseCodec: import("scale-ts").Codec<{
41
+ respondingTo: string;
42
+ payload: import("scale-ts").ResultPayload<Uint8Array<ArrayBufferLike>, import("@novasamatech/scale").CodecError<undefined, "RegisterRingVrfKeyErr::NotConnected"> | import("@novasamatech/scale").CodecError<undefined, "RegisterRingVrfKeyErr::Rejected"> | import("@novasamatech/scale").CodecError<{
43
+ reason: string;
44
+ }, "RegisterRingVrfKeyErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "RegisterRingVrfKeyErr::RingNotFound">>;
45
+ }>;
46
+ /**
47
+ * Host → Account Holder listing of the registry entries owned by `owner`.
48
+ *
49
+ * `owner` may be `callingProductId` — permissionless — or another product, in
50
+ * which case the caller needs a grant. `PublicKey` disclosure additionally
51
+ * returns the member public key, which is linkable across every ring it appears
52
+ * in and so is permissioned cross-product even though the handle is not.
53
+ */
54
+ export type ListRingVrfKeysRequest = CodecType<typeof ListRingVrfKeysRequestCodec>;
55
+ export declare const ListRingVrfKeysRequestCodec: import("scale-ts").Codec<{
56
+ callingProductId: string;
57
+ owner: string;
58
+ disclosure: "Anonymized" | "PublicKey";
59
+ }>;
60
+ export type ListRingVrfKeysResponse = CodecType<typeof ListRingVrfKeysResponseCodec>;
61
+ export declare const ListRingVrfKeysResponseCodec: import("scale-ts").Codec<{
62
+ respondingTo: string;
63
+ payload: import("scale-ts").ResultPayload<{
64
+ handle: [string, {
65
+ tag: "Index";
66
+ value: number;
67
+ } | {
68
+ tag: "Raw";
69
+ value: Uint8Array<ArrayBufferLike>;
70
+ }];
71
+ rings: {
72
+ chainId: `0x${string}`;
73
+ junctions: ({
74
+ tag: "PalletInstance";
75
+ value: number;
76
+ } | {
77
+ tag: "CollectionId";
78
+ value: Uint8Array<ArrayBufferLike>;
79
+ })[];
80
+ }[];
81
+ publicKey: Uint8Array<ArrayBufferLike> | undefined;
82
+ }[], import("@novasamatech/scale").CodecError<undefined, "ListRingVrfKeysErr::NotConnected"> | import("@novasamatech/scale").CodecError<undefined, "ListRingVrfKeysErr::Rejected"> | import("@novasamatech/scale").CodecError<{
83
+ reason: string;
84
+ }, "ListRingVrfKeysErr::Unknown">>;
85
+ }>;
@@ -0,0 +1,22 @@
1
+ import { DerivationIndex, DotNsIdentifier, ListRingVrfKeysErr, RegisterRingVrfKeyErr, RegisteredRingVrfKey, RingLocation, RingVrfKeyDisclosure, RingVrfPublicKey, } from '@novasamatech/host-api';
2
+ import { Result, Struct, Vector, str } from 'scale-ts';
3
+ export const RegisterRingVrfKeyRequestCodec = Struct({
4
+ callingProductId: DotNsIdentifier,
5
+ index: DerivationIndex,
6
+ ring: RingLocation,
7
+ });
8
+ export const RegisterRingVrfKeyResponseCodec = Struct({
9
+ // referencing to RemoteMessage.messageId
10
+ respondingTo: str,
11
+ payload: Result(RingVrfPublicKey, RegisterRingVrfKeyErr),
12
+ });
13
+ export const ListRingVrfKeysRequestCodec = Struct({
14
+ callingProductId: DotNsIdentifier,
15
+ owner: DotNsIdentifier,
16
+ disclosure: RingVrfKeyDisclosure,
17
+ });
18
+ export const ListRingVrfKeysResponseCodec = Struct({
19
+ // referencing to RemoteMessage.messageId
20
+ respondingTo: str,
21
+ payload: Result(Vector(RegisteredRingVrfKey), ListRingVrfKeysErr),
22
+ });
@@ -1,4 +1,4 @@
1
- import { ContextualAlias, ProductProofContext, RingLocation, RingVrfProof, VrfSignature } from '@novasamatech/host-api';
1
+ import { ContextualAlias, DerivationIndex, ProductProofContext, RegisteredRingVrfKey, RingLocation, RingVrfKeyDisclosure, RingVrfKeyHandle, RingVrfProof, VrfSignature } from '@novasamatech/host-api';
2
2
  import type { Encryption, StatementProver, StatementStoreAdapter } from '@novasamatech/statement-store';
3
3
  import type { StorageAdapter } from '@novasamatech/storage-adapter';
4
4
  import { ResultAsync } from 'neverthrow';
@@ -12,6 +12,7 @@ import { RemoteMessageCodec } from './scale/remoteMessage.js';
12
12
  import type { ApAllocationOutcome, ResourceAllocationRequest } from './scale/resourceAllocation.js';
13
13
  import type { SignVrfRequest } from './scale/signVrf.js';
14
14
  import type { SignRawLegacyRequest, SigningPayloadRequest, SigningPayloadResponseData, SigningRawRequest } from './scale/signing.js';
15
+ export declare const processedMessagesKey: (sessionId: string) => string;
15
16
  export type UserSession = StoredUserSession & {
16
17
  /** Read this session's persisted allowance slot-account key for a product/resource. */
17
18
  readAllowance(productId: string, resource: AllowanceResourceKind): ResultAsync<Uint8Array | null, Error>;
@@ -24,8 +25,27 @@ export type UserSession = StoredUserSession & {
24
25
  signRawLegacy(payload: SignRawLegacyRequest): ResultAsync<Uint8Array, Error>;
25
26
  createTransaction(payload: CreateTransactionRequest): ResultAsync<Uint8Array, Error>;
26
27
  createTransactionLegacy(payload: CreateTransactionLegacyRequest): ResultAsync<Uint8Array, Error>;
27
- getRingVrfAlias(callingProductId: string, context: CodecType<typeof ProductProofContext>, ring: CodecType<typeof RingLocation>): ResultAsync<CodecType<typeof ContextualAlias>, Error>;
28
- createRingVrfProof(callingProductId: string, context: CodecType<typeof ProductProofContext>, ring: CodecType<typeof RingLocation>, message: Uint8Array): ResultAsync<CodecType<typeof RingVrfProof>, Error>;
28
+ getRingVrfAlias(callingProductId: string, keyHandle: CodecType<typeof RingVrfKeyHandle>, context: CodecType<typeof ProductProofContext>, ring: CodecType<typeof RingLocation>): ResultAsync<CodecType<typeof ContextualAlias>, Error>;
29
+ createRingVrfProof(callingProductId: string, keyHandle: CodecType<typeof RingVrfKeyHandle>, context: CodecType<typeof ProductProofContext>, ring: CodecType<typeof RingLocation>, message: Uint8Array): ResultAsync<CodecType<typeof RingVrfProof>, Error>;
30
+ /**
31
+ * Signs `message` with the ring VRF member key itself (RFC-0024).
32
+ *
33
+ * Unlike {@link createRingVrfProof} this is not anonymous: the result is
34
+ * verified against the member public key and is linkable to every other use
35
+ * of that key.
36
+ */
37
+ ringVrfSign(callingProductId: string, keyHandle: CodecType<typeof RingVrfKeyHandle>, message: Uint8Array): ResultAsync<Uint8Array, Error>;
38
+ /**
39
+ * Registers a ring VRF key owned by `callingProductId` against `ring`,
40
+ * returning the member public key (RFC-0024).
41
+ *
42
+ * The Account Holder is the authoritative registry, so registration always
43
+ * reaches it. Idempotent — re-registering an index for an additional ring
44
+ * extends the existing entry.
45
+ */
46
+ registerRingVrfKey(callingProductId: string, index: CodecType<typeof DerivationIndex>, ring: CodecType<typeof RingLocation>): ResultAsync<Uint8Array, Error>;
47
+ /** Lists the ring VRF registry entries owned by `owner` (RFC-0024). */
48
+ listRingVrfKeys(callingProductId: string, owner: string, disclosure: CodecType<typeof RingVrfKeyDisclosure>): ResultAsync<CodecType<typeof RegisteredRingVrfKey>[], Error>;
29
49
  /**
30
50
  * Fetches the sr25519 public key of `//product//{productId}` (RFC-0022).
31
51
  *
@@ -1,4 +1,4 @@
1
- import { ContextualAlias, ProductProofContext, RingLocation, RingVrfProof, VrfSignature } from '@novasamatech/host-api';
1
+ import { ContextualAlias, DerivationIndex, ProductProofContext, RegisteredRingVrfKey, RingLocation, RingVrfKeyDisclosure, RingVrfKeyHandle, RingVrfProof, VrfSignature, } from '@novasamatech/host-api';
2
2
  import { enumValue } from '@novasamatech/scale';
3
3
  import { createSession } from '@novasamatech/statement-store';
4
4
  import { fieldListView } from '@novasamatech/storage-adapter';
@@ -16,6 +16,8 @@ import { RemoteMessageCodec } from './scale/remoteMessage.js';
16
16
  const QUEUE_TASK_TIMEOUT_MS = 240_000;
17
17
  // Mobile SSO statements allow 500 KiB total; keep headroom for statement/session overhead.
18
18
  const MAX_SSO_REQUEST_SIZE = 498 * 1024;
19
+ // Per-session dedup log of already-handled peer message ids.
20
+ export const processedMessagesKey = (sessionId) => `sso_processed_${sessionId}`;
19
21
  function withQueueTimeout(resultAsync, label) {
20
22
  const timeoutPromise = new Promise(resolve => setTimeout(() => resolve(err(new Error(`${label} timed out — queue freed`))), QUEUE_TASK_TIMEOUT_MS));
21
23
  return ResultAsync.fromPromise(Promise.race([resultAsync, timeoutPromise]), toError).andThen(r => r);
@@ -106,7 +108,7 @@ export function createUserSession({ userSession, statementStore, encryption, sto
106
108
  });
107
109
  const processedMessages = fieldListView({
108
110
  storage,
109
- key: `sso_processed_${userSession.id}`,
111
+ key: processedMessagesKey(userSession.id),
110
112
  from: JSON.parse,
111
113
  to: JSON.stringify,
112
114
  });
@@ -244,11 +246,12 @@ export function createUserSession({ userSession, statementStore, encryption, sto
244
246
  return withHostActionTrace(withQueueTimeout(inner, 'createTransactionLegacy'), messageId, userSession.id);
245
247
  });
246
248
  },
247
- getRingVrfAlias(callingProductId, context, ring) {
249
+ getRingVrfAlias(callingProductId, keyHandle, context, ring) {
248
250
  return enqueue(() => {
249
251
  const messageId = nanoid();
250
252
  const data = enumValue('v1', enumValue('RingVrfAliasRequest', {
251
253
  callingProductId,
254
+ keyHandle,
252
255
  context,
253
256
  ring,
254
257
  }));
@@ -265,11 +268,12 @@ export function createUserSession({ userSession, statementStore, encryption, sto
265
268
  return withHostActionTrace(awaitReplyOrAckFailure(request, reply).andThen(result => result.success ? ok(result.value) : err(result.value)), messageId, userSession.id);
266
269
  });
267
270
  },
268
- createRingVrfProof(callingProductId, context, ring, message) {
271
+ createRingVrfProof(callingProductId, keyHandle, context, ring, message) {
269
272
  return enqueue(() => {
270
273
  const messageId = nanoid();
271
274
  const data = enumValue('v1', enumValue('RingVrfProofRequest', {
272
275
  callingProductId,
276
+ keyHandle,
273
277
  context,
274
278
  ring,
275
279
  message,
@@ -287,6 +291,60 @@ export function createUserSession({ userSession, statementStore, encryption, sto
287
291
  return withHostActionTrace(awaitReplyOrAckFailure(request, reply).andThen(result => result.success ? ok(result.value) : err(result.value)), messageId, userSession.id);
288
292
  });
289
293
  },
294
+ ringVrfSign(callingProductId, keyHandle, message) {
295
+ return enqueue(() => {
296
+ const messageId = nanoid();
297
+ const data = enumValue('v1', enumValue('RingVrfSignRequest', { callingProductId, keyHandle, message }));
298
+ emitHostAction(messageId, actionKindFromMessageData(data), userSession.id);
299
+ const responseFilter = (incoming) => {
300
+ if (incoming.data.tag === 'v1' &&
301
+ incoming.data.value.tag === 'RingVrfSignResponse' &&
302
+ incoming.data.value.value.respondingTo === messageId) {
303
+ return incoming.data.value.value.payload;
304
+ }
305
+ };
306
+ const request = session.request(RemoteMessageCodec, { messageId, data });
307
+ const reply = session.waitForRequestMessage(RemoteMessageCodec, responseFilter);
308
+ const inner = awaitReplyOrAckFailure(request, reply).andThen(result => result.success ? ok(result.value) : err(result.value));
309
+ return withHostActionTrace(withQueueTimeout(inner, 'ringVrfSign'), messageId, userSession.id);
310
+ });
311
+ },
312
+ registerRingVrfKey(callingProductId, index, ring) {
313
+ return enqueue(() => {
314
+ const messageId = nanoid();
315
+ const data = enumValue('v1', enumValue('RegisterRingVrfKeyRequest', { callingProductId, index, ring }));
316
+ emitHostAction(messageId, actionKindFromMessageData(data), userSession.id);
317
+ const responseFilter = (incoming) => {
318
+ if (incoming.data.tag === 'v1' &&
319
+ incoming.data.value.tag === 'RegisterRingVrfKeyResponse' &&
320
+ incoming.data.value.value.respondingTo === messageId) {
321
+ return incoming.data.value.value.payload;
322
+ }
323
+ };
324
+ const request = session.request(RemoteMessageCodec, { messageId, data });
325
+ const reply = session.waitForRequestMessage(RemoteMessageCodec, responseFilter);
326
+ const inner = awaitReplyOrAckFailure(request, reply).andThen(result => result.success ? ok(result.value) : err(result.value));
327
+ return withHostActionTrace(withQueueTimeout(inner, 'registerRingVrfKey'), messageId, userSession.id);
328
+ });
329
+ },
330
+ listRingVrfKeys(callingProductId, owner, disclosure) {
331
+ return enqueue(() => {
332
+ const messageId = nanoid();
333
+ const data = enumValue('v1', enumValue('ListRingVrfKeysRequest', { callingProductId, owner, disclosure }));
334
+ emitHostAction(messageId, actionKindFromMessageData(data), userSession.id);
335
+ const responseFilter = (incoming) => {
336
+ if (incoming.data.tag === 'v1' &&
337
+ incoming.data.value.tag === 'ListRingVrfKeysResponse' &&
338
+ incoming.data.value.value.respondingTo === messageId) {
339
+ return incoming.data.value.value.payload;
340
+ }
341
+ };
342
+ const request = session.request(RemoteMessageCodec, { messageId, data });
343
+ const reply = session.waitForRequestMessage(RemoteMessageCodec, responseFilter);
344
+ const inner = awaitReplyOrAckFailure(request, reply).andThen(result => result.success ? ok(result.value) : err(result.value));
345
+ return withHostActionTrace(withQueueTimeout(inner, 'listRingVrfKeys'), messageId, userSession.id);
346
+ });
347
+ },
290
348
  getProductSubtree(productId) {
291
349
  return enqueue(() => {
292
350
  const messageId = nanoid();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@novasamatech/host-papp",
3
3
  "type": "module",
4
- "version": "0.9.2",
4
+ "version": "0.9.4",
5
5
  "description": "Polkadot app integration",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
@@ -38,10 +38,10 @@
38
38
  "@noble/ciphers": "2.2.0",
39
39
  "@noble/curves": "2.2.0",
40
40
  "@noble/hashes": "2.2.0",
41
- "@novasamatech/host-api": "0.9.2",
42
- "@novasamatech/scale": "0.9.2",
43
- "@novasamatech/statement-store": "0.9.2",
44
- "@novasamatech/storage-adapter": "0.9.2",
41
+ "@novasamatech/host-api": "0.9.4",
42
+ "@novasamatech/scale": "0.9.4",
43
+ "@novasamatech/statement-store": "0.9.4",
44
+ "@novasamatech/storage-adapter": "0.9.4",
45
45
  "@polkadot-api/utils": "^0.4.0",
46
46
  "@polkadot-labs/hdkd-helpers": "^0.0.31",
47
47
  "nanoevents": "10.0.0",