@trezor/connect
Version:
High-level javascript interface for Trezor hardware wallet.
112 lines • 4.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.verifyEntropy = exports.generateEntropy = void 0;
const crypto_1 = require("@noble/hashes/crypto");
const hmac_1 = require("@noble/hashes/hmac");
const sha256_1 = require("@noble/hashes/sha256");
const utils_1 = require("@noble/hashes/utils");
const bip39_1 = require("@scure/bip39");
const crypto_utils_1 = require("@trezor/crypto-utils");
const utxo_lib_1 = require("@trezor/utxo-lib");
const constants_1 = require("../../constants");
const generateEntropy = (len) => {
try {
return Buffer.from((0, utils_1.randomBytes)(len));
}
catch {
throw new Error('generateEntropy: Environment does not support crypto random');
}
};
exports.generateEntropy = generateEntropy;
const BASE_ITERATION_COUNT = 10000;
const ROUND_COUNT = 4;
const roundFunction = async (i, passphrase, e, salt, r) => {
const data = Buffer.concat([Buffer.from([i]), passphrase]);
const iterations = Math.floor((BASE_ITERATION_COUNT << e) / ROUND_COUNT);
const { subtle } = crypto_1.crypto;
const key = await subtle.importKey('raw', data, 'PBKDF2', false, ['deriveBits']);
const bits = await subtle.deriveBits({
name: 'PBKDF2',
hash: 'SHA-256',
salt: Buffer.concat([salt, r]),
iterations,
}, key, r.length * 8);
return Buffer.from(bits);
};
const xor = (a, b) => {
if (a.length !== b.length) {
throw new Error('Buffers must be of equal length to XOR.');
}
const result = Buffer.alloc(a.length);
for (let i = 0; i < a.length; i++) {
result[i] = a[i] ^ b[i];
}
return result;
};
const entropyToSeedSlip39 = async (encryptedSecret) => {
const iterationExponent = 1;
const passphrase = Buffer.from('', 'utf-8');
const salt = Buffer.alloc(0);
const half = Math.floor(encryptedSecret.length / 2);
let l = encryptedSecret.subarray(0, half);
let r = encryptedSecret.subarray(half);
for (let round = ROUND_COUNT - 1; round >= 0; round--) {
const f = await roundFunction(round, passphrase, iterationExponent, salt, r);
const rr = xor(l, f);
l = r;
r = rr;
}
return Buffer.concat([r, l]);
};
const getEntropy = (trezorEntropy, hostEntropy, strength) => {
const data = Buffer.concat([
Buffer.from(trezorEntropy, 'hex'),
Buffer.from(hostEntropy, 'hex'),
]);
const entropy = (0, sha256_1.sha256)(data);
return Buffer.from(entropy.subarray(0, Math.floor(strength / 8)));
};
const computeSeed = (type, secret) => {
const BackupType = constants_1.PROTO.Enum_BackupType;
if (type &&
[
BackupType.Slip39_Basic,
BackupType.Slip39_Advanced,
BackupType.Slip39_Single_Extendable,
BackupType.Slip39_Basic_Extendable,
BackupType.Slip39_Advanced_Extendable,
].includes(type)) {
return entropyToSeedSlip39(secret);
}
return (0, bip39_1.mnemonicToSeed)((0, bip39_1.entropyToMnemonic)(secret, [...crypto_utils_1.bip39])).then(seed => Buffer.from(seed));
};
const verifyCommitment = (entropy, commitment) => {
const hmacDigest = (0, hmac_1.hmac)(sha256_1.sha256, Buffer.from(entropy, 'hex'), Buffer.alloc(0));
if (!Buffer.from(hmacDigest).equals(Buffer.from(commitment, 'hex'))) {
throw new Error('Invalid entropy commitment');
}
};
const verifyEntropy = async ({ type, strength, trezorEntropy, hostEntropy, commitment, xpubs, }) => {
try {
if (!trezorEntropy || !commitment || !strength || Object.keys(xpubs).length < 1) {
throw new Error('Missing verifyEntropy data');
}
verifyCommitment(trezorEntropy, commitment);
const secret = getEntropy(trezorEntropy, hostEntropy, strength);
const seed = await computeSeed(type, secret);
const node = utxo_lib_1.bip32.fromSeed(seed);
Object.keys(xpubs).forEach(path => {
const pubKey = node.derivePath(path);
const xpub = pubKey.neutered().toBase58();
if (xpub !== xpubs[path]) {
throw new Error('verifyEntropy xpub mismatch');
}
});
return { success: true };
}
catch (error) {
return { success: false, error: error.message };
}
};
exports.verifyEntropy = verifyEntropy;
//# sourceMappingURL=verifyEntropy.js.map