Talking about this specifically: https://web3js.readthedocs.io/en/v1.2.11/web3-eth-accounts.html#encrypt
Takes a private key and outputs a JSON keystore file.
Tezos Stack Exchange is a question and answer site for users, experts, and developers of the Tezos blockchain project. It only takes a minute to sign up.
Sign up to join this communityTalking about this specifically: https://web3js.readthedocs.io/en/v1.2.11/web3-eth-accounts.html#encrypt
Takes a private key and outputs a JSON keystore file.
Here is a quick implementation based on Kukai's model:
import { Curves, generateSecretKey, InMemorySigner } from "@taquito/signer";
import { mnemonicToSeedSync, validateMnemonic } from "bip39";
import * as scrypt from "scryptsy";
import * as cryptob from "crypto-browserify";
import * as forge from "node-forge";
interface Keystore {
encryptedSeed: string;
iv: string;
}
export class TezosKeystore {
private _mnemonic: string;
private _passphrase: string;
constructor(words: string, password: string = "") {
this._mnemonic = words;
this._passphrase = password;
}
async encrypt(encryptionKey: string, hdWallet: boolean = false) {
const seed = await this._extractSeed(this._mnemonic, hdWallet);
console.log(1, 1, seed);
const encrypted = await this._encrypt(seed, encryptionKey);
return { encryptedSeed: encrypted.chiphertext, iv: encrypted.iv };
}
async decrypt(
keystore: Keystore,
decryptionKey: string,
derivationPath: string = "m/44'/60'/0'/0/0",
curve: Curves = "secp256k1"
): Promise<string> {
const decryptedSeed = await this._decrypt(
keystore.encryptedSeed,
decryptionKey,
keystore.iv
);
console.log(1, 2, decryptedSeed);
return generateSecretKey(decryptedSeed, derivationPath, curve);
}
private async _extractSeed(mnemonic: string, hdWallet: boolean = false) {
if (!validateMnemonic(mnemonic)) {
throw new Error("InvalidMnemonic");
}
return mnemonicToSeedSync(this._mnemonic, this._passphrase).slice(
0,
hdWallet ? 64 : 32
);
}
private async _encrypt(
plaintext: any,
password: string,
salt?: string
): Promise<{ chiphertext: string; iv: string }> {
if (!password) {
throw new Error("Missing password");
}
let salty;
if (salt) {
salty = Buffer.from(salt, "hex");
} else {
salty = cryptob.randomBytes(16);
}
const key = await scrypt.async(password, salty, 65536, 8, 1, 32);
const cipher: any = forge.cipher.createCipher(
"AES-GCM",
key.toString("binary")
);
cipher.start({
iv: salty,
});
const byteStringBuffer = forge.util.createBuffer(
plaintext.toString("binary"),
"utf-8"
);
cipher.update(byteStringBuffer);
cipher.finish();
const chiphertext = cipher.output.toHex() + "==" + cipher.mode.tag.toHex();
return { chiphertext: chiphertext, iv: salty.toString("hex") };
}
async _decrypt(
chipher: string,
password: string,
salt: string
): Promise<Buffer> {
try {
if (!password || !salt) {
throw new Error("Missing password or salt");
}
const parts = chipher.split("==");
const chiphertext = parts[0];
const tag = parts[1];
const key = await scrypt.async(
password,
Buffer.from(salt, "hex"),
65536,
8,
1,
32
);
const decipher: any = forge.cipher.createDecipher(
"AES-GCM",
key.toString("binary")
);
decipher.start({
iv: Buffer.from(salt, "hex"),
tag: forge.util.createBuffer(
Buffer.from(tag, "hex").toString("binary"),
"utf-8"
), // authentication tag from encryption
});
decipher.update(
forge.util.createBuffer(
Buffer.from(chiphertext, "hex").toString("binary"),
"utf-8"
)
);
const pass = decipher.finish();
if (pass) {
return Buffer.from(decipher.output.toHex(), "hex");
} else {
return null;
}
} catch (err) {
return null;
}
}
}