@prosopo/keyring 2.9.61 → 2.9.63

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.
Files changed (55) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +2 -2
  2. package/.turbo/turbo-build$colon$tsc.log +11 -11
  3. package/.turbo/turbo-build.log +3 -3
  4. package/CHANGELOG.md +14 -0
  5. package/dist/accounts/getPair.test.d.ts +2 -0
  6. package/dist/accounts/getPair.test.d.ts.map +1 -0
  7. package/dist/accounts/getPair.test.js +86 -0
  8. package/dist/accounts/getPair.test.js.map +1 -0
  9. package/dist/accounts/mnemonic.test.d.ts +2 -0
  10. package/dist/accounts/mnemonic.test.d.ts.map +1 -0
  11. package/dist/accounts/mnemonic.test.js +64 -0
  12. package/dist/accounts/mnemonic.test.js.map +1 -0
  13. package/dist/accounts/testAccounts.test.d.ts +2 -0
  14. package/dist/accounts/testAccounts.test.d.ts.map +1 -0
  15. package/dist/accounts/testAccounts.test.js +66 -0
  16. package/dist/accounts/testAccounts.test.js.map +1 -0
  17. package/dist/keyring/keyring.test.d.ts +2 -0
  18. package/dist/keyring/keyring.test.d.ts.map +1 -0
  19. package/dist/keyring/keyring.test.js +239 -0
  20. package/dist/keyring/keyring.test.js.map +1 -0
  21. package/dist/keyring/pairs.test.d.ts +2 -0
  22. package/dist/keyring/pairs.test.d.ts.map +1 -0
  23. package/dist/keyring/pairs.test.js +84 -0
  24. package/dist/keyring/pairs.test.js.map +1 -0
  25. package/dist/keyring/testing.test.d.ts +2 -0
  26. package/dist/keyring/testing.test.d.ts.map +1 -0
  27. package/dist/keyring/testing.test.js +97 -0
  28. package/dist/keyring/testing.test.js.map +1 -0
  29. package/dist/keyring.test-d.d.ts +2 -0
  30. package/dist/keyring.test-d.d.ts.map +1 -0
  31. package/dist/keyring.test-d.js +66 -0
  32. package/dist/keyring.test-d.js.map +1 -0
  33. package/dist/pair/decode.spec.js +1 -1
  34. package/dist/pair/decode.spec.js.map +1 -1
  35. package/dist/pair/encode.spec.js +1 -1
  36. package/dist/pair/encode.spec.js.map +1 -1
  37. package/dist/pair/nobody.test.d.ts +2 -0
  38. package/dist/pair/nobody.test.d.ts.map +1 -0
  39. package/dist/pair/nobody.test.js +77 -0
  40. package/dist/pair/nobody.test.js.map +1 -0
  41. package/dist/pair/toJson.spec.js +1 -1
  42. package/dist/pair/toJson.spec.js.map +1 -1
  43. package/package.json +3 -3
  44. package/src/accounts/getPair.test.ts +142 -0
  45. package/src/accounts/mnemonic.test.ts +103 -0
  46. package/src/accounts/testAccounts.test.ts +100 -0
  47. package/src/keyring/keyring.test.ts +373 -0
  48. package/src/keyring/pairs.test.ts +133 -0
  49. package/src/keyring/testing.test.ts +146 -0
  50. package/src/keyring.test-d.ts +134 -0
  51. package/src/pair/decode.spec.ts +3 -1
  52. package/src/pair/encode.spec.ts +9 -3
  53. package/src/pair/nobody.test.ts +127 -0
  54. package/src/pair/toJson.spec.ts +18 -12
  55. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,373 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ import { u8aToHex } from "@polkadot/util";
