UNPKG

@ton/crypto

Version:

[![Version npm](https://img.shields.io/npm/v/@ton/crypto.svg?logo=npm)](https://www.npmjs.com/package/@ton/crypto)

241 lines (240 loc) 9.59 kB
"use strict"; /** * Copyright (c) Whales Corp. * All Rights Reserved. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.mnemonicFromRandomSeed = exports.mnemonicIndexesToBytes = exports.bytesToMnemonics = exports.bytesToMnemonicIndexes = exports.mnemonicNew = exports.mnemonicValidate = exports.mnemonicToHDSeed = exports.mnemonicToWalletKey = exports.mnemonicToPrivateKey = exports.mnemonicToSeed = exports.mnemonicToEntropy = void 0; const tweetnacl_1 = __importDefault(require("tweetnacl")); const getSecureRandom_1 = require("../primitives/getSecureRandom"); const hmac_sha512_1 = require("../primitives/hmac_sha512"); const pbkdf2_sha512_1 = require("../primitives/pbkdf2_sha512"); const binary_1 = require("../utils/binary"); const wordlist_1 = require("./wordlist"); const PBKDF_ITERATIONS = 100000; async function isPasswordNeeded(mnemonicArray) { const passlessEntropy = await mnemonicToEntropy(mnemonicArray); return (await isPasswordSeed(passlessEntropy)) && !(await isBasicSeed(passlessEntropy)); } function normalizeMnemonic(src) { return src.map((v) => v.toLowerCase().trim()); } async function isBasicSeed(entropy) { // https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/tonlib/tonlib/keys/Mnemonic.cpp#L68 // bool Mnemonic::is_basic_seed() { // td::SecureString hash(64); // td::pbkdf2_sha512(as_slice(to_entropy()), "TON seed version", td::max(1, PBKDF_ITERATIONS / 256), // hash.as_mutable_slice()); // return hash.as_slice()[0] == 0; // } const seed = await (0, pbkdf2_sha512_1.pbkdf2_sha512)(entropy, 'TON seed version', Math.max(1, Math.floor(PBKDF_ITERATIONS / 256)), 64); return seed[0] == 0; } async function isPasswordSeed(entropy) { // https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/tonlib/tonlib/keys/Mnemonic.cpp#L75 // bool Mnemonic::is_password_seed() { // td::SecureString hash(64); // td::pbkdf2_sha512(as_slice(to_entropy()), "TON fast seed version", 1, hash.as_mutable_slice()); // return hash.as_slice()[0] == 1; // } const seed = await (0, pbkdf2_sha512_1.pbkdf2_sha512)(entropy, 'TON fast seed version', 1, 64); return seed[0] == 1; } async function mnemonicToEntropy(mnemonicArray, password) { // https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/tonlib/tonlib/keys/Mnemonic.cpp#L52 // td::SecureString Mnemonic::to_entropy() const { // td::SecureString res(64); // td::hmac_sha512(join(words_), password_, res.as_mutable_slice()); // return res; // } return await (0, hmac_sha512_1.hmac_sha512)(mnemonicArray.join(' '), password && password.length > 0 ? password : ''); } exports.mnemonicToEntropy = mnemonicToEntropy; async function mnemonicToSeed(mnemonicArray, seed, password) { // https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/tonlib/tonlib/keys/Mnemonic.cpp#L58 // td::SecureString Mnemonic::to_seed() const { // td::SecureString hash(64); // td::pbkdf2_sha512(as_slice(to_entropy()), "TON default seed", PBKDF_ITERATIONS, hash.as_mutable_slice()); // return hash; // } const entropy = await mnemonicToEntropy(mnemonicArray, password); return await (0, pbkdf2_sha512_1.pbkdf2_sha512)(entropy, seed, PBKDF_ITERATIONS, 64); } exports.mnemonicToSeed = mnemonicToSeed; /** * Extract private key from mnemonic * @param mnemonicArray mnemonic array * @param password mnemonic password * @returns Key Pair */ async function mnemonicToPrivateKey(mnemonicArray, password) { // https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/tonlib/tonlib/keys/Mnemonic.cpp#L64 // td::Ed25519::PrivateKey Mnemonic::to_private_key() const { // return td::Ed25519::PrivateKey(td::SecureString(as_slice(to_seed()).substr(0, td::Ed25519::PrivateKey::LENGTH))); // } mnemonicArray = normalizeMnemonic(mnemonicArray); const seed = (await mnemonicToSeed(mnemonicArray, 'TON default seed', password)); let keyPair = tweetnacl_1.default.sign.keyPair.fromSeed(seed.slice(0, 32)); return { publicKey: Buffer.from(keyPair.publicKey), secretKey: Buffer.from(keyPair.secretKey) }; } exports.mnemonicToPrivateKey = mnemonicToPrivateKey; /** * Convert mnemonic to wallet key pair * @param mnemonicArray mnemonic array * @param password mnemonic password * @returns Key Pair */ async function mnemonicToWalletKey(mnemonicArray, password) { let seedPk = await mnemonicToPrivateKey(mnemonicArray, password); let seedSecret = seedPk.secretKey.slice(0, 32); const keyPair = tweetnacl_1.default.sign.keyPair.fromSeed(seedSecret); return { publicKey: Buffer.from(keyPair.publicKey), secretKey: Buffer.from(keyPair.secretKey) }; } exports.mnemonicToWalletKey = mnemonicToWalletKey; /** * Convert mnemonics to HD seed * @param mnemonicArray mnemonic array * @param password mnemonic password * @returns 64 byte seed */ async function mnemonicToHDSeed(mnemonicArray, password) { mnemonicArray = normalizeMnemonic(mnemonicArray); return (await mnemonicToSeed(mnemonicArray, 'TON HD Keys seed', password)); } exports.mnemonicToHDSeed = mnemonicToHDSeed; /** * Validate Mnemonic * @param mnemonicArray mnemonic array * @param password mnemonic password * @returns true for valid mnemonic */ async function mnemonicValidate(mnemonicArray, password) { // Normalize mnemonicArray = normalizeMnemonic(mnemonicArray); // Validate mnemonic words for (let word of mnemonicArray) { if (wordlist_1.wordlist.indexOf(word) < 0) { return false; } } // Check password if (password && password.length > 0) { if (!await isPasswordNeeded(mnemonicArray)) { return false; } } // Validate seed return await isBasicSeed(await mnemonicToEntropy(mnemonicArray, password)); } exports.mnemonicValidate = mnemonicValidate; /** * Generate new Mnemonic * @param wordsCount number of words to generate * @param password mnemonic password * @returns */ async function mnemonicNew(wordsCount = 24, password) { // https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/tonlib/tonlib/keys/Mnemonic.cpp#L159 let mnemonicArray = []; while (true) { // Regenerate new mnemonics mnemonicArray = []; for (let i = 0; i < wordsCount; i++) { let ind = await (0, getSecureRandom_1.getSecureRandomNumber)(0, wordlist_1.wordlist.length); mnemonicArray.push(wordlist_1.wordlist[ind]); } // Chek password conformance if (password && password.length > 0) { if (!await isPasswordNeeded(mnemonicArray)) { continue; } } // Check if basic seed correct if (!(await isBasicSeed(await mnemonicToEntropy(mnemonicArray, password)))) { continue; } break; } return mnemonicArray; } exports.mnemonicNew = mnemonicNew; /** * Converts bytes to mnemonics array (could be invalid for TON) * @param src source buffer * @param wordsCount number of words */ function bytesToMnemonicIndexes(src, wordsCount) { let bits = (0, binary_1.bytesToBits)(src); let indexes = []; for (let i = 0; i < wordsCount; i++) { let sl = bits.slice(i * 11, i * 11 + 11); indexes.push(parseInt(sl, 2)); } return indexes; } exports.bytesToMnemonicIndexes = bytesToMnemonicIndexes; function bytesToMnemonics(src, wordsCount) { let mnemonics = bytesToMnemonicIndexes(src, wordsCount); let res = []; for (let m of mnemonics) { res.push(wordlist_1.wordlist[m]); } return res; } exports.bytesToMnemonics = bytesToMnemonics; /** * Converts mnemonics indexes to buffer with zero padding in the end * @param src source indexes * @returns Buffer */ function mnemonicIndexesToBytes(src) { let res = ''; for (let s of src) { if (!Number.isSafeInteger(s)) { throw Error('Invalid input'); } if (s < 0 || s >= 2028) { throw Error('Invalid input'); } res += (0, binary_1.lpad)(s.toString(2), '0', 11); } while (res.length % 8 !== 0) { res = res + '0'; } return (0, binary_1.bitsToBytes)(res); } exports.mnemonicIndexesToBytes = mnemonicIndexesToBytes; /** * Generates deterministically mnemonics * @param seed * @param wordsCount * @param password */ async function mnemonicFromRandomSeed(seed, wordsCount = 24, password) { const bytesLength = Math.ceil(wordsCount * 11 / 8); let currentSeed = seed; while (true) { // Create entropy let entropy = await (0, pbkdf2_sha512_1.pbkdf2_sha512)(currentSeed, 'TON mnemonic seed', Math.max(1, Math.floor(PBKDF_ITERATIONS / 256)), bytesLength); // Create mnemonics let mnemonics = bytesToMnemonics(entropy, wordsCount); // Check if mnemonics are valid if (await mnemonicValidate(mnemonics, password)) { return mnemonics; } currentSeed = entropy; } } exports.mnemonicFromRandomSeed = mnemonicFromRandomSeed;