chaingate
Version:
Multi-chain cryptocurrency SDK for TypeScript — unified API for Bitcoin, Ethereum, Litecoin, Dogecoin, Bitcoin Cash, Polygon, Arbitrum, and any EVM-compatible chain. Create wallets, query balances, send transactions, and manage tokens and NFTs across UTXO
78 lines (77 loc) • 3.58 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Web3Keystore = void 0;
const scrypt_js_1 = require("@noble/hashes/scrypt.js");
const sha3_js_1 = require("@noble/hashes/sha3.js");
const encoding_1 = require("../utils/encoding");
const errors_1 = require("../errors");
/** Web3 (V3) keystore. */
class Web3Keystore {
constructor(keystoreData) {
this.keystoreData = keystoreData;
}
async checkPassword(password) {
try {
const derivedKey = await this.deriveKey(password);
return this.verifyMac(derivedKey);
}
catch {
return false;
}
}
async decrypt(password) {
const derivedKey = await this.deriveKey(password);
if (!this.verifyMac(derivedKey)) {
throw new errors_1.IncorrectKeystorePasswordError();
}
const iv = (0, encoding_1.hexToBytes)(this.keystoreData.crypto.cipherparams.iv);
const ciphertext = (0, encoding_1.hexToBytes)(this.keystoreData.crypto.ciphertext);
const encryptionKey = derivedKey.slice(0, 16);
const cryptoKey = await crypto.subtle.importKey('raw', encryptionKey.buffer, 'AES-CTR', false, ['decrypt']);
const decrypted = await crypto.subtle.decrypt({ name: 'AES-CTR', counter: iv.buffer, length: 128 }, cryptoKey, ciphertext.buffer);
return new Uint8Array(decrypted);
}
async deriveKey(password) {
const kdfparams = this.keystoreData.crypto.kdfparams;
const salt = (0, encoding_1.hexToBytes)(kdfparams.salt);
const dkLen = kdfparams.dklen;
const passwordBytes = new TextEncoder().encode(password);
if (this.keystoreData.crypto.kdf === 'scrypt') {
const N = kdfparams.n;
const r = kdfparams.r;
const p = kdfparams.p;
if (!N || !r || !p) {
throw new errors_1.InvalidKeystoreError('Missing scrypt parameters');
}
return (0, scrypt_js_1.scryptAsync)(passwordBytes, salt, { N, r, p, dkLen });
}
if (this.keystoreData.crypto.kdf === 'pbkdf2') {
const iterations = kdfparams.c;
if (!iterations) {
throw new errors_1.InvalidKeystoreError('Missing PBKDF2 iterations');
}
const prf = kdfparams.prf ?? 'hmac-sha256';
const hashAlgo = prf === 'hmac-sha256' ? 'SHA-256' : 'SHA-512';
const keyMaterial = await crypto.subtle.importKey('raw', passwordBytes, 'PBKDF2', false, [
'deriveBits',
]);
const bits = await crypto.subtle.deriveBits({ name: 'PBKDF2', salt: salt.buffer, iterations, hash: hashAlgo }, keyMaterial, dkLen * 8);
return new Uint8Array(bits);
}
throw new errors_1.InvalidKeystoreError(`Unsupported KDF: ${this.keystoreData.crypto.kdf}`);
}
verifyMac(derivedKey) {
const ciphertext = (0, encoding_1.hexToBytes)(this.keystoreData.crypto.ciphertext);
// MAC = keccak256(derivedKey[16:32] + ciphertext)
const macInput = new Uint8Array(16 + ciphertext.length);
macInput.set(derivedKey.slice(16, 32));
macInput.set(ciphertext, 16);
const computedMac = (0, encoding_1.bytesToHex)((0, sha3_js_1.keccak_256)(macInput));
return computedMac === this.keystoreData.crypto.mac;
}
/** Checks whether a parsed JSON object looks like a V3 keystore. */
static isKeystore(obj) {
return 'version' in obj && obj.version == 3;
}
}
exports.Web3Keystore = Web3Keystore;