@dojima-wallet/connection
Version:
Initialise and connection for layer 1&2 blockchain
194 lines (175 loc) • 4.58 kB
text/typescript
import * as bip39 from "bip39";
import crypto from "crypto";
import { blake256 } from "foundry-primitives";
import { v4 as uuidv4 } from "uuid";
import { pbkdf2Async } from "./utils";
// Constants
const cipher = "aes-128-ctr";
const kdf = "pbkdf2";
const prf = "hmac-sha256";
const dklen = 32;
const c = 262144;
const hashFunction = "sha256";
const meta = "d11k-keystore";
/**
* The Keystore interface
*/
export type Keystore = {
crypto: {
cipher: string;
ciphertext: string;
cipherparams: {
iv: string;
};
kdf: string;
kdfparams: {
prf: string;
dklen: number;
salt: string;
c: number;
};
mac: string;
};
id: string;
version: number;
meta: string;
};
/**
* Determines if the current environment is node
*
* @returns {boolean} True if current environment is node
*/
const _isNode = (): boolean => {
return typeof window === "undefined";
};
/**
* Generate a new phrase.
*
* @param {string} size The new phrase size.
* @returns {string} The generated phrase based on the size.
*/
export const generatePhrase = (size = 12): string => {
if (_isNode()) {
const bytes = crypto.randomBytes((size == 12 ? 128 : 256) / 8);
const phrase = bip39.entropyToMnemonic(bytes);
return phrase;
} else {
const entropy = size == 12 ? 128 : 256;
const phrase = bip39.generateMnemonic(entropy);
return phrase;
}
};
/**
* Validate the given phrase.
*
* @param {string} phrase
* @returns {boolean} `true` or `false`
*/
export const validatePhrase = (phrase: string): boolean => {
return bip39.validateMnemonic(phrase);
};
/**
* Get the seed from the given phrase.
*
* @param {string} phrase
* @returns {Buffer} The seed from the given phrase.
*
* @throws {"Invalid BIP39 phrase"} Thrown if phrase is an invalid one.
*/
export const getSeed = (phrase: string): Buffer => {
if (!validatePhrase(phrase)) {
throw new Error("Invalid BIP39 phrase");
}
return bip39.mnemonicToSeedSync(phrase);
};
/**
* Get the Keystore interface from the given phrase and password.
*
* @param {string} phrase
* @param {string} password
* @returns {Keystore} The keystore interface generated from the given phrase and password.
*
* @throws {"Invalid BIP39 phrase"} Thrown if phrase is an invalid one.
*/
export const encryptToKeyStore = async (
phrase: string,
password: string
): Promise<Keystore> => {
if (!validatePhrase(phrase)) {
throw new Error("Invalid BIP39 phrase");
}
const ID = _isNode() ? require("uuid").v4() : uuidv4();
const salt = crypto.randomBytes(32);
const iv = crypto.randomBytes(16);
const kdfParams = {
prf: prf,
dklen: dklen,
salt: salt.toString("hex"),
c: c,
};
const cipherParams = {
iv: iv.toString("hex"),
};
const derivedKey = await pbkdf2Async(
Buffer.from(password),
salt,
kdfParams.c,
kdfParams.dklen,
hashFunction
);
const cipherIV = crypto.createCipheriv(cipher, derivedKey.slice(0, 16), iv);
const cipherText = Buffer.concat([
cipherIV.update(Buffer.from(phrase, "utf8")),
cipherIV.final(),
]);
const mac = blake256(
Buffer.concat([derivedKey.slice(16, 32), Buffer.from(cipherText)])
);
const cryptoStruct = {
cipher: cipher,
ciphertext: cipherText.toString("hex"),
cipherparams: cipherParams,
kdf: kdf,
kdfparams: kdfParams,
mac: mac,
};
const keystore = {
crypto: cryptoStruct,
id: ID,
version: 1,
meta: meta,
};
return keystore;
};
/**
* Get the phrase from the keystore
*
* @param {Keystore} keystore
* @param {string} password
* @returns {Keystore} The phrase from the keystore.
*
* @throws {"Invalid password"} Thrown if password is an incorrect one.
*/
export const decryptFromKeystore = async (
keystore: Keystore,
password: string
): Promise<string> => {
const kdfparams = keystore.crypto.kdfparams;
const derivedKey = await pbkdf2Async(
Buffer.from(password),
Buffer.from(kdfparams.salt, "hex"),
kdfparams.c,
kdfparams.dklen,
hashFunction
);
const ciphertext = Buffer.from(keystore.crypto.ciphertext, "hex");
const mac = blake256(Buffer.concat([derivedKey.slice(16, 32), ciphertext]));
if (mac !== keystore.crypto.mac) throw new Error("Invalid password");
const decipher = crypto.createDecipheriv(
keystore.crypto.cipher,
derivedKey.slice(0, 16),
Buffer.from(keystore.crypto.cipherparams.iv, "hex")
);
const phrase = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
return phrase.toString("utf8");
};