16
+ import type { KeyringPair, KeyringPair$Json } from "@prosopo/types";
17
+ import type { KeypairType } from "@prosopo/util-crypto";
18
+ import {
19
+ decodeAddress,
20
+ encodeAddress,
21
+ mnemonicToMiniSecret,
22
+ sr25519FromSeed,
23
+ } from "@prosopo/util-crypto";
24
+ import { describe, expect, it } from "vitest";
25
+ import { DEV_PHRASE, Keyring } from "./keyring.js";
26
+
27
+ const ALICE_SURI = `${DEV_PHRASE}//Alice`;
28
+ // Well-known dev key: the public half of `//Alice` derived from the standard
29
+ // substrate dev phrase, published in substrate's own test fixtures.
30
+ const ALICE_PUBLIC =
31
+ "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d";
32
+ const SEED_32 = new Uint8Array(32).fill(1);
33
+
34
+ // Key derivation runs scrypt/pbkdf2 with production parameters and takes
35
+ // seconds per call, so these suites need more than the 10s default.
36
+ const SLOW = { timeout: 60000 };
37
+
38
+ describe("Keyring construction", SLOW, () => {
39
+ it("defaults to sr25519", () => {
40
+ expect(new Keyring().type).toBe("sr25519");
41
+ expect(new Keyring({}).type).toBe("sr25519");
42
+ });
43
+
44
+ it("accepts an explicit sr25519 type", () => {
45
+ expect(new Keyring({ type: "sr25519" }).type).toBe("sr25519");
46
+ });
47
+
48
+ it("rejects every other curve, naming what it found", () => {
49
+ // This fork only implements sr25519; silently accepting ed25519 would
50
+ // produce pairs that throw deep inside signing instead.
51
+ for (const type of ["ed25519", "ecdsa", "ethereum"] as KeypairType[]) {
52
+ expect(() => new Keyring({ type })).toThrow(
53
+ `Expected a keyring type of either 'sr25519', found '${type}`,
54
+ );
55
+ }
56
+ });
57
+
58
+ it("starts with no pairs", () => {
59
+ const keyring = new Keyring();
60
+ expect(keyring.pairs).toEqual([]);
61
+ expect(keyring.getPairs()).toEqual([]);
62
+ expect(keyring.publicKeys).toEqual([]);
63
+ });
64
+ });
65
+
66
+ describe("Keyring address encoding", SLOW, () => {
67
+ it("uses the configured ss58 format", () => {
68
+ const publicKey = new Uint8Array(32).fill(2);
69
+ expect(new Keyring({ ss58Format: 42 }).encodeAddress(publicKey)).toBe(
70
+ encodeAddress(publicKey, 42),
71
+ );
72
+ expect(new Keyring({ ss58Format: 2 }).encodeAddress(publicKey)).toBe(
73
+ encodeAddress(publicKey, 2),
74
+ );
75
+ });
76
+
77
+ it("lets an explicit format override the configured one", () => {
78
+ const publicKey = new Uint8Array(32).fill(3);
79
+ expect(new Keyring({ ss58Format: 42 }).encodeAddress(publicKey, 2)).toBe(
80
+ encodeAddress(publicKey, 2),
81
+ );
82
+ });
83
+
84
+ it("applies a format set after construction to new pairs", () => {
85
+ const keyring = new Keyring({ ss58Format: 42 });
86
+ const before = keyring.addFromUri(ALICE_SURI).address;
87
+ keyring.setSS58Format(2);
88
+ const after = keyring.addFromUri(ALICE_SURI).address;
89
+
90
+ expect(after).not.toBe(before);
91
+ expect(decodeAddress(after).toString()).toBe(
92
+ decodeAddress(before).toString(),
93
+ );
94
+ });
95
+
96
+ it("exposes decodeAddress as the inverse of its own encoding", () => {
97
+ const keyring = new Keyring({ ss58Format: 42 });
98
+ const publicKey = new Uint8Array(32).fill(4);
99
+ expect(keyring.decodeAddress(keyring.encodeAddress(publicKey))).toEqual(
100
+ publicKey,
101
+ );
102
+ });
103
+ });
104
+
105
+ describe("Keyring.createFromUri", SLOW, () => {
106
+ it("derives the well-known Alice key from the dev phrase", () => {
107
+ const pair = new Keyring().createFromUri(ALICE_SURI);
108
+ expect(u8aToHex(pair.publicKey)).toBe(ALICE_PUBLIC);
109
+ });
110
+
111
+ it("expands a bare hard-derivation path against the dev phrase", () => {
112
+ // `//Alice` on its own is the shorthand every dev script uses.
113
+ expect(u8aToHex(new Keyring().createFromUri("//Alice").publicKey)).toBe(
114
+ ALICE_PUBLIC,
115
+ );
116
+ });
117
+
118
+ it("does not create the pair in the keyring", () => {
119
+ const keyring = new Keyring();
120
+ keyring.createFromUri(ALICE_SURI);
121
+ expect(keyring.getPairs()).toEqual([]);
122
+ });
123
+
124
+ it("treats a 256-bit hex phrase as a raw seed", () => {
125
+ const seedHex = u8aToHex(SEED_32);
126
+ expect(u8aToHex(new Keyring().createFromUri(seedHex).publicKey)).toBe(
127
+ u8aToHex(sr25519FromSeed(SEED_32).publicKey),
128
+ );
129
+ });
130
+
131
+ it("pads a short non-mnemonic phrase out to 32 bytes", () => {
132
+ const pair = new Keyring().createFromUri("hello");
133
+ const padded = new Uint8Array(32).fill(32);
134
+ padded.set(new TextEncoder().encode("hello"));
135
+ expect(u8aToHex(pair.publicKey)).toBe(
136
+ u8aToHex(sr25519FromSeed(padded).publicKey),
137
+ );
138
+ });
139
+
140
+ it("rejects a phrase that is neither a mnemonic nor short enough to pad", () => {
141
+ // Silently truncating would give a key the caller never intended and
142
+ // could not recover funds from.
143
+ expect(() => new Keyring().createFromUri("x".repeat(33))).toThrow(
144
+ "specified phrase is not a valid mnemonic and is invalid as a raw seed at > 32 bytes",
145
+ );
146
+ });
147
+
148
+ it("accepts a 32 character phrase at the padding boundary", () => {
149
+ expect(() => new Keyring().createFromUri("x".repeat(32))).not.toThrow();
150
+ });
151
+
152
+ it("derives a 12 word mnemonic through the mini secret rather than padding", () => {
153
+ expect(u8aToHex(new Keyring().createFromUri(DEV_PHRASE).publicKey)).toBe(
154
+ u8aToHex(sr25519FromSeed(mnemonicToMiniSecret(DEV_PHRASE)).publicKey),
155
+ );
156
+ });
157
+
158
+ it("treats a word count outside the mnemonic set as a raw phrase", () => {
159
+ // 13 words is not a valid BIP39 length, and the string is well over 32
160
+ // bytes, so it must be refused rather than quietly padded or hashed.
161
+ expect(() => new Keyring().createFromUri(`${DEV_PHRASE} extra`)).toThrow(
162
+ "is not a valid mnemonic",
163
+ );
164
+ });
165
+
166
+ it("gives a different key for a different password", () => {
167
+ const plain = new Keyring().createFromUri(DEV_PHRASE);
168
+ const withPassword = new Keyring().createFromUri(`${DEV_PHRASE}///pass`);
169
+ expect(u8aToHex(withPassword.publicKey)).not.toBe(
170
+ u8aToHex(plain.publicKey),
171
+ );
172
+ });
173
+
174
+ it("distinguishes soft and hard derivation of the same name", () => {
175
+ const soft = new Keyring().createFromUri(`${DEV_PHRASE}/Alice`);
176
+ const hard = new Keyring().createFromUri(ALICE_SURI);
177
+ expect(u8aToHex(soft.publicKey)).not.toBe(u8aToHex(hard.publicKey));
178
+ });
179
+
180
+ it("carries meta through to the pair", () => {
181
+ const pair = new Keyring().createFromUri(ALICE_SURI, { name: "alice" });
182
+ expect(pair.meta.name).toBe("alice");
183
+ });
184
+
185
+ it("refuses ethereum derivation for a mnemonic", () => {
186
+ expect(() =>
187
+ new Keyring().createFromUri(DEV_PHRASE, {}, "ethereum"),
188
+ ).toThrow("Not implemented - Prosopo Keyring supports sr25519 only");
189
+ });
190
+ });
191
+
192
+ describe("Keyring add* methods", SLOW, () => {
193
+ it("stores a pair created from a uri and returns it on lookup", () => {
194
+ const keyring = new Keyring();
195
+ const pair = keyring.addFromUri(ALICE_SURI);
196
+
197
+ expect(keyring.getPairs()).toEqual([pair]);
198
+ expect(keyring.getPair(pair.address)).toBe(pair);
199
+ expect(keyring.getPair(pair.publicKey)).toBe(pair);
200
+ });
201
+
202
+ it("treats addFromMnemonic as addFromUri", () => {
203
+ const fromMnemonic = new Keyring().addFromMnemonic(DEV_PHRASE);
204
+ const fromUri = new Keyring().addFromUri(DEV_PHRASE);
205
+ expect(fromMnemonic.address).toBe(fromUri.address);
206
+ });
207
+
208
+ it("derives a pair from a raw seed", () => {
209
+ const pair = new Keyring().addFromSeed(SEED_32);
210
+ expect(u8aToHex(pair.publicKey)).toBe(
211
+ u8aToHex(sr25519FromSeed(SEED_32).publicKey),
212
+ );
213
+ });
214
+
215
+ it("refuses a seed for an unimplemented curve", () => {
216
+ expect(() => new Keyring().addFromSeed(SEED_32, {}, "ed25519")).toThrow(
217
+ "Not Implemented",
218
+ );
219
+ });
220
+
221
+ it("adds an address-only pair that has no secret and cannot sign", () => {
222
+ // Watch-only accounts are legitimate, but they must not silently
223
+ // produce a signature made from an empty secret.
224
+ const keyring = new Keyring();
225
+ const address = keyring.addFromUri(ALICE_SURI).address;
226
+ const watchOnly = new Keyring().addFromAddress(address);
227
+
228
+ expect(watchOnly.address).toBe(address);
229
+ expect(watchOnly.isLocked).toBe(true);
230
+ expect(() => watchOnly.sign(new Uint8Array([1]))).toThrow();
231
+ });
232
+
233
+ it("adds a pair from an explicit keypair", () => {
234
+ const keypair = sr25519FromSeed(SEED_32);
235
+ const keyring = new Keyring();
236
+ const pair = keyring.addFromPair(keypair, { name: "explicit" });
237
+
238
+ expect(u8aToHex(pair.publicKey)).toBe(u8aToHex(keypair.publicKey));
239
+ expect(pair.meta.name).toBe("explicit");
240
+ expect(keyring.getPairs()).toEqual([pair]);
241
+ });
242
+
243
+ it("exposes public keys for every stored pair", () => {
244
+ const keyring = new Keyring();
245
+ const first = keyring.addFromUri(ALICE_SURI);
246
+ const second = keyring.addFromUri(`${DEV_PHRASE}//Bob`);
247
+ expect(keyring.publicKeys).toEqual([first.publicKey, second.publicKey]);
248
+ });
249
+
250
+ it("removes a pair", () => {
251
+ const keyring = new Keyring();
252
+ const pair = keyring.addFromUri(ALICE_SURI);
253
+ keyring.removePair(pair.address);
254
+ expect(keyring.pairs).toEqual([]);
255
+ expect(() => keyring.getPair(pair.address)).toThrow(
256
+ "Unable to retrieve keypair",
257
+ );
258
+ });
259
+
260
+ it("keeps addPair and the pairs getter in agreement", () => {
261
+ const keyring = new Keyring();
262
+ const pair = keyring.createFromUri(ALICE_SURI);
263
+ expect(keyring.addPair(pair)).toBe(pair);
264
+ expect(keyring.pairs).toEqual(keyring.getPairs());
265
+ });
266
+ });
267
+
268
+ describe("Keyring.createFromJson", SLOW, () => {
269
+ const json = (
270
+ overrides: Partial<KeyringPair$Json> = {},
271
+ ): KeyringPair$Json => ({
272
+ address: encodeAddress(new Uint8Array(32).fill(5), 42),
273
+ encoded: "0x00",
274
+ encoding: {
275
+ content: ["pkcs8", "sr25519"],
276
+ type: ["none"],
277
+ version: "3",
278
+ },
279
+ meta: { name: "from-json" },
280
+ ...overrides,
281
+ });
282
+
283
+ it("round trips address, meta and type", () => {
284
+ const pair = new Keyring().createFromJson(json());
285
+ expect(pair.meta.name).toBe("from-json");
286
+ expect(pair.type).toBe("sr25519");
287
+ expect(u8aToHex(pair.publicKey)).toBe(u8aToHex(new Uint8Array(32).fill(5)));
288
+ });
289
+
290
+ it("accepts a hex address as the public key directly", () => {
291
+ const pair = new Keyring().createFromJson(json({ address: ALICE_PUBLIC }));
292
+ expect(u8aToHex(pair.publicKey)).toBe(ALICE_PUBLIC);
293
+ });
294
+
295
+ it("rejects a v3 file that is not pkcs8", () => {
296
+ expect(() =>
297
+ new Keyring().createFromJson(
298
+ json({
299
+ encoding: {
300
+ content: ["none", "sr25519"],
301
+ type: ["none"],
302
+ version: "3",
303
+ },
304
+ }),
305
+ ),
306
+ ).toThrow("Unable to decode non-pkcs8 type");
307
+ });
308
+
309
+ it("falls back to the keyring type for a v0 file", () => {
310
+ // v0 predates the content array, so the crypto type is implied.
311
+ const pair = new Keyring().createFromJson(
312
+ json({
313
+ encoding: {
314
+ content: ["pkcs8", "ed25519"],
315
+ type: ["none"],
316
+ version: "0",
317
+ },
318
+ }),
319
+ );
320
+ expect(pair.type).toBe("sr25519");
321
+ });
322
+
323
+ it("rejects a crypto type this keyring cannot handle", () => {
324
+ expect(() =>
325
+ new Keyring().createFromJson(
326
+ json({
327
+ encoding: {
328
+ content: ["pkcs8", "ed25519"],
329
+ type: ["none"],
330
+ version: "3",
331
+ },
332
+ }),
333
+ ),
334
+ ).toThrow("Unknown crypto type ed25519");
335
+ });
336
+
337
+ it("normalises a single encoding type into a list", () => {
338
+ expect(() =>
339
+ new Keyring().createFromJson(
340
+ json({
341
+ encoding: {
342
+ content: ["pkcs8", "sr25519"],
343
+ type: "none",
344
+ version: "3",
345
+ },
346
+ }),
347
+ ),
348
+ ).not.toThrow();
349
+ });
350
+
351
+ it("stores the pair when added rather than created", () => {
352
+ const keyring = new Keyring();
353
+ const pair = keyring.addFromJson(json());
354
+ expect(keyring.getPairs()).toEqual([pair]);
355
+ });
356
+ });
357
+
358
+ describe("Keyring.toJson", SLOW, () => {
359
+ it("delegates to the stored pair", () => {
360
+ const keyring = new Keyring();
361
+ const pair: KeyringPair = keyring.addFromUri(ALICE_SURI);
362
+ const asJson = keyring.toJson(pair.address);
363
+
364
+ expect(asJson.address).toBe(pair.address);
365
+ expect(asJson.encoding.content).toContain("sr25519");
366
+ });
367
+
368
+ it("throws for an address it does not hold", () => {
369
+ expect(() =>
370
+ new Keyring().toJson(encodeAddress(new Uint8Array(32).fill(6), 42)),
371
+ ).toThrow("Unable to retrieve keypair");
372
+ });
373
+ });
@@ -0,0 +1,133 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ import type { KeyringPair } from "@prosopo/types";
16
+ import { decodeAddress, encodeAddress } from "@prosopo/util-crypto";
17
+ import { describe, expect, it } from "vitest";
18
+ import { nobody } from "../pair/nobody.js";
19
+ import { Pairs } from "./pairs.js";
20
+
21
+ /**
22
+ * A pair is stored under `decodeAddress(pair.address).toString()`, which is the
23
+ * comma-joined bytes of the public key. Only `address` and `publicKey` are read
24
+ * by `Pairs`, so the inert `nobody` pair supplies the rest of the surface.
25
+ */
26
+ const stubPair = (publicKey: Uint8Array): KeyringPair => ({
27
+ ...nobody(),
28
+ address: encodeAddress(publicKey, 42),
29
+ addressRaw: publicKey,
30
+ publicKey,
31
+ });
32
+
33
+ const keyOf = (byte: number): Uint8Array => new Uint8Array(32).fill(byte);
34
+
35
+ describe("Pairs", () => {
36
+ it("returns the pair it was given, so callers can chain off add", () => {
37
+ const pairs = new Pairs();
38
+ const pair = stubPair(keyOf(1));
39
+ expect(pairs.add(pair)).toBe(pair);
40
+ });
41
+
42
+ it("starts empty", () => {
43
+ expect(new Pairs().all()).toEqual([]);
44
+ });
45
+
46
+ it("retrieves a pair by SS58 address, public key or raw bytes", () => {
47
+ const pairs = new Pairs();
48
+ const publicKey = keyOf(2);
49
+ const pair = pairs.add(stubPair(publicKey));
50
+
51
+ expect(pairs.get(pair.address)).toBe(pair);
52
+ expect(pairs.get(publicKey)).toBe(pair);
53
+ // A different SS58 prefix decodes to the same public key, so it must
54
+ // find the same pair — the map is keyed on bytes, not on the string.
55
+ expect(pairs.get(encodeAddress(publicKey, 2))).toBe(pair);
56
+ });
57
+
58
+ it("keeps only the latest pair for a public key", () => {
59
+ const pairs = new Pairs();
60
+ const publicKey = keyOf(3);
61
+ pairs.add(stubPair(publicKey));
62
+ const second = stubPair(publicKey);
63
+ pairs.add(second);
64
+
65
+ expect(pairs.all()).toHaveLength(1);
66
+ expect(pairs.get(publicKey)).toBe(second);
67
+ });
68
+
69
+ it("keeps distinct public keys apart", () => {
70
+ const pairs = new Pairs();
71
+ const first = pairs.add(stubPair(keyOf(4)));
72
+ const second = pairs.add(stubPair(keyOf(5)));
73
+
74
+ expect(pairs.all()).toEqual([first, second]);
75
+ expect(pairs.get(first.address)).toBe(first);
76
+ expect(pairs.get(second.address)).toBe(second);
77
+ });
78
+
79
+ it("names the address it could not find, formatting bytes as hex", () => {
80
+ const pairs = new Pairs();
81
+ // A caller debugging a missing key needs to see which key was asked
82
+ // for; a bare "not found" would be useless.
83
+ expect(() => pairs.get(keyOf(6))).toThrow(
84
+ `Unable to retrieve keypair '0x${"06".repeat(32)}'`,
85
+ );
86
+ });
87
+
88
+ it("names the address verbatim when it was given as SS58", () => {
89
+ const address = encodeAddress(keyOf(7), 42);
90
+ expect(() => new Pairs().get(address)).toThrow(
91
+ `Unable to retrieve keypair '${address}'`,
92
+ );
93
+ });
94
+
95
+ it("rejects an address that is not decodable at all", () => {
96
+ expect(() => new Pairs().get("not-an-address")).toThrow();
97
+ });
98
+
99
+ it("removes a pair, after which lookup throws again", () => {
100
+ const pairs = new Pairs();
101
+ const publicKey = keyOf(8);
102
+ pairs.add(stubPair(publicKey));
103
+ pairs.remove(publicKey);
104
+
105
+ expect(pairs.all()).toEqual([]);
106
+ expect(() => pairs.get(publicKey)).toThrow("Unable to retrieve keypair");
107
+ });
108
+
109
+ it("removes by any encoding of the same key", () => {
110
+ const pairs = new Pairs();
111
+ const publicKey = keyOf(9);
112
+ const pair = pairs.add(stubPair(publicKey));
113
+ pairs.remove(encodeAddress(publicKey, 2));
114
+ expect(pairs.all()).toEqual([]);
115
+ expect(pair.address).toBe(encodeAddress(publicKey, 42));
116
+ });
117
+
118
+ it("ignores removal of a key that was never added", () => {
119
+ const pairs = new Pairs();
120
+ const kept = pairs.add(stubPair(keyOf(10)));
121
+ expect(() => pairs.remove(keyOf(11))).not.toThrow();
122
+ expect(pairs.all()).toEqual([kept]);
123
+ });
124
+
125
+ it("decodes the address for the map key rather than trusting the string", () => {
126
+ // Guards the assumption the class is built on: two encodings of one
127
+ // key must decode to identical bytes.
128
+ const publicKey = keyOf(12);
129
+ expect(decodeAddress(encodeAddress(publicKey, 42)).toString()).toBe(
130
+ decodeAddress(encodeAddress(publicKey, 2)).toString(),
131
+ );
132
+ });
133
+ });
@@ -0,0 +1,146 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ import { u8aToHex } from "@polkadot/util";
16
+ import { describe, expect, it } from "vitest";
17
+ import { PAIRSSR25519, createTestKeyring } from "./testing.js";
18
+ import { createTestPairs } from "./testingPairs.js";
19
+
20
+ // Key derivation runs scrypt/pbkdf2 with production parameters and takes
21
+ // seconds per call, so these suites need more than the 10s default.
22
+ const SLOW = { timeout: 60000 };
23
+
24
+ describe("createTestKeyring", SLOW, () => {
25
+ it("adds a pair for every well-known dev account", () => {
26
+ expect(createTestKeyring().getPairs()).toHaveLength(PAIRSSR25519.length);
27
+ });
28
+
29
+ it("names each pair after its seed, lowercased with the path flattened", () => {
30
+ // `Alice//stash` becomes `alice_stash`, which is the name the rest of
31
+ // the stack looks pairs up by.
32
+ const names = createTestKeyring()
33
+ .getPairs()
34
+ .map((pair) => pair.meta.name);
35
+ expect(names).toEqual([
36
+ "alice",
37
+ "alice_stash",
38
+ "bob",
39
+ "bob_stash",
40
+ "charlie",
41
+ "dave",
42
+ "eve",
43
+ "ferdie",
44
+ ]);
45
+ });
46
+
47
+ it("marks every pair as a testing pair", () => {
48
+ for (const pair of createTestKeyring().getPairs()) {
49
+ expect(pair.meta.isTesting).toBe(true);
50
+ }
51
+ });
52
+
53
+ it("uses the hard-coded public keys, not derivation, by default", () => {
54
+ const keyring = createTestKeyring();
55
+ for (const { p } of PAIRSSR25519) {
56
+ expect(u8aToHex(keyring.getPair(p).publicKey)).toBe(p);
57
+ }
58
+ });
59
+
60
+ it("replaces lock with a no-op so the fixtures stay usable", () => {
61
+ // The pairs carry their secret in the clear; locking them would make
62
+ // every downstream test fail to sign.
63
+ const pair = createTestKeyring().getPairs()[0];
64
+ expect(pair).toBeDefined();
65
+ if (!pair) return;
66
+ expect(() => pair.lock()).not.toThrow();
67
+ expect(pair.isLocked).toBe(false);
68
+ });
69
+
70
+ it("produces signable pairs", () => {
71
+ const pair = createTestKeyring().getPair(PAIRSSR25519[0]?.p ?? "0x");
72
+ const message = new TextEncoder().encode("hello");
73
+ expect(pair.verify(message, pair.sign(message), pair.publicKey)).toBe(true);
74
+ });
75
+
76
+ it("honours a requested ss58 format", () => {
77
+ const alice = PAIRSSR25519[0]?.p ?? "0x";
78
+ expect(
79
+ createTestKeyring({ ss58Format: 42 }).getPair(alice).address,
80
+ ).not.toBe(createTestKeyring({ ss58Format: 2 }).getPair(alice).address);
81
+ });
82
+
83
+ it("derives different keys when asked to, because the seeds are bare names", () => {
84
+ // The `isDerived = false` branch feeds the seed straight to
85
+ // `addFromUri`, and the seeds are names like "Alice" rather than
86
+ // "//Alice". They are therefore padded raw phrases, not dev-phrase
87
+ // derivations, so the pairs are NOT the well-known dev accounts.
88
+ // Anything relying on Alice's address must use the default mode.
89
+ const derived = createTestKeyring({}, false);
90
+ expect(derived.getPairs()).toHaveLength(PAIRSSR25519.length);
91
+ for (const { p } of PAIRSSR25519) {
92
+ expect(() => derived.getPair(p)).toThrow("Unable to retrieve keypair");
93
+ }
94
+ });
95
+
96
+ it("rejects a keyring type it cannot support", () => {
97
+ expect(() => createTestKeyring({ type: "ed25519" })).toThrow(
98
+ "Expected a keyring type of either 'sr25519'",
99
+ );
100
+ });
101
+ });
102
+
103
+ describe("createTestPairs", SLOW, () => {
104
+ it("exposes every named pair plus nobody", () => {
105
+ const pairs = createTestPairs();
106
+ for (const name of [
107
+ "nobody",
108
+ "alice",
109
+ "alice_stash",
110
+ "bob",
111
+ "bob_stash",
112
+ "charlie",
113
+ "dave",
114
+ "eve",
115
+ "ferdie",
116
+ ]) {
117
+ expect(pairs[name], name).toBeDefined();
118
+ }
119
+ });
120
+
121
+ it("maps alice to the well-known Alice public key", () => {
122
+ expect(
123
+ u8aToHex(createTestPairs().alice?.publicKey ?? new Uint8Array()),
124
+ ).toBe(PAIRSSR25519[0]?.p);
125
+ });
126
+
127
+ it("includes nobody, which cannot verify anything", () => {
128
+ const nobodyPair = createTestPairs().nobody;
129
+ expect(nobodyPair).toBeDefined();
130
+ expect(nobodyPair?.isLocked).toBe(true);
131
+ expect(
132
+ nobodyPair?.verify(
133
+ new Uint8Array(1),
134
+ new Uint8Array(64),
135
+ nobodyPair.publicKey,
136
+ ),
137
+ ).toBe(false);
138
+ });
139
+
140
+ it("returns independent maps so one test cannot poison another", () => {
141
+ const first = createTestPairs();
142
+ const second = createTestPairs();
143
+ expect(first).not.toBe(second);
144
+ expect(first.alice).not.toBe(second.alice);
145
+ });
146
+ });