@novasamatech/host-api-wrapper 0.9.2 → 0.9.3

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
@@ -262,32 +262,62 @@ if (accountResult.isOk()) {
262
262
  console.log('Public key:', account.publicKey);
263
263
  }
264
264
 
265
- // Ring VRF: a contextual alias and a proof are addressed by a product-scoped
266
- // `context` (`[productId, suffix]`) and a `ring` location on a chain (RFC 0004).
267
- // The suffix is the same selector as an account's derivation index and expands
268
- // to the same 32-byte value (RFC 0022).
265
+ // Ring VRF: a contextual alias and a proof are addressed by an explicit member
266
+ // key handle, a product-scoped `context` (`[productId, suffix]`) and a `ring`
267
+ // location on a chain (RFC 0004, amended by RFC 0024). The suffix is the same
268
+ // selector as an account's derivation index and expands to the same 32-byte
269
+ // value (RFC 0022).
269
270
  const context: ProofContext = ['product.dot', 0]; // [productId, selector]
270
271
  const ring = {
271
272
  chainId: '0x…', // 32-byte chain genesis hash
272
273
  junctions: [{ tag: 'PalletInstance', value: 42 }],
273
274
  };
274
275
 
275
- // Get the contextual alias for that (context, ring).
276
- const aliasResult = await accounts.getContextualAlias(context, ring);
276
+ // Register a key your own product owns for that ring. Permissionless and
277
+ // prompt-free ownership is the calling product, never a parameter. Returns the
278
+ // member public key. Registering the same index for another ring extends the
279
+ // existing entry rather than adding a second one.
280
+ const registerResult = await accounts.registerRingVrfKey(0, ring);
281
+
282
+ // Discover another product's keys. Handles are opaque: select by the rings an
283
+ // entry declares, NEVER by index — the index is the owner's implementation
284
+ // detail and hardcoding it breaks the moment the owner rotates or adds a key.
285
+ const keysResult = await accounts.listRingVrfKeys('peopl.dot'); // 'Anonymized' by default
286
+ const personKey = keysResult.isOk()
287
+ ? keysResult.value.find(entry => entry.rings.some(r => r.chainId === ring.chainId))
288
+ : undefined;
289
+
290
+ // A key handle for one of your own keys — for a foreign key use the handle from
291
+ // `listRingVrfKeys` verbatim instead.
292
+ import { ringVrfKeyHandle } from '@novasamatech/host-api-wrapper';
293
+
294
+ const ownHandle = ringVrfKeyHandle('product.dot', 0);
295
+
296
+ // Get the contextual alias for that (handle, context, ring).
297
+ const aliasResult = await accounts.getContextualAlias(ownHandle, context, ring);
277
298
 
278
299
  if (aliasResult.isOk()) {
279
300
  const { context: contextBytes, alias } = aliasResult.value;
280
301
  console.log('Alias:', alias);
281
302
  }
282
303
 
283
- // Create a ring VRF proof binding `message`; the host selects the member key.
284
- const proofResult = await accounts.createRingVRFProof(context, ring, new Uint8Array([0x48, 0x69]));
304
+ // Create a ring VRF proof binding `message` with an explicit member key.
305
+ // A proof is a bearer token for its context's alias, so a *foreign* handle is
306
+ // admitted only when its owner allowlisted your product in its manifest — there
307
+ // is no user-prompt fallback, and you get `NotAllowlisted` otherwise.
308
+ const proofResult = await accounts.createRingVRFProof(ownHandle, context, ring, new Uint8Array([0x48, 0x69]));
285
309
 
286
310
  if (proofResult.isOk()) {
287
311
  const { proof, contextualAlias, ringIndex, ringRevision } = proofResult.value;
288
312
  console.log('Proof:', proof, 'at ring index', ringIndex, 'revision', ringRevision);
289
313
  }
290
314
 
315
+ // Sign with the member key itself rather than proving membership anonymously
316
+ // (RFC 0024). No context and no ring: it derives no alias and proves nothing, so
317
+ // there is nothing for either to scope. Verified against the member public key,
318
+ // which makes the signature linkable to every other use of that key.
319
+ const signatureResult = await accounts.ringVrfSign(ownHandle, new Uint8Array([0x48, 0x69]));
320
+
291
321
  // sr25519 VRF signature over a product account (RFC-0023). The transcript is a
292
322
  // recipe — a root label plus ordered `(label, value)` items — that the host
