cfx-simple-keyring
Version:
A simple standard interface for a series of Conflux private keys.
220 lines (194 loc) • 7.06 kB
JavaScript
const EventEmitter = require("events").EventEmitter;
const { Wallet } = require("cfx-wallet");
const ethUtil = require("cfx-util");
const type = "Simple Key Pair";
const sigUtil = require("cfx-sig-util");
class SimpleKeyring extends EventEmitter {
/* PUBLIC METHODS */
constructor(opts) {
super();
this.type = type;
this.wallets = [];
this.deserialize(opts);
}
serialize() {
return Promise.resolve(
this.wallets.map((w) => w.getPrivateKey().toString("hex"))
);
}
deserialize(privateKeys = []) {
return new Promise((resolve, reject) => {
try {
this.wallets = privateKeys.map((privateKey) => {
const stripped = ethUtil.stripHexPrefix(privateKey);
const buffer = new Buffer(stripped, "hex");
const wallet = Wallet.fromPrivateKey(buffer);
return wallet;
});
} catch (e) {
reject(e);
}
resolve();
});
}
addAccounts(n = 1) {
var newWallets = [];
for (var i = 0; i < n; i++) {
newWallets.push(Wallet.generate());
}
this.wallets = this.wallets.concat(newWallets);
const hexWallets = newWallets.map((w) =>
ethUtil.bufferToHex(w.getAddress())
);
return Promise.resolve(hexWallets);
}
getAccounts() {
return Promise.resolve(
this.wallets.map((w) => ethUtil.bufferToHex(w.getAddress()))
);
}
// tx is an instance of the ethereumjs-transaction class.
signTransaction(address, tx, opts = {}) {
const privKey = this.getPrivateKeyFor(address, opts);
tx.sign(privKey, opts.networkId);
return Promise.resolve(tx);
}
// For eth_sign, we need to sign arbitrary data:
signMessage(address, data, opts = {}) {
const message = ethUtil.stripHexPrefix(data);
const privKey = this.getPrivateKeyFor(address, opts);
var msgSig = ethUtil.ecsign(new Buffer(message, "hex"), privKey);
var rawMsgSig = ethUtil.bufferToHex(
sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s)
);
return Promise.resolve(rawMsgSig);
}
// For eth_sign, we need to sign transactions:
newGethSignMessage(withAccount, msgHex, opts = {}) {
const privKey = this.getPrivateKeyFor(withAccount, opts);
const msgBuffer = ethUtil.toBuffer(msgHex);
const msgHash = ethUtil.hashPersonalMessage(msgBuffer);
const msgSig = ethUtil.ecsign(msgHash, privKey);
const rawMsgSig = ethUtil.bufferToHex(
sigUtil.concatSig(msgSig.v, msgSig.r, msgSig.s)
);
return Promise.resolve(rawMsgSig);
}
// For personal_sign, we need to prefix the message:
signPersonalMessage(address, msgHex, opts = {}) {
const privKey = this.getPrivateKeyFor(address, opts);
const privKeyBuffer = new Buffer(privKey, "hex");
const sig = sigUtil.personalSign(privKeyBuffer, { data: msgHex });
return Promise.resolve(sig);
}
// For eth_decryptMessage:
decryptMessage(withAccount, encryptedData) {
const wallet = this._getWalletForAccount(withAccount);
const privKey = ethUtil.stripHexPrefix(wallet.getPrivateKey());
const privKeyBuffer = new Buffer(privKey, "hex");
const sig = sigUtil.decrypt(encryptedData, privKey);
return Promise.resolve(sig);
}
// personal_signTypedData, signs data along with the schema
signTypedData(withAccount, typedData, opts = { version: "V1" }) {
switch (opts.version) {
case "V1":
return this.signTypedData_v1(withAccount, typedData, opts);
case "V3":
return this.signTypedData_v3(withAccount, typedData, opts);
case "V4":
return this.signTypedData_v4(withAccount, typedData, opts);
default:
return this.signTypedData_v1(withAccount, typedData, opts);
}
}
// personal_signTypedData, signs data along with the schema
signTypedData_v1(withAccount, typedData, opts = {}) {
const privKey = this.getPrivateKeyFor(withAccount, opts);
const sig = sigUtil.signTypedDataLegacy(privKey, { data: typedData });
return Promise.resolve(sig);
}
// personal_signTypedData, signs data along with the schema
signTypedData_v3(withAccount, typedData, opts = {}) {
const privKey = this.getPrivateKeyFor(withAccount, opts);
const sig = sigUtil.signTypedData(privKey, { data: typedData });
return Promise.resolve(sig);
}
// personal_signTypedData, signs data along with the schema
signTypedData_v4(withAccount, typedData, opts = {}) {
const privKey = this.getPrivateKeyFor(withAccount, opts);
const sig = sigUtil.signTypedData_v4(privKey, { data: typedData });
return Promise.resolve(sig);
}
// get public key for nacl
getEncryptionPublicKey(withAccount, opts = {}) {
const privKey = this.getPrivateKeyFor(withAccount, opts);
const publicKey = sigUtil.getEncryptionPublicKey(privKey);
return Promise.resolve(publicKey);
}
getPrivateKeyFor(address, opts = {}) {
if (!address) {
throw new Error("Must specify address.");
}
const wallet = this._getWalletForAccount(address, opts);
const privKey = ethUtil.toBuffer(wallet.getPrivateKey());
return privKey;
}
// returns an address specific to an app
getAppKeyAddress(address, origin) {
if (!origin || typeof origin !== "string") {
throw new Error(`'origin' must be a non-empty string`);
}
return new Promise((resolve, reject) => {
try {
const wallet = this._getWalletForAccount(address, {
withAppKeyOrigin: origin,
});
const appKeyAddress = sigUtil.normalize(
wallet.getAddress().toString("hex")
);
return resolve(appKeyAddress);
} catch (e) {
return reject(e);
}
});
}
// exportAccount should return a hex-encoded private key:
exportAccount(address, opts = {}) {
const wallet = this._getWalletForAccount(address, opts);
return Promise.resolve(wallet.getPrivateKey().toString("hex"));
}
removeAccount(address) {
if (
!this.wallets
.map((w) => ethUtil.bufferToHex(w.getAddress()).toLowerCase())
.includes(address.toLowerCase())
) {
throw new Error(`Address ${address} not found in this keyring`);
}
this.wallets = this.wallets.filter(
(w) =>
ethUtil.bufferToHex(w.getAddress()).toLowerCase() !==
address.toLowerCase()
);
}
/* PRIVATE METHODS */
_getWalletForAccount(account, opts = {}) {
const address = sigUtil.normalize(account);
let wallet = this.wallets.find(
(w) => ethUtil.bufferToHex(w.getAddress()) === address
);
if (!wallet)
throw new Error("Simple Keyring - Unable to find matching address.");
if (opts.withAppKeyOrigin) {
const privKey = wallet.getPrivateKey();
const appKeyOriginBuffer = Buffer.from(opts.withAppKeyOrigin, "utf8");
const appKeyBuffer = Buffer.concat([privKey, appKeyOriginBuffer]);
const appKeyPrivKey = ethUtil.keccak(appKeyBuffer, 256);
wallet = Wallet.fromPrivateKey(appKeyPrivKey);
}
return wallet;
}
}
SimpleKeyring.type = type;
module.exports = SimpleKeyring;