Skip to content
This repository has been archived by the owner on Jul 23, 2024. It is now read-only.

Modification of wallet layer classes #316

Merged
merged 6 commits into from
May 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ global.rootRequire = name => require(`${__dirname}/packages/${name}/src/index.js
const { packageInit, providers } = require('./packages/caver-core')
const Klay = require('./packages/caver-klay')
const Account = require('./packages/caver-account')
const Wallet = require('./packages/caver-wallet')
const KeyringContainer = require('./packages/caver-wallet')
const Keyring = require('./packages/caver-wallet/src/keyring/keyring')
const Transaction = require('./packages/caver-transaction')
const RPC = require('./packages/caver-rpc')

Expand All @@ -60,7 +61,8 @@ function Caver(provider, net) {
this.Method = Method

this.account = Account
this.wallet = new Wallet()
this.wallet = new KeyringContainer()
this.wallet.keyring = Keyring

this.transaction = Transaction

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
"oboe": "2.1.3",
"request": "2.87.0",
"requestretry": "^2.0.2",
"scrypt-shim": "github:web3-js/scrypt-shim",
"@web3-js/scrypt-shim": "^0.1.0",
"semver": "6.2.0",
"utf8": "2.1.1",
"uuid": "^3.0.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/caver-klay/caver-klay-accounts/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const Bytes = require('eth-lib/lib/bytes')
const cryp = typeof global === 'undefined' ? require('crypto-browserify') : require('crypto')
const uuid = require('uuid')
const elliptic = require('elliptic')
const scrypt = require('scrypt-shim')
const scrypt = require('@web3-js/scrypt-shim')
const utils = require('../../../caver-utils')
const helpers = require('../../../caver-core-helpers')

Expand Down
243 changes: 212 additions & 31 deletions packages/caver-wallet/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,56 +16,237 @@
along with the caver-js. If not, see <http://www.gnu.org/licenses/>.
*/

const AccountLib = require('eth-lib/lib/account')
const KeyringContainer = require('./keyringContainer')
const _ = require('lodash')
const Keyring = require('./keyring/keyring')
const TransactionHasher = require('../../caver-transaction/src/transactionHasher/transactionHasher')
const utils = require('../../caver-utils/src')

/**
* representing a Wallet class in caver-wallet package.
* This Wallet class is a wrapper for KeyringContainer.
* representing a Keyring container which manages keyrings.
* @class
*/
class Wallet {
class KeyringContainer {
/**
* creates a keyringContainer.
* @param {Array.<Keyring>} keyrings - The keyrings to be managed in KeyringContainer.
*/
constructor(keyrings) {
this.keyringContainer = new KeyringContainer(keyrings)
this.keyring = Keyring

// Bind methods of KeyringContainer to Wallet
this.generate = this.keyringContainer.generate.bind(this.keyringContainer)
this.newKeyring = this.keyringContainer.newKeyring.bind(this.keyringContainer)
this.updateKeyring = this.keyringContainer.updateKeyring.bind(this.keyringContainer)
this.getKeyring = this.keyringContainer.getKeyring.bind(this.keyringContainer)

this.add = this.keyringContainer.add.bind(this.keyringContainer)
this.remove = this.keyringContainer.remove.bind(this.keyringContainer)

this.signMessage = this.keyringContainer.signMessage.bind(this.keyringContainer)
this.signWithKey = this.keyringContainer.signWithKey.bind(this.keyringContainer)
this.signWithKeys = this.keyringContainer.signWithKeys.bind(this.keyringContainer)
this.signFeePayerWithKey = this.keyringContainer.signFeePayerWithKey.bind(this.keyringContainer)
this.signFeePayerWithKeys = this.keyringContainer.signFeePayerWithKeys.bind(this.keyringContainer)
keyrings = keyrings || []
this._addressKeyringMap = new Map()

// add keyrings to keyringContainer
for (const keyring of keyrings) {
this.add(keyring)
}
}

/**
* @type {number}
*/
get length() {
return this.keyringContainer.length
return this._addressKeyringMap.size
}

/**
* generates keyrings in the keyringContainer with randomly generated key pairs.
*
* @param {number} numberOfKeyrings The number of accounts to create.
* @param {string} [entropy] A random string to increase entropy. If undefined, a random string will be generated using randomHex.
* @return {Array.<string>}
*/
generate(numberOfKeyrings, entropy) {
const addresses = []
for (let i = 0; i < numberOfKeyrings; ++i) {
addresses.push(this.add(Keyring.generate(entropy)).address)
}
return addresses
}

/**
* creates a keyring instance with given parameters and adds it to the keyringContainer.
* KeyringContainer manages Keyring instance using Map <string:Keyring> which has address as key value.
*
* @param {string} address The address of the keyring.
* @param {string|Array.<string>|Array.<Array.<string>>} key Private key string(s) to use in keyring. If different keys are used for each role, key must be defined as a two-dimensional array.
* @return {Keyring}
*/
newKeyring(address, key) {
// The format of key parameter can be
// 1. single private key string => `0x{private key}`
// 2. multiple private key string =>[`0x{private key}`, `0x{private key}`, ...]
// 3. role based private keys => [[`0x{private key}`, `0x{private key}`, ...], [], [`0x{private key}`]]

let keyring

if (_.isString(key)) keyring = Keyring.createWithSingleKey(address, key)

if (_.isArray(key)) {
if (key.length === 0) throw new Error(`Insufficient private key information: Empty array`)
if (_.isArray(key[0])) {
keyring = Keyring.createWithRoleBasedKey(address, key)
} else {
keyring = Keyring.createWithMultipleKey(address, key)
}
}

if (!(keyring instanceof Keyring)) throw new Error(`Unsupported type value: ${key} (type:${typeof key})`)

return this.add(keyring)
}

/**
* updates the keyring inside the keyringContainer.
* Query the keyring to be updated from keyringContainer with the keyring's address,
* and an error occurs when the keyring is not found in the keyringContainer.
*
* @param {Keyring} keyring The keyring with new key.
* @return {Keyring}
*/
updateKeyring(keyring) {
const founded = this._addressKeyringMap.get(keyring.address.toLowerCase())
if (founded === undefined) throw new Error(`Failed to find keyring to update`)

founded.keys = keyring.copy().keys
return founded
}

/**
* Get the keyring in container corresponding to the address
*
* @param {string} address The address of keyring to query.
* @return {Keyring}
*/
getKeyring(address) {
if (!utils.isAddress(address))
throw new Error(
`Invalid address ${address}. To get keyring from wallet, you need to pass a valid address string as a parameter.`
)

const founded = this._addressKeyringMap.get(address.toLowerCase())

return founded
}

/**
* adds a keyring to the keyringContainer.
*
* @param {Keyring} keyring A keyring instance to add to keyringContainer.
* @return {Keyring}
*/
add(keyring) {
if (this._addressKeyringMap.get(keyring.address.toLowerCase()) !== undefined)
throw new Error(`Duplicate Account ${keyring.address}. Please use updateKeyring() instead.`)

const keyringToAdd = keyring.copy()

this._addressKeyringMap.set(keyringToAdd.address.toLowerCase(), keyringToAdd)

return keyringToAdd
}

/**
* deletes the keyring that associates with the given address from keyringContainer.
*
* @param {string} address An address of the keyring to be deleted in keyringContainer.
* @return {boolean}
*/
remove(address) {
let keyringToRemove
if (utils.isAddress(address)) {
keyringToRemove = this.getKeyring(address)
} else {
throw new Error(`To remove the keyring, the first parameter should be an address string.`)
}

if (keyringToRemove === undefined) return false

// deallocate keyring object created for keyringContainer
keyringToRemove.keys = null
this._addressKeyringMap.delete(keyringToRemove.address.toLowerCase())

return true
}

/**
* generates a private key string
* signs with data and returns the result object that includes `signature`, `message` and `messageHash`
*
* `caver.wallet.generatePrivateKey()`
* @param {string} address An address of keyring in keyringContainer.
* @param {string} data The data string to sign.
* @param {number} [role] A number indicating the role of the key. You can use `caver.wallet.keyring.role`.
* @param {number} [index] An index of key to use for signing.
* @return {object}
*/
signMessage(address, data, role, index) {
const keyring = this.getKeyring(address)
if (keyring === undefined) throw new Error(`Failed to find keyring from wallet with ${address}`)
return keyring.signMessage(data, role, index)
}

/**
* signs the transaction using one key and return the transactionHash
*
* @param {string} entropy A random string to increase entropy.
* @return {string}
* @param {string} address An address of keyring in keyringContainer.
* @param {Transaction} transaction A transaction object.
* @param {number} [index] An index of key to use for signing.
* @param {function} [hasher] A function to return hash of transaction. In order to use a custom hasher, the index must be defined.
* @return {Transaction}
*/
// eslint-disable-next-line class-methods-use-this
generatePrivateKey(entropy) {
return AccountLib.create(entropy || utils.randomHex(32)).privateKey
async signWithKey(address, transaction, index = 0, hasher = TransactionHasher.getHashForSignature) {
const keyring = this.getKeyring(address)
if (keyring === undefined) throw new Error(`Failed to find keyring from wallet with ${address}`)
const signed = await transaction.signWithKey(keyring, index, hasher)

return signed
}

/**
* signs the transaction using keys and return the transactionHash
*
* @param {string} address An address of keyring in keyringContainer.
* @param {Transaction} transaction A transaction object.
* @param {function} [hasher] A function to return hash of transaction.
* @return {Transaction}
*/
async signWithKeys(address, transaction, hasher = TransactionHasher.getHashForSignature) {
const keyring = this.getKeyring(address)
if (keyring === undefined) throw new Error(`Failed to find the keyring from the wallet with the given address: ${address}`)
const signed = await transaction.signWithKeys(keyring, hasher)

return signed
}

/**
* signs the transaction as a fee payer using one key and return the transactionHash
*
* @param {string} address An address of keyring in keyringContainer.
* @param {Transaction} transaction A transaction object. This should be `FEE_DELEGATED` type.
* @param {number} [index] An index of key to use for signing.
* @param {function} [hasher] A function to return hash of transaction. In order to use a custom hasher, the index must be defined.
* @return {Transaction}
*/
async signFeePayerWithKey(address, transaction, index = 0, hasher = TransactionHasher.getHashForFeePayerSignature) {
const keyring = this.getKeyring(address)
if (keyring === undefined) throw new Error(`Failed to find keyring from wallet with ${address}`)
const signed = await transaction.signFeePayerWithKey(keyring, index, hasher)

return signed
}

/**
* signs the transaction as a fee payer using keys and return the transactionHash
*
* @param {string} address An address of keyring in keyringContainer.
* @param {Transaction} transaction A transaction object. This should be `FEE_DELEGATED` type.
* @param {function} [hasher] A function to return hash of transaction.
* @return {Transaction}
*/
async signFeePayerWithKeys(address, transaction, hasher = TransactionHasher.getHashForFeePayerSignature) {
const keyring = this.getKeyring(address)
if (keyring === undefined) throw new Error(`Failed to find keyring from wallet with ${address}`)
const signed = await transaction.signFeePayerWithKeys(keyring, hasher)

return signed
}
}

module.exports = Wallet
module.exports = KeyringContainer
63 changes: 62 additions & 1 deletion packages/caver-wallet/src/keyring/keyring.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*/

const _ = require('lodash')
const scrypt = require('scrypt-shim')
const scrypt = require('@web3-js/scrypt-shim')
const uuid = require('uuid')
const cryp = typeof global === 'undefined' ? require('crypto-browserify') : require('crypto')
const AccountLib = require('eth-lib/lib/account')
Expand Down Expand Up @@ -49,6 +49,67 @@ class Keyring {
return Keyring.createWithSingleKey(random.address, random.privateKey)
}

/**
* generates a decoupled keyring instance
*
* `caver.wallet.keyring.generateSingleKey()`
*
* @param {string} entropy A random string to increase entropy.
* @return {String}
*/
static generateSingleKey(entropy) {
return AccountLib.create(entropy || utils.randomHex(32)).privateKey
}

/**
* generates an keyring instance with multiple keys
*
* `caver.wallet.keyring.generateMultipleKeys()`
*
* @param {number} num A length of keys.
* @param {string} entropy A random string to increase entropy.
* @return {Array.<String>}
*/
static generateMultipleKeys(num, entropy) {
if (num === undefined || !_.isNumber(num) || _.isString(num)) {
throw new Error(`To generate random multiple private keys, the number of keys should be defined.`)
}

const randomKeys = []
for (let i = 0; i < num; i++) {
randomKeys.push(AccountLib.create(entropy || utils.randomHex(32)).privateKey)
}
return randomKeys
}

/**
* generates an keyring instance with role-based keys
*
* `caver.wallet.keyring.generateRoleBasedKeys()`
*
* @param {Array.<number>} numArr An array containing the number of keys for each role.
* @param {string} entropy A random string to increase entropy.
* @return {Array.<Array.<String>>}
*/
static generateRoleBasedKeys(numArr, entropy) {
if (numArr === undefined || !_.isArray(numArr) || _.isString(numArr)) {
throw new Error(
`To generate random role-based private keys, an array containing the number of keys for each role should be defined.`
)
}
if (numArr.length > KEY_ROLE.RoleLast) {
throw new Error(`Unsupported role. The length of array should be less than ${KEY_ROLE.RoleLast}.`)
}

const randomKeys = [[], [], []]
for (let i = 0; i < numArr.length; i++) {
for (let j = 0; j < numArr[i]; j++) {
randomKeys[i].push(AccountLib.create(entropy || utils.randomHex(32)).privateKey)
}
}
return randomKeys
}

/**
* creates a keyring instance with parameters
*
Expand Down
Loading