2

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.

1
  • I'm not aware of any, Taquito is the most popular library, you could open a feature request for them: github.com/ecadlabs/taquito . Kukai web wallet exports a keystore file and the code is open source, you can copy that if you like
    – Simon McLoughlin
    Feb 13 at 12:41

1 Answer 1

0

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;
    }
  }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.