293
323
  // replays verbatim (`Transcript::new(label)` then one `append_message` per item)
@@ -1,4 +1,4 @@
1
- import type { AccountConnectionStatus as AccountConnectionStatusCodec, AccountSelector, CodecType, LegacyAccount as LegacyAccountCodec, ProductAccountId as ProductAccountIdCodec, Subscription, Transport, VrfTranscriptItem as VrfTranscriptItemCodec } from '@novasamatech/host-api';
1
+ import type { AccountConnectionStatus as AccountConnectionStatusCodec, AccountSelector, CodecType, LegacyAccount as LegacyAccountCodec, ProductAccountId as ProductAccountIdCodec, RegisteredRingVrfKey as RegisteredRingVrfKeyCodec, RingVrfKeyDisclosure as RingVrfKeyDisclosureCodec, RingVrfKeyHandle as RingVrfKeyHandleCodec, Subscription, Transport, VrfTranscriptItem as VrfTranscriptItemCodec } from '@novasamatech/host-api';
2
2
  import { RingLocation } from '@novasamatech/host-api';
3
3
  import type { PolkadotSigner } from 'polkadot-api';
4
4
  export type { AccountSelector } from '@novasamatech/host-api';
@@ -18,6 +18,29 @@ export type ProductAccount = {
18
18
  * product account's.
19
19
  */
20
20
  export type ProofContext = [productId: string, suffix: AccountSelector];
21
+ /**
22
+ * Public name of a registered ring VRF key (RFC-0024).
23
+ *
24
+ * Deliberately kept in wire form: the index is the owning product's
25
+ * implementation detail, so a handle obtained from {@link
26
+ * createAccountsProvider}'s `listRingVrfKeys` must be passed through opaquely.
27
+ * **Never hardcode another product's index** — select by declared ring instead;
28
+ * hardcoding breaks the moment the owner rotates or adds a key. Use
29
+ * {@link ringVrfKeyHandle} to name a key your own product owns.
30
+ */
31
+ export type RingVrfKeyHandle = CodecType<typeof RingVrfKeyHandleCodec>;
32
+ /** A ring VRF key registry entry (RFC-0024). */
33
+ export type RegisteredRingVrfKey = CodecType<typeof RegisteredRingVrfKeyCodec>;
34
+ /** How much of a registry entry to ask for: `'Anonymized'` or `'PublicKey'` (RFC-0024). */
35
+ export type RingVrfKeyDisclosure = CodecType<typeof RingVrfKeyDisclosureCodec>;
36
+ /**
37
+ * Builds a {@link RingVrfKeyHandle} for a key inside `owner`'s ring VRF domain.
38
+ *
39
+ * Intended for naming your *own* keys — the ones you passed to
40
+ * `registerRingVrfKey`. For a foreign key, take the handle from
41
+ * `listRingVrfKeys` instead of constructing one.
42
+ */
43
+ export declare function ringVrfKeyHandle(owner: string, index: AccountSelector): RingVrfKeyHandle;
21
44
  export type LegacyAccount = CodecType<typeof LegacyAccountCodec>;
22
45
  export type AccountConnectionStatus = CodecType<typeof AccountConnectionStatusCodec>;
23
46
  /** One `transcript.append_message(label, value)` call replayed by the host (RFC-0023). */
@@ -38,19 +61,87 @@ export declare const createAccountsProvider: (transport?: Transport) => {
38
61
  }, import("@novasamatech/scale").CodecError<undefined, "RequestCredentialsErr::NotConnected"> | import("@novasamatech/scale").CodecError<undefined, "RequestCredentialsErr::Rejected"> | import("@novasamatech/scale").CodecError<undefined, "RequestCredentialsErr::DomainNotValid"> | import("@novasamatech/scale").CodecError<{
39
62
  reason: string;
40
63
  }, "RequestCredentialsErr::Unknown">>;
41
- getContextualAlias(context: ProofContext, ring: CodecType<typeof RingLocation>): import("neverthrow").ResultAsync<{
64
+ /**
65
+ * Registers a ring VRF key this product owns, declaring the ring it is
66
+ * intended for (RFC-0024).
67
+ *
68
+ * Ownership is the calling product, never a parameter, so this is
69
+ * permissionless and prompt-free. Registering the same `index` for another
70
+ * `ring` extends the existing entry rather than creating a second one.
71
+ *
72
+ * Registration declares *intent*, not membership: it says "this is the key
73
+ * I will use for that ring", not "the user is a person". Membership is
74
+ * still discovered only by attempting a proof.
75
+ */
76
+ registerRingVrfKey(index: AccountSelector, ring: CodecType<typeof RingLocation>): import("neverthrow").ResultAsync<Uint8Array<ArrayBufferLike>, import("@novasamatech/scale").CodecError<undefined, "RegisterRingVrfKeyErr::NotConnected"> | import("@novasamatech/scale").CodecError<undefined, "RegisterRingVrfKeyErr::Rejected"> | import("@novasamatech/scale").CodecError<{
77
+ reason: string;
78
+ }, "RegisterRingVrfKeyErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "RegisterRingVrfKeyErr::RingNotFound">>;
79
+ /**
80
+ * Lists the ring VRF registry entries owned by `owner` — this product or
81
+ * another one (RFC-0024).
82
+ *
83
+ * Listing your own keys is permissionless; a foreign `owner` needs a grant
84
+ * or produces a user prompt. `'PublicKey'` disclosure additionally returns
85
+ * the member public key, which is linkable across every ring it appears in
86
+ * and so is permissioned cross-product.
87
+ *
88
+ * Select the entry you want by the rings it declares, never by index.
89
+ */
90
+ listRingVrfKeys(owner: string, disclosure?: RingVrfKeyDisclosure): import("neverthrow").ResultAsync<{
91
+ handle: [string, {
92
+ tag: "Index";
93
+ value: number;
94
+ } | {
95
+ tag: "Raw";
96
+ value: Uint8Array<ArrayBufferLike>;
97
+ }];
98
+ rings: {
99
+ chainId: `0x${string}`;
100
+ junctions: ({
101
+ tag: "PalletInstance";
102
+ value: number;
103
+ } | {
104
+ tag: "CollectionId";
105
+ value: Uint8Array<ArrayBufferLike>;
106
+ })[];
107
+ }[];
108
+ publicKey: Uint8Array<ArrayBufferLike> | undefined;
109
+ }[], import("@novasamatech/scale").CodecError<undefined, "ListRingVrfKeysErr::NotConnected"> | import("@novasamatech/scale").CodecError<undefined, "ListRingVrfKeysErr::Rejected"> | import("@novasamatech/scale").CodecError<{
110
+ reason: string;
111
+ }, "ListRingVrfKeysErr::Unknown">>;
112
+ /**
113
+ * Reads the contextual alias `keyHandle` resolves to under `context` in `ring`
114
+ * (RFC-0004, amended by RFC-0024).
115
+ *
116
+ * `ring` stays explicit even though the handle carries its declared rings,
117
+ * because a key may be registered for several and the caller must say which
118
+ * the alias is against; the host fails with `KeyNotInRing` otherwise.
119
+ *
120
+ * Check `ringRevision` on each use of the resulting alias and renew when it
121
+ * has moved — nothing else watches for it.
122
+ */
123
+ getContextualAlias(keyHandle: RingVrfKeyHandle, context: ProofContext, ring: CodecType<typeof RingLocation>): import("neverthrow").ResultAsync<{
42
124
  context: Uint8Array<ArrayBufferLike>;
43
125
  alias: Uint8Array<ArrayBufferLike>;
44
126
  }, import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::Rejected"> | import("@novasamatech/scale").CodecError<{
45
127
  reason: string;
46
- }, "GetAliasErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::RingNotFound"> | import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::NotMember">>;
128
+ }, "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">>;
47
129
  getLegacyAccounts(): import("neverthrow").ResultAsync<{
48
130
  publicKey: Uint8Array<ArrayBufferLike>;
49
131
  name: string | undefined;
50
132
  }[], import("@novasamatech/scale").CodecError<undefined, "RequestCredentialsErr::NotConnected"> | import("@novasamatech/scale").CodecError<undefined, "RequestCredentialsErr::Rejected"> | import("@novasamatech/scale").CodecError<undefined, "RequestCredentialsErr::DomainNotValid"> | import("@novasamatech/scale").CodecError<{
51
133
  reason: string;
52
134
  }, "RequestCredentialsErr::Unknown">>;
