@zlattice/lattice-js
Version:
Lattice blockchain TypeScript SDK with dual module support (CJS + ESM)
187 lines • 7.12 kB
JavaScript
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