UNPKG

@ckb-ccc/core

Version:

Core of CCC - CKBer's Codebase

107 lines (106 loc) 3.66 kB
import { ctr } from "@noble/ciphers/aes"; import { scryptAsync } from "@noble/hashes/scrypt"; import { keccak_256 } from "@noble/hashes/sha3"; import { randomBytes } from "@noble/hashes/utils"; import { bytesConcat, bytesFrom } from "../bytes/index.js"; import { hexFrom } from "../hex/index.js"; // The parameter r ("blockSize") const DEFAULT_SCRYPT_PARAM_R = 8; // The parallelization parameter p const DEFAULT_SCRYPT_PARAM_P = 1; // The CPU/Memory cost parameter N const DEFAULT_SCRYPT_PARAM_N = 262144; function mac(derivedKey, cipherText) { return hexFrom(keccak_256(bytesConcat(derivedKey.slice(16, 32), cipherText))).slice(2); } /** * @public */ export async function keystoreEncrypt(privateKeyLike, chainCodeLike, password) { const salt = randomBytes(32); const iv = randomBytes(16); const kdfparams = { dklen: 32, salt: hexFrom(salt).slice(2), n: DEFAULT_SCRYPT_PARAM_N, r: DEFAULT_SCRYPT_PARAM_R, p: DEFAULT_SCRYPT_PARAM_P, }; const derivedKey = await scryptAsync(bytesFrom(password, "utf8"), salt, { N: kdfparams.n, r: kdfparams.r, p: kdfparams.p, dkLen: kdfparams.dklen, }); const cipher = ctr(derivedKey.slice(0, 16), iv.map((v) => v)); const ciphertext = cipher.encrypt(bytesConcat(bytesFrom(privateKeyLike), bytesFrom(chainCodeLike))); return { id: hexFrom(randomBytes(16)).slice(2), crypto: { ciphertext: hexFrom(ciphertext).slice(2), cipherparams: { iv: hexFrom(iv).slice(2), }, cipher: "aes-128-ctr", kdf: "scrypt", kdfparams, mac: mac(derivedKey, ciphertext), }, }; } /** * @public */ export async function keystoreDecrypt(keystore, password) { if (typeof keystore !== "object" || keystore === null || !("crypto" in keystore)) { throw Error("Invalid keystore"); } const crypto = keystore.crypto; if (typeof crypto !== "object" || crypto === null || !("kdfparams" in crypto) || !("ciphertext" in crypto) || typeof crypto.ciphertext !== "string" || !("mac" in crypto) || typeof crypto.mac !== "string" || !("cipherparams" in crypto) || typeof crypto.cipherparams !== "object" || crypto.cipherparams === null || !("iv" in crypto.cipherparams) || typeof crypto.cipherparams.iv !== "string") { throw Error("Invalid crypto"); } const kdfparams = crypto.kdfparams; if (typeof kdfparams !== "object" || kdfparams === null || !("n" in kdfparams) || typeof kdfparams.n !== "number" || !("r" in kdfparams) || typeof kdfparams.r !== "number" || !("p" in kdfparams) || typeof kdfparams.p !== "number" || !("dklen" in kdfparams) || typeof kdfparams.dklen !== "number" || !("salt" in kdfparams) || typeof kdfparams.salt !== "string") { throw Error("Invalid kdfparams"); } const derivedKey = await scryptAsync(bytesFrom(password, "utf8"), bytesFrom(kdfparams.salt), { N: kdfparams.n, r: kdfparams.r, p: kdfparams.p, dkLen: kdfparams.dklen, }); const ciphertext = bytesFrom(crypto.ciphertext); if (mac(derivedKey, ciphertext) !== crypto.mac) { throw Error("Invalid password"); } const cipher = ctr(derivedKey.slice(0, 16), bytesFrom(crypto.cipherparams.iv)); const result = cipher.decrypt(ciphertext); return { privateKey: result.slice(0, 32), chainCode: result.slice(32), }; }