53
- createRingVRFProof(context: ProofContext, ring: CodecType<typeof RingLocation>, message: Uint8Array): import("neverthrow").ResultAsync<{
135
+ /**
136
+ * Produces an anonymous ring VRF proof with `keyHandle` under `context` in
137
+ * `ring` (RFC-0004, amended by RFC-0024).
138
+ *
139
+ * A proof is a bearer token for its context's alias, so a *foreign*
140
+ * `keyHandle` is admitted only when the owning product allowlisted this one
141
+ * in its manifest — there is no user-prompt fallback, and the host returns
142
+ * `NotAllowlisted` otherwise.
143
+ */
144
+ createRingVRFProof(keyHandle: RingVrfKeyHandle, context: ProofContext, ring: CodecType<typeof RingLocation>, message: Uint8Array): import("neverthrow").ResultAsync<{
54
145
  proof: Uint8Array<ArrayBufferLike>;
55
146
  contextualAlias: {
56
147
  context: Uint8Array<ArrayBufferLike>;
@@ -60,7 +151,22 @@ export declare const createAccountsProvider: (transport?: Transport) => {
60
151
  ringRevision: number;
61
152
  }, import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::Rejected"> | import("@novasamatech/scale").CodecError<{
62
153
  reason: string;
63
- }, "CreateProofErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::RingNotFound"> | import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::NotMember">>;
154
+ }, "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">>;
155
+ /**
156
+ * Signs `message` with the ring VRF member key itself, producing an ordinary
157
+ * signature rather than an anonymous ring proof (RFC-0024).
158
+ *
159
+ * Takes no context and no ring: it derives no alias and proves no
160
+ * membership. A verifier needs the member public key, so the signature is
161
+ * **linkable** to every other use of that key — including every ring the key
162
+ * is a member of.
163
+ *
164
+ * Like `createRingVRFProof`, a foreign `keyHandle` requires the owner's
165
+ * manifest allowlist and has no prompt fallback.
166
+ */
167
+ ringVrfSign(keyHandle: RingVrfKeyHandle, message: Uint8Array): import("neverthrow").ResultAsync<Uint8Array<ArrayBufferLike>, import("@novasamatech/scale").CodecError<undefined, "RingVrfSignErr::NotConnected"> | import("@novasamatech/scale").CodecError<undefined, "RingVrfSignErr::Rejected"> | import("@novasamatech/scale").CodecError<{
168
+ reason: string;
169
+ }, "RingVrfSignErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "RingVrfSignErr::KeyNotRegistered"> | import("@novasamatech/scale").CodecError<undefined, "RingVrfSignErr::NotAllowlisted">>;
64
170
  /**
65
171
  * Produces an sr25519 (schnorrkel) VRF signature from a product account (RFC-0023).
66
172
  *
@@ -101,19 +207,87 @@ export declare const accounts: {
101
207
  }, import("@novasamatech/scale").CodecError<undefined, "RequestCredentialsErr::NotConnected"> | import("@novasamatech/scale").CodecError<undefined, "RequestCredentialsErr::Rejected"> | import("@novasamatech/scale").CodecError<undefined, "RequestCredentialsErr::DomainNotValid"> | import("@novasamatech/scale").CodecError<{
102
208
  reason: string;
103
209
  }, "RequestCredentialsErr::Unknown">>;
104
- getContextualAlias(context: ProofContext, ring: CodecType<typeof RingLocation>): import("neverthrow").ResultAsync<{
210
+ /**
211
+ * Registers a ring VRF key this product owns, declaring the ring it is
212
+ * intended for (RFC-0024).
213
+ *
214
+ * Ownership is the calling product, never a parameter, so this is
215
+ * permissionless and prompt-free. Registering the same `index` for another
216
+ * `ring` extends the existing entry rather than creating a second one.
217
+ *
218
+ * Registration declares *intent*, not membership: it says "this is the key
219
+ * I will use for that ring", not "the user is a person". Membership is
220
+ * still discovered only by attempting a proof.
221
+ */
222
+ registerRingVrfKey(index: AccountSelector, ring: CodecType<typeof RingLocation>): import("neverthrow").ResultAsync<Uint8Array<ArrayBufferLike>, import("@novasamatech/scale").CodecError<undefined, "RegisterRingVrfKeyErr::NotConnected"> | import("@novasamatech/scale").CodecError<undefined, "RegisterRingVrfKeyErr::Rejected"> | import("@novasamatech/scale").CodecError<{
223
+ reason: string;
224
+ }, "RegisterRingVrfKeyErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "RegisterRingVrfKeyErr::RingNotFound">>;
225
+ /**
226
+ * Lists the ring VRF registry entries owned by `owner` — this product or
227
+ * another one (RFC-0024).
228
+ *
229
+ * Listing your own keys is permissionless; a foreign `owner` needs a grant
230
+ * or produces a user prompt. `'PublicKey'` disclosure additionally returns
231
+ * the member public key, which is linkable across every ring it appears in
232
+ * and so is permissioned cross-product.
233
+ *
234
+ * Select the entry you want by the rings it declares, never by index.
235
+ */
236
+ listRingVrfKeys(owner: string, disclosure?: RingVrfKeyDisclosure): import("neverthrow").ResultAsync<{
237
+ handle: [string, {
238
+ tag: "Index";
239
+ value: number;
240
+ } | {
241
+ tag: "Raw";
242
+ value: Uint8Array<ArrayBufferLike>;
243
+ }];
244
+ rings: {
245
+ chainId: `0x${string}`;
246
+ junctions: ({
247
+ tag: "PalletInstance";
248
+ value: number;
249
+ } | {
250
+ tag: "CollectionId";
251
+ value: Uint8Array<ArrayBufferLike>;
252
+ })[];
253
+ }[];
254
+ publicKey: Uint8Array<ArrayBufferLike> | undefined;
255
+ }[], import("@novasamatech/scale").CodecError<undefined, "ListRingVrfKeysErr::NotConnected"> | import("@novasamatech/scale").CodecError<undefined, "ListRingVrfKeysErr::Rejected"> | import("@novasamatech/scale").CodecError<{
256
+ reason: string;
257
+ }, "ListRingVrfKeysErr::Unknown">>;
258
+ /**
259
+ * Reads the contextual alias `keyHandle` resolves to under `context` in `ring`
260
+ * (RFC-0004, amended by RFC-0024).
261
+ *
262
+ * `ring` stays explicit even though the handle carries its declared rings,
263
+ * because a key may be registered for several and the caller must say which
264
+ * the alias is against; the host fails with `KeyNotInRing` otherwise.
265
+ *
266
+ * Check `ringRevision` on each use of the resulting alias and renew when it
267
+ * has moved — nothing else watches for it.
268
+ */
269
+ getContextualAlias(keyHandle: RingVrfKeyHandle, context: ProofContext, ring: CodecType<typeof RingLocation>): import("neverthrow").ResultAsync<{
105
270
  context: Uint8Array<ArrayBufferLike>;
106
271
  alias: Uint8Array<ArrayBufferLike>;
107
272
  }, import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::Rejected"> | import("@novasamatech/scale").CodecError<{
108
273
  reason: string;
109
- }, "GetAliasErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::RingNotFound"> | import("@novasamatech/scale").CodecError<undefined, "GetAliasErr::NotMember">>;
274
+ }, "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">>;
110
275
  getLegacyAccounts(): import("neverthrow").ResultAsync<{
111
276
  publicKey: Uint8Array<ArrayBufferLike>;
112
277
  name: string | undefined;
113
278
  }[], import("@novasamatech/scale").CodecError<undefined, "RequestCredentialsErr::NotConnected"> | import("@novasamatech/scale").CodecError<undefined, "RequestCredentialsErr::Rejected"> | import("@novasamatech/scale").CodecError<undefined, "RequestCredentialsErr::DomainNotValid"> | import("@novasamatech/scale").CodecError<{
114
279
  reason: string;
115
280
  }, "RequestCredentialsErr::Unknown">>;
