@parity/product-sdk-host 0.15.1 → 0.16.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/chain-discovery-nLrPzb3d.d.ts +70 -0
- package/dist/index.d.ts +102 -13
- package/dist/index.js +148 -27
- package/dist/index.js.map +1 -1
- package/dist/testing.d.ts +32 -8
- package/dist/testing.js +23 -5
- package/dist/testing.js.map +1 -1
- package/package.json +4 -4
- package/src/accounts.ts +451 -33
- package/src/chain-discovery.ts +287 -0
- package/src/index.ts +12 -1
- package/src/payments.ts +1 -1
- package/src/testing.ts +106 -11
- package/src/truapi.ts +3 -2
- package/dist/transport-B0cdhwrp.d.ts +0 -31
package/src/accounts.ts
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* `getAccountsProvider()` returns the full accounts surface — user identity
|
|
7
7
|
* (`getUserId` / `requestLogin`), the user's existing wallet accounts
|
|
8
8
|
* (`getLegacyAccounts`), app-scoped product accounts (`getProductAccount` /
|
|
9
|
-
* `getProductAccountAlias`), Ring VRF proofs (`createRingVRFProof`),
|
|
9
|
+
* `getProductAccountAlias`), Ring VRF proofs (`createRingVRFProof`), sr25519 VRF
|
|
10
|
+
* signatures over a caller-supplied Merlin transcript (`signVrf`), connection
|
|
10
11
|
* status, and PAPI `PolkadotSigner` factories for both product and legacy
|
|
11
12
|
* accounts.
|
|
12
13
|
*
|
|
@@ -27,7 +28,6 @@ import { AccountId, type PolkadotSigner } from "polkadot-api";
|
|
|
27
28
|
|
|
28
29
|
import type {
|
|
29
30
|
ContextualAlias as WireAlias,
|
|
30
|
-
DerivationIndex,
|
|
31
31
|
HostAccountConnectionStatusSubscribeItem,
|
|
32
32
|
HostAccountCreateProofResponse as WireRingVRFProof,
|
|
33
33
|
HostRequestLoginResponse,
|
|
@@ -35,14 +35,22 @@ import type {
|
|
|
35
35
|
ProductAccount as WireProductAccount,
|
|
36
36
|
ProductAccountId,
|
|
37
37
|
ProductProofContext,
|
|
38
|
+
RegisteredRingVrfKey as WireRegisteredRingVrfKey,
|
|
38
39
|
RingLocation,
|
|
40
|
+
RingVrfKeyDisclosure,
|
|
39
41
|
TrUApiClient,
|
|
40
42
|
VersionedHostAccountCreateProofError,
|
|
41
43
|
VersionedHostAccountGetAliasError,
|
|
42
44
|
VersionedHostAccountGetError,
|
|
45
|
+
VersionedHostAccountListRingVrfKeysError,
|
|
46
|
+
VersionedHostAccountRegisterRingVrfKeyError,
|
|
47
|
+
VersionedHostAccountRingVrfSignError,
|
|
48
|
+
VersionedHostAccountSignVrfError,
|
|
43
49
|
VersionedHostGetLegacyAccountsError,
|
|
44
50
|
VersionedHostGetUserIdError,
|
|
45
51
|
VersionedHostRequestLoginError,
|
|
52
|
+
VrfSignature as WireVrfSignature,
|
|
53
|
+
VrfTranscriptItem as WireVrfTranscriptItem,
|
|
46
54
|
scale,
|
|
47
55
|
} from "@parity/truapi";
|
|
48
56
|
|
|
@@ -58,17 +66,22 @@ import type { HostSubscription } from "./types.js";
|
|
|
58
66
|
* (`{ productId, suffix }`), expanded by the host into the 32-byte context a
|
|
59
67
|
* proof or alias is bound to.
|
|
60
68
|
* - `DerivationIndex` — the tagged selector `ProductProofContext.suffix`
|
|
61
|
-
* carries: `{ tag: "
|
|
62
|
-
* `{ tag: "
|
|
69
|
+
* carries: `{ tag: "Index", value: number }` for a plain index, or
|
|
70
|
+
* `{ tag: "Raw", value: HexString }` for a raw 32-byte index.
|
|
63
71
|
*/
|
|
64
|
-
export type {
|
|
72
|
+
export type {
|
|
73
|
+
DerivationIndex,
|
|
74
|
+
ProductProofContext,
|
|
75
|
+
RingLocation,
|
|
76
|
+
RingVrfKeyDisclosure,
|
|
77
|
+
} from "@parity/truapi";
|
|
65
78
|
|
|
66
79
|
// The account/alias shapes come from `@parity/truapi`'s generated specs; we
|
|
67
80
|
// derive the SDK-facing views from them so the field inventory tracks the
|
|
68
81
|
// protocol automatically, and override only the fields the adapter re-encodes:
|
|
69
82
|
// byte fields decoded from `0x`-prefixed `HexString`s to `Uint8Array`s, and
|
|
70
83
|
// the tagged derivation-index selector kept as a plain `number` (wrapped back
|
|
71
|
-
// into `
|
|
84
|
+
// into `Index` at the wire boundary). Shapes re-exported verbatim (e.g.
|
|
72
85
|
// `ProductProofContext`) track the wire as-is. Same pattern as
|
|
73
86
|
// `@parity/product-sdk-statement-store`.
|
|
74
87
|
|
|
@@ -105,6 +118,72 @@ export type ProductAccount = Omit<ProductAccountId, "derivationIndex"> &
|
|
|
105
118
|
publicKey: Uint8Array;
|
|
106
119
|
};
|
|
107
120
|
|
|
121
|
+
/**
|
|
122
|
+
* How callers address a product account: app identifier plus an optional index,
|
|
123
|
+
* defaulting to 0. A {@link ProductAccount} satisfies this, so an account from
|
|
124
|
+
* {@link AccountsProvider.getProductAccount} can be passed straight back in.
|
|
125
|
+
*/
|
|
126
|
+
export type ProductAccountLookup = Omit<ProductAccountId, "derivationIndex"> & {
|
|
127
|
+
/** Plain account index within the product subtree. Defaults to 0. */
|
|
128
|
+
derivationIndex?: number;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
declare const ringVrfKeyHandleBrand: unique symbol;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Opaque public name of a registered ring-VRF key.
|
|
135
|
+
*
|
|
136
|
+
* Handles come from {@link AccountsProvider.listRingVrfKeys}; product code
|
|
137
|
+
* cannot construct one from a derivation index.
|
|
138
|
+
*/
|
|
139
|
+
export type RingVrfKeyHandle = {
|
|
140
|
+
readonly [ringVrfKeyHandleBrand]: "RingVrfKeyHandle";
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/** Ring-VRF member public key, decoded from the wire's hex string. */
|
|
144
|
+
export type RingVrfPublicKey = Uint8Array;
|
|
145
|
+
|
|
146
|
+
/** Registered key metadata returned by the host. */
|
|
147
|
+
export type RegisteredRingVrfKey = Omit<WireRegisteredRingVrfKey, "handle" | "publicKey"> & {
|
|
148
|
+
/** Opaque handle to pass back for alias and proof requests. */
|
|
149
|
+
handle: RingVrfKeyHandle;
|
|
150
|
+
/** Present when public-key disclosure was granted. */
|
|
151
|
+
publicKey?: RingVrfPublicKey;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
function sameRingLocation(a: RingLocation, b: RingLocation): boolean {
|
|
155
|
+
if (
|
|
156
|
+
a.chainId.toLowerCase() !== b.chainId.toLowerCase() ||
|
|
157
|
+
a.junctions.length !== b.junctions.length
|
|
158
|
+
) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
return a.junctions.every((junction, index) => {
|
|
162
|
+
const candidate = b.junctions[index];
|
|
163
|
+
if (junction.tag === "PalletInstance") {
|
|
164
|
+
return candidate.tag === "PalletInstance" && junction.value === candidate.value;
|
|
165
|
+
}
|
|
166
|
+
return (
|
|
167
|
+
candidate.tag === "CollectionId" &&
|
|
168
|
+
junction.value.toLowerCase() === candidate.value.toLowerCase()
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Select a registered key by its declared ring and return its opaque handle.
|
|
175
|
+
*
|
|
176
|
+
* Consumers must not hard-code another product's derivation index. Registry
|
|
177
|
+
* order breaks ties when an owner declares multiple keys for the same ring.
|
|
178
|
+
*/
|
|
179
|
+
export function findRingVrfKeyHandle(
|
|
180
|
+
keys: RegisteredRingVrfKey[],
|
|
181
|
+
ring: RingLocation,
|
|
182
|
+
): RingVrfKeyHandle | undefined {
|
|
183
|
+
return keys.find((key) => key.rings.some((candidate) => sameRingLocation(candidate, ring)))
|
|
184
|
+
?.handle;
|
|
185
|
+
}
|
|
186
|
+
|
|
108
187
|
/**
|
|
109
188
|
* A contextual alias obtained from Ring VRF.
|
|
110
189
|
*
|
|
@@ -129,6 +208,21 @@ export type RingVRFProof = Omit<WireRingVRFProof, "proof" | "contextualAlias"> &
|
|
|
129
208
|
contextualAlias: ContextualAlias;
|
|
130
209
|
};
|
|
131
210
|
|
|
211
|
+
/**
|
|
212
|
+
* One `append_message(label, value)` call replayed against a VRF transcript.
|
|
213
|
+
* Merlin labels are ASCII by convention: use `utf8ToBytes("round")`.
|
|
214
|
+
*
|
|
215
|
+
* Derived from `@parity/truapi`'s `VrfTranscriptItem`, decoded to bytes.
|
|
216
|
+
*/
|
|
217
|
+
export type VrfTranscriptItem = { [K in keyof WireVrfTranscriptItem]: Uint8Array };
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* An sr25519 VRF signature: the pre-output and its DLEQ proof.
|
|
221
|
+
*
|
|
222
|
+
* Derived from `@parity/truapi`'s `VrfSignature`, decoded to bytes.
|
|
223
|
+
*/
|
|
224
|
+
export type VrfSignature = { [K in keyof WireVrfSignature]: Uint8Array };
|
|
225
|
+
|
|
132
226
|
/**
|
|
133
227
|
* Accounts provider handle, backed by `truApi.account.*` / `truApi.signing.*`.
|
|
134
228
|
* Surfaces the user's wallet accounts, app-scoped product accounts, Ring VRF,
|
|
@@ -152,10 +246,32 @@ export interface AccountsProvider {
|
|
|
152
246
|
derivationIndex?: number,
|
|
153
247
|
): ResultAsync<ProductAccount, scale.CallErrorValue<VersionedHostAccountGetError>>;
|
|
154
248
|
/**
|
|
155
|
-
*
|
|
156
|
-
*
|
|
249
|
+
* Register a ring-VRF key owned by the calling product.
|
|
250
|
+
*
|
|
251
|
+
* `index` is the plain derivation index within the product's ring-VRF
|
|
252
|
+
* domain; the adapter wraps it into the wire's tagged selector.
|
|
253
|
+
*
|
|
254
|
+
* Registration returns the key's public key. Call {@link listRingVrfKeys}
|
|
255
|
+
* afterward to obtain the opaque handle required by alias and proof calls.
|
|
157
256
|
*/
|
|
257
|
+
registerRingVrfKey(
|
|
258
|
+
index: number,
|
|
259
|
+
ring: RingLocation,
|
|
260
|
+
): ResultAsync<
|
|
261
|
+
RingVrfPublicKey,
|
|
262
|
+
scale.CallErrorValue<VersionedHostAccountRegisterRingVrfKeyError>
|
|
263
|
+
>;
|
|
264
|
+
/** List an owner's registered ring-VRF keys. */
|
|
265
|
+
listRingVrfKeys(
|
|
266
|
+
owner: string,
|
|
267
|
+
disclosure?: RingVrfKeyDisclosure,
|
|
268
|
+
): ResultAsync<
|
|
269
|
+
RegisteredRingVrfKey[],
|
|
270
|
+
scale.CallErrorValue<VersionedHostAccountListRingVrfKeysError>
|
|
271
|
+
>;
|
|
272
|
+
/** Derive a contextual alias with an explicitly registered ring-VRF key. */
|
|
158
273
|
getProductAccountAlias(
|
|
274
|
+
keyHandle: RingVrfKeyHandle,
|
|
159
275
|
context: ProductProofContext,
|
|
160
276
|
location: RingLocation,
|
|
161
277
|
): ResultAsync<ContextualAlias, scale.CallErrorValue<VersionedHostAccountGetAliasError>>;
|
|
@@ -164,15 +280,50 @@ export interface AccountsProvider {
|
|
|
164
280
|
scale.CallErrorValue<VersionedHostGetLegacyAccountsError>
|
|
165
281
|
>;
|
|
166
282
|
/**
|
|
167
|
-
* Generate a Ring VRF proof
|
|
168
|
-
* `
|
|
169
|
-
* carries the proof plus its verification values ({@link RingVRFProof}).
|
|
283
|
+
* Generate a Ring VRF proof with an explicitly registered key, binding
|
|
284
|
+
* `message` to the product-scoped `context`.
|
|
170
285
|
*/
|
|
171
286
|
createRingVRFProof(
|
|
287
|
+
keyHandle: RingVrfKeyHandle,
|
|
172
288
|
context: ProductProofContext,
|
|
173
289
|
location: RingLocation,
|
|
174
290
|
message: Uint8Array,
|
|
175
291
|
): ResultAsync<RingVRFProof, scale.CallErrorValue<VersionedHostAccountCreateProofError>>;
|
|
292
|
+
/**
|
|
293
|
+
* Sign `message` directly with an explicitly registered ring-VRF key.
|
|
294
|
+
*
|
|
295
|
+
* Unlike {@link createRingVRFProof} this proves nothing about ring
|
|
296
|
+
* membership; it is the plain signature under the member key, for
|
|
297
|
+
* protocols that carry their own proof.
|
|
298
|
+
*/
|
|
299
|
+
ringVrfSign(
|
|
300
|
+
keyHandle: RingVrfKeyHandle,
|
|
301
|
+
message: Uint8Array,
|
|
302
|
+
): ResultAsync<Uint8Array, scale.CallErrorValue<VersionedHostAccountRingVrfSignError>>;
|
|
303
|
+
/**
|
|
304
|
+
* Produce an sr25519 VRF signature from a product account (RFC-0023).
|
|
305
|
+
*
|
|
306
|
+
* The host builds a Merlin transcript from `transcriptLabel` and `items`,
|
|
307
|
+
* then signs it with the account's key. Unlike {@link createRingVRFProof},
|
|
308
|
+
* this names the signing account instead of proving ring membership.
|
|
309
|
+
*
|
|
310
|
+
* The caller owns four things the types cannot enforce:
|
|
311
|
+
*
|
|
312
|
+
* - Domain separation. A label borrowed from another protocol makes the
|
|
313
|
+
* output replayable across both.
|
|
314
|
+
* - Freshness. The VRF is deterministic, so per-round values belong in
|
|
315
|
+
* `items`.
|
|
316
|
+
* - Size. Hosts cap the transcript at 32 items and 8 KiB total.
|
|
317
|
+
* - Authorization. An `AutoSigning` allowance makes these calls silent. It
|
|
318
|
+
* is not VRF-scoped, so it covers other signing by that account too.
|
|
319
|
+
*
|
|
320
|
+
* Hosts predating the call reject it through the error channel.
|
|
321
|
+
*/
|
|
322
|
+
signVrf(
|
|
323
|
+
account: ProductAccountLookup,
|
|
324
|
+
transcriptLabel: Uint8Array,
|
|
325
|
+
items: VrfTranscriptItem[],
|
|
326
|
+
): ResultAsync<VrfSignature, scale.CallErrorValue<VersionedHostAccountSignVrfError>>;
|
|
176
327
|
/**
|
|
177
328
|
* Build a `PolkadotSigner` for a product account. Signing routes through the
|
|
178
329
|
* host's `createTransaction` path: the host decodes the metadata and forwards
|
|
@@ -228,12 +379,17 @@ function toHostExtensions(
|
|
|
228
379
|
}));
|
|
229
380
|
}
|
|
230
381
|
|
|
231
|
-
/**
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
382
|
+
/**
|
|
383
|
+
* Build the wire `ProductAccountId`: default the index to 0, wrap it as `Index`.
|
|
384
|
+
*
|
|
385
|
+
* Destructured rather than spread, so passing a full {@link ProductAccount}
|
|
386
|
+
* cannot leak its `publicKey` onto the wire.
|
|
387
|
+
*/
|
|
388
|
+
function toWireProductAccountId({
|
|
389
|
+
dotNsIdentifier,
|
|
390
|
+
derivationIndex = 0,
|
|
391
|
+
}: ProductAccountLookup): ProductAccountId {
|
|
392
|
+
return { dotNsIdentifier, derivationIndex: { tag: "Index", value: derivationIndex } };
|
|
237
393
|
}
|
|
238
394
|
|
|
239
395
|
/** Build an {@link AccountsProvider} over a TruAPI client's `account` / `signing` domains. */
|
|
@@ -253,7 +409,7 @@ function adaptAccountsProvider(client: TrUApiClient): AccountsProvider {
|
|
|
253
409
|
getProductAccount(dotNsIdentifier, derivationIndex = 0) {
|
|
254
410
|
return account
|
|
255
411
|
.getAccount({
|
|
256
|
-
productAccountId: toWireProductAccountId(dotNsIdentifier, derivationIndex),
|
|
412
|
+
productAccountId: toWireProductAccountId({ dotNsIdentifier, derivationIndex }),
|
|
257
413
|
})
|
|
258
414
|
.map((response) => ({
|
|
259
415
|
publicKey: fromHex(response.account.publicKey),
|
|
@@ -261,11 +417,31 @@ function adaptAccountsProvider(client: TrUApiClient): AccountsProvider {
|
|
|
261
417
|
derivationIndex,
|
|
262
418
|
}));
|
|
263
419
|
},
|
|
264
|
-
|
|
265
|
-
return account
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
420
|
+
registerRingVrfKey(index, ring) {
|
|
421
|
+
return account
|
|
422
|
+
.registerRingVrfKey({ index: { tag: "Index", value: index }, ring })
|
|
423
|
+
.map(fromHex);
|
|
424
|
+
},
|
|
425
|
+
listRingVrfKeys(owner, disclosure = "Anonymized") {
|
|
426
|
+
return account.listRingVrfKeys({ owner, disclosure }).map((keys) =>
|
|
427
|
+
keys.map((key) => ({
|
|
428
|
+
...key,
|
|
429
|
+
handle: key.handle as unknown as RingVrfKeyHandle,
|
|
430
|
+
publicKey: key.publicKey === undefined ? undefined : fromHex(key.publicKey),
|
|
431
|
+
})),
|
|
432
|
+
);
|
|
433
|
+
},
|
|
434
|
+
getProductAccountAlias(keyHandle, context, location) {
|
|
435
|
+
return account
|
|
436
|
+
.getAccountAlias({
|
|
437
|
+
keyHandle: keyHandle as unknown as ProductAccountId,
|
|
438
|
+
context,
|
|
439
|
+
ringLocation: location,
|
|
440
|
+
})
|
|
441
|
+
.map((response) => ({
|
|
442
|
+
context: fromHex(response.context),
|
|
443
|
+
alias: fromHex(response.alias),
|
|
444
|
+
}));
|
|
269
445
|
},
|
|
270
446
|
getLegacyAccounts() {
|
|
271
447
|
return account.getLegacyAccounts().map((response) =>
|
|
@@ -275,9 +451,10 @@ function adaptAccountsProvider(client: TrUApiClient): AccountsProvider {
|
|
|
275
451
|
})),
|
|
276
452
|
);
|
|
277
453
|
},
|
|
278
|
-
createRingVRFProof(context, location, message) {
|
|
454
|
+
createRingVRFProof(keyHandle, context, location, message) {
|
|
279
455
|
return account
|
|
280
456
|
.createAccountProof({
|
|
457
|
+
keyHandle: keyHandle as unknown as ProductAccountId,
|
|
281
458
|
context,
|
|
282
459
|
ringLocation: location,
|
|
283
460
|
message: toHex(message),
|
|
@@ -292,11 +469,31 @@ function adaptAccountsProvider(client: TrUApiClient): AccountsProvider {
|
|
|
292
469
|
ringRevision: response.ringRevision,
|
|
293
470
|
}));
|
|
294
471
|
},
|
|
472
|
+
ringVrfSign(keyHandle, message) {
|
|
473
|
+
return account
|
|
474
|
+
.ringVrfSign({
|
|
475
|
+
keyHandle: keyHandle as unknown as ProductAccountId,
|
|
476
|
+
message: toHex(message),
|
|
477
|
+
})
|
|
478
|
+
.map(fromHex);
|
|
479
|
+
},
|
|
480
|
+
signVrf(account_, transcriptLabel, items) {
|
|
481
|
+
return account
|
|
482
|
+
.signVrf({
|
|
483
|
+
account: toWireProductAccountId(account_),
|
|
484
|
+
transcriptLabel: toHex(transcriptLabel),
|
|
485
|
+
items: items.map(({ label, value }) => ({
|
|
486
|
+
label: toHex(label),
|
|
487
|
+
value: toHex(value),
|
|
488
|
+
})),
|
|
489
|
+
})
|
|
490
|
+
.map((response) => ({
|
|
491
|
+
preOutput: fromHex(response.preOutput),
|
|
492
|
+
proof: fromHex(response.proof),
|
|
493
|
+
}));
|
|
494
|
+
},
|
|
295
495
|
getProductAccountSigner(account_) {
|
|
296
|
-
const productAccountId = toWireProductAccountId(
|
|
297
|
-
account_.dotNsIdentifier,
|
|
298
|
-
account_.derivationIndex,
|
|
299
|
-
);
|
|
496
|
+
const productAccountId = toWireProductAccountId(account_);
|
|
300
497
|
|
|
301
498
|
return {
|
|
302
499
|
publicKey: account_.publicKey,
|
|
@@ -405,6 +602,35 @@ if (import.meta.vitest) {
|
|
|
405
602
|
account: {
|
|
406
603
|
getUserId: method("getUserId", { primaryUsername: "alice.dot" }),
|
|
407
604
|
getAccount: method("getAccount", { account: { publicKey: "0xaa" } }),
|
|
605
|
+
registerRingVrfKey: method("registerRingVrfKey", "0x0304"),
|
|
606
|
+
ringVrfSign: method("ringVrfSign", "0xba5eba11"),
|
|
607
|
+
listRingVrfKeys: method("listRingVrfKeys", [
|
|
608
|
+
{
|
|
609
|
+
handle: {
|
|
610
|
+
dotNsIdentifier: "people.dot",
|
|
611
|
+
derivationIndex: { tag: "Index", value: 0 },
|
|
612
|
+
},
|
|
613
|
+
rings: [
|
|
614
|
+
{
|
|
615
|
+
chainId: "0x01",
|
|
616
|
+
junctions: [{ tag: "PalletInstance", value: 1 }],
|
|
617
|
+
},
|
|
618
|
+
],
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
handle: {
|
|
622
|
+
dotNsIdentifier: "people.dot",
|
|
623
|
+
derivationIndex: { tag: "Index", value: 1 },
|
|
624
|
+
},
|
|
625
|
+
rings: [
|
|
626
|
+
{
|
|
627
|
+
chainId: "0x02",
|
|
628
|
+
junctions: [{ tag: "CollectionId", value: "0xaabb" }],
|
|
629
|
+
},
|
|
630
|
+
],
|
|
631
|
+
publicKey: "0x0102",
|
|
632
|
+
},
|
|
633
|
+
]),
|
|
408
634
|
getAccountAlias: method("getAccountAlias", { context: "0x01", alias: "0x02" }),
|
|
409
635
|
getLegacyAccounts: method("getLegacyAccounts", {
|
|
410
636
|
accounts: [{ publicKey: "0xbb", name: "Bob" }],
|
|
@@ -415,6 +641,7 @@ if (import.meta.vitest) {
|
|
|
415
641
|
ringIndex: 3,
|
|
416
642
|
ringRevision: 7,
|
|
417
643
|
}),
|
|
644
|
+
signVrf: method("signVrf", { preOutput: "0xaa11", proof: "0xbb22" }),
|
|
418
645
|
connectionStatusSubscribe: () => ({
|
|
419
646
|
subscribe: () => ({ unsubscribe: vi.fn() }),
|
|
420
647
|
[Symbol.observable as symbol]() {
|
|
@@ -452,7 +679,7 @@ if (import.meta.vitest) {
|
|
|
452
679
|
{
|
|
453
680
|
productAccountId: {
|
|
454
681
|
dotNsIdentifier: "app.dot",
|
|
455
|
-
derivationIndex: { tag: "
|
|
682
|
+
derivationIndex: { tag: "Index", value: 2 },
|
|
456
683
|
},
|
|
457
684
|
},
|
|
458
685
|
]);
|
|
@@ -463,13 +690,135 @@ if (import.meta.vitest) {
|
|
|
463
690
|
});
|
|
464
691
|
});
|
|
465
692
|
|
|
693
|
+
test("getProductAccount defaults the derivation index in both the request and the result", async () => {
|
|
694
|
+
const calls: Array<[string, unknown]> = [];
|
|
695
|
+
const client = makeFakeClient({ onCall: (m, a) => calls.push([m, a]) });
|
|
696
|
+
const provider = adaptAccountsProvider(client);
|
|
697
|
+
const account = await provider.getProductAccount("app.dot").match(
|
|
698
|
+
(a) => a,
|
|
699
|
+
() => null,
|
|
700
|
+
);
|
|
701
|
+
expect(calls[0]).toEqual([
|
|
702
|
+
"getAccount",
|
|
703
|
+
{
|
|
704
|
+
productAccountId: {
|
|
705
|
+
dotNsIdentifier: "app.dot",
|
|
706
|
+
derivationIndex: { tag: "Index", value: 0 },
|
|
707
|
+
},
|
|
708
|
+
},
|
|
709
|
+
]);
|
|
710
|
+
// The resolved index must reach the caller too, not just the wire.
|
|
711
|
+
expect(account?.derivationIndex).toBe(0);
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
test("registerRingVrfKey wraps the numeric index and decodes the public key", async () => {
|
|
715
|
+
const calls: Array<[string, unknown]> = [];
|
|
716
|
+
const provider = adaptAccountsProvider(
|
|
717
|
+
makeFakeClient({ onCall: (method, args) => calls.push([method, args]) }),
|
|
718
|
+
);
|
|
719
|
+
const ring: RingLocation = {
|
|
720
|
+
chainId: "0x01",
|
|
721
|
+
junctions: [{ tag: "PalletInstance", value: 67 }],
|
|
722
|
+
};
|
|
723
|
+
const index = 2;
|
|
724
|
+
const publicKey = await provider.registerRingVrfKey(index, ring).match(
|
|
725
|
+
(value) => value,
|
|
726
|
+
() => null,
|
|
727
|
+
);
|
|
728
|
+
|
|
729
|
+
expect(calls[0]).toEqual([
|
|
730
|
+
"registerRingVrfKey",
|
|
731
|
+
{ index: { tag: "Index", value: 2 }, ring },
|
|
732
|
+
]);
|
|
733
|
+
expect(publicKey).toEqual(fromHex("0x0304"));
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
test("listRingVrfKeys selects by ring without exposing a raw index", async () => {
|
|
737
|
+
const calls: Array<[string, unknown]> = [];
|
|
738
|
+
const provider = adaptAccountsProvider(
|
|
739
|
+
makeFakeClient({ onCall: (method, args) => calls.push([method, args]) }),
|
|
740
|
+
);
|
|
741
|
+
const keys = await provider.listRingVrfKeys("people.dot", "PublicKey").match(
|
|
742
|
+
(value) => value,
|
|
743
|
+
() => [],
|
|
744
|
+
);
|
|
745
|
+
expect(calls[0]).toEqual([
|
|
746
|
+
"listRingVrfKeys",
|
|
747
|
+
{ owner: "people.dot", disclosure: "PublicKey" },
|
|
748
|
+
]);
|
|
749
|
+
expect(keys[1].publicKey).toEqual(fromHex("0x0102"));
|
|
750
|
+
expect(
|
|
751
|
+
findRingVrfKeyHandle(keys, {
|
|
752
|
+
chainId: "0x02",
|
|
753
|
+
junctions: [{ tag: "CollectionId", value: "0xAABB" }],
|
|
754
|
+
}),
|
|
755
|
+
).toEqual(keys[1].handle);
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
test("getProductAccountAlias passes the selected key handle", async () => {
|
|
759
|
+
const calls: Array<[string, unknown]> = [];
|
|
760
|
+
const provider = adaptAccountsProvider(
|
|
761
|
+
makeFakeClient({ onCall: (method, args) => calls.push([method, args]) }),
|
|
762
|
+
);
|
|
763
|
+
const keys = await provider.listRingVrfKeys("people.dot").match(
|
|
764
|
+
(value) => value,
|
|
765
|
+
() => [],
|
|
766
|
+
);
|
|
767
|
+
calls.length = 0;
|
|
768
|
+
const keyHandle = keys[1].handle;
|
|
769
|
+
const context: ProductProofContext = {
|
|
770
|
+
productId: "app.dot",
|
|
771
|
+
suffix: { tag: "Index", value: 0 },
|
|
772
|
+
};
|
|
773
|
+
const ring: RingLocation = {
|
|
774
|
+
chainId: "0x01",
|
|
775
|
+
junctions: [{ tag: "PalletInstance", value: 1 }],
|
|
776
|
+
};
|
|
777
|
+
const alias = await provider.getProductAccountAlias(keyHandle, context, ring).match(
|
|
778
|
+
(value) => value,
|
|
779
|
+
() => null,
|
|
780
|
+
);
|
|
781
|
+
expect(calls[0]).toEqual(["getAccountAlias", { keyHandle, context, ringLocation: ring }]);
|
|
782
|
+
expect(alias).toEqual({ context: fromHex("0x01"), alias: fromHex("0x02") });
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
test("ringVrfSign passes the selected handle and decodes the signature", async () => {
|
|
786
|
+
const calls: Array<[string, unknown]> = [];
|
|
787
|
+
const provider = adaptAccountsProvider(
|
|
788
|
+
makeFakeClient({ onCall: (method, args) => calls.push([method, args]) }),
|
|
789
|
+
);
|
|
790
|
+
const keys = await provider.listRingVrfKeys("people.dot").match(
|
|
791
|
+
(value) => value,
|
|
792
|
+
() => [],
|
|
793
|
+
);
|
|
794
|
+
calls.length = 0;
|
|
795
|
+
const keyHandle = keys[1].handle;
|
|
796
|
+
const signature = await provider.ringVrfSign(keyHandle, new Uint8Array([1, 2, 3])).match(
|
|
797
|
+
(value) => value,
|
|
798
|
+
() => null,
|
|
799
|
+
);
|
|
800
|
+
expect(calls[0]).toEqual([
|
|
801
|
+
"ringVrfSign",
|
|
802
|
+
{ keyHandle, message: toHex(new Uint8Array([1, 2, 3])) },
|
|
803
|
+
]);
|
|
804
|
+
expect(signature).toEqual(fromHex("0xba5eba11"));
|
|
805
|
+
});
|
|
806
|
+
|
|
466
807
|
test("createRingVRFProof hex-encodes the message and decodes the proof response", async () => {
|
|
467
808
|
const calls: Array<[string, unknown]> = [];
|
|
468
809
|
const client = makeFakeClient({ onCall: (m, a) => calls.push([m, a]) });
|
|
469
810
|
const provider = adaptAccountsProvider(client);
|
|
811
|
+
const keyHandle = (
|
|
812
|
+
await provider.listRingVrfKeys("people.dot").match(
|
|
813
|
+
(value) => value,
|
|
814
|
+
() => [],
|
|
815
|
+
)
|
|
816
|
+
)[0].handle;
|
|
817
|
+
calls.length = 0;
|
|
470
818
|
const proof = await provider
|
|
471
819
|
.createRingVRFProof(
|
|
472
|
-
|
|
820
|
+
keyHandle,
|
|
821
|
+
{ productId: "app.dot", suffix: { tag: "Index", value: 0 } },
|
|
473
822
|
{ chainId: "0x01", junctions: [{ tag: "PalletInstance", value: 1 }] },
|
|
474
823
|
new Uint8Array([1, 2, 3]),
|
|
475
824
|
)
|
|
@@ -479,7 +828,11 @@ if (import.meta.vitest) {
|
|
|
479
828
|
);
|
|
480
829
|
expect(calls[0][0]).toBe("createAccountProof");
|
|
481
830
|
expect(calls[0][1]).toEqual({
|
|
482
|
-
|
|
831
|
+
keyHandle: {
|
|
832
|
+
dotNsIdentifier: "people.dot",
|
|
833
|
+
derivationIndex: { tag: "Index", value: 0 },
|
|
834
|
+
},
|
|
835
|
+
context: { productId: "app.dot", suffix: { tag: "Index", value: 0 } },
|
|
483
836
|
ringLocation: { chainId: "0x01", junctions: [{ tag: "PalletInstance", value: 1 }] },
|
|
484
837
|
message: toHex(new Uint8Array([1, 2, 3])),
|
|
485
838
|
});
|
|
@@ -491,6 +844,68 @@ if (import.meta.vitest) {
|
|
|
491
844
|
});
|
|
492
845
|
});
|
|
493
846
|
|
|
847
|
+
test("signVrf hex-encodes the transcript and decodes the signature", async () => {
|
|
848
|
+
const calls: Array<[string, unknown]> = [];
|
|
849
|
+
const client = makeFakeClient({ onCall: (m, a) => calls.push([m, a]) });
|
|
850
|
+
const provider = adaptAccountsProvider(client);
|
|
851
|
+
const transcriptLabel = new Uint8Array([1, 2, 3]);
|
|
852
|
+
const itemLabel = new Uint8Array([4]);
|
|
853
|
+
const itemValue = new Uint8Array([5, 6]);
|
|
854
|
+
const signature = await provider
|
|
855
|
+
.signVrf({ dotNsIdentifier: "app.dot", derivationIndex: 3 }, transcriptLabel, [
|
|
856
|
+
{ label: itemLabel, value: itemValue },
|
|
857
|
+
])
|
|
858
|
+
.match(
|
|
859
|
+
(s) => s,
|
|
860
|
+
() => null,
|
|
861
|
+
);
|
|
862
|
+
expect(calls[0]).toEqual([
|
|
863
|
+
"signVrf",
|
|
864
|
+
{
|
|
865
|
+
account: {
|
|
866
|
+
dotNsIdentifier: "app.dot",
|
|
867
|
+
derivationIndex: { tag: "Index", value: 3 },
|
|
868
|
+
},
|
|
869
|
+
transcriptLabel: toHex(transcriptLabel),
|
|
870
|
+
items: [{ label: toHex(itemLabel), value: toHex(itemValue) }],
|
|
871
|
+
},
|
|
872
|
+
]);
|
|
873
|
+
expect(signature).toEqual({ preOutput: fromHex("0xaa11"), proof: fromHex("0xbb22") });
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
test("signVrf defaults the derivation index", async () => {
|
|
877
|
+
const calls: Array<[string, unknown]> = [];
|
|
878
|
+
const client = makeFakeClient({ onCall: (m, a) => calls.push([m, a]) });
|
|
879
|
+
const provider = adaptAccountsProvider(client);
|
|
880
|
+
await provider.signVrf({ dotNsIdentifier: "app.dot" }, new Uint8Array([1]), []).match(
|
|
881
|
+
(s) => s,
|
|
882
|
+
() => null,
|
|
883
|
+
);
|
|
884
|
+
expect((calls[0][1] as { account: unknown }).account).toEqual({
|
|
885
|
+
dotNsIdentifier: "app.dot",
|
|
886
|
+
derivationIndex: { tag: "Index", value: 0 },
|
|
887
|
+
});
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
test("signVrf sends only the id fields when given a full product account", async () => {
|
|
891
|
+
const calls: Array<[string, unknown]> = [];
|
|
892
|
+
const client = makeFakeClient({ onCall: (m, a) => calls.push([m, a]) });
|
|
893
|
+
const provider = adaptAccountsProvider(client);
|
|
894
|
+
const account: ProductAccount = {
|
|
895
|
+
dotNsIdentifier: "app.dot",
|
|
896
|
+
derivationIndex: 1,
|
|
897
|
+
publicKey: new Uint8Array(32).fill(0xaa),
|
|
898
|
+
};
|
|
899
|
+
await provider.signVrf(account, new Uint8Array([1]), []).match(
|
|
900
|
+
(s) => s,
|
|
901
|
+
() => null,
|
|
902
|
+
);
|
|
903
|
+
expect(Object.keys((calls[0][1] as { account: object }).account)).toEqual([
|
|
904
|
+
"dotNsIdentifier",
|
|
905
|
+
"derivationIndex",
|
|
906
|
+
]);
|
|
907
|
+
});
|
|
908
|
+
|
|
494
909
|
test("the product signer signs bytes via signing.signRaw", async () => {
|
|
495
910
|
const calls: Array<[string, unknown]> = [];
|
|
496
911
|
const client = makeFakeClient({ onCall: (m, a) => calls.push([m, a]) });
|
|
@@ -504,7 +919,10 @@ if (import.meta.vitest) {
|
|
|
504
919
|
expect(calls.at(-1)).toEqual([
|
|
505
920
|
"signRaw",
|
|
506
921
|
{
|
|
507
|
-
account: {
|
|
922
|
+
account: {
|
|
923
|
+
dotNsIdentifier: "app.dot",
|
|
924
|
+
derivationIndex: { tag: "Index", value: 0 },
|
|
925
|
+
},
|
|
508
926
|
payload: { tag: "Bytes", value: { bytes: toHex(new Uint8Array([9, 9])) } },
|
|
509
927
|
},
|
|
510
928
|
]);
|
|
@@ -591,7 +1009,7 @@ if (import.meta.vitest) {
|
|
|
591
1009
|
expect(calls.at(-1)).toEqual([
|
|
592
1010
|
"createTransaction",
|
|
593
1011
|
{
|
|
594
|
-
signer: { dotNsIdentifier: "app.dot", derivationIndex: { tag: "
|
|
1012
|
+
signer: { dotNsIdentifier: "app.dot", derivationIndex: { tag: "Index", value: 0 } },
|
|
595
1013
|
genesisHash: toHex(new Uint8Array([0x01, 0x02])),
|
|
596
1014
|
callData: toHex(new Uint8Array([0xca, 0x11])),
|
|
597
1015
|
extensions: expectedHostExtensions,
|