@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.
@@ -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>>;
@@ -1 +1,2 @@
1
1
  export * from "./exports";
2
+ export * from "./pbkdf2";
@@ -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 salt = password => {
26
- return new TextEncoder().encode(`mnemonic${password.normalize("NFKD")}`);
27
- };
28
- const entropyToSeedSubstrate = (entropy, password) => {
29
- return pbkdf2.pbkdf2(sha512.sha512, entropy, salt(password ?? ""), {
30
- c: 2048,
31
- dkLen: 32
32
- });
33
- };
34
- const entropyToSeedClassic = (entropy, password) => {
35
- const mnemonic = entropyToMnemonic(entropy);
36
- return pbkdf2.pbkdf2(sha512.sha512, mnemonic.normalize("NFKD"), salt(password ?? ""), {
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 salt = password => {
26
- return new TextEncoder().encode(`mnemonic${password.normalize("NFKD")}`);
27
- };
28
- const entropyToSeedSubstrate = (entropy, password) => {
29
- return pbkdf2.pbkdf2(sha512.sha512, entropy, salt(password ?? ""), {
30
- c: 2048,
31
- dkLen: 32
32
- });
33
- };
34
- const entropyToSeedClassic = (entropy, password) => {
35
- const mnemonic = entropyToMnemonic(entropy);
36
- return pbkdf2.pbkdf2(sha512.sha512, mnemonic.normalize("NFKD"), salt(password ?? ""), {
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 { HDKD, secretFromSeed, getPublicKey } from 'micro-sr25519';
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 salt = password => {
25
- return new TextEncoder().encode(`mnemonic${password.normalize("NFKD")}`);
26
- };
27
- const entropyToSeedSubstrate = (entropy, password) => {
28
- return pbkdf2(sha512, entropy, salt(password ?? ""), {
29
- c: 2048,
30
- dkLen: 32
31
- });
32
- };
33
- const entropyToSeedClassic = (entropy, password) => {
34
- const mnemonic = entropyToMnemonic(entropy);
35
- return pbkdf2(sha512, mnemonic.normalize("NFKD"), salt(password ?? ""), {
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@talismn/crypto",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "author": "Talisman",
5
5
  "homepage": "https://talisman.xyz",
6
6
  "license": "GPL-3.0-or-later",