import bip39 from 'bip39'; import { Wallet } from 'ethers'; import secp256k1 from 'secp256k1'; import keccak256 from 'keccak256'; import { DEFAULT_DERIVATION_PATH } from '../utils/constants'; import { PublicKey } from './PublicKey'; import { Address } from './Address'; import * as BytesUtils from '@ethersproject/bytes'; import { signTypedData, SignTypedDataVersion } from '@metamask/eth-sig-util'; /** * Class for wrapping SigningKey that is used for signature creation and public key derivation. * * @group Util */ export class PrivateKey { private wallet: Wallet; private constructor(wallet: Wallet) { this.wallet = wallet; } /** * Generate new private key with random mnemonic phrase * @returns { privateKey: PrivateKey, mnemonic: string } */ static generate(): { privateKey: PrivateKey; mnemonic: string } { const mnemonic = bip39.generateMnemonic(); const privateKey = PrivateKey.fromMnemonic(mnemonic); return { privateKey, mnemonic, }; } /** * Create a PrivateKey instance from a given mnemonic phrase and a HD derivation path. * If path is not given, default to Band's HD prefix 494 and all other indexes being zeroes. * @param {string} words the mnemonic phrase * @param {string|undefined} path the HD path that follows the BIP32 standard (optional) * @returns {PrivateKey} Initialized PrivateKey object */ static fromMnemonic( words: string, path: string | undefined = DEFAULT_DERIVATION_PATH ): PrivateKey { return new PrivateKey(Wallet.fromMnemonic(words, path)); } /** * Create a PrivateKey instance from a given private key and a HD derivation path. * If path is not given, default to Band's HD prefix 494 and all other indexes being zeroes. * @param {string} privateKey the private key * @returns {PrivateKey} Initialized PrivateKey object */ static fromPrivateKey(privateKey: string): PrivateKey { return new PrivateKey(new Wallet(privateKey)); } /** * Return a hex representation of signing key. * @returns {string} */ toHex(): string { return this.wallet.address.startsWith('0x') ? this.wallet.address : `0x${this.wallet.address}`; } /** * Return the PublicKey associated with this private key. * @returns {PublicKey} a Public key that can be used to verify the signatures made with this PrivateKey **/ toPublicKey(): PublicKey { return PublicKey.fromHex(this.wallet.privateKey); } /** * Return the Address associated with this private key. * @returns {Address} **/ toAddress(): Address { return Address.fromHex(this.toHex()); } /** * Return the Router address associated with this private key. * @returns {string} **/ toBech32(): string { return Address.fromHex(this.toHex()).toBech32(); } /** * Sign the given message using the wallet's _signingKey function. * @param {string} messageBytes: the message that will be hashed and signed, a Buffer made of bytes * @returns {Uint8Array} a signature of this private key over the given message */ async sign(messageBytes: Uint8Array): Promise { const { wallet } = this; //@ts-ignore const msgHash = keccak256(messageBytes); const signature = await wallet._signingKey().signDigest(msgHash); const splitSignature = BytesUtils.splitSignature(signature); return BytesUtils.arrayify( BytesUtils.concat([splitSignature.r, splitSignature.s]) ); } async request({ method, params, }: { method: string; params: string[]; }) { if (method !== 'eth_signTypedData_v4') { throw new Error('Unsupported Signtype'); } if (params.length !== 2) { throw new Error('Unsupported Params'); } const { wallet } = this; const eipToSign: any = JSON.parse(params[1]); const privateKeyHex = wallet.privateKey.startsWith('0x') ? wallet.privateKey.slice(2) : wallet.privateKey; const privateKey = Buffer.from(privateKeyHex, 'hex'); const signature = signTypedData({ privateKey: privateKey, data: eipToSign, version: SignTypedDataVersion.V4, }); return signature; } /** * Sign the given message using the edcsa sign_deterministic function. * @param {Buffer} messageBytes: the message that will be hashed and signed, a Buffer made of bytes * @returns {Uint8Array} a signature of this private key over the given message */ async signEcda(messageBytes: Buffer): Promise { const { wallet } = this; const msgHash = keccak256(messageBytes); const privateKeyHex = wallet.privateKey.startsWith('0x') ? wallet.privateKey.slice(2) : wallet.privateKey; const privateKey = Uint8Array.from( Buffer.from(privateKeyHex, 'hex') ); const { signature } = secp256k1.ecdsaSign(msgHash, privateKey); return signature; } /** * Sign the given message using the wallet's _signingKey function. * @param {string} messageHashedBytes: the message that will be signed, a Buffer made of bytes * @returns {Uint8Array} a signature of this private key over the given message */ async signHashed(messageHashedBytes: Buffer): Promise { const { wallet } = this; const signature = await wallet ._signingKey() .signDigest(messageHashedBytes); const splitSignature = BytesUtils.splitSignature(signature); return BytesUtils.arrayify( BytesUtils.concat([splitSignature.r, splitSignature.s]) ); } /** * Sign the given message using the edcsa sign_deterministic function. * @param {Buffer} messageHashedBytes: the message that will be signed, a Buffer made of bytes * @returns {Uint8Array} a signature of this private key over the given message */ async signHashedEcda(messageHashedBytes: Buffer): Promise { const { wallet } = this; const privateKeyHex = wallet.privateKey.startsWith('0x') ? wallet.privateKey.slice(2) : wallet.privateKey; const privateKey = Uint8Array.from( Buffer.from(privateKeyHex, 'hex') ); const { signature } = secp256k1.ecdsaSign( messageHashedBytes, privateKey ); return signature; } }