UNPKG

@hashgraph/cryptography

Version:

Cryptographic utilities and primitives for the Hiero SDK

360 lines (328 loc) 13.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.SLIP44_ECDSA_HEDERA_PATH = exports.SLIP44_ECDSA_ETH_PATH = exports.HEDERA_PATH = exports.HARDENED = void 0; var _Cache = _interopRequireDefault(require("./Cache.cjs")); var _Ed25519PrivateKey = _interopRequireDefault(require("./Ed25519PrivateKey.cjs")); var _BadMnemonicError = _interopRequireDefault(require("./BadMnemonicError.cjs")); var _BadMnemonicReason = _interopRequireDefault(require("./BadMnemonicReason.cjs")); var _legacy = _interopRequireDefault(require("./words/legacy.cjs")); var _bip = _interopRequireDefault(require("./words/bip39.cjs")); var _tweetnacl = _interopRequireDefault(require("tweetnacl")); var sha256 = _interopRequireWildcard(require("./primitive/sha256.cjs")); var hmac = _interopRequireWildcard(require("./primitive/hmac.cjs")); var slip10 = _interopRequireWildcard(require("./primitive/slip10.cjs")); var bip32 = _interopRequireWildcard(require("./primitive/bip32.cjs")); var bip39 = _interopRequireWildcard(require("./primitive/bip39.cjs")); var entropy = _interopRequireWildcard(require("./util/entropy.cjs")); var random = _interopRequireWildcard(require("./primitive/random.cjs")); var _EcdsaPrivateKey = _interopRequireDefault(require("./EcdsaPrivateKey.cjs")); var _PrivateKey = _interopRequireDefault(require("./PrivateKey.cjs")); var ecdsa = _interopRequireWildcard(require("./primitive/ecdsa.cjs")); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } const ED25519_SEED_TEXT = "ed25519 seed"; const ECDSA_SEED_TEXT = "Bitcoin seed"; const HARDENED = exports.HARDENED = 0x80000000; /// m/44'/3030'/0'/0' - All paths in EdDSA derivation are implicitly hardened. const HEDERA_PATH = exports.HEDERA_PATH = [44, 3030, 0, 0]; /// m/44'/3030'/0'/0 const SLIP44_ECDSA_HEDERA_PATH = exports.SLIP44_ECDSA_HEDERA_PATH = [44 | HARDENED, 3030 | HARDENED, 0 | HARDENED, 0]; /// m/44'/60'/0'/0 const SLIP44_ECDSA_ETH_PATH = exports.SLIP44_ECDSA_ETH_PATH = [44 | HARDENED, 60 | HARDENED, 0 | HARDENED, 0, 0]; /** * Multi-word mnemonic phrase (BIP-39). * * Compatible with the official Hedera mobile * wallets (24-words or 22-words) and BRD (12-words). */ class Mnemonic { /** * @param {object} props * @param {string[]} props.words * @throws {BadMnemonicError} * @hideconstructor * @private */ constructor({ words }) { this.words = words; } /** * Returns a new random 24-word mnemonic from the BIP-39 * standard English word list. * @returns {Promise<Mnemonic>} */ static generate() { return Mnemonic._generate(24); } /** * Returns a new random 12-word mnemonic from the BIP-39 * standard English word list. * @returns {Promise<Mnemonic>} */ static generate12() { return Mnemonic._generate(12); } /** * @param {number} length * @returns {Promise<Mnemonic>} */ static async _generate(length) { // only 12-word or 24-word lengths are supported let neededEntropy; if (length === 12) neededEntropy = 16;else if (length === 24) neededEntropy = 32;else { throw new Error(`unsupported phrase length ${length}, only 12 or 24 are supported`); } // inlined from (ISC) with heavy alternations for modern crypto // https://github.com/bitcoinjs/bip39/blob/8461e83677a1d2c685d0d5a9ba2a76bd228f74c6/ts_src/index.ts#L125 const seed = await random.bytesAsync(neededEntropy); const entropyBits = bytesToBinary(Array.from(seed)); const checksumBits = await deriveChecksumBits(seed); const bits = entropyBits + checksumBits; const chunks = bits.match(/(.{1,11})/g); const words = (chunks != null ? chunks : []).map(binary => _bip.default[binaryToByte(binary)]); return new Mnemonic({ words }); } /** * Construct a mnemonic from a list of words. Handles 12, 22 (legacy), and 24 words. * * An exception of BadMnemonicError will be thrown if the mnemonic * contains unknown words or fails the checksum. An invalid mnemonic * can still be used to create private keys, the exception will * contain the failing mnemonic in case you wish to ignore the * validation error and continue. * @param {string[]} words * @throws {BadMnemonicError} * @returns {Promise<Mnemonic>} */ static fromWords(words) { return new Mnemonic({ words })._validate(); } /** * @deprecated - Use `toStandardEd25519PrivateKey()` or `toStandardECDSAsecp256k1PrivateKey()` instead * Recover a private key from this mnemonic phrase, with an * optional passphrase. * @param {string} [passphrase] * @returns {Promise<PrivateKey>} */ toPrivateKey(passphrase = "") { // eslint-disable-next-line deprecation/deprecation return this.toEd25519PrivateKey(passphrase); } /** * @deprecated - Use `toStandardEd25519PrivateKey()` or `toStandardECDSAsecp256k1PrivateKey()` instead * Recover an Ed25519 private key from this mnemonic phrase, with an * optional passphrase. * @param {string} [passphrase] * @param {number[]} [path] * @returns {Promise<PrivateKey>} */ async toEd25519PrivateKey(passphrase = "", path = HEDERA_PATH) { let { keyData, chainCode } = await this._toKeyData(passphrase, ED25519_SEED_TEXT); for (const index of path) { ({ keyData, chainCode } = await slip10.derive(keyData, chainCode, index)); } const keyPair = _tweetnacl.default.sign.keyPair.fromSeed(keyData); if (_Cache.default.privateKeyConstructor == null) { throw new Error("PrivateKey not found in cache"); } return _Cache.default.privateKeyConstructor(new _Ed25519PrivateKey.default(keyPair, chainCode)); } /** * Recover an Ed25519 private key from this mnemonic phrase, with an * optional passphrase. * @param {string} [passphrase] * @param {number} [index] * @returns {Promise<PrivateKey>} */ async toStandardEd25519PrivateKey(passphrase = "", index) { const seed = await Mnemonic.toSeed(this.words, passphrase); let derivedKey = await _PrivateKey.default.fromSeedED25519(seed); index = index == null ? 0 : index; for (const currentIndex of [44, 3030, 0, 0, index]) { derivedKey = await derivedKey.derive(currentIndex); } return derivedKey; } /** * @deprecated - Use `toStandardEd25519PrivateKey()` or `toStandardECDSAsecp256k1PrivateKey()` instead * Recover an ECDSA private key from this mnemonic phrase, with an * optional passphrase. * @param {string} [passphrase] * @param {number[]} [path] * @returns {Promise<PrivateKey>} */ async toEcdsaPrivateKey(passphrase = "", path = HEDERA_PATH) { let { keyData, chainCode } = await this._toKeyData(passphrase, ECDSA_SEED_TEXT); for (const index of path) { ({ keyData, chainCode } = await bip32.derive(keyData, chainCode, index)); } if (_Cache.default.privateKeyConstructor == null) { throw new Error("PrivateKey not found in cache"); } return _Cache.default.privateKeyConstructor(new _EcdsaPrivateKey.default(ecdsa.fromBytes(keyData), chainCode)); } /** * Recover an ECDSA private key from this mnemonic phrase, with an * optional passphrase. * @param {string} [passphrase] * @param {number} [index] * @returns {Promise<PrivateKey>} */ async toStandardECDSAsecp256k1PrivateKey(passphrase = "", index) { const seed = await Mnemonic.toSeed(this.words, passphrase); let derivedKey = await _PrivateKey.default.fromSeedECDSAsecp256k1(seed); index = index == null ? 0 : index; for (const currentIndex of [bip32.toHardenedIndex(44), bip32.toHardenedIndex(3030), bip32.toHardenedIndex(0), 0, index]) { derivedKey = await derivedKey.derive(currentIndex); } return derivedKey; } /** * @param {string[]} words * @param {string} passphrase * @returns {Promise<Uint8Array>} */ static async toSeed(words, passphrase) { return await bip39.toSeed(words, passphrase); } /** * @param {string} passphrase * @param {string} seedText * @returns {Promise<{ keyData: Uint8Array; chainCode: Uint8Array }>} seedText */ async _toKeyData(passphrase, seedText) { const seed = await bip39.toSeed(this.words, passphrase); const digest = await hmac.hash(hmac.HashAlgorithm.Sha512, seedText, seed); return { keyData: digest.subarray(0, 32), chainCode: digest.subarray(32) }; } /** * Recover a mnemonic phrase from a string, splitting on spaces. Handles 12, 22 (legacy), and 24 words. * @param {string} mnemonic * @returns {Promise<Mnemonic>} */ static async fromString(mnemonic) { return Mnemonic.fromWords(mnemonic.split(/\s|,/)); } /** * @returns {Promise<Mnemonic>} * @private */ async _validate() { //NOSONAR // Validate that this is a valid BIP-39 mnemonic // as generated by BIP-39's rules. // Technically, invalid mnemonics can still be used to generate valid private keys, // but if they became invalid due to user error then it will be difficult for the user // to tell the difference unless they compare the generated keys. // During validation, the following conditions are checked in order // 1)) 24 or 12 words // 2) All strings in {@link this.words} exist in the BIP-39 // standard English word list (no normalization is done) // 3) The calculated checksum for the mnemonic equals the // checksum encoded in the mnemonic // If words count is 22, it means that this is a legacy private key if (this.words.length === 22) { const unknownWordIndices = this.words.reduce((/** @type {number[]} */unknowns, word, index) => _legacy.default.includes(word.toLowerCase()) ? unknowns : [...unknowns, index], []); if (unknownWordIndices.length > 0) { throw new _BadMnemonicError.default(this, _BadMnemonicReason.default.UnknownWords, unknownWordIndices); } const [seed, checksum] = entropy.legacy1(this.words, _legacy.default); const newChecksum = entropy.crc8(seed); if (checksum !== newChecksum) { throw new _BadMnemonicError.default(this, _BadMnemonicReason.default.ChecksumMismatch, []); } } else { if (!(this.words.length === 12 || this.words.length === 24)) { throw new _BadMnemonicError.default(this, _BadMnemonicReason.default.BadLength, []); } const unknownWordIndices = this.words.reduce((/** @type {number[]} */unknowns, word, index) => _bip.default.includes(word) ? unknowns : [...unknowns, index], []); if (unknownWordIndices.length > 0) { throw new _BadMnemonicError.default(this, _BadMnemonicReason.default.UnknownWords, unknownWordIndices); } // FIXME: calculate checksum and compare // https://github.com/bitcoinjs/bip39/blob/master/ts_src/index.ts#L112 const bits = this.words.map(word => { return _bip.default.indexOf(word).toString(2).padStart(11, "0"); }).join(""); const dividerIndex = Math.floor(bits.length / 33) * 32; const entropyBits = bits.slice(0, dividerIndex); const checksumBits = bits.slice(dividerIndex); const entropyBitsRegex = entropyBits.match(/(.{1,8})/g); const entropyBytes = /** @type {RegExpMatchArray} */entropyBitsRegex.map(binaryToByte); const newChecksum = await deriveChecksumBits(Uint8Array.from(entropyBytes)); if (newChecksum !== checksumBits) { throw new _BadMnemonicError.default(this, _BadMnemonicReason.default.ChecksumMismatch, []); } } return this; } /** * @returns {Promise<PrivateKey>} */ async toLegacyPrivateKey() { let seed; if (this.words.length === 22) { [seed] = entropy.legacy1(this.words, _legacy.default); } else { seed = await entropy.legacy2(this.words, _bip.default); } if (_Cache.default.privateKeyFromBytes == null) { throw new Error("PrivateKey not found in cache"); } return _Cache.default.privateKeyFromBytes(seed); } /** * @returns {string} */ toString() { return this.words.join(" "); } } /** * @param {string} bin * @returns {number} */ exports.default = Mnemonic; function binaryToByte(bin) { return parseInt(bin, 2); } /** * @param {number[]} bytes * @returns {string} */ function bytesToBinary(bytes) { return bytes.map(x => x.toString(2).padStart(8, "0")).join(""); } /** * @param {Uint8Array} entropyBuffer * @returns {Promise<string>} */ async function deriveChecksumBits(entropyBuffer) { const ENT = entropyBuffer.length * 8; const CS = ENT / 32; const hash = await sha256.digest(entropyBuffer); return bytesToBinary(Array.from(hash)).slice(0, CS); }