@pezkuwi/util-crypto 14.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/package.json +45 -0
- package/src/address/addressToEvm.spec.ts +16 -0
- package/src/address/addressToEvm.ts +12 -0
- package/src/address/check.spec.ts +44 -0
- package/src/address/check.ts +34 -0
- package/src/address/checksum.spec.ts +45 -0
- package/src/address/checksum.ts +25 -0
- package/src/address/decode.spec.ts +138 -0
- package/src/address/decode.ts +41 -0
- package/src/address/defaults.ts +12 -0
- package/src/address/derive.spec.ts +26 -0
- package/src/address/derive.ts +36 -0
- package/src/address/encode.spec.ts +177 -0
- package/src/address/encode.ts +43 -0
- package/src/address/encodeDerived.spec.ts +14 -0
- package/src/address/encodeDerived.ts +19 -0
- package/src/address/encodeMulti.spec.ts +18 -0
- package/src/address/encodeMulti.ts +18 -0
- package/src/address/eq.spec.ts +45 -0
- package/src/address/eq.ts +24 -0
- package/src/address/evmToAddress.spec.ts +20 -0
- package/src/address/evmToAddress.ts +24 -0
- package/src/address/index.ts +21 -0
- package/src/address/is.spec.ts +113 -0
- package/src/address/is.ts +14 -0
- package/src/address/keyDerived.spec.ts +24 -0
- package/src/address/keyDerived.ts +22 -0
- package/src/address/keyMulti.spec.ts +20 -0
- package/src/address/keyMulti.ts +23 -0
- package/src/address/setSS58Format.spec.ts +21 -0
- package/src/address/setSS58Format.ts +20 -0
- package/src/address/sort.spec.ts +22 -0
- package/src/address/sort.ts +17 -0
- package/src/address/sshash.ts +12 -0
- package/src/address/types.ts +6 -0
- package/src/address/util.ts +8 -0
- package/src/address/validate.spec.ts +113 -0
- package/src/address/validate.ts +10 -0
- package/src/base32/bs32.ts +53 -0
- package/src/base32/decode.spec.ts +34 -0
- package/src/base32/encode.spec.ts +30 -0
- package/src/base32/helpers.ts +93 -0
- package/src/base32/index.ts +8 -0
- package/src/base32/is.spec.ts +32 -0
- package/src/base32/validate.spec.ts +44 -0
- package/src/base58/bs58.ts +43 -0
- package/src/base58/decode.spec.ts +31 -0
- package/src/base58/encode.spec.ts +26 -0
- package/src/base58/index.ts +8 -0
- package/src/base58/validate.spec.ts +20 -0
- package/src/base64/bs64.ts +43 -0
- package/src/base64/decode.spec.ts +42 -0
- package/src/base64/encode.spec.ts +14 -0
- package/src/base64/index.ts +10 -0
- package/src/base64/pad.spec.ts +14 -0
- package/src/base64/pad.ts +10 -0
- package/src/base64/trim.spec.ts +14 -0
- package/src/base64/trim.ts +14 -0
- package/src/base64/validate.spec.ts +32 -0
- package/src/blake2/asHex.spec.ts +57 -0
- package/src/blake2/asU8a.spec.ts +74 -0
- package/src/blake2/asU8a.ts +40 -0
- package/src/blake2/index.ts +8 -0
- package/src/bn.ts +15 -0
- package/src/bundle.ts +33 -0
- package/src/bundleInit.ts +11 -0
- package/src/crypto.spec.ts +18 -0
- package/src/crypto.ts +18 -0
- package/src/ed25519/deriveHard.ts +18 -0
- package/src/ed25519/index.ts +13 -0
- package/src/ed25519/pair/fromRandom.spec.ts +28 -0
- package/src/ed25519/pair/fromRandom.ts +25 -0
- package/src/ed25519/pair/fromSecret.spec.ts +33 -0
- package/src/ed25519/pair/fromSecret.ts +29 -0
- package/src/ed25519/pair/fromSeed.spec.ts +42 -0
- package/src/ed25519/pair/fromSeed.ts +41 -0
- package/src/ed25519/pair/fromString.spec.ts +17 -0
- package/src/ed25519/pair/fromString.ts +31 -0
- package/src/ed25519/sign.spec.ts +40 -0
- package/src/ed25519/sign.ts +38 -0
- package/src/ed25519/verify.spec.ts +84 -0
- package/src/ed25519/verify.ts +41 -0
- package/src/ethereum/encode.spec.ts +59 -0
- package/src/ethereum/encode.ts +39 -0
- package/src/ethereum/index.ts +6 -0
- package/src/ethereum/isAddress.spec.ts +34 -0
- package/src/ethereum/isAddress.ts +16 -0
- package/src/ethereum/isChecksum.ts +27 -0
- package/src/ethereum/isCheksum.spec.ts +30 -0
- package/src/hd/ethereum/index.spec.ts +54 -0
- package/src/hd/ethereum/index.ts +69 -0
- package/src/hd/index.ts +6 -0
- package/src/hd/ledger/derivePrivate.ts +34 -0
- package/src/hd/ledger/index.spec.ts +64 -0
- package/src/hd/ledger/index.ts +42 -0
- package/src/hd/ledger/master.spec.ts +19 -0
- package/src/hd/ledger/master.ts +26 -0
- package/src/hd/validatePath.spec.ts +30 -0
- package/src/hd/validatePath.ts +24 -0
- package/src/helpers.ts +38 -0
- package/src/hmac/index.ts +4 -0
- package/src/hmac/shaAsU8a.spec.ts +45 -0
- package/src/hmac/shaAsU8a.ts +48 -0
- package/src/index.ts +6 -0
- package/src/json/constants.ts +11 -0
- package/src/json/decrypt.ts +25 -0
- package/src/json/decryptData.ts +45 -0
- package/src/json/encrypt.ts +25 -0
- package/src/json/encryptFormat.ts +20 -0
- package/src/json/index.ts +7 -0
- package/src/json/types.ts +22 -0
- package/src/keccak/asHex.spec.ts +30 -0
- package/src/keccak/asU8a.spec.ts +56 -0
- package/src/keccak/asU8a.ts +45 -0
- package/src/keccak/index.ts +8 -0
- package/src/key/DeriveJunction.ts +79 -0
- package/src/key/extractPath.spec.ts +51 -0
- package/src/key/extractPath.ts +37 -0
- package/src/key/extractSuri.spec.ts +147 -0
- package/src/key/extractSuri.ts +40 -0
- package/src/key/fromPath.ts +28 -0
- package/src/key/hdkdDerive.ts +17 -0
- package/src/key/hdkdEcdsa.ts +8 -0
- package/src/key/hdkdEd25519.ts +7 -0
- package/src/key/hdkdSr25519.ts +14 -0
- package/src/key/index.ts +12 -0
- package/src/mnemonic/bip39.spec.ts +80 -0
- package/src/mnemonic/bip39.ts +127 -0
- package/src/mnemonic/generate.spec.ts +58 -0
- package/src/mnemonic/generate.ts +25 -0
- package/src/mnemonic/index.ts +11 -0
- package/src/mnemonic/toEntropy.spec.ts +36 -0
- package/src/mnemonic/toEntropy.ts +13 -0
- package/src/mnemonic/toLegacySeed.spec.ts +52 -0
- package/src/mnemonic/toLegacySeed.ts +39 -0
- package/src/mnemonic/toMiniSecret.spec.ts +67 -0
- package/src/mnemonic/toMiniSecret.ts +23 -0
- package/src/mnemonic/toMiniSecretCmp.spec.ts +64 -0
- package/src/mnemonic/validate.spec.ts +39 -0
- package/src/mnemonic/validate.ts +26 -0
- package/src/mnemonic/wordlists/en.ts +7 -0
- package/src/mnemonic/wordlists/es.ts +7 -0
- package/src/mnemonic/wordlists/fr.ts +7 -0
- package/src/mnemonic/wordlists/index.ts +11 -0
- package/src/mnemonic/wordlists/it.ts +7 -0
- package/src/mnemonic/wordlists/jp.ts +7 -0
- package/src/mnemonic/wordlists/ko.ts +7 -0
- package/src/mnemonic/wordlists/zh-s.ts +7 -0
- package/src/mnemonic/wordlists/zh-t.ts +7 -0
- package/src/mod.ts +4 -0
- package/src/nacl/decrypt.spec.ts +26 -0
- package/src/nacl/decrypt.ts +22 -0
- package/src/nacl/encrypt.spec.ts +20 -0
- package/src/nacl/encrypt.ts +31 -0
- package/src/nacl/index.ts +8 -0
- package/src/nacl/tweetnacl-secretbox-data.spec.ts +4629 -0
- package/src/nacl/tweetnacl-secretbox.spec.ts +161 -0
- package/src/nacl/tweetnacl.ts +1159 -0
- package/src/networks.ts +5 -0
- package/src/packageDetect.ts +14 -0
- package/src/packageInfo.ts +6 -0
- package/src/pbkdf2/encode.spec.ts +54 -0
- package/src/pbkdf2/encode.ts +29 -0
- package/src/pbkdf2/index.ts +4 -0
- package/src/random/asHex.spec.ts +38 -0
- package/src/random/asNumber.spec.ts +16 -0
- package/src/random/asNumber.ts +28 -0
- package/src/random/asU8a.spec.ts +36 -0
- package/src/random/asU8a.ts +30 -0
- package/src/random/index.ts +9 -0
- package/src/scrypt/defaults.ts +19 -0
- package/src/scrypt/encode.spec.ts +43 -0
- package/src/scrypt/encode.ts +30 -0
- package/src/scrypt/fromU8a.ts +44 -0
- package/src/scrypt/index.ts +6 -0
- package/src/scrypt/toU8a.ts +17 -0
- package/src/scrypt/types.ts +9 -0
- package/src/secp256k1/compress.spec.ts +47 -0
- package/src/secp256k1/compress.ts +21 -0
- package/src/secp256k1/deriveHard.ts +17 -0
- package/src/secp256k1/expand.spec.ts +47 -0
- package/src/secp256k1/expand.ts +30 -0
- package/src/secp256k1/hasher.spec.ts +24 -0
- package/src/secp256k1/hasher.ts +13 -0
- package/src/secp256k1/index.ts +10 -0
- package/src/secp256k1/pair/fromSeed.spec.ts +75 -0
- package/src/secp256k1/pair/fromSeed.ts +42 -0
- package/src/secp256k1/recover.spec.ts +35 -0
- package/src/secp256k1/recover.ts +36 -0
- package/src/secp256k1/sign.spec.ts +39 -0
- package/src/secp256k1/sign.ts +37 -0
- package/src/secp256k1/signVerify.spec.ts +94 -0
- package/src/secp256k1/tweakAdd.spec.ts +35 -0
- package/src/secp256k1/tweakAdd.ts +65 -0
- package/src/secp256k1/types.ts +4 -0
- package/src/secp256k1/verify.spec.ts +81 -0
- package/src/secp256k1/verify.ts +32 -0
- package/src/sha/asU8a.ts +30 -0
- package/src/sha/asU8a256.spec.ts +55 -0
- package/src/sha/asU8a512.spec.ts +33 -0
- package/src/sha/index.ts +8 -0
- package/src/signature/index.ts +8 -0
- package/src/signature/verify.spec.ts +230 -0
- package/src/signature/verify.ts +114 -0
- package/src/sr25519/agreement.spec.ts +31 -0
- package/src/sr25519/agreement.ts +23 -0
- package/src/sr25519/derive.ts +21 -0
- package/src/sr25519/deriveHard.ts +9 -0
- package/src/sr25519/derivePublic.ts +18 -0
- package/src/sr25519/deriveSoft.ts +9 -0
- package/src/sr25519/index.ts +12 -0
- package/src/sr25519/pair/fromSeed.spec.ts +35 -0
- package/src/sr25519/pair/fromSeed.ts +28 -0
- package/src/sr25519/pair/fromU8a.ts +23 -0
- package/src/sr25519/pair/testing.spec.ts +161 -0
- package/src/sr25519/pair/toU8a.ts +10 -0
- package/src/sr25519/sign.spec.ts +28 -0
- package/src/sr25519/sign.ts +22 -0
- package/src/sr25519/verify.spec.ts +42 -0
- package/src/sr25519/verify.ts +23 -0
- package/src/sr25519/vrfSign.ts +24 -0
- package/src/sr25519/vrfSignVerify.spec.ts +73 -0
- package/src/sr25519/vrfVerify.ts +25 -0
- package/src/test/index.ts +8 -0
- package/src/test/performance.ts +17 -0
- package/src/types.ts +33 -0
- package/src/xxhash/asHex.spec.ts +36 -0
- package/src/xxhash/asU8a.spec.ts +48 -0
- package/src/xxhash/asU8a.ts +45 -0
- package/src/xxhash/index.ts +8 -0
- package/src/xxhash/xxhash64.ts +155 -0
- package/tsconfig.build.json +18 -0
- package/tsconfig.spec.json +20 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { BN, bnToU8a, compactAddLength, hexToU8a, isBigInt, isBn, isHex, isNumber, isString, stringToU8a } from '@pezkuwi/util';
|
|
5
|
+
|
|
6
|
+
import { blake2AsU8a } from '../blake2/asU8a.js';
|
|
7
|
+
import { BN_LE_256_OPTS } from '../bn.js';
|
|
8
|
+
|
|
9
|
+
const RE_NUMBER = /^\d+$/;
|
|
10
|
+
|
|
11
|
+
const JUNCTION_ID_LEN = 32;
|
|
12
|
+
|
|
13
|
+
export class DeriveJunction {
|
|
14
|
+
readonly #chainCode: Uint8Array = new Uint8Array(32);
|
|
15
|
+
|
|
16
|
+
#isHard = false;
|
|
17
|
+
|
|
18
|
+
public static from (value: string): DeriveJunction {
|
|
19
|
+
const result = new DeriveJunction();
|
|
20
|
+
const [code, isHard] = value.startsWith('/')
|
|
21
|
+
? [value.substring(1), true]
|
|
22
|
+
: [value, false];
|
|
23
|
+
|
|
24
|
+
result.soft(
|
|
25
|
+
RE_NUMBER.test(code)
|
|
26
|
+
? new BN(code, 10)
|
|
27
|
+
: code
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
return isHard
|
|
31
|
+
? result.harden()
|
|
32
|
+
: result;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public get chainCode (): Uint8Array {
|
|
36
|
+
return this.#chainCode;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public get isHard (): boolean {
|
|
40
|
+
return this.#isHard;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public get isSoft (): boolean {
|
|
44
|
+
return !this.#isHard;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public hard (value: number | string | bigint | BN | Uint8Array): DeriveJunction {
|
|
48
|
+
return this.soft(value).harden();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public harden (): DeriveJunction {
|
|
52
|
+
this.#isHard = true;
|
|
53
|
+
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public soft (value: number | string | bigint | BN | Uint8Array): DeriveJunction {
|
|
58
|
+
if (isNumber(value) || isBn(value) || isBigInt(value)) {
|
|
59
|
+
return this.soft(bnToU8a(value, BN_LE_256_OPTS));
|
|
60
|
+
} else if (isHex(value)) {
|
|
61
|
+
return this.soft(hexToU8a(value));
|
|
62
|
+
} else if (isString(value)) {
|
|
63
|
+
return this.soft(compactAddLength(stringToU8a(value)));
|
|
64
|
+
} else if (value.length > JUNCTION_ID_LEN) {
|
|
65
|
+
return this.soft(blake2AsU8a(value));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.#chainCode.fill(0);
|
|
69
|
+
this.#chainCode.set(value, 0);
|
|
70
|
+
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public soften (): DeriveJunction {
|
|
75
|
+
this.#isHard = false;
|
|
76
|
+
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
|
5
|
+
|
|
6
|
+
import type { ExtractResult } from './extractPath.js';
|
|
7
|
+
|
|
8
|
+
import { keyExtractPath } from './extractPath.js';
|
|
9
|
+
|
|
10
|
+
describe('keyExtractPath', (): void => {
|
|
11
|
+
it('extracts properly from soft', (): void => {
|
|
12
|
+
const test = keyExtractPath('/1');
|
|
13
|
+
|
|
14
|
+
expect(test.parts).toEqual(['/1']);
|
|
15
|
+
expect(test.path.length).toEqual(1);
|
|
16
|
+
expect(test.path[0].isHard).toEqual(false);
|
|
17
|
+
expect(test.path[0].chainCode).toEqual(Uint8Array.from([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('extracts properly from hard', (): void => {
|
|
21
|
+
const test = keyExtractPath('//1');
|
|
22
|
+
|
|
23
|
+
expect(test.parts).toEqual(['//1']);
|
|
24
|
+
expect(test.path.length).toEqual(1);
|
|
25
|
+
expect(test.path[0].isHard).toEqual(true);
|
|
26
|
+
expect(test.path[0].chainCode).toEqual(Uint8Array.from([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('extracts properly from hard/soft', (): void => {
|
|
30
|
+
const test = keyExtractPath('//1/2');
|
|
31
|
+
|
|
32
|
+
expect(test.parts).toEqual(['//1', '/2']);
|
|
33
|
+
expect(test.path.length).toEqual(2);
|
|
34
|
+
expect(test.path[0].isHard).toEqual(true);
|
|
35
|
+
expect(test.path[0].chainCode).toEqual(Uint8Array.from([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
|
|
36
|
+
expect(test.path[1].isHard).toEqual(false);
|
|
37
|
+
expect(test.path[1].chainCode).toEqual(Uint8Array.from([2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('does not extract from invalid paths (1)', (): void => {
|
|
41
|
+
expect(
|
|
42
|
+
(): ExtractResult => keyExtractPath('1/2')
|
|
43
|
+
).toThrow(/does not match input/);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('does not extract from invalid paths (2)', (): void => {
|
|
47
|
+
expect(
|
|
48
|
+
(): ExtractResult => keyExtractPath('hello')
|
|
49
|
+
).toThrow(/does not match input/);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { DeriveJunction } from './DeriveJunction.js';
|
|
5
|
+
|
|
6
|
+
const RE_JUNCTION = /\/(\/?)([^/]+)/g;
|
|
7
|
+
|
|
8
|
+
export interface ExtractResult {
|
|
9
|
+
parts: string[] | null;
|
|
10
|
+
path: DeriveJunction[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @description Extract derivation junctions from the supplied path
|
|
15
|
+
*/
|
|
16
|
+
export function keyExtractPath (derivePath: string): ExtractResult {
|
|
17
|
+
const parts = derivePath.match(RE_JUNCTION);
|
|
18
|
+
const path: DeriveJunction[] = [];
|
|
19
|
+
let constructed = '';
|
|
20
|
+
|
|
21
|
+
if (parts) {
|
|
22
|
+
constructed = parts.join('');
|
|
23
|
+
|
|
24
|
+
for (const p of parts) {
|
|
25
|
+
path.push(DeriveJunction.from(p.substring(1)));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (constructed !== derivePath) {
|
|
30
|
+
throw new Error(`Re-constructed path "${constructed}" does not match input`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
parts,
|
|
35
|
+
path
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
|
5
|
+
|
|
6
|
+
import type { ExtractResult } from './extractSuri.js';
|
|
7
|
+
|
|
8
|
+
import { keyExtractSuri } from './extractSuri.js';
|
|
9
|
+
|
|
10
|
+
describe('keyExtractSuri', (): void => {
|
|
11
|
+
it('does not extract from invalid suri', (): void => {
|
|
12
|
+
expect(
|
|
13
|
+
(): ExtractResult => keyExtractSuri('//2')
|
|
14
|
+
).toThrow('Unable to match provided value to a secret URI');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('derives on "hello world"', (): void => {
|
|
18
|
+
const test = keyExtractSuri('hello world');
|
|
19
|
+
|
|
20
|
+
expect(test.phrase).toEqual('hello world');
|
|
21
|
+
expect(test.path.length).toEqual(0);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('derives on "hello world/1', (): void => {
|
|
25
|
+
const test = keyExtractSuri('hello world/1');
|
|
26
|
+
|
|
27
|
+
expect(test.password).not.toBeDefined();
|
|
28
|
+
expect(test.phrase).toEqual('hello world');
|
|
29
|
+
expect(test.path.length).toEqual(1);
|
|
30
|
+
expect(test.path[0].isHard).toEqual(false);
|
|
31
|
+
expect(test.path[0].chainCode).toEqual(Uint8Array.from([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('derives on "hello world/DOT', (): void => {
|
|
35
|
+
const test = keyExtractSuri('hello world/DOT');
|
|
36
|
+
|
|
37
|
+
expect(test.password).not.toBeDefined();
|
|
38
|
+
expect(test.phrase).toEqual('hello world');
|
|
39
|
+
expect(test.path.length).toEqual(1);
|
|
40
|
+
expect(test.path[0].isHard).toEqual(false);
|
|
41
|
+
expect(test.path[0].chainCode).toEqual(Uint8Array.from([12, 68, 79, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('derives on "hello world//1', (): void => {
|
|
45
|
+
const test = keyExtractSuri('hello world//1');
|
|
46
|
+
|
|
47
|
+
expect(test.password).not.toBeDefined();
|
|
48
|
+
expect(test.phrase).toEqual('hello world');
|
|
49
|
+
expect(test.path.length).toEqual(1);
|
|
50
|
+
expect(test.path[0].isHard).toEqual(true);
|
|
51
|
+
expect(test.path[0].chainCode).toEqual(Uint8Array.from([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('derives on "hello world//DOT', (): void => {
|
|
55
|
+
const test = keyExtractSuri('hello world//DOT');
|
|
56
|
+
|
|
57
|
+
expect(test.password).not.toBeDefined();
|
|
58
|
+
expect(test.phrase).toEqual('hello world');
|
|
59
|
+
expect(test.path.length).toEqual(1);
|
|
60
|
+
expect(test.path[0].isHard).toEqual(true);
|
|
61
|
+
expect(test.path[0].chainCode).toEqual(Uint8Array.from([12, 68, 79, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('derives on "hello world//1/DOT', (): void => {
|
|
65
|
+
const test = keyExtractSuri('hello world//1/DOT');
|
|
66
|
+
|
|
67
|
+
expect(test.password).not.toBeDefined();
|
|
68
|
+
expect(test.phrase).toEqual('hello world');
|
|
69
|
+
expect(test.path.length).toEqual(2);
|
|
70
|
+
expect(test.path[0].isHard).toEqual(true);
|
|
71
|
+
expect(test.path[0].chainCode).toEqual(Uint8Array.from([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
|
|
72
|
+
expect(test.path[1].isHard).toEqual(false);
|
|
73
|
+
expect(test.path[1].chainCode).toEqual(Uint8Array.from([12, 68, 79, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('derives on "hello world//DOT/1', (): void => {
|
|
77
|
+
const test = keyExtractSuri('hello world//DOT/1');
|
|
78
|
+
|
|
79
|
+
expect(test.password).not.toBeDefined();
|
|
80
|
+
expect(test.phrase).toEqual('hello world');
|
|
81
|
+
expect(test.path.length).toEqual(2);
|
|
82
|
+
expect(test.path[0].isHard).toEqual(true);
|
|
83
|
+
expect(test.path[0].chainCode).toEqual(Uint8Array.from([12, 68, 79, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
|
|
84
|
+
expect(test.path[1].isHard).toEqual(false);
|
|
85
|
+
expect(test.path[1].chainCode).toEqual(Uint8Array.from([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('derives on "hello world///password"', (): void => {
|
|
89
|
+
const test = keyExtractSuri('hello world///password');
|
|
90
|
+
|
|
91
|
+
expect(test.password).toEqual('password');
|
|
92
|
+
expect(test.phrase).toEqual('hello world');
|
|
93
|
+
expect(test.path.length).toEqual(0);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('derives on "hello world//1/DOT///password"', (): void => {
|
|
97
|
+
const test = keyExtractSuri('hello world//1/DOT///password');
|
|
98
|
+
|
|
99
|
+
expect(test.password).toEqual('password');
|
|
100
|
+
expect(test.phrase).toEqual('hello world');
|
|
101
|
+
expect(test.path.length).toEqual(2);
|
|
102
|
+
expect(test.path[0].isHard).toEqual(true);
|
|
103
|
+
expect(test.path[0].chainCode).toEqual(Uint8Array.from([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
|
|
104
|
+
expect(test.path[1].isHard).toEqual(false);
|
|
105
|
+
expect(test.path[1].chainCode).toEqual(Uint8Array.from([12, 68, 79, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('derives on "hello world/1//DOT///password"', (): void => {
|
|
109
|
+
const test = keyExtractSuri('hello world/1//DOT///password');
|
|
110
|
+
|
|
111
|
+
expect(test.password).toEqual('password');
|
|
112
|
+
expect(test.phrase).toEqual('hello world');
|
|
113
|
+
expect(test.path.length).toEqual(2);
|
|
114
|
+
expect(test.path[0].isHard).toEqual(false);
|
|
115
|
+
expect(test.path[0].chainCode).toEqual(Uint8Array.from([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
|
|
116
|
+
expect(test.path[1].isHard).toEqual(true);
|
|
117
|
+
expect(test.path[1].chainCode).toEqual(Uint8Array.from([12, 68, 79, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('derives on actual Alice', (): void => {
|
|
121
|
+
const test = keyExtractSuri('bottom drive obey lake curtain smoke basket hold race lonely fit walk//Alice');
|
|
122
|
+
|
|
123
|
+
expect(test.password).not.toBeDefined();
|
|
124
|
+
expect(test.phrase).toEqual('bottom drive obey lake curtain smoke basket hold race lonely fit walk');
|
|
125
|
+
expect(test.path.length).toEqual(1);
|
|
126
|
+
expect(test.path[0].isHard).toEqual(true);
|
|
127
|
+
expect(test.path[0].chainCode).toEqual(Uint8Array.from([20, 65, 108, 105, 99, 101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('derives on uncommon characters', (): void => {
|
|
131
|
+
const languageMnemonics = {
|
|
132
|
+
chineseSimplified: '熙 礼 淀 谋 耗 搜 雨 瑞 雷 合 析 感',
|
|
133
|
+
chineseTraditional: '召 胸 捕 乏 講 祥 隙 幫 動 框 場 給',
|
|
134
|
+
french: 'ruiner minute maison ouragan palourde piscine nerveux descente romance édifier ancien médaille',
|
|
135
|
+
japanese: 'ほったん はちみつ おやゆび ほかん いりぐち さんいん てぶくろ だいじょうぶ ふとん でぬかえ ちしき あわてる',
|
|
136
|
+
korean: '김밥 방향 논리 저절로 증상 지진 회장 오히려 시리즈 최근 학용품 곡식'
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
Object.keys(languageMnemonics).forEach((mnemonic) => {
|
|
140
|
+
const test = keyExtractSuri(mnemonic);
|
|
141
|
+
|
|
142
|
+
expect(test.password).not.toBeDefined();
|
|
143
|
+
expect(test.phrase).toEqual(mnemonic);
|
|
144
|
+
expect(test.path.length).toEqual(0);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import type { DeriveJunction } from './DeriveJunction.js';
|
|
5
|
+
|
|
6
|
+
import { keyExtractPath } from './extractPath.js';
|
|
7
|
+
|
|
8
|
+
export interface ExtractResult {
|
|
9
|
+
derivePath: string,
|
|
10
|
+
password?: string;
|
|
11
|
+
path: DeriveJunction[];
|
|
12
|
+
phrase: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const RE_CAPTURE = /^((0x[a-fA-F0-9]+|[\p{L}\d]+(?: [\p{L}\d]+)*))((\/\/?[^/]+)*)(\/\/\/(.*))?$/u;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @description Extracts the phrase, path and password from a SURI format for specifying secret keys `<secret>/<soft-key>//<hard-key>///<password>` (the `///password` may be omitted, and `/<soft-key>` and `//<hard-key>` maybe repeated and mixed).
|
|
19
|
+
*/
|
|
20
|
+
export function keyExtractSuri (suri: string): ExtractResult {
|
|
21
|
+
// Normalize Unicode to NFC to avoid accent-related mismatches
|
|
22
|
+
const normalizedSuri = suri.normalize('NFC');
|
|
23
|
+
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/prefer-regexp-exec
|
|
25
|
+
const matches = normalizedSuri.match(RE_CAPTURE);
|
|
26
|
+
|
|
27
|
+
if (matches === null) {
|
|
28
|
+
throw new Error('Unable to match provided value to a secret URI');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const [, phrase, , derivePath, , , password] = matches;
|
|
32
|
+
const { path } = keyExtractPath(derivePath);
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
derivePath,
|
|
36
|
+
password,
|
|
37
|
+
path,
|
|
38
|
+
phrase
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import type { Keypair, KeypairType } from '../types.js';
|
|
5
|
+
import type { DeriveJunction } from './DeriveJunction.js';
|
|
6
|
+
|
|
7
|
+
import { keyHdkdEcdsa } from './hdkdEcdsa.js';
|
|
8
|
+
import { keyHdkdEd25519 } from './hdkdEd25519.js';
|
|
9
|
+
import { keyHdkdSr25519 } from './hdkdSr25519.js';
|
|
10
|
+
|
|
11
|
+
const generators = {
|
|
12
|
+
ecdsa: keyHdkdEcdsa,
|
|
13
|
+
ed25519: keyHdkdEd25519,
|
|
14
|
+
// FIXME This is Substrate-compatible, not Ethereum-compatible
|
|
15
|
+
ethereum: keyHdkdEcdsa,
|
|
16
|
+
sr25519: keyHdkdSr25519
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function keyFromPath (pair: Keypair, path: DeriveJunction[], type: KeypairType): Keypair {
|
|
20
|
+
const keyHdkd = generators[type];
|
|
21
|
+
let result = pair;
|
|
22
|
+
|
|
23
|
+
for (const junction of path) {
|
|
24
|
+
result = keyHdkd(result, junction);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import type { Keypair } from '../types.js';
|
|
5
|
+
import type { DeriveJunction } from './DeriveJunction.js';
|
|
6
|
+
|
|
7
|
+
export function createSeedDeriveFn (fromSeed: (seed: Uint8Array) => Keypair, derive: (seed: Uint8Array, chainCode: Uint8Array) => Uint8Array): (keypair: Keypair, junction: DeriveJunction) => Keypair {
|
|
8
|
+
return (keypair: Keypair, { chainCode, isHard }: DeriveJunction): Keypair => {
|
|
9
|
+
if (!isHard) {
|
|
10
|
+
throw new Error('A soft key was found in the path and is not supported');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return fromSeed(
|
|
14
|
+
derive(keypair.secretKey.subarray(0, 32), chainCode)
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { secp256k1DeriveHard } from '../secp256k1/deriveHard.js';
|
|
5
|
+
import { secp256k1PairFromSeed } from '../secp256k1/pair/fromSeed.js';
|
|
6
|
+
import { createSeedDeriveFn } from './hdkdDerive.js';
|
|
7
|
+
|
|
8
|
+
export const keyHdkdEcdsa = /*#__PURE__*/ createSeedDeriveFn(secp256k1PairFromSeed, secp256k1DeriveHard);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { ed25519DeriveHard, ed25519PairFromSeed } from '../ed25519/index.js';
|
|
5
|
+
import { createSeedDeriveFn } from './hdkdDerive.js';
|
|
6
|
+
|
|
7
|
+
export const keyHdkdEd25519 = /*#__PURE__*/ createSeedDeriveFn(ed25519PairFromSeed, ed25519DeriveHard);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import type { Keypair } from '../types.js';
|
|
5
|
+
import type { DeriveJunction } from './DeriveJunction.js';
|
|
6
|
+
|
|
7
|
+
import { sr25519DeriveHard } from '../sr25519/deriveHard.js';
|
|
8
|
+
import { sr25519DeriveSoft } from '../sr25519/deriveSoft.js';
|
|
9
|
+
|
|
10
|
+
export function keyHdkdSr25519 (keypair: Keypair, { chainCode, isSoft }: DeriveJunction): Keypair {
|
|
11
|
+
return isSoft
|
|
12
|
+
? sr25519DeriveSoft(keypair, chainCode)
|
|
13
|
+
: sr25519DeriveHard(keypair, chainCode);
|
|
14
|
+
}
|
package/src/key/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @summary Create keys from paths, seeds and password
|
|
6
|
+
*/
|
|
7
|
+
export { keyExtractPath } from './extractPath.js';
|
|
8
|
+
export { keyExtractSuri } from './extractSuri.js';
|
|
9
|
+
export { keyFromPath } from './fromPath.js';
|
|
10
|
+
export { keyHdkdEcdsa } from './hdkdEcdsa.js';
|
|
11
|
+
export { keyHdkdEd25519 } from './hdkdEd25519.js';
|
|
12
|
+
export { keyHdkdSr25519 } from './hdkdSr25519.js';
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
|
5
|
+
|
|
6
|
+
import * as lists from './wordlists/index.js';
|
|
7
|
+
import { entropyToMnemonic, generateMnemonic, mnemonicToEntropy, validateMnemonic } from './bip39.js';
|
|
8
|
+
|
|
9
|
+
describe('wordlists', (): void => {
|
|
10
|
+
for (const [lang, words] of Object.entries(lists)) {
|
|
11
|
+
describe(`language ${lang}`, (): void => {
|
|
12
|
+
it('has the correct number of words', (): void => {
|
|
13
|
+
expect(words).toHaveLength(2048);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('has no empty words', (): void => {
|
|
17
|
+
expect(
|
|
18
|
+
words.some((w) => !w.length || w.trim() !== w)
|
|
19
|
+
).toEqual(false);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('bip39', (): void => {
|
|
26
|
+
const m = 'seed sock milk update focus rotate barely fade car face mechanic mercy';
|
|
27
|
+
const e = mnemonicToEntropy(m);
|
|
28
|
+
|
|
29
|
+
it('generates a known entropy', (): void => {
|
|
30
|
+
expect(e).toEqual(
|
|
31
|
+
new Uint8Array([194, 249, 194, 50, 119, 69, 163, 120, 68, 162, 142, 34, 74, 50, 40, 197])
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('has a two-way entropy <-> mnemonic (default wordlist)', (): void => {
|
|
36
|
+
expect(
|
|
37
|
+
entropyToMnemonic(mnemonicToEntropy(entropyToMnemonic(e)))
|
|
38
|
+
).toEqual(m);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
for (const [lang, words] of Object.entries(lists)) {
|
|
42
|
+
const isUsingList = (test: string, length = 12): void => {
|
|
43
|
+
const split = test.split(' ');
|
|
44
|
+
|
|
45
|
+
expect(
|
|
46
|
+
split
|
|
47
|
+
).toHaveLength(length);
|
|
48
|
+
expect(
|
|
49
|
+
split.some((w) => words.indexOf(w) === -1)
|
|
50
|
+
).toEqual(false);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
describe(`language ${lang}`, (): void => {
|
|
54
|
+
it('has a two-way entropy <-> mnemonic', (): void => {
|
|
55
|
+
const test = entropyToMnemonic(e, words);
|
|
56
|
+
const entr = mnemonicToEntropy(test, words);
|
|
57
|
+
|
|
58
|
+
isUsingList(test);
|
|
59
|
+
|
|
60
|
+
expect(
|
|
61
|
+
entr
|
|
62
|
+
).toEqual(e);
|
|
63
|
+
|
|
64
|
+
expect(
|
|
65
|
+
entropyToMnemonic(entr, words)
|
|
66
|
+
).toEqual(test);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('generates a valid mnemonic', (): void => {
|
|
70
|
+
const test = generateMnemonic(24, words);
|
|
71
|
+
|
|
72
|
+
isUsingList(test, 24);
|
|
73
|
+
|
|
74
|
+
expect(
|
|
75
|
+
validateMnemonic(test, words)
|
|
76
|
+
).toEqual(true);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
});
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// Adapted from the bitcoinjs/bip39 source
|
|
5
|
+
// https://github.com/bitcoinjs/bip39/blob/1d063b6a6aee4145b34d701037cd3e67f5446ff9/ts_src/index.ts
|
|
6
|
+
// Copyright (c) 2014, Wei Lu <luwei.here@gmail.com> and Daniel Cousens <email@dcousens.com>
|
|
7
|
+
// ISC Licence
|
|
8
|
+
//
|
|
9
|
+
// Change made in this version -
|
|
10
|
+
// - Adjust formatting (just eslint differences)
|
|
11
|
+
// - Only English wordlist (this aligns with the wasm-crypto implementation)
|
|
12
|
+
// - Use util-crypto randomAsU8a (instead of randombytes)
|
|
13
|
+
// - Remove setting of wordlist passing of wordlist in functions
|
|
14
|
+
// - Remove mnemonicToSeed (we only use the sync variant)
|
|
15
|
+
// - generateMnemonic takes number of words (instead of strength)
|
|
16
|
+
|
|
17
|
+
import { stringToU8a, u8aToU8a } from '@pezkuwi/util';
|
|
18
|
+
|
|
19
|
+
import { pbkdf2Encode } from '../pbkdf2/index.js';
|
|
20
|
+
import { randomAsU8a } from '../random/index.js';
|
|
21
|
+
import { sha256AsU8a } from '../sha/index.js';
|
|
22
|
+
import DEFAULT_WORDLIST from './wordlists/en.js';
|
|
23
|
+
|
|
24
|
+
const INVALID_MNEMONIC = 'Invalid mnemonic';
|
|
25
|
+
const INVALID_ENTROPY = 'Invalid entropy';
|
|
26
|
+
const INVALID_CHECKSUM = 'Invalid mnemonic checksum';
|
|
27
|
+
|
|
28
|
+
/** @internal */
|
|
29
|
+
function normalize (str?: string): string {
|
|
30
|
+
return (str || '').normalize('NFKD');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** @internal */
|
|
34
|
+
function binaryToByte (bin: string): number {
|
|
35
|
+
return parseInt(bin, 2);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** @internal */
|
|
39
|
+
function bytesToBinary (bytes: number[]): string {
|
|
40
|
+
return bytes.map((x) => x.toString(2).padStart(8, '0')).join('');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** @internal */
|
|
44
|
+
function deriveChecksumBits (entropyBuffer: Uint8Array): string {
|
|
45
|
+
return bytesToBinary(
|
|
46
|
+
Array.from(sha256AsU8a(entropyBuffer))
|
|
47
|
+
).slice(0, (entropyBuffer.length * 8) / 32);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function mnemonicToSeedSync (mnemonic: string, password?: string): Uint8Array {
|
|
51
|
+
return pbkdf2Encode(
|
|
52
|
+
stringToU8a(normalize(mnemonic)),
|
|
53
|
+
stringToU8a(`mnemonic${normalize(password)}`)
|
|
54
|
+
).password;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function mnemonicToEntropy (mnemonic: string, wordlist: string[] = DEFAULT_WORDLIST): Uint8Array {
|
|
58
|
+
const words = normalize(mnemonic).split(' ');
|
|
59
|
+
|
|
60
|
+
if (words.length % 3 !== 0) {
|
|
61
|
+
throw new Error(INVALID_MNEMONIC);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// convert word indices to 11 bit binary strings
|
|
65
|
+
const bits = words
|
|
66
|
+
.map((word): string => {
|
|
67
|
+
const index = wordlist.indexOf(word);
|
|
68
|
+
|
|
69
|
+
if (index === -1) {
|
|
70
|
+
throw new Error(INVALID_MNEMONIC);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return index.toString(2).padStart(11, '0');
|
|
74
|
+
})
|
|
75
|
+
.join('');
|
|
76
|
+
|
|
77
|
+
// split the binary string into ENT/CS
|
|
78
|
+
const dividerIndex = Math.floor(bits.length / 33) * 32;
|
|
79
|
+
const entropyBits = bits.slice(0, dividerIndex);
|
|
80
|
+
const checksumBits = bits.slice(dividerIndex);
|
|
81
|
+
|
|
82
|
+
// calculate the checksum and compare
|
|
83
|
+
const matched = entropyBits.match(/(.{1,8})/g);
|
|
84
|
+
const entropyBytes = matched?.map(binaryToByte);
|
|
85
|
+
|
|
86
|
+
if (!entropyBytes || (entropyBytes.length % 4 !== 0) || (entropyBytes.length < 16) || (entropyBytes.length > 32)) {
|
|
87
|
+
throw new Error(INVALID_ENTROPY);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const entropy = u8aToU8a(entropyBytes);
|
|
91
|
+
|
|
92
|
+
if (deriveChecksumBits(entropy) !== checksumBits) {
|
|
93
|
+
throw new Error(INVALID_CHECKSUM);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return entropy;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function entropyToMnemonic (entropy: Uint8Array, wordlist: string[] = DEFAULT_WORDLIST): string {
|
|
100
|
+
// 128 <= ENT <= 256
|
|
101
|
+
if ((entropy.length % 4 !== 0) || (entropy.length < 16) || (entropy.length > 32)) {
|
|
102
|
+
throw new Error(INVALID_ENTROPY);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const matched = `${bytesToBinary(Array.from(entropy))}${deriveChecksumBits(entropy)}`.match(/(.{1,11})/g);
|
|
106
|
+
const mapped = matched?.map((b) => wordlist[binaryToByte(b)]);
|
|
107
|
+
|
|
108
|
+
if (!mapped || (mapped.length < 12)) {
|
|
109
|
+
throw new Error('Unable to map entropy to mnemonic');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return mapped.join(' ');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function generateMnemonic (numWords: 12 | 15 | 18 | 21 | 24, wordlist?: string[]): string {
|
|
116
|
+
return entropyToMnemonic(randomAsU8a((numWords / 3) * 4), wordlist);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function validateMnemonic (mnemonic: string, wordlist?: string[]): boolean {
|
|
120
|
+
try {
|
|
121
|
+
mnemonicToEntropy(mnemonic, wordlist);
|
|
122
|
+
} catch {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return true;
|
|
127
|
+
}
|