@talismn/crypto 0.1.1 → 0.1.3

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,2 +1,3 @@
1
1
  import type { AddressEncoding, KeypairCurve } from "../../types";
2
+ /** NOTE: Try not to use this too much, it will need to change */
2
3
  export declare const addressEncodingFromCurve: (curve: KeypairCurve) => AddressEncoding;
@@ -0,0 +1,37 @@
1
+ export declare const isBitcoinAddress: (address: string) => boolean;
2
+ export declare function isBech32mAddress(address: string): boolean;
3
+ export declare function isBech32Address(address: string): boolean;
4
+ export declare function isBase58CheckAddress(address: string): boolean;
5
+ /**
6
+ * Converts a Bech32m encoded address to its corresponding data representation.
7
+ * @param address - The Bech32m encoded address.
8
+ * @returns An object containing the version, prefix, and data of the address.
9
+ * @throws {TypeError} If the address uses the wrong encoding.
10
+ */
11
+ export declare function fromBech32m(address: string): {
12
+ version: number;
13
+ prefix: string;
14
+ data: Uint8Array<ArrayBuffer>;
15
+ };
16
+ /**
17
+ * Converts a Bech32 encoded address to its corresponding data representation.
18
+ * @param address - The Bech32 encoded address.
19
+ * @returns An object containing the version, prefix, and data of the address.
20
+ * @throws {TypeError} If the address uses the wrong encoding.
21
+ */
22
+ export declare function fromBech32(address: string): {
23
+ version: number;
24
+ prefix: string;
25
+ data: Uint8Array<ArrayBuffer>;
26
+ };
27
+ /**
28
+ * Decodes a base58check encoded Bitcoin address and returns the version and hash.
29
+ *
30
+ * @param address - The base58check encoded Bitcoin address to decode.
31
+ * @returns An object containing the version and hash of the decoded address.
32
+ * @throws {TypeError} If the address is too short or too long.
33
+ */
34
+ export declare function fromBase58Check(address: string): {
35
+ version: number;
36
+ hash: Uint8Array<ArrayBuffer>;
37
+ };
@@ -1,5 +1,6 @@
1
+ export * from "./addressEncodingFromCurve";
1
2
  export * from "./base58";
3
+ export * from "./bitcoin";
4
+ export * from "./detectAddressEncoding";
2
5
  export * from "./ethereum";
3
6
  export * from "./ss58";
4
- export * from "./addressEncodingFromCurve";
5
- export * from "./detectAddressEncoding";
@@ -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,9 +1,9 @@
1
- export type KeypairCurve = "ecdsa" | "ed25519" | "sr25519" | "ethereum" | "solana";
2
- export type AddressEncoding = "ss58" | "ethereum" | "base58";
1
+ export type KeypairCurve = "ecdsa" | "ed25519" | "sr25519" | "ethereum" | "bitcoin-ed25519" | "bitcoin-ecdsa" | "solana";
2
+ export type AddressEncoding = "ss58" | "ethereum" | "bech32m" | "bech32" | "base58check" | "base58";
3
3
  export type Keypair = {
4
4
  type: KeypairCurve;
5
5
  secretKey: Uint8Array;
6
6
  publicKey: Uint8Array;
7
7
  address: string;
8
8
  };
9
- export type Platform = "ethereum" | "polkadot" | "solana";
9
+ export type Platform = "ethereum" | "polkadot" | "bitcoin" | "solana";
@@ -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,10 +1,10 @@
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');
6
+ var bech32 = require('bech32');
7
+ var bs58check = require('bs58check');
8
8
  var sha3 = require('@noble/hashes/sha3');
9
9
  var utils = require('@noble/hashes/utils');
10
10
  var blake2b = require('@noble/hashes/blake2b');
@@ -14,30 +14,45 @@ var scaleTs = require('scale-ts');
14
14
  var ed25519 = require('@noble/curves/ed25519');
15
15
  var bip32 = require('@scure/bip32');
16
16
  var hmac = require('@noble/hashes/hmac');
17
+ var sha512 = require('@noble/hashes/sha512');
17
18
  var microSr25519 = require('micro-sr25519');
18
19
 
20
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
21
+
22
+ var bs58check__default = /*#__PURE__*/_interopDefault(bs58check);
23
+
24
+ const pbkdf2 = async (hash, entropy, salt, iterations, outputLenBytes) => {
25
+ // NOTE: react-native-quick-crypto (our `global.crypto` polyfill on Talisman Mobile) doesn't support `crypto.subtle.deriveKey`.
26
+ // But, we can work around this by using `crypto.subtle.deriveBits` and `crypto.subtle.importKey`, which when used together
27
+ // can provide the same functionality as `crypto.subtle.deriveKey`.
28
+ const keyMaterial = await crypto.subtle.importKey("raw", entropy, "PBKDF2", false, ["deriveBits"]);
29
+ const derivedBits = await crypto.subtle.deriveBits({
30
+ name: "PBKDF2",
31
+ salt,
32
+ iterations,
33
+ hash
34
+ }, keyMaterial, outputLenBytes * 8);
35
+ return new Uint8Array(derivedBits);
36
+ };
37
+
19
38
  const mnemonicToEntropy = mnemonic => {
20
39
  return bip39.mnemonicToEntropy(mnemonic, english.wordlist);
21
40
  };