116
- createRingVRFProof(context: ProofContext, ring: CodecType<typeof RingLocation>, message: Uint8Array): import("neverthrow").ResultAsync<{
281
+ /**
282
+ * Produces an anonymous ring VRF proof with `keyHandle` under `context` in
283
+ * `ring` (RFC-0004, amended by RFC-0024).
284
+ *
285
+ * A proof is a bearer token for its context's alias, so a *foreign*
286
+ * `keyHandle` is admitted only when the owning product allowlisted this one
287
+ * in its manifest — there is no user-prompt fallback, and the host returns
288
+ * `NotAllowlisted` otherwise.
289
+ */
290
+ createRingVRFProof(keyHandle: RingVrfKeyHandle, context: ProofContext, ring: CodecType<typeof RingLocation>, message: Uint8Array): import("neverthrow").ResultAsync<{
117
291
  proof: Uint8Array<ArrayBufferLike>;
118
292
  contextualAlias: {
119
293
  context: Uint8Array<ArrayBufferLike>;
@@ -123,7 +297,22 @@ export declare const accounts: {
123
297
  ringRevision: number;
124
298
  }, import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::Rejected"> | import("@novasamatech/scale").CodecError<{
125
299
  reason: string;
126
- }, "CreateProofErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::RingNotFound"> | import("@novasamatech/scale").CodecError<undefined, "CreateProofErr::NotMember">>;
300
+ }, "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">>;
301
+ /**
302
+ * Signs `message` with the ring VRF member key itself, producing an ordinary
303
+ * signature rather than an anonymous ring proof (RFC-0024).
304
+ *
305
+ * Takes no context and no ring: it derives no alias and proves no
306
+ * membership. A verifier needs the member public key, so the signature is
307
+ * **linkable** to every other use of that key — including every ring the key
308
+ * is a member of.
309
+ *
310
+ * Like `createRingVRFProof`, a foreign `keyHandle` requires the owner's
311
+ * manifest allowlist and has no prompt fallback.
312
+ */
313
+ ringVrfSign(keyHandle: RingVrfKeyHandle, message: Uint8Array): import("neverthrow").ResultAsync<Uint8Array<ArrayBufferLike>, import("@novasamatech/scale").CodecError<undefined, "RingVrfSignErr::NotConnected"> | import("@novasamatech/scale").CodecError<undefined, "RingVrfSignErr::Rejected"> | import("@novasamatech/scale").CodecError<{
314
+ reason: string;
315
+ }, "RingVrfSignErr::Unknown"> | import("@novasamatech/scale").CodecError<undefined, "RingVrfSignErr::KeyNotRegistered"> | import("@novasamatech/scale").CodecError<undefined, "RingVrfSignErr::NotAllowlisted">>;
127
316
  /**
128
317
  * Produces an sr25519 (schnorrkel) VRF signature from a product account (RFC-0023).
129
318
  *
package/dist/accounts.js CHANGED
@@ -1,9 +1,19 @@
1
- import { CreateProofErr, GetAliasErr, GetUserIdErr, LoginErr, ProductProofContext, RequestCredentialsErr, RingLocation, SignVrfErr, SigningPayload, SigningPayloadWithoutAccount, SigningRawPayload, SigningRawPayloadWithoutAccount, assertEnumVariant, createHostApi, derivationIndexOf, enumValue, fromHex, isEnumVariant, toHex, } from '@novasamatech/host-api';
1
+ import { CreateProofErr, GetAliasErr, GetUserIdErr, ListRingVrfKeysErr, LoginErr, ProductProofContext, RegisterRingVrfKeyErr, RequestCredentialsErr, RingLocation, RingVrfSignErr, SignVrfErr, SigningPayload, SigningPayloadWithoutAccount, SigningRawPayload, SigningRawPayloadWithoutAccount, assertEnumVariant, createHostApi, derivationIndexOf, enumValue, fromHex, isEnumVariant, toHex, } from '@novasamatech/host-api';
2
2
  import { decAnyMetadata, unifyMetadata } from '@polkadot-api/substrate-bindings';
3
3
  import { err, ok } from 'neverthrow';
4
4
  import { AccountId } from 'polkadot-api';
5
5
  import { getPolkadotSignerFromPjs } from 'polkadot-api/pjs-signer';
6
6
  import { sandboxTransport } from './sandboxTransport.js';
7
+ /**
8
+ * Builds a {@link RingVrfKeyHandle} for a key inside `owner`'s ring VRF domain.
9
+ *
10
+ * Intended for naming your *own* keys — the ones you passed to
11
+ * `registerRingVrfKey`. For a foreign key, take the handle from
12
+ * `listRingVrfKeys` instead of constructing one.
13
+ */
14
+ export function ringVrfKeyHandle(owner, index) {
15
+ return [owner, derivationIndexOf(index)];
16
+ }
7
17
  const UNSUPPORTED_VERSION_ERROR = 'Unsupported message version';
8
18
  export const createAccountsProvider = (transport = sandboxTransport) => {
9
19
  const hostApi = createHostApi(transport);
@@ -48,9 +58,67 @@ export const createAccountsProvider = (transport = sandboxTransport) => {
48
58
  return err(new RequestCredentialsErr.Unknown({ reason: `Unsupported response version ${response.tag}` }));
49
59
  });
50
60
  },
