@prosopo/keyring 2.9.61 → 2.9.62

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 +5 -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 +1 -1
  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,103 @@
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 {
17
+ mnemonicToMiniSecret,
18
+ mnemonicValidate,
19
+ sr25519FromSeed,
20
+ } from "@prosopo/util-crypto";
21
+ import { describe, expect, it } from "vitest";
22
+ import { Keyring } from "../keyring/index.js";
23
+ import { generateMiniSecret, generateMnemonic } from "./mnemonic.js";
24
+
25
+ // Key derivation runs scrypt/pbkdf2 with production parameters and takes
26
+ // seconds per call, so these suites need more than the 10s default.
27
+ const SLOW = { timeout: 60000 };
28
+
29
+ describe("generateMnemonic", SLOW, () => {
30
+ it("returns a valid twelve word mnemonic and its address", async () => {
31
+ const [mnemonic, address] = await generateMnemonic();
32
+ expect(mnemonic.split(" ")).toHaveLength(12);
33
+ expect(mnemonicValidate(mnemonic)).toBe(true);
34
+ expect(address).toBe(new Keyring().addFromMnemonic(mnemonic).address);
35
+ });
36
+
37
+ it("generates a fresh mnemonic each call", async () => {
38
+ // A repeated mnemonic would mean every generated account shares a key.
39
+ const [first] = await generateMnemonic();
40
+ const [second] = await generateMnemonic();
41
+ expect(second).not.toBe(first);
42
+ });
43
+
44
+ it("adds the account to a keyring it is given", async () => {
45
+ const keyring = new Keyring();
46
+ const [, address] = await generateMnemonic(keyring);
47
+ expect(keyring.getPairs()).toHaveLength(1);
48
+ expect(keyring.getPair(address).address).toBe(address);
49
+ });
50
+
51
+ it("uses a fresh keyring when none is passed, leaving no shared state", async () => {
52
+ const keyring = new Keyring();
53
+ await generateMnemonic();
54
+ expect(keyring.getPairs()).toEqual([]);
55
+ });
56
+
57
+ it("honours the requested pair type when it creates the keyring", async () => {
58
+ const [, address] = await generateMnemonic(undefined, "sr25519");
59
+ expect(typeof address).toBe("string");
60
+ await expect(generateMnemonic(undefined, "ed25519")).rejects.toThrow(
61
+ "Expected a keyring type of either 'sr25519'",
62
+ );
63
+ });
64
+
65
+ it("ignores the pair type when a keyring is supplied", async () => {
66
+ // The keyring already fixes the curve; the argument is only used to
67
+ // construct one, so an incompatible value must not throw here.
68
+ const keyring = new Keyring({ type: "sr25519" });
69
+ await expect(generateMnemonic(keyring, "ed25519")).resolves.toBeDefined();
70
+ });
71
+ });
72
+
73
+ describe("generateMiniSecret", SLOW, () => {
74
+ it("returns the 32 byte mini secret for the generated mnemonic", async () => {
75
+ const keyring = new Keyring();
76
+ const [secret, address] = await generateMiniSecret(keyring);
77
+
78
+ expect(secret).toHaveLength(32);
79
+ expect(u8aToHex(sr25519FromSeed(secret).publicKey)).toBe(
80
+ u8aToHex(keyring.getPair(address).publicKey),
81
+ );
82
+ });
83
+
84
+ it("derives the secret from the same mnemonic it registered", async () => {
85
+ const keyring = new Keyring();
86
+ const [secret] = await generateMiniSecret(keyring);
87
+ const registered = keyring.getPairs()[0];
88
+ expect(registered).toBeDefined();
89
+ expect(u8aToHex(secret)).not.toBe(u8aToHex(new Uint8Array(32)));
90
+ });
91
+
92
+ it("agrees with mnemonicToMiniSecret for a known phrase", async () => {
93
+ const phrase =
94
+ "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
95
+ expect(u8aToHex(mnemonicToMiniSecret(phrase))).toHaveLength(66);
96
+ });
97
+
98
+ it("generates a fresh secret each call", async () => {
99
+ const [first] = await generateMiniSecret();
100
+ const [second] = await generateMiniSecret();
101
+ expect(u8aToHex(second)).not.toBe(u8aToHex(first));
102
+ });
103
+ });
@@ -0,0 +1,100 @@
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 { CaptchaType } from "@prosopo/types";
16
+ import { describe, expect, it } from "vitest";
17
+ import { DEV_PHRASE } from "../keyring/index.js";
18
+ import { getPair } from "./getPair.js";
19
+ import { getDefaultProviders, getDefaultSiteKeys } from "./testAccounts.js";
20
+
21
+ // Key derivation runs scrypt/pbkdf2 with production parameters and takes
22
+ // seconds per call, so these suites need more than the 10s default.
23
+ const SLOW = { timeout: 60000 };
24
+
25
+ describe("getDefaultSiteKeys", SLOW, () => {
26
+ it("provides one site per captcha type, in a stable order", () => {
27
+ expect(
28
+ getDefaultSiteKeys().map((site) => site.settings.captchaType),
29
+ ).toEqual([
30
+ CaptchaType.image,
31
+ CaptchaType.pow,
32
+ CaptchaType.frictionless,
33
+ CaptchaType.puzzle,
34
+ ]);
35
+ });
36
+
37
+ it("derives each site key from the dev phrase and its captcha type", () => {
38
+ // The seeded dev site keys are checked into fixtures and referenced by
39
+ // the demos, so the derivation must not drift.
40
+ for (const site of getDefaultSiteKeys()) {
41
+ expect(site.secret).toBe(`${DEV_PHRASE}//${site.settings.captchaType}`);
42
+ expect(site.address).toBe(getPair(site.secret).address);
43
+ expect(site.pair?.address).toBe(site.address);
44
+ }
45
+ });
46
+
47
+ it("gives every site a distinct address", () => {
48
+ const addresses = getDefaultSiteKeys().map((site) => site.address);
49
+ expect(new Set(addresses).size).toBe(addresses.length);
50
+ });
51
+
52
+ it("writes the settings explicitly rather than leaning on schema defaults", () => {
53
+ for (const site of getDefaultSiteKeys()) {
54
+ expect(site.settings.domains).toEqual(["localhost"]);
55
+ expect(site.settings.imageMaxRounds).toBe(2);
56
+ expect(site.settings.frictionlessThreshold).toBe(0.8);
57
+ }
58
+ });
59
+
60
+ it("returns a fresh array each call, so callers cannot corrupt the seed", () => {
61
+ const first = getDefaultSiteKeys();
62
+ const second = getDefaultSiteKeys();
63
+ expect(first).not.toBe(second);
64
+ first.pop();
65
+ expect(second).toHaveLength(4);
66
+ });
67
+ });
68
+
69
+ describe("getDefaultProviders", SLOW, () => {
70
+ it("provides a single local provider with a matching pair and address", () => {
71
+ const providers = getDefaultProviders();
72
+ expect(providers).toHaveLength(1);
73
+ const provider = providers[0];
74
+ expect(provider).toBeDefined();
75
+ if (!provider) return;
76
+ expect(provider.url).toBe("https://localhost:9229");
77
+ expect(provider.address).toBe(provider.pair?.address);
78
+ });
79
+
80
+ it("points at the checked-in dev dataset", () => {
81
+ const provider = getDefaultProviders()[0];
82
+ expect(provider?.datasetFile).toBe("./dev/data/captchas.json");
83
+ expect(provider?.captchaDatasetId).toMatch(/^0x[0-9a-f]{64}$/);
84
+ });
85
+
86
+ it("is deterministic across calls", () => {
87
+ expect(getDefaultProviders()[0]?.address).toBe(
88
+ getDefaultProviders()[0]?.address,
89
+ );
90
+ });
91
+
92
+ it("does not reuse a site key as the provider key", () => {
93
+ const siteAddresses = new Set(
94
+ getDefaultSiteKeys().map((site) => site.address),
95
+ );
96
+ expect(siteAddresses.has(getDefaultProviders()[0]?.address ?? "")).toBe(
97
+ false,
98
+ );
99
+ });
100
+ });
@@ -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
+ });