@talismn/crypto 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/declarations/src/derivation/common.d.ts +2 -2
- package/dist/declarations/src/derivation/deriveEcdsa.d.ts +1 -1
- package/dist/declarations/src/derivation/deriveEd25519.d.ts +1 -1
- package/dist/declarations/src/derivation/deriveEthereum.d.ts +1 -1
- package/dist/declarations/src/derivation/deriveSolana.d.ts +1 -1
- package/dist/declarations/src/derivation/utils.d.ts +3 -3
- package/dist/declarations/src/hashing/index.d.ts +2 -2
- package/dist/declarations/src/mnemonic/index.d.ts +3 -3
- package/dist/declarations/src/utils/index.d.ts +1 -0
- package/dist/declarations/src/utils/pbkdf2.d.ts +1 -0
- package/dist/talismn-crypto.cjs.dev.js +67 -28
- package/dist/talismn-crypto.cjs.prod.js +67 -28
- package/dist/talismn-crypto.esm.js +68 -30
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
type DerivationDescriptor = [type: "hard" | "soft", code: string];
|
|
2
2
|
export declare const parseSubstrateDerivations: (derivationsStr: string) => DerivationDescriptor[];
|
|
3
|
-
export declare const createChainCode: (code: string) => Uint8Array
|
|
4
|
-
export declare const deriveSubstrateSecretKey: (seed: Uint8Array, derivationPath: string, prefix: string) => Uint8Array
|
|
3
|
+
export declare const createChainCode: (code: string) => Uint8Array<ArrayBuffer>;
|
|
4
|
+
export declare const deriveSubstrateSecretKey: (seed: Uint8Array, derivationPath: string, prefix: string) => Uint8Array<ArrayBufferLike>;
|
|
5
5
|
export {};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { Keypair } from "../types";
|
|
2
2
|
export declare const deriveEcdsa: (seed: Uint8Array, derivationPath: string) => Keypair;
|
|
3
|
-
export declare const getPublicKeyEcdsa: (secretKey: Uint8Array) => Uint8Array
|
|
3
|
+
export declare const getPublicKeyEcdsa: (secretKey: Uint8Array) => Uint8Array<ArrayBufferLike>;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { Keypair } from "../types";
|
|
2
2
|
export declare const deriveEd25519: (seed: Uint8Array, derivationPath: string) => Keypair;
|
|
3
|
-
export declare const getPublicKeyEd25519: (secretKey: Uint8Array) => Uint8Array
|
|
3
|
+
export declare const getPublicKeyEd25519: (secretKey: Uint8Array) => Uint8Array<ArrayBufferLike>;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { Keypair } from "../types";
|
|
2
2
|
export declare const deriveEthereum: (seed: Uint8Array, derivationPath: string) => Keypair;
|
|
3
|
-
export declare const getPublicKeyEthereum: (secretKey: Uint8Array) => Uint8Array
|
|
3
|
+
export declare const getPublicKeyEthereum: (secretKey: Uint8Array) => Uint8Array<ArrayBufferLike>;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { Keypair } from "../types";
|
|
2
2
|
export declare const deriveSolana: (seed: Uint8Array, derivationPath: string) => Keypair;
|
|
3
|
-
export declare const getPublicKeySolana: (secretKey: Uint8Array) => Uint8Array
|
|
3
|
+
export declare const getPublicKeySolana: (secretKey: Uint8Array) => Uint8Array<ArrayBufferLike>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { KeypairCurve } from "../types";
|
|
2
2
|
export declare const deriveKeypair: (seed: Uint8Array, derivationPath: string, curve: KeypairCurve) => import("../types").Keypair;
|
|
3
3
|
export declare const getPublicKeyFromSecret: (secretKey: Uint8Array, curve: KeypairCurve) => Uint8Array;
|
|
4
|
-
export declare const addressFromSuri: (suri: string, type: KeypairCurve) => string
|
|
4
|
+
export declare const addressFromSuri: (suri: string, type: KeypairCurve) => Promise<string>;
|
|
5
5
|
/**
|
|
6
6
|
* @dev we only expect suri to contain a mnemonic and derivation path.
|
|
7
7
|
* for other cases see https://polkadot.js.org/docs/keyring/start/suri/
|
|
@@ -12,5 +12,5 @@ export declare const parseSuri: (suri: string) => {
|
|
|
12
12
|
password: string | undefined;
|
|
13
13
|
};
|
|
14
14
|
export declare const removeHexPrefix: (secretKey: string) => string;
|
|
15
|
-
export declare const parseSecretKey: (secretKey: string, curve: KeypairCurve) => Uint8Array
|
|
16
|
-
export declare const isValidDerivationPath: (derivationPath: string, curve: KeypairCurve) => boolean
|
|
15
|
+
export declare const parseSecretKey: (secretKey: string, curve: KeypairCurve) => Uint8Array<ArrayBufferLike>;
|
|
16
|
+
export declare const isValidDerivationPath: (derivationPath: string, curve: KeypairCurve) => Promise<boolean>;
|
|
@@ -4,6 +4,6 @@ export declare const blake3: {
|
|
|
4
4
|
blockLen: number;
|
|
5
5
|
create(opts: Object): import("@noble/hashes/utils").HashXOF<import("@noble/hashes/utils").HashXOF<import("@noble/hashes/utils").HashXOF<any>>>;
|
|
6
6
|
};
|
|
7
|
-
export declare const blake2b256: (msg: Uint8Array) => Uint8Array
|
|
8
|
-
export declare const blake2b512: (msg: Uint8Array) => Uint8Array
|
|
7
|
+
export declare const blake2b256: (msg: Uint8Array) => Uint8Array<ArrayBufferLike>;
|
|
8
|
+
export declare const blake2b512: (msg: Uint8Array) => Uint8Array<ArrayBufferLike>;
|
|
9
9
|
export declare const getSafeHash: (bytes: Uint8Array) => string;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { KeypairCurve } from "../types";
|
|
2
|
-
export declare const mnemonicToEntropy: (mnemonic: string) => Uint8Array
|
|
2
|
+
export declare const mnemonicToEntropy: (mnemonic: string) => Uint8Array<ArrayBufferLike>;
|
|
3
3
|
export declare const entropyToMnemonic: (entropy: Uint8Array) => string;
|
|
4
|
-
export declare const entropyToSeed: (entropy: Uint8Array, curve: KeypairCurve, password?: string) => Uint8Array
|
|
4
|
+
export declare const entropyToSeed: (entropy: Uint8Array, curve: KeypairCurve, password?: string) => Promise<Uint8Array<ArrayBuffer>>;
|
|
5
5
|
export declare const isValidMnemonic: (mnemonic: string) => boolean;
|
|
6
6
|
export declare const generateMnemonic: (words: 12 | 24) => string;
|
|
7
7
|
export declare const DEV_MNEMONIC_POLKADOT = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
|
|
8
8
|
export declare const DEV_MNEMONIC_ETHEREUM = "test test test test test test test test test test test junk";
|
|
9
|
-
export declare const getDevSeed: (curve: KeypairCurve) => Uint8Array
|
|
9
|
+
export declare const getDevSeed: (curve: KeypairCurve) => Promise<Uint8Array<ArrayBufferLike>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const pbkdf2: (hash: "SHA-256" | "SHA-512", entropy: Uint8Array, salt: Uint8Array, iterations: number, outputLenBytes: number) => Promise<Uint8Array<ArrayBuffer>>;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var pbkdf2 = require('@noble/hashes/pbkdf2');
|
|
4
|
-
var sha512 = require('@noble/hashes/sha512');
|
|
5
3
|
var bip39 = require('@scure/bip39');
|
|
6
4
|
var english = require('@scure/bip39/wordlists/english');
|
|
7
5
|
var base = require('@scure/base');
|
|
@@ -14,30 +12,41 @@ var scaleTs = require('scale-ts');
|
|
|
14
12
|
var ed25519 = require('@noble/curves/ed25519');
|
|
15
13
|
var bip32 = require('@scure/bip32');
|
|
16
14
|
var hmac = require('@noble/hashes/hmac');
|
|
15
|
+
var sha512 = require('@noble/hashes/sha512');
|
|
17
16
|
var microSr25519 = require('micro-sr25519');
|
|
18
17
|
|
|
18
|
+
const pbkdf2 = async (hash, entropy, salt, iterations, outputLenBytes) => {
|
|
19
|
+
// NOTE: react-native-quick-crypto (our `global.crypto` polyfill on Talisman Mobile) doesn't support `crypto.subtle.deriveKey`.
|
|
20
|
+
// But, we can work around this by using `crypto.subtle.deriveBits` and `crypto.subtle.importKey`, which when used together
|
|
21
|
+
// can provide the same functionality as `crypto.subtle.deriveKey`.
|
|
22
|
+
const keyMaterial = await crypto.subtle.importKey("raw", entropy, "PBKDF2", false, ["deriveBits"]);
|
|
23
|
+
const derivedBits = await crypto.subtle.deriveBits({
|
|
24
|
+
name: "PBKDF2",
|
|
25
|
+
salt,
|
|
26
|
+
iterations,
|
|
27
|
+
hash
|
|
28
|
+
}, keyMaterial, outputLenBytes * 8);
|
|
29
|
+
return new Uint8Array(derivedBits);
|
|
30
|
+
};
|
|
31
|
+
|
|
19
32
|
const mnemonicToEntropy = mnemonic => {
|
|
20
33
|
return bip39.mnemonicToEntropy(mnemonic, english.wordlist);
|
|
21
34
|
};
|
|
22
35
|
const entropyToMnemonic = entropy => {
|
|
23
36
|
return bip39.entropyToMnemonic(entropy, english.wordlist);
|
|
24
37
|
};
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
c: 2048,
|
|
38
|
-
dkLen: 64
|
|
39
|
-
});
|
|
40
|
-
};
|
|
38
|
+
const entropyToSeedSubstrate = async (entropy, password) => await pbkdf2("SHA-512", entropy, mnemonicPasswordToSalt(password ?? ""), 2048,
|
|
39
|
+
// 2048 iterations
|
|
40
|
+
32 // 32 bytes (32 * 8 == 256 bits)
|
|
41
|
+
);
|
|
42
|
+
const entropyToSeedClassic = async (entropy, password) => await pbkdf2("SHA-512", encodeNormalized(entropyToMnemonic(entropy)), mnemonicPasswordToSalt(password ?? ""), 2048,
|
|
43
|
+
// 2048 iterations
|
|
44
|
+
64 // 64 bytes (64 * 8 == 512 bits)
|
|
45
|
+
);
|
|
46
|
+
const mnemonicPasswordToSalt = password => encodeNormalized(`mnemonic${password}`);
|
|
47
|
+
|
|
48
|
+
/** Normalizes a UTF-8 string using `NFKD` form, then encodes it into bytes */
|
|
49
|
+
const encodeNormalized = utf8 => new TextEncoder().encode(utf8.normalize("NFKD"));
|
|
41
50
|
const getSeedDerivationType = curve => {
|
|
42
51
|
switch (curve) {
|
|
43
52
|
case "sr25519":
|
|
@@ -52,13 +61,13 @@ const getSeedDerivationType = curve => {
|
|
|
52
61
|
|
|
53
62
|
// when deriving keys from a mnemonic, we usually dont want a password here.
|
|
54
63
|
// a password provided here would be used as a 25th mnemonic word.
|
|
55
|
-
const entropyToSeed = (entropy, curve, password) => {
|
|
64
|
+
const entropyToSeed = async (entropy, curve, password) => {
|
|
56
65
|
const type = getSeedDerivationType(curve);
|
|
57
66
|
switch (type) {
|
|
58
67
|
case "classic":
|
|
59
|
-
return entropyToSeedClassic(entropy, password);
|
|
68
|
+
return await entropyToSeedClassic(entropy, password);
|
|
60
69
|
case "substrate":
|
|
61
|
-
return entropyToSeedSubstrate(entropy, password);
|
|
70
|
+
return await entropyToSeedSubstrate(entropy, password);
|
|
62
71
|
}
|
|
63
72
|
};
|
|
64
73
|
const isValidMnemonic = mnemonic => {
|
|
@@ -81,21 +90,21 @@ const DEV_MNEMONIC_ETHEREUM = "test test test test test test test test test test
|
|
|
81
90
|
|
|
82
91
|
// keep dev seeds in cache as we will reuse them to validate multiple derivation paths
|
|
83
92
|
const DEV_SEED_CACHE = new Map();
|
|
84
|
-
const getDevSeed = curve => {
|
|
93
|
+
const getDevSeed = async curve => {
|
|
85
94
|
const type = getSeedDerivationType(curve);
|
|
86
95
|
if (!DEV_SEED_CACHE.has(type)) {
|
|
87
96
|
switch (type) {
|
|
88
97
|
case "classic":
|
|
89
98
|
{
|
|
90
99
|
const entropy = mnemonicToEntropy(DEV_MNEMONIC_ETHEREUM);
|
|
91
|
-
const seed = entropyToSeedClassic(entropy); // 80ms
|
|
100
|
+
const seed = await entropyToSeedClassic(entropy); // 80ms
|
|
92
101
|
DEV_SEED_CACHE.set(type, seed);
|
|
93
102
|
break;
|
|
94
103
|
}
|
|
95
104
|
case "substrate":
|
|
96
105
|
{
|
|
97
106
|
const entropy = mnemonicToEntropy(DEV_MNEMONIC_POLKADOT);
|
|
98
|
-
const seed = entropyToSeedSubstrate(entropy); // 80ms
|
|
107
|
+
const seed = await entropyToSeedSubstrate(entropy); // 80ms
|
|
99
108
|
DEV_SEED_CACHE.set(type, seed);
|
|
100
109
|
break;
|
|
101
110
|
}
|
|
@@ -326,9 +335,38 @@ const deriveEd25519 = (seed, derivationPath) => {
|
|
|
326
335
|
};
|
|
327
336
|
};
|
|
328
337
|
const getPublicKeyEd25519 = secretKey => {
|
|
338
|
+
// When importing ed25519 polkadot-js accounts via json, which we do inside of `packages/extension-core/src/domains/keyring/getSecretKeyFromPjsJson.ts`,
|
|
339
|
+
// the secretKey we produce is 64 bytes in length.
|
|
340
|
+
//
|
|
341
|
+
// When using the ed25519 curve to derive a publicKey for this 64 bytes privateKey, we should only take the first 32 bytes:
|
|
342
|
+
// - https://github.com/paulmillr/noble-curves/issues/53#issuecomment-1577362759
|
|
343
|
+
// - https://github.com/paulmillr/noble-curves/discussions/33#discussioncomment-5685971
|
|
344
|
+
// - https://github.com/paulmillr/noble-curves/pull/54
|
|
345
|
+
// - https://github.com/paulmillr/noble-curves/issues/88
|
|
346
|
+
//
|
|
347
|
+
// When you compare the ed25519 publicKey of a given account produced by this function to the publicKey produced by
|
|
348
|
+
// polkadot-js, you will find that they are the same as eachother.
|
|
349
|
+
if (secretKey.length === 64) {
|
|
350
|
+
const [privateComponent, publicComponent] = [secretKey.slice(0, 32), secretKey.slice(32)];
|
|
351
|
+
const publicKey = ed25519.ed25519.getPublicKey(privateComponent);
|
|
352
|
+
|
|
353
|
+
// NOTE: We only accept a 64 byte secretKey when the first 32 bytes successfully produce a public key which equals the second 32 bytes of the secretKey.
|
|
354
|
+
//
|
|
355
|
+
// In this scenario, we assume the creator of the secretKey has given us an array of bytes which equals `[...privateKey, ...publicKey]`.
|
|
356
|
+
//
|
|
357
|
+
// However, if the second 32 bytes **don't** match the publicKey produced by the first 32 bytes, we no longer know what's going on.
|
|
358
|
+
//
|
|
359
|
+
// In that case we pass the 64 bytes directly through to `@noble/curves/ed25519`, which we expect will throw the error: `private key of length 32 expected, got 64`.
|
|
360
|
+
// But if there's some 64 byte key format for ed25519 we don't know about, or one is added in the future, `@noble/curves/ed25519` can handle that for us instead of throwing.
|
|
361
|
+
if (!isUint8ArrayEq(publicComponent, publicKey)) return ed25519.ed25519.getPublicKey(secretKey);
|
|
362
|
+
return publicKey;
|
|
363
|
+
}
|
|
329
364
|
return ed25519.ed25519.getPublicKey(secretKey);
|
|
330
365
|
};
|
|
331
366
|
|
|
367
|
+
/** If a is identical to b, this function returns true, otherwise it returns false */
|
|
368
|
+
const isUint8ArrayEq = (a, b) => a.length !== b.length || a.some((v, i) => v !== b[i]) ? false : true;
|
|
369
|
+
|
|
332
370
|
const deriveEthereum = (seed, derivationPath) => {
|
|
333
371
|
const hdkey = bip32.HDKey.fromMasterSeed(seed);
|
|
334
372
|
const childKey = hdkey.derive(derivationPath);
|
|
@@ -430,14 +468,14 @@ const getPublicKeyFromSecret = (secretKey, curve) => {
|
|
|
430
468
|
return getPublicKeySolana(secretKey);
|
|
431
469
|
}
|
|
432
470
|
};
|
|
433
|
-
const addressFromSuri = (suri, type) => {
|
|
471
|
+
const addressFromSuri = async (suri, type) => {
|
|
434
472
|
const {
|
|
435
473
|
mnemonic,
|
|
436
474
|
derivationPath,
|
|
437
475
|
password
|
|
438
476
|
} = parseSuri(suri);
|
|
439
477
|
const entropy = mnemonicToEntropy(mnemonic);
|
|
440
|
-
const seed = entropyToSeed(entropy, type, password); // ~80ms
|
|
478
|
+
const seed = await entropyToSeed(entropy, type, password); // ~80ms
|
|
441
479
|
const {
|
|
442
480
|
secretKey
|
|
443
481
|
} = deriveKeypair(seed, derivationPath, type);
|
|
@@ -487,9 +525,9 @@ const parseSecretKey = (secretKey, curve) => {
|
|
|
487
525
|
};
|
|
488
526
|
|
|
489
527
|
// @dev: didn't find a reliable source of information on which characters are valid => assume it s valid if a keypair can be generated from it
|
|
490
|
-
const isValidDerivationPath = (derivationPath, curve) => {
|
|
528
|
+
const isValidDerivationPath = async (derivationPath, curve) => {
|
|
491
529
|
try {
|
|
492
|
-
deriveKeypair(getDevSeed(curve), derivationPath, curve);
|
|
530
|
+
deriveKeypair(await getDevSeed(curve), derivationPath, curve);
|
|
493
531
|
return true;
|
|
494
532
|
} catch (err) {
|
|
495
533
|
return false;
|
|
@@ -562,6 +600,7 @@ exports.mnemonicToEntropy = mnemonicToEntropy;
|
|
|
562
600
|
exports.normalizeAddress = normalizeAddress;
|
|
563
601
|
exports.parseSecretKey = parseSecretKey;
|
|
564
602
|
exports.parseSuri = parseSuri;
|
|
603
|
+
exports.pbkdf2 = pbkdf2;
|
|
565
604
|
exports.platformFromAddress = platformFromAddress;
|
|
566
605
|
exports.platformFromCurve = platformFromCurve;
|
|
567
606
|
exports.platformFromEncoding = platformFromEncoding;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var pbkdf2 = require('@noble/hashes/pbkdf2');
|
|
4
|
-
var sha512 = require('@noble/hashes/sha512');
|
|
5
3
|
var bip39 = require('@scure/bip39');
|
|
6
4
|
var english = require('@scure/bip39/wordlists/english');
|
|
7
5
|
var base = require('@scure/base');
|
|
@@ -14,30 +12,41 @@ var scaleTs = require('scale-ts');
|
|
|
14
12
|
var ed25519 = require('@noble/curves/ed25519');
|
|
15
13
|
var bip32 = require('@scure/bip32');
|
|
16
14
|
var hmac = require('@noble/hashes/hmac');
|
|
15
|
+
var sha512 = require('@noble/hashes/sha512');
|
|
17
16
|
var microSr25519 = require('micro-sr25519');
|
|
18
17
|
|
|
18
|
+
const pbkdf2 = async (hash, entropy, salt, iterations, outputLenBytes) => {
|
|
19
|
+
// NOTE: react-native-quick-crypto (our `global.crypto` polyfill on Talisman Mobile) doesn't support `crypto.subtle.deriveKey`.
|
|
20
|
+
// But, we can work around this by using `crypto.subtle.deriveBits` and `crypto.subtle.importKey`, which when used together
|
|
21
|
+
// can provide the same functionality as `crypto.subtle.deriveKey`.
|
|
22
|
+
const keyMaterial = await crypto.subtle.importKey("raw", entropy, "PBKDF2", false, ["deriveBits"]);
|
|
23
|
+
const derivedBits = await crypto.subtle.deriveBits({
|
|
24
|
+
name: "PBKDF2",
|
|
25
|
+
salt,
|
|
26
|
+
iterations,
|
|
27
|
+
hash
|
|
28
|
+
}, keyMaterial, outputLenBytes * 8);
|
|
29
|
+
return new Uint8Array(derivedBits);
|
|
30
|
+
};
|
|
31
|
+
|
|
19
32
|
const mnemonicToEntropy = mnemonic => {
|
|
20
33
|
return bip39.mnemonicToEntropy(mnemonic, english.wordlist);
|
|
21
34
|
};
|
|
22
35
|
const entropyToMnemonic = entropy => {
|
|
23
36
|
return bip39.entropyToMnemonic(entropy, english.wordlist);
|
|
24
37
|
};
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
c: 2048,
|
|
38
|
-
dkLen: 64
|
|
39
|
-
});
|
|
40
|
-
};
|
|
38
|
+
const entropyToSeedSubstrate = async (entropy, password) => await pbkdf2("SHA-512", entropy, mnemonicPasswordToSalt(password ?? ""), 2048,
|
|
39
|
+
// 2048 iterations
|
|
40
|
+
32 // 32 bytes (32 * 8 == 256 bits)
|
|
41
|
+
);
|
|
42
|
+
const entropyToSeedClassic = async (entropy, password) => await pbkdf2("SHA-512", encodeNormalized(entropyToMnemonic(entropy)), mnemonicPasswordToSalt(password ?? ""), 2048,
|
|
43
|
+
// 2048 iterations
|
|
44
|
+
64 // 64 bytes (64 * 8 == 512 bits)
|
|
45
|
+
);
|
|
46
|
+
const mnemonicPasswordToSalt = password => encodeNormalized(`mnemonic${password}`);
|
|
47
|
+
|
|
48
|
+
/** Normalizes a UTF-8 string using `NFKD` form, then encodes it into bytes */
|
|
49
|
+
const encodeNormalized = utf8 => new TextEncoder().encode(utf8.normalize("NFKD"));
|
|
41
50
|
const getSeedDerivationType = curve => {
|
|
42
51
|
switch (curve) {
|
|
43
52
|
case "sr25519":
|
|
@@ -52,13 +61,13 @@ const getSeedDerivationType = curve => {
|
|
|
52
61
|
|
|
53
62
|
// when deriving keys from a mnemonic, we usually dont want a password here.
|
|
54
63
|
// a password provided here would be used as a 25th mnemonic word.
|
|
55
|
-
const entropyToSeed = (entropy, curve, password) => {
|
|
64
|
+
const entropyToSeed = async (entropy, curve, password) => {
|
|
56
65
|
const type = getSeedDerivationType(curve);
|
|
57
66
|
switch (type) {
|
|
58
67
|
case "classic":
|
|
59
|
-
return entropyToSeedClassic(entropy, password);
|
|
68
|
+
return await entropyToSeedClassic(entropy, password);
|
|
60
69
|
case "substrate":
|
|
61
|
-
return entropyToSeedSubstrate(entropy, password);
|
|
70
|
+
return await entropyToSeedSubstrate(entropy, password);
|
|
62
71
|
}
|
|
63
72
|
};
|
|
64
73
|
const isValidMnemonic = mnemonic => {
|
|
@@ -81,21 +90,21 @@ const DEV_MNEMONIC_ETHEREUM = "test test test test test test test test test test
|
|
|
81
90
|
|
|
82
91
|
// keep dev seeds in cache as we will reuse them to validate multiple derivation paths
|
|
83
92
|
const DEV_SEED_CACHE = new Map();
|
|
84
|
-
const getDevSeed = curve => {
|
|
93
|
+
const getDevSeed = async curve => {
|
|
85
94
|
const type = getSeedDerivationType(curve);
|
|
86
95
|
if (!DEV_SEED_CACHE.has(type)) {
|
|
87
96
|
switch (type) {
|
|
88
97
|
case "classic":
|
|
89
98
|
{
|
|
90
99
|
const entropy = mnemonicToEntropy(DEV_MNEMONIC_ETHEREUM);
|
|
91
|
-
const seed = entropyToSeedClassic(entropy); // 80ms
|
|
100
|
+
const seed = await entropyToSeedClassic(entropy); // 80ms
|
|
92
101
|
DEV_SEED_CACHE.set(type, seed);
|
|
93
102
|
break;
|
|
94
103
|
}
|
|
95
104
|
case "substrate":
|
|
96
105
|
{
|
|
97
106
|
const entropy = mnemonicToEntropy(DEV_MNEMONIC_POLKADOT);
|
|
98
|
-
const seed = entropyToSeedSubstrate(entropy); // 80ms
|
|
107
|
+
const seed = await entropyToSeedSubstrate(entropy); // 80ms
|
|
99
108
|
DEV_SEED_CACHE.set(type, seed);
|
|
100
109
|
break;
|
|
101
110
|
}
|
|
@@ -326,9 +335,38 @@ const deriveEd25519 = (seed, derivationPath) => {
|
|
|
326
335
|
};
|
|
327
336
|
};
|
|
328
337
|
const getPublicKeyEd25519 = secretKey => {
|
|
338
|
+
// When importing ed25519 polkadot-js accounts via json, which we do inside of `packages/extension-core/src/domains/keyring/getSecretKeyFromPjsJson.ts`,
|
|
339
|
+
// the secretKey we produce is 64 bytes in length.
|
|
340
|
+
//
|
|
341
|
+
// When using the ed25519 curve to derive a publicKey for this 64 bytes privateKey, we should only take the first 32 bytes:
|
|
342
|
+
// - https://github.com/paulmillr/noble-curves/issues/53#issuecomment-1577362759
|
|
343
|
+
// - https://github.com/paulmillr/noble-curves/discussions/33#discussioncomment-5685971
|
|
344
|
+
// - https://github.com/paulmillr/noble-curves/pull/54
|
|
345
|
+
// - https://github.com/paulmillr/noble-curves/issues/88
|
|
346
|
+
//
|
|
347
|
+
// When you compare the ed25519 publicKey of a given account produced by this function to the publicKey produced by
|
|
348
|
+
// polkadot-js, you will find that they are the same as eachother.
|
|
349
|
+
if (secretKey.length === 64) {
|
|
350
|
+
const [privateComponent, publicComponent] = [secretKey.slice(0, 32), secretKey.slice(32)];
|
|
351
|
+
const publicKey = ed25519.ed25519.getPublicKey(privateComponent);
|
|
352
|
+
|
|
353
|
+
// NOTE: We only accept a 64 byte secretKey when the first 32 bytes successfully produce a public key which equals the second 32 bytes of the secretKey.
|
|
354
|
+
//
|
|
355
|
+
// In this scenario, we assume the creator of the secretKey has given us an array of bytes which equals `[...privateKey, ...publicKey]`.
|
|
356
|
+
//
|
|
357
|
+
// However, if the second 32 bytes **don't** match the publicKey produced by the first 32 bytes, we no longer know what's going on.
|
|
358
|
+
//
|
|
359
|
+
// In that case we pass the 64 bytes directly through to `@noble/curves/ed25519`, which we expect will throw the error: `private key of length 32 expected, got 64`.
|
|
360
|
+
// But if there's some 64 byte key format for ed25519 we don't know about, or one is added in the future, `@noble/curves/ed25519` can handle that for us instead of throwing.
|
|
361
|
+
if (!isUint8ArrayEq(publicComponent, publicKey)) return ed25519.ed25519.getPublicKey(secretKey);
|
|
362
|
+
return publicKey;
|
|
363
|
+
}
|
|
329
364
|
return ed25519.ed25519.getPublicKey(secretKey);
|
|
330
365
|
};
|
|
331
366
|
|
|
367
|
+
/** If a is identical to b, this function returns true, otherwise it returns false */
|
|
368
|
+
const isUint8ArrayEq = (a, b) => a.length !== b.length || a.some((v, i) => v !== b[i]) ? false : true;
|
|
369
|
+
|
|
332
370
|
const deriveEthereum = (seed, derivationPath) => {
|
|
333
371
|
const hdkey = bip32.HDKey.fromMasterSeed(seed);
|
|
334
372
|
const childKey = hdkey.derive(derivationPath);
|
|
@@ -430,14 +468,14 @@ const getPublicKeyFromSecret = (secretKey, curve) => {
|
|
|
430
468
|
return getPublicKeySolana(secretKey);
|
|
431
469
|
}
|
|
432
470
|
};
|
|
433
|
-
const addressFromSuri = (suri, type) => {
|
|
471
|
+
const addressFromSuri = async (suri, type) => {
|
|
434
472
|
const {
|
|
435
473
|
mnemonic,
|
|
436
474
|
derivationPath,
|
|
437
475
|
password
|
|
438
476
|
} = parseSuri(suri);
|
|
439
477
|
const entropy = mnemonicToEntropy(mnemonic);
|
|
440
|
-
const seed = entropyToSeed(entropy, type, password); // ~80ms
|
|
478
|
+
const seed = await entropyToSeed(entropy, type, password); // ~80ms
|
|
441
479
|
const {
|
|
442
480
|
secretKey
|
|
443
481
|
} = deriveKeypair(seed, derivationPath, type);
|
|
@@ -487,9 +525,9 @@ const parseSecretKey = (secretKey, curve) => {
|
|
|
487
525
|
};
|
|
488
526
|
|
|
489
527
|
// @dev: didn't find a reliable source of information on which characters are valid => assume it s valid if a keypair can be generated from it
|
|
490
|
-
const isValidDerivationPath = (derivationPath, curve) => {
|
|
528
|
+
const isValidDerivationPath = async (derivationPath, curve) => {
|
|
491
529
|
try {
|
|
492
|
-
deriveKeypair(getDevSeed(curve), derivationPath, curve);
|
|
530
|
+
deriveKeypair(await getDevSeed(curve), derivationPath, curve);
|
|
493
531
|
return true;
|
|
494
532
|
} catch (err) {
|
|
495
533
|
return false;
|
|
@@ -562,6 +600,7 @@ exports.mnemonicToEntropy = mnemonicToEntropy;
|
|
|
562
600
|
exports.normalizeAddress = normalizeAddress;
|
|
563
601
|
exports.parseSecretKey = parseSecretKey;
|
|
564
602
|
exports.parseSuri = parseSuri;
|
|
603
|
+
exports.pbkdf2 = pbkdf2;
|
|
565
604
|
exports.platformFromAddress = platformFromAddress;
|
|
566
605
|
exports.platformFromCurve = platformFromCurve;
|
|
567
606
|
exports.platformFromEncoding = platformFromEncoding;
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { pbkdf2 } from '@noble/hashes/pbkdf2';
|
|
2
|
-
import { sha512 } from '@noble/hashes/sha512';
|
|
3
1
|
import { mnemonicToEntropy as mnemonicToEntropy$1, entropyToMnemonic as entropyToMnemonic$1, validateMnemonic, generateMnemonic as generateMnemonic$1 } from '@scure/bip39';
|
|
4
2
|
import { wordlist } from '@scure/bip39/wordlists/english';
|
|
5
3
|
import { base58, bytesToString, stringToBytes } from '@scure/base';
|
|
@@ -13,7 +11,22 @@ import { Tuple, str, Bytes, u32 } from 'scale-ts';
|
|
|
13
11
|
import { ed25519 } from '@noble/curves/ed25519';
|
|
14
12
|
import { HDKey } from '@scure/bip32';
|
|
15
13
|
import { hmac } from '@noble/hashes/hmac';
|
|
16
|
-
import {
|
|
14
|
+
import { sha512 } from '@noble/hashes/sha512';
|
|
15
|
+
import { getPublicKey, HDKD, secretFromSeed } from 'micro-sr25519';
|
|
16
|
+
|
|
17
|
+
const pbkdf2 = async (hash, entropy, salt, iterations, outputLenBytes) => {
|
|
18
|
+
// NOTE: react-native-quick-crypto (our `global.crypto` polyfill on Talisman Mobile) doesn't support `crypto.subtle.deriveKey`.
|
|
19
|
+
// But, we can work around this by using `crypto.subtle.deriveBits` and `crypto.subtle.importKey`, which when used together
|
|
20
|
+
// can provide the same functionality as `crypto.subtle.deriveKey`.
|
|
21
|
+
const keyMaterial = await crypto.subtle.importKey("raw", entropy, "PBKDF2", false, ["deriveBits"]);
|
|
22
|
+
const derivedBits = await crypto.subtle.deriveBits({
|
|
23
|
+
name: "PBKDF2",
|
|
24
|
+
salt,
|
|
25
|
+
iterations,
|
|
26
|
+
hash
|
|
27
|
+
}, keyMaterial, outputLenBytes * 8);
|
|
28
|
+
return new Uint8Array(derivedBits);
|
|
29
|
+
};
|
|
17
30
|
|
|
18
31
|
const mnemonicToEntropy = mnemonic => {
|
|
19
32
|
return mnemonicToEntropy$1(mnemonic, wordlist);
|
|
@@ -21,22 +34,18 @@ const mnemonicToEntropy = mnemonic => {
|
|
|
21
34
|
const entropyToMnemonic = entropy => {
|
|
22
35
|
return entropyToMnemonic$1(entropy, wordlist);
|
|
23
36
|
};
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
c: 2048,
|
|
37
|
-
dkLen: 64
|
|
38
|
-
});
|
|
39
|
-
};
|
|
37
|
+
const entropyToSeedSubstrate = async (entropy, password) => await pbkdf2("SHA-512", entropy, mnemonicPasswordToSalt(password ?? ""), 2048,
|
|
38
|
+
// 2048 iterations
|
|
39
|
+
32 // 32 bytes (32 * 8 == 256 bits)
|
|
40
|
+
);
|
|
41
|
+
const entropyToSeedClassic = async (entropy, password) => await pbkdf2("SHA-512", encodeNormalized(entropyToMnemonic(entropy)), mnemonicPasswordToSalt(password ?? ""), 2048,
|
|
42
|
+
// 2048 iterations
|
|
43
|
+
64 // 64 bytes (64 * 8 == 512 bits)
|
|
44
|
+
);
|
|
45
|
+
const mnemonicPasswordToSalt = password => encodeNormalized(`mnemonic${password}`);
|
|
46
|
+
|
|
47
|
+
/** Normalizes a UTF-8 string using `NFKD` form, then encodes it into bytes */
|
|
48
|
+
const encodeNormalized = utf8 => new TextEncoder().encode(utf8.normalize("NFKD"));
|
|
40
49
|
const getSeedDerivationType = curve => {
|
|
41
50
|
switch (curve) {
|
|
42
51
|
case "sr25519":
|
|
@@ -51,13 +60,13 @@ const getSeedDerivationType = curve => {
|
|
|
51
60
|
|
|
52
61
|
// when deriving keys from a mnemonic, we usually dont want a password here.
|
|
53
62
|
// a password provided here would be used as a 25th mnemonic word.
|
|
54
|
-
const entropyToSeed = (entropy, curve, password) => {
|
|
63
|
+
const entropyToSeed = async (entropy, curve, password) => {
|
|
55
64
|
const type = getSeedDerivationType(curve);
|
|
56
65
|
switch (type) {
|
|
57
66
|
case "classic":
|
|
58
|
-
return entropyToSeedClassic(entropy, password);
|
|
67
|
+
return await entropyToSeedClassic(entropy, password);
|
|
59
68
|
case "substrate":
|
|
60
|
-
return entropyToSeedSubstrate(entropy, password);
|
|
69
|
+
return await entropyToSeedSubstrate(entropy, password);
|
|
61
70
|
}
|
|
62
71
|
};
|
|
63
72
|
const isValidMnemonic = mnemonic => {
|
|
@@ -80,21 +89,21 @@ const DEV_MNEMONIC_ETHEREUM = "test test test test test test test test test test
|
|
|
80
89
|
|
|
81
90
|
// keep dev seeds in cache as we will reuse them to validate multiple derivation paths
|
|
82
91
|
const DEV_SEED_CACHE = new Map();
|
|
83
|
-
const getDevSeed = curve => {
|
|
92
|
+
const getDevSeed = async curve => {
|
|
84
93
|
const type = getSeedDerivationType(curve);
|
|
85
94
|
if (!DEV_SEED_CACHE.has(type)) {
|
|
86
95
|
switch (type) {
|
|
87
96
|
case "classic":
|
|
88
97
|
{
|
|
89
98
|
const entropy = mnemonicToEntropy(DEV_MNEMONIC_ETHEREUM);
|
|
90
|
-
const seed = entropyToSeedClassic(entropy); // 80ms
|
|
99
|
+
const seed = await entropyToSeedClassic(entropy); // 80ms
|
|
91
100
|
DEV_SEED_CACHE.set(type, seed);
|
|
92
101
|
break;
|
|
93
102
|
}
|
|
94
103
|
case "substrate":
|
|
95
104
|
{
|
|
96
105
|
const entropy = mnemonicToEntropy(DEV_MNEMONIC_POLKADOT);
|
|
97
|
-
const seed = entropyToSeedSubstrate(entropy); // 80ms
|
|
106
|
+
const seed = await entropyToSeedSubstrate(entropy); // 80ms
|
|
98
107
|
DEV_SEED_CACHE.set(type, seed);
|
|
99
108
|
break;
|
|
100
109
|
}
|
|
@@ -325,9 +334,38 @@ const deriveEd25519 = (seed, derivationPath) => {
|
|
|
325
334
|
};
|
|
326
335
|
};
|
|
327
336
|
const getPublicKeyEd25519 = secretKey => {
|
|
337
|
+
// When importing ed25519 polkadot-js accounts via json, which we do inside of `packages/extension-core/src/domains/keyring/getSecretKeyFromPjsJson.ts`,
|
|
338
|
+
// the secretKey we produce is 64 bytes in length.
|
|
339
|
+
//
|
|
340
|
+
// When using the ed25519 curve to derive a publicKey for this 64 bytes privateKey, we should only take the first 32 bytes:
|
|
341
|
+
// - https://github.com/paulmillr/noble-curves/issues/53#issuecomment-1577362759
|
|
342
|
+
// - https://github.com/paulmillr/noble-curves/discussions/33#discussioncomment-5685971
|
|
343
|
+
// - https://github.com/paulmillr/noble-curves/pull/54
|
|
344
|
+
// - https://github.com/paulmillr/noble-curves/issues/88
|
|
345
|
+
//
|
|
346
|
+
// When you compare the ed25519 publicKey of a given account produced by this function to the publicKey produced by
|
|
347
|
+
// polkadot-js, you will find that they are the same as eachother.
|
|
348
|
+
if (secretKey.length === 64) {
|
|
349
|
+
const [privateComponent, publicComponent] = [secretKey.slice(0, 32), secretKey.slice(32)];
|
|
350
|
+
const publicKey = ed25519.getPublicKey(privateComponent);
|
|
351
|
+
|
|
352
|
+
// NOTE: We only accept a 64 byte secretKey when the first 32 bytes successfully produce a public key which equals the second 32 bytes of the secretKey.
|
|
353
|
+
//
|
|
354
|
+
// In this scenario, we assume the creator of the secretKey has given us an array of bytes which equals `[...privateKey, ...publicKey]`.
|
|
355
|
+
//
|
|
356
|
+
// However, if the second 32 bytes **don't** match the publicKey produced by the first 32 bytes, we no longer know what's going on.
|
|
357
|
+
//
|
|
358
|
+
// In that case we pass the 64 bytes directly through to `@noble/curves/ed25519`, which we expect will throw the error: `private key of length 32 expected, got 64`.
|
|
359
|
+
// But if there's some 64 byte key format for ed25519 we don't know about, or one is added in the future, `@noble/curves/ed25519` can handle that for us instead of throwing.
|
|
360
|
+
if (!isUint8ArrayEq(publicComponent, publicKey)) return ed25519.getPublicKey(secretKey);
|
|
361
|
+
return publicKey;
|
|
362
|
+
}
|
|
328
363
|
return ed25519.getPublicKey(secretKey);
|
|
329
364
|
};
|
|
330
365
|
|
|
366
|
+
/** If a is identical to b, this function returns true, otherwise it returns false */
|
|
367
|
+
const isUint8ArrayEq = (a, b) => a.length !== b.length || a.some((v, i) => v !== b[i]) ? false : true;
|
|
368
|
+
|
|
331
369
|
const deriveEthereum = (seed, derivationPath) => {
|
|
332
370
|
const hdkey = HDKey.fromMasterSeed(seed);
|
|
333
371
|
const childKey = hdkey.derive(derivationPath);
|
|
@@ -429,14 +467,14 @@ const getPublicKeyFromSecret = (secretKey, curve) => {
|
|
|
429
467
|
return getPublicKeySolana(secretKey);
|
|
430
468
|
}
|
|
431
469
|
};
|
|
432
|
-
const addressFromSuri = (suri, type) => {
|
|
470
|
+
const addressFromSuri = async (suri, type) => {
|
|
433
471
|
const {
|
|
434
472
|
mnemonic,
|
|
435
473
|
derivationPath,
|
|
436
474
|
password
|
|
437
475
|
} = parseSuri(suri);
|
|
438
476
|
const entropy = mnemonicToEntropy(mnemonic);
|
|
439
|
-
const seed = entropyToSeed(entropy, type, password); // ~80ms
|
|
477
|
+
const seed = await entropyToSeed(entropy, type, password); // ~80ms
|
|
440
478
|
const {
|
|
441
479
|
secretKey
|
|
442
480
|
} = deriveKeypair(seed, derivationPath, type);
|
|
@@ -486,9 +524,9 @@ const parseSecretKey = (secretKey, curve) => {
|
|
|
486
524
|
};
|
|
487
525
|
|
|
488
526
|
// @dev: didn't find a reliable source of information on which characters are valid => assume it s valid if a keypair can be generated from it
|
|
489
|
-
const isValidDerivationPath = (derivationPath, curve) => {
|
|
527
|
+
const isValidDerivationPath = async (derivationPath, curve) => {
|
|
490
528
|
try {
|
|
491
|
-
deriveKeypair(getDevSeed(curve), derivationPath, curve);
|
|
529
|
+
deriveKeypair(await getDevSeed(curve), derivationPath, curve);
|
|
492
530
|
return true;
|
|
493
531
|
} catch (err) {
|
|
494
532
|
return false;
|
|
@@ -522,4 +560,4 @@ const platformFromAddress = address => {
|
|
|
522
560
|
return platformFromEncoding(encoding);
|
|
523
561
|
};
|
|
524
562
|
|
|
525
|
-
export { DEV_MNEMONIC_ETHEREUM, DEV_MNEMONIC_POLKADOT, addressEncodingFromCurve, addressFromPublicKey, addressFromSuri, blake2b256, blake2b512, blake3, checksumEthereumAddress, decodeSs58Address, deriveKeypair, detectAddressEncoding, encodeAddressBase58, encodeAddressEthereum, encodeAddressSs58, entropyToMnemonic, entropyToSeed, generateMnemonic, getDevSeed, getPublicKeyFromSecret, getSafeHash, isAddressEqual, isBase58Address, isEthereumAddress, isSs58Address, isValidDerivationPath, isValidMnemonic, mnemonicToEntropy, normalizeAddress, parseSecretKey, parseSuri, platformFromAddress, platformFromCurve, platformFromEncoding, removeHexPrefix };
|
|
563
|
+
export { DEV_MNEMONIC_ETHEREUM, DEV_MNEMONIC_POLKADOT, addressEncodingFromCurve, addressFromPublicKey, addressFromSuri, blake2b256, blake2b512, blake3, checksumEthereumAddress, decodeSs58Address, deriveKeypair, detectAddressEncoding, encodeAddressBase58, encodeAddressEthereum, encodeAddressSs58, entropyToMnemonic, entropyToSeed, generateMnemonic, getDevSeed, getPublicKeyFromSecret, getSafeHash, isAddressEqual, isBase58Address, isEthereumAddress, isSs58Address, isValidDerivationPath, isValidMnemonic, mnemonicToEntropy, normalizeAddress, parseSecretKey, parseSuri, pbkdf2, platformFromAddress, platformFromCurve, platformFromEncoding, removeHexPrefix };
|