UNPKG

@zlattice/lattice-js

Version:

Lattice blockchain TypeScript SDK with dual module support (CJS + ESM)

187 lines 7.12 kB
import { Curves, HEX_PREFIX } from "../common/constants.js"; import { createCrypto } from "../crypto/index.js"; import { hexlify } from "@ethersproject/bytes"; import { scrypt } from "@noble/hashes/scrypt"; import { randomBytes } from "@noble/hashes/utils"; import * as CryptoJS from "crypto-js"; import { err, ok } from "neverthrow"; import { v4 as uuidv4 } from "uuid"; const AES_128_CTR = "aes-128-ctr"; const KDF_SCRYPT = "scrypt"; const SCRYPT_N = 1 << 18; // 1<<18 = 262144,CPU/内存成本因子,控制计算和内存的使用量。 const SCRYPT_P = 1; // 1,并行度因子,控制 scrypt 函数的并行度。 const SCRYPT_R = 8; // 8,块大小因子,影响内部工作状态和内存占用。 const SCRYPT_KEY_LEN = 32; // 32,生成的密钥长度,单位byte const AES_BLOCK_SIZE = 16; // 16,AES 块大小,单位byte class FileKey { constructor(uuid, address, cipher, isGm) { this.uuid = uuid; this.address = address; this.cipher = cipher; this.isGm = isGm; } static fromJson(json) { try { const data = JSON.parse(json); return ok(new FileKey(data.uuid, data.address, new Cipher(new Aes(data.cipher.aes.cipher, data.cipher.aes.iv), new Kdf(data.cipher.kdf.kdf, new KdfParams(data.cipher.kdf.kdfParams.DkLen, data.cipher.kdf.kdfParams.n, data.cipher.kdf.kdfParams.p, data.cipher.kdf.kdfParams.r, data.cipher.kdf.kdfParams.salt)), data.cipher.cipherText, data.cipher.mac), data.isGM)); } catch (error) { return err(new Error("Invalid file key")); } } } class Cipher { constructor(aes, kdf, cipherText, mac) { this.aes = aes; this.kdf = kdf; this.cipherText = cipherText; this.mac = mac; } } class Aes { constructor(cipher, iv) { this.cipher = cipher; this.iv = iv; } } class Kdf { constructor(kdf, // 密钥派生函数,scrypt, PBKDF2, bcrypt, HKDF kdfParams) { this.kdf = kdf; this.kdfParams = kdfParams; } } class KdfParams { constructor(DkLen, // 生成的密钥长度,单位byte n, // CPU/内存成本因子,控制计算和内存的使用量。 p, // 并行度因子,控制 scrypt 函数的并行度。 r, // 块大小因子,影响内部工作状态和内存占用。 salt // 盐值,在密钥派生过程中加入随机性。 ) { this.DkLen = DkLen; this.n = n; this.p = p; this.r = r; this.salt = salt; } } /** * Generate file key * @param privateKey - The private key * @param passphrase - The passphrase * @param curve - The curve * @returns The file key */ function generateFileKey(privateKey, passphrase, curve) { const crypto = createCrypto(curve); const address = crypto.publicKeyToAddress(crypto.getPublicKeyFromPrivateKey(privateKey)); const uuid = uuidv4(); const generateCipherResult = generateCipher(privateKey, passphrase, curve); if (generateCipherResult.isErr()) { return err(generateCipherResult.error); } return ok(new FileKey(uuid, address, generateCipherResult.value, curve === Curves.Sm2p256v1)); } /** * Decrypt file key * @param fileKey - The file key * @param passphrase - The passphrase * @returns The private key */ function decryptFileKey(fileKey, passphrase) { const result = typeof fileKey === "string" ? FileKey.fromJson(fileKey) : ok(fileKey); if (result.isErr()) { return err(result.error); } const fk = result.value; const salt = Buffer.from(fk.cipher.kdf.kdfParams.salt, "hex"); const key = scryptKey(passphrase, salt, SCRYPT_N); const aesKey = key.subarray(0, AES_BLOCK_SIZE); const hashKey = key.subarray(AES_BLOCK_SIZE, AES_BLOCK_SIZE * 2); // compact amc const cipher = Buffer.from(fk.cipher.cipherText, "hex"); const curve = fk.isGm ? Curves.Sm2p256v1 : Curves.Secp256k1; const actualMac = createCrypto(curve).hash(Buffer.concat([hashKey, cipher])); const expectedMac = Buffer.from(fk.cipher.mac, "hex"); if (actualMac.toString("hex") !== expectedMac.toString("hex")) { return err(new Error("根据密码无法解析出私钥,请检查密码")); } const iv = Buffer.from(fk.cipher.aes.iv, "hex"); const privateKey = aesCtrDecrypt(aesKey, iv, cipher); return ok(hexlify(privateKey)); } /** * Generate cipher * @param privateKey - The private key * @param passphrase - The passphrase * @param curve - The curve * @returns The cipher */ function generateCipher(privateKey, passphrase, curve) { // generate salt const salt = Buffer.from(randomBytes(32)); const key = scryptKey(passphrase, salt, SCRYPT_N); const aesKey = key.subarray(0, AES_BLOCK_SIZE); const hashKey = key.subarray(AES_BLOCK_SIZE, AES_BLOCK_SIZE * 2); // compact amc const ivBytes = Buffer.from(randomBytes(AES_BLOCK_SIZE)); const cipher = aesCtrEncrypt(aesKey, ivBytes, Buffer.from(privateKey.startsWith(HEX_PREFIX) ? privateKey.slice(HEX_PREFIX.length) : privateKey, "hex")); const mac = createCrypto(curve).hash(Buffer.concat([hashKey, cipher])); return ok(new Cipher(new Aes(AES_128_CTR, ivBytes.toString("hex")), new Kdf(KDF_SCRYPT, new KdfParams(32, SCRYPT_N, SCRYPT_P, SCRYPT_R, salt.toString("hex"))), cipher.toString("hex"), mac.toString("hex"))); } /** * Scrypt key * @param password - The password * @param salt - The salt * @param n - The n * @returns The derived key */ function scryptKey(password, salt, n) { const derivedKey = scrypt(password, salt, { N: n, r: SCRYPT_R, p: SCRYPT_P, dkLen: SCRYPT_KEY_LEN }); return Buffer.from(derivedKey); } /** * AES-CTR encrypt * @param key - The key * @param iv - The iv * @param message - The message * @returns The encrypted buffer */ function aesCtrEncrypt(key, iv, message) { const encrypted = CryptoJS.AES.encrypt(CryptoJS.enc.Hex.parse(message.toString("hex")), CryptoJS.enc.Hex.parse(key.toString("hex")), { iv: CryptoJS.enc.Hex.parse(iv.toString("hex")), mode: CryptoJS.mode.CTR, padding: CryptoJS.pad.NoPadding // no padding }); return Buffer.from(encrypted.ciphertext.toString(CryptoJS.enc.Hex), "hex"); } /** * AES-CTR decrypt * @param key - The key * @param iv - The iv * @param cipher - The cipher * @returns The decrypted buffer */ function aesCtrDecrypt(key, iv, cipher) { const decrypted = CryptoJS.AES.decrypt(CryptoJS.lib.CipherParams.create({ ciphertext: CryptoJS.enc.Hex.parse(cipher.toString("hex")) }), CryptoJS.enc.Hex.parse(key.toString("hex")), { iv: CryptoJS.enc.Hex.parse(iv.toString("hex")), mode: CryptoJS.mode.CTR, padding: CryptoJS.pad.NoPadding // no padding }); return Buffer.from(decrypted.toString(CryptoJS.enc.Hex), "hex"); } export { // Constants AES_128_CTR, KDF_SCRYPT, SCRYPT_N, SCRYPT_P, SCRYPT_R, SCRYPT_KEY_LEN, // Classes FileKey, Cipher, Aes, Kdf, KdfParams, // Functions generateCipher, generateFileKey, decryptFileKey }; //# sourceMappingURL=file_key.js.map