@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,142 @@
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$Json } from "@prosopo/types";
17
+ import { encodeAddress, sr25519FromSeed } from "@prosopo/util-crypto";
18
+ import { describe, expect, it } from "vitest";
19
+ import { DEV_PHRASE, Keyring } from "../keyring/index.js";
20
+ import { getPair } from "./getPair.js";
21
+
22
+ const SEED_32 = new Uint8Array(32).fill(7);
23
+ const SEED_HEX = u8aToHex(SEED_32);
24
+ const ALICE_PUBLIC =
25
+ "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d";
26
+
27
+ const pairJson = (
28
+ overrides: Partial<KeyringPair$Json> = {},
29
+ ): KeyringPair$Json => ({
30
+ address: encodeAddress(new Uint8Array(32).fill(8), 42),
31
+ encoded: "0x00",
32
+ encoding: { content: ["pkcs8", "sr25519"], type: ["none"], version: "3" },
33
+ meta: {},
34
+ ...overrides,
35
+ });
36
+
37
+ // Key derivation runs scrypt/pbkdf2 with production parameters and takes
38
+ // seconds per call, so these suites need more than the 10s default.
39
+ const SLOW = { timeout: 60000 };
40
+
41
+ describe("getPair secret handling", SLOW, () => {
42
+ it("derives from a mnemonic", () => {
43
+ expect(getPair(DEV_PHRASE).address).toBe(
44
+ new Keyring({ ss58Format: 42 }).addFromMnemonic(DEV_PHRASE).address,
45
+ );
46
+ });
47
+
48
+ it("derives from a hex seed", () => {
49
+ expect(u8aToHex(getPair(SEED_HEX).publicKey)).toBe(
50
+ u8aToHex(sr25519FromSeed(SEED_32).publicKey),
51
+ );
52
+ });
53
+
54
+ it("derives from a suri with a hard derivation path", () => {
55
+ expect(u8aToHex(getPair(`${DEV_PHRASE}//Alice`).publicKey)).toBe(
56
+ ALICE_PUBLIC,
57
+ );
58
+ });
59
+
60
+ it("prefers the mnemonic branch over the suri branch", () => {
61
+ // A bare valid mnemonic contains no "//", but the ordering matters if
62
+ // one is ever added: mnemonic validation must win so the derivation
63
+ // path is honoured rather than the whole string being padded.
64
+ const suri = `${DEV_PHRASE}//1`;
65
+ expect(getPair(suri).address).not.toBe(getPair(DEV_PHRASE).address);
66
+ });
67
+
68
+ it("parses a JSON string, taking the crypto type from its encoding", () => {
69
+ const pair = getPair(JSON.stringify(pairJson()));
70
+ expect(pair.type).toBe("sr25519");
71
+ expect(u8aToHex(pair.publicKey)).toBe(u8aToHex(new Uint8Array(32).fill(8)));
72
+ });
73
+
74
+ it("accepts a JSON object directly", () => {
75
+ expect(getPair(pairJson()).type).toBe("sr25519");
76
+ });
77
+
78
+ it("reports a missing secret when the string is neither key nor JSON", () => {
79
+ // The unparseable case funnels into the same error as a genuinely
80
+ // absent secret, so an operator sees one actionable message.
81
+ expect(() => getPair("this is not a secret")).toThrow();
82
+ });
83
+
84
+ it("reports a missing secret for JSON without an encoding block", () => {
85
+ expect(() => getPair('{"address":"x"}')).toThrow();
86
+ });
87
+
88
+ it("throws when neither a secret nor an account is given", () => {
89
+ expect(() => getPair()).toThrow();
90
+ });
91
+
92
+ it("throws when the secret is an empty string and no account is given", () => {
93
+ expect(() => getPair("")).toThrow();
94
+ });
95
+ });
96
+
97
+ describe("getPair account handling", SLOW, () => {
98
+ it("builds a watch-only pair from an address when no secret is given", () => {
99
+ const address = getPair(DEV_PHRASE).address;
100
+ const watchOnly = getPair(undefined, address);
101
+ expect(watchOnly.address).toBe(address);
102
+ expect(watchOnly.isLocked).toBe(true);
103
+ });
104
+
105
+ it("accepts a raw public key as the account", () => {
106
+ const pair = getPair(DEV_PHRASE);
107
+ expect(getPair(undefined, pair.publicKey).address).toBe(pair.address);
108
+ });
109
+
110
+ it("ignores the account once a secret is supplied", () => {
111
+ const other = getPair(`${DEV_PHRASE}//other`).address;
112
+ expect(getPair(DEV_PHRASE, other).address).toBe(
113
+ getPair(DEV_PHRASE).address,
114
+ );
115
+ });
116
+ });
117
+
118
+ describe("getPair defaults", SLOW, () => {
119
+ it("defaults to sr25519 and ss58 format 42", () => {
120
+ const pair = getPair(DEV_PHRASE);
121
+ expect(pair.type).toBe("sr25519");
122
+ expect(pair.address).toBe(encodeAddress(pair.publicKey, 42));
123
+ });
124
+
125
+ it("honours an explicit ss58 format", () => {
126
+ const pair = getPair(DEV_PHRASE, undefined, "sr25519", 2);
127
+ expect(pair.address).toBe(encodeAddress(pair.publicKey, 2));
128
+ });
129
+
130
+ it("rejects a pair type the keyring does not support", () => {
131
+ expect(() => getPair(DEV_PHRASE, undefined, "ed25519")).toThrow(
132
+ "Expected a keyring type of either 'sr25519'",
133
+ );
134
+ });
135
+
136
+ it("treats ss58 format 0 as unset, because it is falsy", () => {
137
+ // Documents a live quirk of the `||` defaulting: Polkadot's own
138
+ // format 0 cannot be requested and silently becomes 42.
139
+ const pair = getPair(DEV_PHRASE, undefined, "sr25519", 0);
140
+ expect(pair.address).toBe(encodeAddress(pair.publicKey, 42));
141
+ });
142
+ });
@@ -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
+ });