22
41
  const entropyToMnemonic = entropy => {
23
42
  return bip39.entropyToMnemonic(entropy, english.wordlist);
24
43
  };
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
- };
44
+ const entropyToSeedSubstrate = async (entropy, password) => await pbkdf2("SHA-512", entropy, mnemonicPasswordToSalt(password ?? ""), 2048,
45
+ // 2048 iterations
46
+ 32 // 32 bytes (32 * 8 == 256 bits)
47
+ );
48
+ const entropyToSeedClassic = async (entropy, password) => await pbkdf2("SHA-512", encodeNormalized(entropyToMnemonic(entropy)), mnemonicPasswordToSalt(password ?? ""), 2048,
49
+ // 2048 iterations
50
+ 64 // 64 bytes (64 * 8 == 512 bits)
51
+ );
52
+ const mnemonicPasswordToSalt = password => encodeNormalized(`mnemonic${password}`);
53
+
54
+ /** Normalizes a UTF-8 string using `NFKD` form, then encodes it into bytes */
55
+ const encodeNormalized = utf8 => new TextEncoder().encode(utf8.normalize("NFKD"));
41
56
  const getSeedDerivationType = curve => {
42
57
  switch (curve) {
43
58
  case "sr25519":
@@ -47,18 +62,21 @@ const getSeedDerivationType = curve => {
47
62
  case "ethereum":
48
63
  case "solana":
49
64
  return "classic";
65
+ case "bitcoin-ecdsa":
66
+ case "bitcoin-ed25519":
67
+ throw new Error("seed derivation is not implemented for Bitcoin");
50
68
  }
51
69
  };
52
70
 
53
71
  // when deriving keys from a mnemonic, we usually dont want a password here.
54
72
  // a password provided here would be used as a 25th mnemonic word.
55
- const entropyToSeed = (entropy, curve, password) => {
73
+ const entropyToSeed = async (entropy, curve, password) => {
56
74
  const type = getSeedDerivationType(curve);
57
75
  switch (type) {
58
76
  case "classic":
59
- return entropyToSeedClassic(entropy, password);
77
+ return await entropyToSeedClassic(entropy, password);
60
78
  case "substrate":
61
- return entropyToSeedSubstrate(entropy, password);
79
+ return await entropyToSeedSubstrate(entropy, password);
62
80
  }
63
81
  };
64
82
  const isValidMnemonic = mnemonic => {
@@ -81,21 +99,21 @@ const DEV_MNEMONIC_ETHEREUM = "test test test test test test test test test test
81
99
 
82
100
  // keep dev seeds in cache as we will reuse them to validate multiple derivation paths
83
101
  const DEV_SEED_CACHE = new Map();
84
- const getDevSeed = curve => {
102
+ const getDevSeed = async curve => {
85
103
  const type = getSeedDerivationType(curve);
86
104
  if (!DEV_SEED_CACHE.has(type)) {
87
105
  switch (type) {
88
106
  case "classic":
89
107
  {
90
108
  const entropy = mnemonicToEntropy(DEV_MNEMONIC_ETHEREUM);
91
- const seed = entropyToSeedClassic(entropy); // 80ms
109
+ const seed = await entropyToSeedClassic(entropy); // 80ms
92
110
  DEV_SEED_CACHE.set(type, seed);
93
111
  break;
94
112
  }
95
113
  case "substrate":
96
114
  {
97
115
  const entropy = mnemonicToEntropy(DEV_MNEMONIC_POLKADOT);
98
- const seed = entropyToSeedSubstrate(entropy); // 80ms
116
+ const seed = await entropyToSeedSubstrate(entropy); // 80ms
99
117
  DEV_SEED_CACHE.set(type, seed);
100
118
  break;
101
119
  }
@@ -106,6 +124,26 @@ const getDevSeed = curve => {
106
124
  return DEV_SEED_CACHE.get(type);
107
125
  };
108
126
 
127
+ /** NOTE: Try not to use this too much, it will need to change */
128
+ const addressEncodingFromCurve = curve => {
129
+ switch (curve) {
130
+ case "sr25519":
131
+ case "ed25519":
132
+ case "ecdsa":
133
+ return "ss58";
134
+ case "bitcoin-ecdsa":
135
+ case "bitcoin-ed25519":
136
+ // NOTE: Bitcoin has multiple address formats, so this isn't necessarily correct
137
+ // The format MAY be bech32m, but it might also be bech32 or base58check.
138
+ // bech32m is the most recent format.
139
+ return "bech32m";
140
+ case "ethereum":
141
+ return "ethereum";
142
+ case "solana":
143
+ return "base58";
144
+ }
145
+ };
146
+
109
147
  const encodeAddressBase58 = publicKey => {
110
148
  return base.base58.encode(publicKey);
111
149
  };
@@ -119,6 +157,94 @@ function isBase58Address(address) {
119
157
  }
120
158
  }
121
159
 
160
+ const isBitcoinAddress = address => isBech32mAddress(address) || isBech32Address(address) || isBase58CheckAddress(address);
161
+ function isBech32mAddress(address) {
162
+ try {
163
+ fromBech32m(address);
164
+ } catch {
165
+ return false;
166
+ }
167
+ return true;
168
+ }
169
+ function isBech32Address(address) {
170
+ try {
171
+ fromBech32(address);
172
+ } catch {
173
+ return false;
174
+ }
175
+ return true;
176
+ }
177
+ function isBase58CheckAddress(address) {
178
+ try {
179
+ fromBase58Check(address);
180
+ } catch {
181
+ return false;
182
+ }
183
+ return true;
184
+ }
185
+
186
+ /**
187
+ * Converts a Bech32m encoded address to its corresponding data representation.
188
+ * @param address - The Bech32m encoded address.
189
+ * @returns An object containing the version, prefix, and data of the address.
190
+ * @throws {TypeError} If the address uses the wrong encoding.
191
+ */
192
+ function fromBech32m(address) {
193
+ const result = bech32.bech32m.decode(address);
194
+ const version = result.words[0];
195
+ if (version === 0) throw new TypeError(address + " uses wrong encoding");
196
+ const data = bech32.bech32m.fromWords(result.words.slice(1));
197
+ return {
198
+ version,
199
+ prefix: result.prefix,
200
+ data: Uint8Array.from(data)
201
+ };
202
+ }
203
+
204
+ /**
205
+ * Converts a Bech32 encoded address to its corresponding data representation.
206
+ * @param address - The Bech32 encoded address.
207
+ * @returns An object containing the version, prefix, and data of the address.
208
+ * @throws {TypeError} If the address uses the wrong encoding.
209
+ */
210
+ function fromBech32(address) {
211
+ const result = bech32.bech32.decode(address);
212
+ const version = result.words[0];
213
+ if (version !== 0) throw new TypeError(address + " uses wrong encoding");
214
+ const data = bech32.bech32.fromWords(result.words.slice(1));
215
+ return {
216
+ version,
217
+ prefix: result.prefix,
218
+ data: Uint8Array.from(data)
219
+ };
220
+ }
221
+
222
+ /**
223
+ * Decodes a base58check encoded Bitcoin address and returns the version and hash.
224
+ *
225
+ * @param address - The base58check encoded Bitcoin address to decode.
226
+ * @returns An object containing the version and hash of the decoded address.
227
+ * @throws {TypeError} If the address is too short or too long.
228
+ */
229
+ function fromBase58Check(address) {
230
+ const payload = bs58check__default.default.decode(address);
231
+ if (payload.length < 21) throw new TypeError(address + " is too short");
232
+ if (payload.length > 21) throw new TypeError(address + " is too long");
233
+ function readUInt8(buffer, offset) {
234
+ if (offset + 1 > buffer.length) {
235
+ throw new Error("Offset is outside the bounds of Uint8Array");
236
+ }
237
+ const buf = Buffer.from(buffer);
238
+ return buf.readUInt8(offset);
239
+ }
240
+ const version = readUInt8(payload, 0);
241
+ const hash = payload.slice(1);
242
+ return {
243
+ version,
244
+ hash
245
+ };
246
+ }
247
+
122
248
  /**
123
249
  * Encodes a public key using H160 encoding with Ethereum checksum.
124
250
  */
@@ -206,23 +332,13 @@ function isSs58Address(address) {
206
332
  }
207
333
  }
208
334
 
209
- const addressEncodingFromCurve = curve => {
210
- switch (curve) {
211
- case "sr25519":
212
- case "ed25519":
213
- case "ecdsa":
214
- return "ss58";
215
- case "ethereum":
216
- return "ethereum";
217
- case "solana":
218
- return "base58";
219
- }
220
- };
221
-
222
335
  const CACHE$1 = new Map();
223
336
  const detectAddressEncodingInner = address => {
224
337
  if (isEthereumAddress(address)) return "ethereum";
225
338
  if (isSs58Address(address)) return "ss58";
339
+ if (isBech32mAddress(address)) return "bech32m";
340
+ if (isBech32Address(address)) return "bech32";
341
+ if (isBase58CheckAddress(address)) return "base58check";
226
342
  if (isBase58Address(address)) return "base58";
227
343
  throw new Error(`Unknown address encoding`);
228
344
  };
@@ -237,6 +353,10 @@ const addressFromPublicKey = (publicKey, encoding, options) => {
237
353
  return encodeAddressSs58(publicKey, options?.ss58Prefix);
238
354
  case "ethereum":
239
355
  return encodeAddressEthereum(publicKey);
356
+ case "bech32m":
357
+ case "bech32":
358
+ case "base58check":
359
+ throw new Error("addressFromPublicKey is not implemented for Bitcoin");
240
360
  case "base58":
241
361
  return encodeAddressBase58(publicKey);
242
362
  }
@@ -259,6 +379,9 @@ const normalizeAnyAddress = address => {
259
379
  switch (detectAddressEncoding(address)) {
260
380
  case "ethereum":
261
381
  return checksumEthereumAddress(address);
382
+ case "bech32m":
383
+ case "bech32":
384
+ case "base58check":
262
385
  case "base58":
263
386
  return address;
264
387
  case "ss58":
@@ -439,6 +562,9 @@ const deriveKeypair = (seed, derivationPath, curve) => {
439
562
  return deriveEd25519(seed, derivationPath);
440
563
  case "ecdsa":
441
564
  return deriveEcdsa(seed, derivationPath);
565
+ case "bitcoin-ecdsa":
566
+ case "bitcoin-ed25519":
567
+ throw new Error("deriveKeypair is not implemented for Bitcoin");
442
568
  case "ethereum":
443
569
  return deriveEthereum(seed, derivationPath);
444
570
  case "solana":
@@ -455,18 +581,21 @@ const getPublicKeyFromSecret = (secretKey, curve) => {
455
581
  return getPublicKeySr25519(secretKey);
456
582
  case "ed25519":
457
583
  return getPublicKeyEd25519(secretKey);
584
+ case "bitcoin-ecdsa":
585
+ case "bitcoin-ed25519":
586
+ throw new Error("getPublicKeyFromSecret is not implemented for Bitcoin");
458
587
  case "solana":
459
588
  return getPublicKeySolana(secretKey);
460
589
  }
461
590
  };
462
- const addressFromSuri = (suri, type) => {
591
+ const addressFromSuri = async (suri, type) => {
463
592
  const {
464
593
  mnemonic,
465
594
  derivationPath,
466
595
  password
467
596
  } = parseSuri(suri);
468
597
  const entropy = mnemonicToEntropy(mnemonic);
469
- const seed = entropyToSeed(entropy, type, password); // ~80ms
598
+ const seed = await entropyToSeed(entropy, type, password); // ~80ms
470
599
  const {
471
600
  secretKey
472
601
  } = deriveKeypair(seed, derivationPath, type);
@@ -510,15 +639,17 @@ const parseSecretKey = (secretKey, curve) => {
510
639
  case "ed25519":
511
640
  case "sr25519":
512
641
  case "ecdsa":
642
+ case "bitcoin-ecdsa":
643
+ case "bitcoin-ed25519":
513
644
  case "solana":
514
645
  throw new Error("Not implemented");
515
646
  }
516
647
  };
517
648
 
518
649
  // @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
519
- const isValidDerivationPath = (derivationPath, curve) => {
650
+ const isValidDerivationPath = async (derivationPath, curve) => {
520
651
  try {
521
- deriveKeypair(getDevSeed(curve), derivationPath, curve);
652
+ deriveKeypair(await getDevSeed(curve), derivationPath, curve);
522
653
  return true;
523
654
  } catch (err) {
524
655
  return false;
@@ -533,6 +664,9 @@ const platformFromCurve = curve => {
533
664
  return "polkadot";
534
665
  case "ethereum":
535
666
  return "ethereum";
667
+ case "bitcoin-ecdsa":
668
+ case "bitcoin-ed25519":
669
+ return "bitcoin";
536
670
  case "solana":
537
671
  return "solana";
538
672
  }
@@ -543,6 +677,10 @@ const platformFromEncoding = encoding => {
543
677
  return "polkadot";
544
678
  case "ethereum":
545
679
  return "ethereum";
680
+ case "bech32m":
681
+ case "bech32":
682
+ case "base58check":
683
+ return "bitcoin";
546
684
  case "base58":
547
685
  return "solana";
548
686
  }
@@ -577,12 +715,19 @@ exports.encodeAddressEthereum = encodeAddressEthereum;
577
715
  exports.encodeAddressSs58 = encodeAddressSs58;
578
716
  exports.entropyToMnemonic = entropyToMnemonic;
579
717
  exports.entropyToSeed = entropyToSeed;
718
+ exports.fromBase58Check = fromBase58Check;
719
+ exports.fromBech32 = fromBech32;
720
+ exports.fromBech32m = fromBech32m;
580
721
  exports.generateMnemonic = generateMnemonic;
581
722
  exports.getDevSeed = getDevSeed;
582
723
  exports.getPublicKeyFromSecret = getPublicKeyFromSecret;
583
724
  exports.getSafeHash = getSafeHash;
584
725
  exports.isAddressEqual = isAddressEqual;
585
726
  exports.isBase58Address = isBase58Address;
727
+ exports.isBase58CheckAddress = isBase58CheckAddress;
728
+ exports.isBech32Address = isBech32Address;
729
+ exports.isBech32mAddress = isBech32mAddress;
730
+ exports.isBitcoinAddress = isBitcoinAddress;
586
731
  exports.isEthereumAddress = isEthereumAddress;
587
732
  exports.isSs58Address = isSs58Address;
588
733
  exports.isValidDerivationPath = isValidDerivationPath;
@@ -591,6 +736,7 @@ exports.mnemonicToEntropy = mnemonicToEntropy;
591
736
  exports.normalizeAddress = normalizeAddress;
592
737
  exports.parseSecretKey = parseSecretKey;
593
738
  exports.parseSuri = parseSuri;
739
+ exports.pbkdf2 = pbkdf2;
594
740
  exports.platformFromAddress = platformFromAddress;
595
741
  exports.platformFromCurve = platformFromCurve;
596
742
  exports.platformFromEncoding = platformFromEncoding;
@@ -1,10 +1,10 @@
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');
6
+ var bech32 = require('bech32');
7
+ var bs58check = require('bs58check');
8
8
  var sha3 = require('@noble/hashes/sha3');
9
9
  var utils = require('@noble/hashes/utils');
10
10
  var blake2b = require('@noble/hashes/blake2b');
@@ -14,30 +14,45 @@ var scaleTs = require('scale-ts');
14
14
  var ed25519 = require('@noble/curves/ed25519');
15
15
  var bip32 = require('@scure/bip32');
16
16
  var hmac = require('@noble/hashes/hmac');
17
+ var sha512 = require('@noble/hashes/sha512');
17
18
  var microSr25519 = require('micro-sr25519');
18
19
 
20
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
21
+
22
+ var bs58check__default = /*#__PURE__*/_interopDefault(bs58check);
23
+
24
+ const pbkdf2 = async (hash, entropy, salt, iterations, outputLenBytes) => {
25
+ // NOTE: react-native-quick-crypto (our `global.crypto` polyfill on Talisman Mobile) doesn't support `crypto.subtle.deriveKey`.
26
+ // But, we can work around this by using `crypto.subtle.deriveBits` and `crypto.subtle.importKey`, which when used together
27
+ // can provide the same functionality as `crypto.subtle.deriveKey`.
28
+ const keyMaterial = await crypto.subtle.importKey("raw", entropy, "PBKDF2", false, ["deriveBits"]);
29
+ const derivedBits = await crypto.subtle.deriveBits({
30
+ name: "PBKDF2",
31
+ salt,
32
+ iterations,
33
+ hash
34
+ }, keyMaterial, outputLenBytes * 8);
35
+ return new Uint8Array(derivedBits);
36
+ };
37
+
19
38
  const mnemonicToEntropy = mnemonic => {
20
39
  return bip39.mnemonicToEntropy(mnemonic, english.wordlist);
21
40
  };
22
41
  const entropyToMnemonic = entropy => {
23
42
  return bip39.entropyToMnemonic(entropy, english.wordlist);
24
43
  };
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
- };
44
+ const entropyToSeedSubstrate = async (entropy, password) => await pbkdf2("SHA-512", entropy, mnemonicPasswordToSalt(password ?? ""), 2048,
45
+ // 2048 iterations
46
+ 32 // 32 bytes (32 * 8 == 256 bits)
47
+ );
48
+ const entropyToSeedClassic = async (entropy, password) => await pbkdf2("SHA-512", encodeNormalized(entropyToMnemonic(entropy)), mnemonicPasswordToSalt(password ?? ""), 2048,
49
+ // 2048 iterations
50
+ 64 // 64 bytes (64 * 8 == 512 bits)
51
+ );
52
+ const mnemonicPasswordToSalt = password => encodeNormalized(`mnemonic${password}`);
53
+
54
+ /** Normalizes a UTF-8 string using `NFKD` form, then encodes it into bytes */
55
+ const encodeNormalized = utf8 => new TextEncoder().encode(utf8.normalize("NFKD"));
41
56
  const getSeedDerivationType = curve => {
42
57
  switch (curve) {
43
58
  case "sr25519":
@@ -47,18 +62,21 @@ const getSeedDerivationType = curve => {
47
62
  case "ethereum":
48
63
  case "solana":
49
64
  return "classic";
65
+ case "bitcoin-ecdsa":
66
+ case "bitcoin-ed25519":
67
+ throw new Error("seed derivation is not implemented for Bitcoin");
50
68
  }
51
69
  };
52
70
 
53
71
  // when deriving keys from a mnemonic, we usually dont want a password here.
54
72
  // a password provided here would be used as a 25th mnemonic word.
55
- const entropyToSeed = (entropy, curve, password) => {
73
+ const entropyToSeed = async (entropy, curve, password) => {
56
74
  const type = getSeedDerivationType(curve);
57
75
  switch (type) {
58
76
  case "classic":
59
- return entropyToSeedClassic(entropy, password);
77
+ return await entropyToSeedClassic(entropy, password);
60
78
  case "substrate":
61
- return entropyToSeedSubstrate(entropy, password);
79
+ return await entropyToSeedSubstrate(entropy, password);
62
80
  }
63
81
  };
64
82
  const isValidMnemonic = mnemonic => {
@@ -81,21 +99,21 @@ const DEV_MNEMONIC_ETHEREUM = "test test test test test test test test test test
81
99
 
82
100
  // keep dev seeds in cache as we will reuse them to validate multiple derivation paths
83
101
  const DEV_SEED_CACHE = new Map();
84
- const getDevSeed = curve => {
102
+ const getDevSeed = async curve => {
85
103
  const type = getSeedDerivationType(curve);
86
104
  if (!DEV_SEED_CACHE.has(type)) {
87
105
  switch (type) {
88
106
  case "classic":
89
107
  {
90
108
  const entropy = mnemonicToEntropy(DEV_MNEMONIC_ETHEREUM);
91
- const seed = entropyToSeedClassic(entropy); // 80ms
109
+ const seed = await entropyToSeedClassic(entropy); // 80ms
92
110
  DEV_SEED_CACHE.set(type, seed);
93
111
  break;
94
112
  }
95
113
  case "substrate":
96
114
  {
97
115
  const entropy = mnemonicToEntropy(DEV_MNEMONIC_POLKADOT);
98
- const seed = entropyToSeedSubstrate(entropy); // 80ms
116
+ const seed = await entropyToSeedSubstrate(entropy); // 80ms
99
117
  DEV_SEED_CACHE.set(type, seed);
100
118
  break;
101
119
  }
@@ -106,6 +124,26 @@ const getDevSeed = curve => {
106
124
  return DEV_SEED_CACHE.get(type);
107
125
  };
108
126
 
127
+ /** NOTE: Try not to use this too much, it will need to change */
128
+ const addressEncodingFromCurve = curve => {
129
+ switch (curve) {
130
+ case "sr25519":
131
+ case "ed25519":
132
+ case "ecdsa":
133
+ return "ss58";
134
+ case "bitcoin-ecdsa":
135
+ case "bitcoin-ed25519":
136
+ // NOTE: Bitcoin has multiple address formats, so this isn't necessarily correct
137
+ // The format MAY be bech32m, but it might also be bech32 or base58check.
138
+ // bech32m is the most recent format.
139
+ return "bech32m";
140
+ case "ethereum":
141
+ return "ethereum";
142
+ case "solana":
143
+ return "base58";
144
+ }
145
+ };
146
+
109
147
  const encodeAddressBase58 = publicKey => {
110
148
  return base.base58.encode(publicKey);
111
149
  };
@@ -119,6 +157,94 @@ function isBase58Address(address) {
119
157
  }
120
158
  }
121
159
 
160
+ const isBitcoinAddress = address => isBech32mAddress(address) || isBech32Address(address) || isBase58CheckAddress(address);
161
+ function isBech32mAddress(address) {
162
+ try {
163
+ fromBech32m(address);
164
+ } catch {
165
+ return false;
166
+ }
167
+ return true;
168
+ }
169
+ function isBech32Address(address) {
170
+ try {
171
+ fromBech32(address);
172
+ } catch {
173
+ return false;
174
+ }
175
+ return true;
176
+ }
177
+ function isBase58CheckAddress(address) {
178
+ try {
179
+ fromBase58Check(address);
180
+ } catch {
181
+ return false;
182
+ }
183
+ return true;
184
+ }
185
+
186
+ /**
187
+ * Converts a Bech32m encoded address to its corresponding data representation.
188
+ * @param address - The Bech32m encoded address.
189
+ * @returns An object containing the version, prefix, and data of the address.
190
+ * @throws {TypeError} If the address uses the wrong encoding.
191
+ */
192
+ function fromBech32m(address) {
193
+ const result = bech32.bech32m.decode(address);
194
+ const version = result.words[0];
195
+ if (version === 0) throw new TypeError(address + " uses wrong encoding");
196
+ const data = bech32.bech32m.fromWords(result.words.slice(1));
197
+ return {
198
+ version,
199
+ prefix: result.prefix,
200
+ data: Uint8Array.from(data)
201
+ };
202
+ }
203
+
204
+ /**
205
+ * Converts a Bech32 encoded address to its corresponding data representation.
206
+ * @param address - The Bech32 encoded address.
207
+ * @returns An object containing the version, prefix, and data of the address.
208
+ * @throws {TypeError} If the address uses the wrong encoding.
209
+ */
210
+ function fromBech32(address) {
211
+ const result = bech32.bech32.decode(address);
212
+ const version = result.words[0];
213
+ if (version !== 0) throw new TypeError(address + " uses wrong encoding");
214
+ const data = bech32.bech32.fromWords(result.words.slice(1));
215
+ return {
216
+ version,
217
+ prefix: result.prefix,
218
+ data: Uint8Array.from(data)
219
+ };
220
+ }
221
+
222
+ /**
223
+ * Decodes a base58check encoded Bitcoin address and returns the version and hash.
224
+ *
225
+ * @param address - The base58check encoded Bitcoin address to decode.
226
+ * @returns An object containing the version and hash of the decoded address.
227
+ * @throws {TypeError} If the address is too short or too long.
228
+ */
229
+ function fromBase58Check(address) {
230
+ const payload = bs58check__default.default.decode(address);
231
+ if (payload.length < 21) throw new TypeError(address + " is too short");
232
+ if (payload.length > 21) throw new TypeError(address + " is too long");
233
+ function readUInt8(buffer, offset) {
234
+ if (offset + 1 > buffer.length) {
235
+ throw new Error("Offset is outside the bounds of Uint8Array");
236
+ }
237
+ const buf = Buffer.from(buffer);
238
+ return buf.readUInt8(offset);
239
+ }
240
+ const version = readUInt8(payload, 0);
241
+ const hash = payload.slice(1);
242
+ return {
243
+ version,
244
+ hash
245
+ };
246
+ }
247
+
122
248
  /**
123
249
  * Encodes a public key using H160 encoding with Ethereum checksum.
124
250
  */
@@ -206,23 +332,13 @@ function isSs58Address(address) {
206
332
  }
207
333
  }
208
334
 
209
- const addressEncodingFromCurve = curve => {
210
- switch (curve) {
211
- case "sr25519":
212
- case "ed25519":
213
- case "ecdsa":
214
- return "ss58";
215
- case "ethereum":
216
- return "ethereum";
217
- case "solana":
218
- return "base58";
219
- }
220
- };
221
-
222
335
  const CACHE$1 = new Map();
223
336
  const detectAddressEncodingInner = address => {
224
337
  if (isEthereumAddress(address)) return "ethereum";
225
338
  if (isSs58Address(address)) return "ss58";
339
+ if (isBech32mAddress(address)) return "bech32m";
340
+ if (isBech32Address(address)) return "bech32";
341
+ if (isBase58CheckAddress(address)) return "base58check";
226
342
  if (isBase58Address(address)) return "base58";
227
343
  throw new Error(`Unknown address encoding`);
228
344
  };
@@ -237,6 +353,10 @@ const addressFromPublicKey = (publicKey, encoding, options) => {
237
353
  return encodeAddressSs58(publicKey, options?.ss58Prefix);
238
354
  case "ethereum":
239
355
  return encodeAddressEthereum(publicKey);
356
+ case "bech32m":
357
+ case "bech32":
358
+ case "base58check":
359
+ throw new Error("addressFromPublicKey is not implemented for Bitcoin");
240
360
  case "base58":
241
361
  return encodeAddressBase58(publicKey);
242
362
  }
@@ -259,6 +379,9 @@ const normalizeAnyAddress = address => {
259
379
  switch (detectAddressEncoding(address)) {
260
380
  case "ethereum":
261
381
  return checksumEthereumAddress(address);
382
+ case "bech32m":
383
+ case "bech32":
384
+ case "base58check":
262
385
  case "base58":
263
386
  return address;
264
387
  case "ss58":
@@ -439,6 +562,9 @@ const deriveKeypair = (seed, derivationPath, curve) => {
439
562
  return deriveEd25519(seed, derivationPath);
440
563
  case "ecdsa":
441
564
  return deriveEcdsa(seed, derivationPath);
565
+ case "bitcoin-ecdsa":
566
+ case "bitcoin-ed25519":
567
+ throw new Error("deriveKeypair is not implemented for Bitcoin");
442
568
  case "ethereum":
443
569
  return deriveEthereum(seed, derivationPath);
444
570
  case "solana":
@@ -455,18 +581,21 @@ const getPublicKeyFromSecret = (secretKey, curve) => {
455
581
  return getPublicKeySr25519(secretKey);
456
582
  case "ed25519":
457
583
  return getPublicKeyEd25519(secretKey);
584
+ case "bitcoin-ecdsa":
585
+ case "bitcoin-ed25519":
586
+ throw new Error("getPublicKeyFromSecret is not implemented for Bitcoin");
458
587
  case "solana":
459
588
  return getPublicKeySolana(secretKey);
460
589
  }
461
590
  };
462
- const addressFromSuri = (suri, type) => {
591
+ const addressFromSuri = async (suri, type) => {
463
592
  const {
464
593
  mnemonic,
465
594
  derivationPath,
466
595
  password
467
596
  } = parseSuri(suri);
468
597
  const entropy = mnemonicToEntropy(mnemonic);
469
- const seed = entropyToSeed(entropy, type, password); // ~80ms
598
+ const seed = await entropyToSeed(entropy, type, password); // ~80ms
470
599
  const {
471
600
  secretKey
472
601
  } = deriveKeypair(seed, derivationPath, type);
@@ -510,15 +639,17 @@ const parseSecretKey = (secretKey, curve) => {
510
639
  case "ed25519":
511
640
  case "sr25519":
512
641
  case "ecdsa":
642
+ case "bitcoin-ecdsa":
643
+ case "bitcoin-ed25519":
513
644
  case "solana":
514
645
  throw new Error("Not implemented");
515
646
  }
516
647
  };
517
648
 
518
649
  // @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
519
- const isValidDerivationPath = (derivationPath, curve) => {
650
+ const isValidDerivationPath = async (derivationPath, curve) => {
520
651
  try {
521
- deriveKeypair(getDevSeed(curve), derivationPath, curve);
652
+ deriveKeypair(await getDevSeed(curve), derivationPath, curve);
522
653
  return true;
523
654
  } catch (err) {
524
655
  return false;
@@ -533,6 +664,9 @@ const platformFromCurve = curve => {
533
664
  return "polkadot";
534
665
  case "ethereum":
535
666
  return "ethereum";
667
+ case "bitcoin-ecdsa":
668
+ case "bitcoin-ed25519":
669
+ return "bitcoin";
536
670
  case "solana":
537
671
  return "solana";
538
672
  }
@@ -543,6 +677,10 @@ const platformFromEncoding = encoding => {
543
677
  return "polkadot";
544
678
  case "ethereum":
545
679
  return "ethereum";
680
+ case "bech32m":
681
+ case "bech32":
682
+ case "base58check":
683
+ return "bitcoin";
546
684
  case "base58":
547
685
  return "solana";
548
686
  }
@@ -577,12 +715,19 @@ exports.encodeAddressEthereum = encodeAddressEthereum;
577
715
  exports.encodeAddressSs58 = encodeAddressSs58;
578
716
  exports.entropyToMnemonic = entropyToMnemonic;
579
717
  exports.entropyToSeed = entropyToSeed;
718
+ exports.fromBase58Check = fromBase58Check;
719
+ exports.fromBech32 = fromBech32;
720
+ exports.fromBech32m = fromBech32m;
580
721
  exports.generateMnemonic = generateMnemonic;
581
722
  exports.getDevSeed = getDevSeed;
582
723
  exports.getPublicKeyFromSecret = getPublicKeyFromSecret;
583
724
  exports.getSafeHash = getSafeHash;
584
725
  exports.isAddressEqual = isAddressEqual;
585
726
  exports.isBase58Address = isBase58Address;
727
+ exports.isBase58CheckAddress = isBase58CheckAddress;
728
+ exports.isBech32Address = isBech32Address;
729
+ exports.isBech32mAddress = isBech32mAddress;
730
+ exports.isBitcoinAddress = isBitcoinAddress;
586
731
  exports.isEthereumAddress = isEthereumAddress;
587
732
  exports.isSs58Address = isSs58Address;
588
733
  exports.isValidDerivationPath = isValidDerivationPath;
@@ -591,6 +736,7 @@ exports.mnemonicToEntropy = mnemonicToEntropy;
591
736
  exports.normalizeAddress = normalizeAddress;
592
737
  exports.parseSecretKey = parseSecretKey;
593
738
  exports.parseSuri = parseSuri;
739
+ exports.pbkdf2 = pbkdf2;
594
740
  exports.platformFromAddress = platformFromAddress;
595
741
  exports.platformFromCurve = platformFromCurve;
596
742
  exports.platformFromEncoding = platformFromEncoding;
@@ -1,9 +1,9 @@
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';
6
4
  export { bytesToString, stringToBytes } from '@scure/base';
5
+ import { bech32m, bech32 } from 'bech32';
6
+ import bs58check from 'bs58check';
7
7
  import { keccak_256 } from '@noble/hashes/sha3';
8
8
  import { bytesToHex, randomBytes } from '@noble/hashes/utils';
9
9
  import { blake2b } from '@noble/hashes/blake2b';
@@ -13,7 +13,22 @@ import { Tuple, str, Bytes, u32 } from 'scale-ts';
13
13
  import { ed25519 } from '@noble/curves/ed25519';
14
14
  import { HDKey } from '@scure/bip32';
15
15
  import { hmac } from '@noble/hashes/hmac';
16
- import { HDKD, secretFromSeed, getPublicKey } from 'micro-sr25519';
16
+ import { sha512 } from '@noble/hashes/sha512';
17
+ import { getPublicKey, HDKD, secretFromSeed } from 'micro-sr25519';
18
+
19
+ const pbkdf2 = async (hash, entropy, salt, iterations, outputLenBytes) => {
20
+ // NOTE: react-native-quick-crypto (our `global.crypto` polyfill on Talisman Mobile) doesn't support `crypto.subtle.deriveKey`.
21
+ // But, we can work around this by using `crypto.subtle.deriveBits` and `crypto.subtle.importKey`, which when used together
22
+ // can provide the same functionality as `crypto.subtle.deriveKey`.
23
+ const keyMaterial = await crypto.subtle.importKey("raw", entropy, "PBKDF2", false, ["deriveBits"]);
24
+ const derivedBits = await crypto.subtle.deriveBits({
25
+ name: "PBKDF2",
26
+ salt,
27
+ iterations,
28
+ hash
29
+ }, keyMaterial, outputLenBytes * 8);
30
+ return new Uint8Array(derivedBits);
31
+ };
17
32
 
18
33
  const mnemonicToEntropy = mnemonic => {
19
34
  return mnemonicToEntropy$1(mnemonic, wordlist);
@@ -21,22 +36,18 @@ const mnemonicToEntropy = mnemonic => {
21
36
  const entropyToMnemonic = entropy => {
22
37
  return entropyToMnemonic$1(entropy, wordlist);
23
38
  };
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
- };
39
+ const entropyToSeedSubstrate = async (entropy, password) => await pbkdf2("SHA-512", entropy, mnemonicPasswordToSalt(password ?? ""), 2048,
40
+ // 2048 iterations
41
+ 32 // 32 bytes (32 * 8 == 256 bits)
42
+ );
43
+ const entropyToSeedClassic = async (entropy, password) => await pbkdf2("SHA-512", encodeNormalized(entropyToMnemonic(entropy)), mnemonicPasswordToSalt(password ?? ""), 2048,
44
+ // 2048 iterations
45
+ 64 // 64 bytes (64 * 8 == 512 bits)
46
+ );
47
+ const mnemonicPasswordToSalt = password => encodeNormalized(`mnemonic${password}`);
48
+
49
+ /** Normalizes a UTF-8 string using `NFKD` form, then encodes it into bytes */
50
+ const encodeNormalized = utf8 => new TextEncoder().encode(utf8.normalize("NFKD"));
40
51
  const getSeedDerivationType = curve => {
41
52
  switch (curve) {
42
53
  case "sr25519":
@@ -46,18 +57,21 @@ const getSeedDerivationType = curve => {
46
57
  case "ethereum":
47
58
  case "solana":
48
59
  return "classic";
60
+ case "bitcoin-ecdsa":
61
+ case "bitcoin-ed25519":
62
+ throw new Error("seed derivation is not implemented for Bitcoin");
49
63
  }
50
64
  };
51
65
 
52
66
  // when deriving keys from a mnemonic, we usually dont want a password here.
53
67
  // a password provided here would be used as a 25th mnemonic word.
54
- const entropyToSeed = (entropy, curve, password) => {
68
+ const entropyToSeed = async (entropy, curve, password) => {
55
69
  const type = getSeedDerivationType(curve);
56
70
  switch (type) {
57
71
  case "classic":
58
- return entropyToSeedClassic(entropy, password);
72
+ return await entropyToSeedClassic(entropy, password);
59
73
  case "substrate":
60
- return entropyToSeedSubstrate(entropy, password);
74
+ return await entropyToSeedSubstrate(entropy, password);
61
75
  }
62
76
  };
63
77
  const isValidMnemonic = mnemonic => {
@@ -80,21 +94,21 @@ const DEV_MNEMONIC_ETHEREUM = "test test test test test test test test test test
80
94
 
81
95
  // keep dev seeds in cache as we will reuse them to validate multiple derivation paths
82
96
  const DEV_SEED_CACHE = new Map();
83
- const getDevSeed = curve => {
97
+ const getDevSeed = async curve => {
84
98
  const type = getSeedDerivationType(curve);
85
99
  if (!DEV_SEED_CACHE.has(type)) {
86
100
  switch (type) {
87
101
  case "classic":
88
102
  {
89
103
  const entropy = mnemonicToEntropy(DEV_MNEMONIC_ETHEREUM);
90
- const seed = entropyToSeedClassic(entropy); // 80ms
104
+ const seed = await entropyToSeedClassic(entropy); // 80ms
91
105
  DEV_SEED_CACHE.set(type, seed);
92
106
  break;
93
107
  }
94
108
  case "substrate":
95
109
  {
96
110
  const entropy = mnemonicToEntropy(DEV_MNEMONIC_POLKADOT);
97
- const seed = entropyToSeedSubstrate(entropy); // 80ms
111
+ const seed = await entropyToSeedSubstrate(entropy); // 80ms
98
112
  DEV_SEED_CACHE.set(type, seed);
99
113
  break;
100
114
  }
@@ -105,6 +119,26 @@ const getDevSeed = curve => {
105
119
  return DEV_SEED_CACHE.get(type);
106
120
  };
107
121
 
122
+ /** NOTE: Try not to use this too much, it will need to change */
123
+ const addressEncodingFromCurve = curve => {
124
+ switch (curve) {
125
+ case "sr25519":
126
+ case "ed25519":
127
+ case "ecdsa":
128
+ return "ss58";
129
+ case "bitcoin-ecdsa":
130
+ case "bitcoin-ed25519":
131
+ // NOTE: Bitcoin has multiple address formats, so this isn't necessarily correct
132
+ // The format MAY be bech32m, but it might also be bech32 or base58check.
133
+ // bech32m is the most recent format.
134
+ return "bech32m";
135
+ case "ethereum":
136
+ return "ethereum";
137
+ case "solana":
138
+ return "base58";
139
+ }
140
+ };
141
+
108
142
  const encodeAddressBase58 = publicKey => {
109
143
  return base58.encode(publicKey);
110
144
  };
@@ -118,6 +152,94 @@ function isBase58Address(address) {
118
152
  }
119
153
  }
120
154
 
155
+ const isBitcoinAddress = address => isBech32mAddress(address) || isBech32Address(address) || isBase58CheckAddress(address);
156
+ function isBech32mAddress(address) {
157
+ try {
158
+ fromBech32m(address);
159
+ } catch {
160
+ return false;
161
+ }
162
+ return true;
163
+ }
164
+ function isBech32Address(address) {
165
+ try {
166
+ fromBech32(address);
167
+ } catch {
168
+ return false;
169
+ }
170
+ return true;
171
+ }
172
+ function isBase58CheckAddress(address) {
173
+ try {
174
+ fromBase58Check(address);
175
+ } catch {
176
+ return false;
177
+ }
178
+ return true;
179
+ }
180
+
181
+ /**
182
+ * Converts a Bech32m encoded address to its corresponding data representation.
183
+ * @param address - The Bech32m encoded address.
184
+ * @returns An object containing the version, prefix, and data of the address.
185
+ * @throws {TypeError} If the address uses the wrong encoding.
186
+ */
187
+ function fromBech32m(address) {
188
+ const result = bech32m.decode(address);
189
+ const version = result.words[0];
190
+ if (version === 0) throw new TypeError(address + " uses wrong encoding");
191
+ const data = bech32m.fromWords(result.words.slice(1));
192
+ return {
193
+ version,
194
+ prefix: result.prefix,
195
+ data: Uint8Array.from(data)
196
+ };
197
+ }
198
+
199
+ /**
200
+ * Converts a Bech32 encoded address to its corresponding data representation.
201
+ * @param address - The Bech32 encoded address.
202
+ * @returns An object containing the version, prefix, and data of the address.
203
+ * @throws {TypeError} If the address uses the wrong encoding.
204
+ */
205
+ function fromBech32(address) {
206
+ const result = bech32.decode(address);
207
+ const version = result.words[0];
208
+ if (version !== 0) throw new TypeError(address + " uses wrong encoding");
209
+ const data = bech32.fromWords(result.words.slice(1));
210
+ return {
211
+ version,
212
+ prefix: result.prefix,
213
+ data: Uint8Array.from(data)
214
+ };
215
+ }
216
+
217
+ /**
218
+ * Decodes a base58check encoded Bitcoin address and returns the version and hash.
219
+ *
220
+ * @param address - The base58check encoded Bitcoin address to decode.
221
+ * @returns An object containing the version and hash of the decoded address.
222
+ * @throws {TypeError} If the address is too short or too long.
223
+ */
224
+ function fromBase58Check(address) {
225
+ const payload = bs58check.decode(address);
226
+ if (payload.length < 21) throw new TypeError(address + " is too short");
227
+ if (payload.length > 21) throw new TypeError(address + " is too long");
228
+ function readUInt8(buffer, offset) {
229
+ if (offset + 1 > buffer.length) {
230
+ throw new Error("Offset is outside the bounds of Uint8Array");
231
+ }
232
+ const buf = Buffer.from(buffer);
233
+ return buf.readUInt8(offset);
234
+ }
235
+ const version = readUInt8(payload, 0);
236
+ const hash = payload.slice(1);
237
+ return {
238
+ version,
239
+ hash
240
+ };
241
+ }
242
+
121
243
  /**
122
244
  * Encodes a public key using H160 encoding with Ethereum checksum.
123
245
  */
@@ -205,23 +327,13 @@ function isSs58Address(address) {
205
327
  }
206
328
  }
207
329
 
208
- const addressEncodingFromCurve = curve => {
209
- switch (curve) {
210
- case "sr25519":
211
- case "ed25519":
212
- case "ecdsa":
213
- return "ss58";
214
- case "ethereum":
215
- return "ethereum";
216
- case "solana":
217
- return "base58";
218
- }
219
- };
220
-
221
330
  const CACHE$1 = new Map();
222
331
  const detectAddressEncodingInner = address => {
223
332
  if (isEthereumAddress(address)) return "ethereum";
224
333
  if (isSs58Address(address)) return "ss58";
334
+ if (isBech32mAddress(address)) return "bech32m";
335
+ if (isBech32Address(address)) return "bech32";
336
+ if (isBase58CheckAddress(address)) return "base58check";
225
337
  if (isBase58Address(address)) return "base58";
226
338
  throw new Error(`Unknown address encoding`);
227
339
  };
@@ -236,6 +348,10 @@ const addressFromPublicKey = (publicKey, encoding, options) => {
236
348
  return encodeAddressSs58(publicKey, options?.ss58Prefix);
237
349
  case "ethereum":
238
350
  return encodeAddressEthereum(publicKey);
351
+ case "bech32m":
352
+ case "bech32":
353
+ case "base58check":
354
+ throw new Error("addressFromPublicKey is not implemented for Bitcoin");
239
355
  case "base58":
240
356
  return encodeAddressBase58(publicKey);
241
357
  }
@@ -258,6 +374,9 @@ const normalizeAnyAddress = address => {
258
374
  switch (detectAddressEncoding(address)) {
259
375
  case "ethereum":
260
376
  return checksumEthereumAddress(address);
377
+ case "bech32m":
378
+ case "bech32":
379
+ case "base58check":
261
380
  case "base58":
262
381
  return address;
263
382
  case "ss58":
@@ -438,6 +557,9 @@ const deriveKeypair = (seed, derivationPath, curve) => {
438
557
  return deriveEd25519(seed, derivationPath);
439
558
  case "ecdsa":
440
559
  return deriveEcdsa(seed, derivationPath);
560
+ case "bitcoin-ecdsa":
561
+ case "bitcoin-ed25519":
562
+ throw new Error("deriveKeypair is not implemented for Bitcoin");
441
563
  case "ethereum":
442
564
  return deriveEthereum(seed, derivationPath);
443
565
  case "solana":
@@ -454,18 +576,21 @@ const getPublicKeyFromSecret = (secretKey, curve) => {
454
576
  return getPublicKeySr25519(secretKey);
455
577
  case "ed25519":
456
578
  return getPublicKeyEd25519(secretKey);
579
+ case "bitcoin-ecdsa":
580
+ case "bitcoin-ed25519":
581
+ throw new Error("getPublicKeyFromSecret is not implemented for Bitcoin");
457
582
  case "solana":
458
583
  return getPublicKeySolana(secretKey);
459
584
  }
460
585
  };
461
- const addressFromSuri = (suri, type) => {
586
+ const addressFromSuri = async (suri, type) => {
462
587
  const {
463
588
  mnemonic,
464
589
  derivationPath,
465
590
  password
466
591
  } = parseSuri(suri);
467
592
  const entropy = mnemonicToEntropy(mnemonic);
468
- const seed = entropyToSeed(entropy, type, password); // ~80ms
593
+ const seed = await entropyToSeed(entropy, type, password); // ~80ms
469
594
  const {
470
595
  secretKey
471
596
  } = deriveKeypair(seed, derivationPath, type);
@@ -509,15 +634,17 @@ const parseSecretKey = (secretKey, curve) => {
509
634
  case "ed25519":
510
635
  case "sr25519":
511
636
  case "ecdsa":
637
+ case "bitcoin-ecdsa":
638
+ case "bitcoin-ed25519":
512
639
  case "solana":
513
640
  throw new Error("Not implemented");
514
641
  }
515
642
  };
516
643
 
517
644
  // @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
518
- const isValidDerivationPath = (derivationPath, curve) => {
645
+ const isValidDerivationPath = async (derivationPath, curve) => {
519
646
  try {
520
- deriveKeypair(getDevSeed(curve), derivationPath, curve);
647
+ deriveKeypair(await getDevSeed(curve), derivationPath, curve);
521
648
  return true;
522
649
  } catch (err) {
523
650
  return false;
@@ -532,6 +659,9 @@ const platformFromCurve = curve => {
532
659
  return "polkadot";
533
660
  case "ethereum":
534
661
  return "ethereum";
662
+ case "bitcoin-ecdsa":
663
+ case "bitcoin-ed25519":
664
+ return "bitcoin";
535
665
  case "solana":
536
666
  return "solana";
537
667
  }
@@ -542,6 +672,10 @@ const platformFromEncoding = encoding => {
542
672
  return "polkadot";
543
673
  case "ethereum":
544
674
  return "ethereum";
675
+ case "bech32m":
676
+ case "bech32":
677
+ case "base58check":
678
+ return "bitcoin";
545
679
  case "base58":
546
680
  return "solana";
547
681
  }
@@ -551,4 +685,4 @@ const platformFromAddress = address => {
551
685
  return platformFromEncoding(encoding);
552
686
  };
553
687
 
554
- 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 };
688
+ export { DEV_MNEMONIC_ETHEREUM, DEV_MNEMONIC_POLKADOT, addressEncodingFromCurve, addressFromPublicKey, addressFromSuri, blake2b256, blake2b512, blake3, checksumEthereumAddress, decodeSs58Address, deriveKeypair, detectAddressEncoding, encodeAddressBase58, encodeAddressEthereum, encodeAddressSs58, entropyToMnemonic, entropyToSeed, fromBase58Check, fromBech32, fromBech32m, generateMnemonic, getDevSeed, getPublicKeyFromSecret, getSafeHash, isAddressEqual, isBase58Address, isBase58CheckAddress, isBech32Address, isBech32mAddress, isBitcoinAddress, 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.1",
3
+ "version": "0.1.3",
4
4
  "author": "Talisman",
5
5
  "homepage": "https://talisman.xyz",
6
6
  "license": "GPL-3.0-or-later",
@@ -27,6 +27,8 @@
27
27
  "@scure/base": "1.2.4",
28
28
  "@scure/bip32": "1.6.2",
29
29
  "@scure/bip39": "1.5.4",
30
+ "bech32": "2.0.0",
31
+ "bs58check": "4.0.0",
30
32
  "micro-sr25519": "0.1.3",
31
33
  "scale-ts": "1.6.1"
32
34
  },
@@ -36,8 +38,8 @@
36
38
  "jest": "^29.7.0",
37
39
  "ts-jest": "^29.2.5",
38
40
  "typescript": "^5.6.3",
39
- "@talismn/tsconfig": "0.0.2",
40
- "@talismn/eslint-config": "0.0.3"
41
+ "@talismn/eslint-config": "0.0.3",
42
+ "@talismn/tsconfig": "0.0.2"
41
43
  },
42
44
  "preconstruct": {
43
45
  "entrypoints": [