51
- getContextualAlias(context, ring) {
61
+ /**
62
+ * Registers a ring VRF key this product owns, declaring the ring it is
63
+ * intended for (RFC-0024).
64
+ *
65
+ * Ownership is the calling product, never a parameter, so this is
66
+ * permissionless and prompt-free. Registering the same `index` for another
67
+ * `ring` extends the existing entry rather than creating a second one.
68
+ *
69
+ * Registration declares *intent*, not membership: it says "this is the key
70
+ * I will use for that ring", not "the user is a person". Membership is
71
+ * still discovered only by attempting a proof.
72
+ */
73
+ registerRingVrfKey(index, ring) {
74
+ return hostApi
75
+ .accountRegisterRingVrfKey(enumValue('v1', [derivationIndexOf(index), ring]))
76
+ .mapErr(e => e.value)
77
+ .andThen(response => {
78
+ if (isEnumVariant(response, 'v1')) {
79
+ return ok(response.value);
80
+ }
81
+ // @ts-expect-error response.tag is never here
82
+ return err(new RegisterRingVrfKeyErr.Unknown({ reason: `Unsupported response version ${response.tag}` }));
83
+ });
84
+ },
85
+ /**
86
+ * Lists the ring VRF registry entries owned by `owner` — this product or
87
+ * another one (RFC-0024).
88
+ *
89
+ * Listing your own keys is permissionless; a foreign `owner` needs a grant
90
+ * or produces a user prompt. `'PublicKey'` disclosure additionally returns
91
+ * the member public key, which is linkable across every ring it appears in
92
+ * and so is permissioned cross-product.
93
+ *
94
+ * Select the entry you want by the rings it declares, never by index.
95
+ */
96
+ listRingVrfKeys(owner, disclosure = 'Anonymized') {
52
97
  return hostApi
53
- .accountGetAlias(enumValue('v1', [toProofContext(context), ring]))
98
+ .accountListRingVrfKeys(enumValue('v1', [owner, disclosure]))
99
+ .mapErr(e => e.value)
100
+ .andThen(response => {
101
+ if (isEnumVariant(response, 'v1')) {
102
+ return ok(response.value);
103
+ }
104
+ // @ts-expect-error response.tag is never here
105
+ return err(new ListRingVrfKeysErr.Unknown({ reason: `Unsupported response version ${response.tag}` }));
106
+ });
107
+ },
108
+ /**
109
+ * Reads the contextual alias `keyHandle` resolves to under `context` in `ring`
110
+ * (RFC-0004, amended by RFC-0024).
111
+ *
112
+ * `ring` stays explicit even though the handle carries its declared rings,
113
+ * because a key may be registered for several and the caller must say which
114
+ * the alias is against; the host fails with `KeyNotInRing` otherwise.
115
+ *
116
+ * Check `ringRevision` on each use of the resulting alias and renew when it
117
+ * has moved — nothing else watches for it.
118
+ */
119
+ getContextualAlias(keyHandle, context, ring) {
120
+ return hostApi
121
+ .accountGetAlias(enumValue('v1', [keyHandle, toProofContext(context), ring]))
54
122
  .mapErr(e => e.value)
55
123
  .andThen(response => {
56
124
  if (isEnumVariant(response, 'v1')) {
@@ -72,9 +140,18 @@ export const createAccountsProvider = (transport = sandboxTransport) => {
72
140
  return err(new RequestCredentialsErr.Unknown({ reason: `Unsupported response version ${response.tag}` }));
73
141
  });
74
142
  },
75
- createRingVRFProof(context, ring, message) {
143
+ /**
144
+ * Produces an anonymous ring VRF proof with `keyHandle` under `context` in
145
+ * `ring` (RFC-0004, amended by RFC-0024).
146
+ *
147
+ * A proof is a bearer token for its context's alias, so a *foreign*
148
+ * `keyHandle` is admitted only when the owning product allowlisted this one
149
+ * in its manifest — there is no user-prompt fallback, and the host returns
150
+ * `NotAllowlisted` otherwise.
151
+ */
152
+ createRingVRFProof(keyHandle, context, ring, message) {
76
153
  return hostApi
77
- .accountCreateProof(enumValue('v1', [toProofContext(context), ring, message]))
154
+ .accountCreateProof(enumValue('v1', [keyHandle, toProofContext(context), ring, message]))
78
155
  .mapErr(e => e.value)
79
156
  .andThen(response => {
80
157
  if (isEnumVariant(response, 'v1')) {
@@ -84,6 +161,30 @@ export const createAccountsProvider = (transport = sandboxTransport) => {
84
161
  return err(new CreateProofErr.Unknown({ reason: `Unsupported response version ${response.tag}` }));
85
162
  });
86
163
  },
164
+ /**
165
+ * Signs `message` with the ring VRF member key itself, producing an ordinary
166
+ * signature rather than an anonymous ring proof (RFC-0024).
167
+ *
168
+ * Takes no context and no ring: it derives no alias and proves no
169
+ * membership. A verifier needs the member public key, so the signature is
170
+ * **linkable** to every other use of that key — including every ring the key
171
+ * is a member of.
172
+ *
173
+ * Like `createRingVRFProof`, a foreign `keyHandle` requires the owner's
174
+ * manifest allowlist and has no prompt fallback.
175
+ */
176
+ ringVrfSign(keyHandle, message) {
177
+ return hostApi
178
+ .accountRingVrfSign(enumValue('v1', [keyHandle, message]))
179
+ .mapErr(e => e.value)
180
+ .andThen(response => {
181
+ if (isEnumVariant(response, 'v1')) {
182
+ return ok(response.value);
183
+ }
184
+ // @ts-expect-error response.tag is never here
185
+ return err(new RingVrfSignErr.Unknown({ reason: `Unsupported response version ${response.tag}` }));
186
+ });
187
+ },
87
188
  /**
88
189
  * Produces an sr25519 (schnorrkel) VRF signature from a product account (RFC-0023).
89
190
  *
package/dist/index.d.ts CHANGED
@@ -8,8 +8,8 @@ export type { ChatBotRegistrationResult, ChatCustomMessageRenderer, ChatCustomMe
8
8
  export { createProductChatManager, matchChatCustomRenderers } from './chat.js';
9
9
  export type { ProductAccountId, ProductAccountRef, SignedStatement, Statement, StatementTopicFilter, StatementsPage, Topic, } from './statementStore.js';
10
10
  export { createStatementStore } from './statementStore.js';
11
- export type { AccountConnectionStatus, AccountSelector, LegacyAccount, ProductAccount, ProofContext, VrfTranscriptItem, } from './accounts.js';
12
- export { accounts, createAccountsProvider } from './accounts.js';
11
+ export type { AccountConnectionStatus, AccountSelector, LegacyAccount, ProductAccount, ProofContext, RegisteredRingVrfKey, RingVrfKeyDisclosure, RingVrfKeyHandle, VrfTranscriptItem, } from './accounts.js';
12
+ export { accounts, createAccountsProvider, ringVrfKeyHandle } from './accounts.js';
13
13
  export type { ThemeMode } from './theme.js';
14
14
  export { createThemeProvider } from './theme.js';
15
15
  export { createLocalStorage, hostLocalStorage } from './localStorage.js';
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ export { createLegacyExtensionEnableFactory, injectSpektrExtension } from './inj
6
6
  export { createPapiProvider } from './papiProvider.js';
7
7
  export { createProductChatManager, matchChatCustomRenderers } from './chat.js';
8
8
  export { createStatementStore } from './statementStore.js';
9
- export { accounts, createAccountsProvider } from './accounts.js';
9
+ export { accounts, createAccountsProvider, ringVrfKeyHandle } from './accounts.js';
10
10
  export { createThemeProvider } from './theme.js';
11
11
  export { createLocalStorage, hostLocalStorage } from './localStorage.js';
12
12
  export { createNotificationManager, notificationManager } from './notification.js';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@novasamatech/host-api-wrapper",
3
3
  "type": "module",
4
- "version": "0.9.2",
4
+ "version": "0.9.3",
5
5
  "description": "Host API wrapper: integrate and run your product inside Polkadot browser.",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
@@ -28,7 +28,7 @@
28
28
  "@polkadot/extension-inject": "^0.63.1",
29
29
  "@polkadot-api/json-rpc-provider-proxy": "^0.4.0",
30
30
  "@polkadot-api/substrate-bindings": "^0.20.3",
31
- "@novasamatech/host-api": "0.9.2",
31
+ "@novasamatech/host-api": "0.9.3",
32
32
  "polkadot-api": ">=2",
33
33
  "neverthrow": "^8.2.0"
34
34
  },