diff --git a/.circleci/config.yml b/.circleci/config.yml
index d0170e30e55..1a53a694298 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -525,7 +525,7 @@ jobs:
command: |
set -e
cd packages/celotool
- ./ci_test_transfers.sh checkout asaj/bls-sign-round
+ ./ci_test_transfers.sh checkout asaj/key-rotation-plus-enode
end-to-end-geth-blockchain-parameters-test:
<<: *e2e-defaults
@@ -543,7 +543,7 @@ jobs:
command: |
set -e
cd packages/celotool
- ./ci_test_blockchain_parameters.sh checkout asaj/bls-sign-round
+ ./ci_test_blockchain_parameters.sh checkout asaj/key-rotation-plus-enode
end-to-end-geth-governance-test:
<<: *e2e-defaults
@@ -563,7 +563,7 @@ jobs:
command: |
set -e
cd packages/celotool
- ./ci_test_governance.sh checkout asaj/bls-sign-round
+ ./ci_test_governance.sh checkout asaj/key-rotation-plus-enode
end-to-end-geth-sync-test:
<<: *e2e-defaults
@@ -582,7 +582,7 @@ jobs:
command: |
set -e
cd packages/celotool
- ./ci_test_sync.sh checkout asaj/bls-sign-round
+ ./ci_test_sync.sh checkout asaj/key-rotation-plus-enode
end-to-end-geth-validator-order-test:
<<: *e2e-defaults
@@ -600,7 +600,7 @@ jobs:
command: |
set -e
cd packages/celotool
- ./ci_test_validator_order.sh checkout asaj/bls-sign-round
+ ./ci_test_validator_order.sh checkout asaj/key-rotation-plus-enode
web:
working_directory: ~/app
diff --git a/packages/celotool/src/e2e-tests/governance_tests.ts b/packages/celotool/src/e2e-tests/governance_tests.ts
index 7f66cc0ee0e..87af3bce44c 100644
--- a/packages/celotool/src/e2e-tests/governance_tests.ts
+++ b/packages/celotool/src/e2e-tests/governance_tests.ts
@@ -1,5 +1,7 @@
+// tslint:disable-next-line: no-reference (Required to make this work w/ ts-node)
+///
import { ContractKit, newKitFromWeb3 } from '@celo/contractkit'
-import { AccountsWrapper } from '@celo/contractkit/lib/wrappers/Accounts'
+import { getBlsPoP, getBlsPublicKey } from '@celo/utils/lib/bls'
import { fromFixed, toFixed } from '@celo/utils/lib/fixidity'
import BigNumber from 'bignumber.js'
import { assert } from 'chai'
@@ -8,17 +10,21 @@ import {
assertAlmostEqual,
getContext,
getEnode,
+ GethInstanceConfig,
importGenesis,
initAndStartGeth,
sleep,
} from './utils'
+// TODO(asa): Test independent rotation of ecdsa, bls keys.
describe('governance tests', () => {
const gethConfig = {
migrate: true,
instances: [
+ // Validators 0 and 1 are swapped in and out of the group.
{ name: 'validator0', validating: true, syncmode: 'full', port: 30303, rpcport: 8545 },
{ name: 'validator1', validating: true, syncmode: 'full', port: 30305, rpcport: 8547 },
+ // Validator 2 will authorize a validating key every other epoch.
{ name: 'validator2', validating: true, syncmode: 'full', port: 30307, rpcport: 8549 },
{ name: 'validator3', validating: true, syncmode: 'full', port: 30309, rpcport: 8551 },
{ name: 'validator4', validating: true, syncmode: 'full', port: 30311, rpcport: 8553 },
@@ -34,7 +40,7 @@ describe('governance tests', () => {
let goldToken: any
let registry: any
let validators: any
- let accounts: AccountsWrapper
+ let accounts: any
let kit: ContractKit
before(async function(this: any) {
@@ -55,7 +61,7 @@ describe('governance tests', () => {
registry = await kit._web3Contracts.getRegistry()
election = await kit._web3Contracts.getElection()
epochRewards = await kit._web3Contracts.getEpochRewards()
- accounts = await kit.contracts.getAccounts()
+ accounts = await kit._web3Contracts.getAccounts()
}
const unlockAccount = async (address: string, theWeb3: any) => {
@@ -79,9 +85,17 @@ describe('governance tests', () => {
}
}
- const getValidatorGroupKeys = async () => {
+ const getValidatorSigner = (address: string, blockNumber?: number) => {
+ if (blockNumber) {
+ return accounts.methods.getValidatorSigner(address).call({}, blockNumber)
+ } else {
+ return accounts.methods.getValidatorSigner(address).call()
+ }
+ }
+
+ const getValidatorGroupPrivateKey = async () => {
const [groupAddress] = await validators.methods.getRegisteredValidatorGroups().call()
- const name = await accounts.getName(groupAddress)
+ const name = await accounts.methods.getName(groupAddress).call()
const encryptedKeystore64 = name.split(' ')[1]
const encryptedKeystore = JSON.parse(Buffer.from(encryptedKeystore64, 'base64').toString())
// The validator group ID is the validator group keystore encrypted with validator 0's
@@ -89,7 +103,7 @@ describe('governance tests', () => {
// @ts-ignore
const encryptionKey = `0x${gethConfig.instances[0].privateKey}`
const decryptedKeystore = web3.eth.accounts.decrypt(encryptedKeystore, encryptionKey)
- return [groupAddress, decryptedKeystore.privateKey]
+ return decryptedKeystore.privateKey
}
const activate = async (account: string, txOptions: any = {}) => {
@@ -103,12 +117,8 @@ describe('governance tests', () => {
return tx.send({ from: account, ...txOptions, gas })
}
- const removeMember = async (
- groupWeb3: any,
- group: string,
- member: string,
- txOptions: any = {}
- ) => {
+ const removeMember = async (groupWeb3: any, member: string, txOptions: any = {}) => {
+ const group = (await groupWeb3.eth.getAccounts())[0]
await unlockAccount(group, groupWeb3)
const tx = validators.methods.removeMember(member)
let gas = txOptions.gas
@@ -118,7 +128,8 @@ describe('governance tests', () => {
return tx.send({ from: group, ...txOptions, gas })
}
- const addMember = async (groupWeb3: any, group: string, member: string, txOptions: any = {}) => {
+ const addMember = async (groupWeb3: any, member: string, txOptions: any = {}) => {
+ const group = (await groupWeb3.eth.getAccounts())[0]
await unlockAccount(group, groupWeb3)
const tx = validators.methods.addMember(member)
let gas = txOptions.gas
@@ -128,6 +139,37 @@ describe('governance tests', () => {
return tx.send({ from: group, ...txOptions, gas })
}
+ const authorizeValidatorSigner = async (validatorWeb3: any, signerWeb3: any) => {
+ const validator: string = (await validatorWeb3.eth.getAccounts())[0]
+ const signer: string = (await signerWeb3.eth.getAccounts())[0]
+ await unlockAccount(validator, validatorWeb3)
+ await unlockAccount(signer, signerWeb3)
+ const pop = await (await newKitFromWeb3(
+ signerWeb3
+ ).contracts.getAccounts()).generateProofOfSigningKeyPossession(validator, signer)
+ const accountsWrapper = await newKitFromWeb3(validatorWeb3).contracts.getAccounts()
+ return (await accountsWrapper.authorizeValidatorSigner(signer, pop)).sendAndWaitForReceipt({
+ from: validator,
+ })
+ }
+
+ const updateValidatorBlsKey = async (
+ validatorWeb3: any,
+ signerWeb3: any,
+ signerPrivateKey: string
+ ) => {
+ const validator: string = (await validatorWeb3.eth.getAccounts())[0]
+ const signer: string = (await signerWeb3.eth.getAccounts())[0]
+ await unlockAccount(signer, signerWeb3)
+ const blsPublicKey = getBlsPublicKey(signerPrivateKey)
+ const blsPop = getBlsPoP(validator, signerPrivateKey)
+ // TODO(asa): Send this from the signer instead.
+ const validatorsWrapper = await newKitFromWeb3(validatorWeb3).contracts.getValidators()
+ return validatorsWrapper
+ .updateBlsPublicKey(blsPublicKey, blsPop)
+ .sendAndWaitForReceipt({ from: validator })
+ }
+
const isLastBlockOfEpoch = (blockNumber: number, epochSize: number) => {
return blockNumber % epochSize === 0
}
@@ -144,32 +186,72 @@ describe('governance tests', () => {
const previousBalance = new BigNumber(
await token.methods.balanceOf(address).call({}, blockNumber - 1)
)
- assert.isNotNaN(currentBalance)
- assert.isNotNaN(previousBalance)
+ assert.isFalse(currentBalance.isNaN())
+ assert.isFalse(previousBalance.isNaN())
assertAlmostEqual(currentBalance.minus(previousBalance), expected)
}
describe('when the validator set is changing', () => {
let epoch: number
const blockNumbers: number[] = []
- let allValidators: string[]
+ let validatorAccounts: string[]
before(async function(this: any) {
this.timeout(0) // Disable test timeout
await restart()
- const [groupAddress, groupPrivateKey] = await getValidatorGroupKeys()
-
- const groupInstance = {
- name: 'validatorGroup',
- validating: false,
- syncmode: 'full',
- port: 30325,
- wsport: 8567,
- privateKey: groupPrivateKey.slice(2),
- peers: [await getEnode(8545)],
- }
- await initAndStartGeth(context.hooks.gethBinaryPath, groupInstance)
- allValidators = await getValidatorGroupMembers()
- assert.equal(allValidators.length, 5)
+ const groupPrivateKey = await getValidatorGroupPrivateKey()
+ const rotation0PrivateKey =
+ '0xa42ac9c99f6ab2c96ee6cae1b40d36187f65cd878737f6623cd363fb94ba7087'
+ const rotation1PrivateKey =
+ '0x4519cae145fb9499358be484ca60c80d8f5b7f9c13ff82c88ec9e13283e9de1a'
+ const additionalNodes: GethInstanceConfig[] = [
+ {
+ name: 'validatorGroup',
+ validating: false,
+ syncmode: 'full',
+ port: 30313,
+ wsport: 8555,
+ rpcport: 8557,
+ privateKey: groupPrivateKey.slice(2),
+ peers: [await getEnode(8545)],
+ },
+ ]
+ await Promise.all(
+ additionalNodes.map((nodeConfig) =>
+ initAndStartGeth(context.hooks.gethBinaryPath, nodeConfig)
+ )
+ )
+ // Connect the validating nodes to the non-validating nodes, to test that announce messages
+ // are properly gossiped.
+ const additionalValidatingNodes = [
+ {
+ name: 'validator2KeyRotation0',
+ validating: true,
+ syncmode: 'full',
+ lightserv: false,
+ port: 30315,
+ wsport: 8559,
+ privateKey: rotation0PrivateKey.slice(2),
+ peers: [await getEnode(8557)],
+ },
+ {
+ name: 'validator2KeyRotation1',
+ validating: true,
+ syncmode: 'full',
+ lightserv: false,
+ port: 30317,
+ wsport: 8561,
+ privateKey: rotation1PrivateKey.slice(2),
+ peers: [await getEnode(8557)],
+ },
+ ]
+ await Promise.all(
+ additionalValidatingNodes.map((nodeConfig) =>
+ initAndStartGeth(context.hooks.gethBinaryPath, nodeConfig)
+ )
+ )
+
+ validatorAccounts = await getValidatorGroupMembers()
+ assert.equal(validatorAccounts.length, 5)
epoch = new BigNumber(await validators.methods.getEpochSize().call()).toNumber()
assert.equal(epoch, 10)
@@ -180,26 +262,59 @@ describe('governance tests', () => {
await sleep(0.1)
} while (blockNumber % epoch !== 1)
- await activate(allValidators[0])
- const groupWeb3 = new Web3('ws://localhost:8567')
+ await activate(validatorAccounts[0])
+
+ // Prepare for member swapping.
+ const groupWeb3 = new Web3('ws://localhost:8555')
const groupKit = newKitFromWeb3(groupWeb3)
validators = await groupKit._web3Contracts.getValidators()
- const membersToSwap = [allValidators[0], allValidators[1]]
- let includedMemberIndex = 1
- await removeMember(groupWeb3, groupAddress, membersToSwap[0])
+ const membersToSwap = [validatorAccounts[0], validatorAccounts[1]]
+ await removeMember(groupWeb3, membersToSwap[1])
+
+ // Prepare for key rotation.
+ const validatorWeb3 = new Web3('http://localhost:8549')
+ const authorizedWeb3s = [new Web3('ws://localhost:8559'), new Web3('ws://localhost:8561')]
+ const authorizedPrivateKeys = [rotation0PrivateKey, rotation1PrivateKey]
+
+ let index = 0
+ let errorWhileChangingValidatorSet = ''
+ // Can't recycle signing keys.
+ let doneAuthorizing = false
const changeValidatorSet = async (header: any) => {
- blockNumbers.push(header.number)
- // At the start of epoch N, swap members so the validator set is different for epoch N + 1.
- if (header.number % epoch === 1) {
- const memberToRemove = membersToSwap[includedMemberIndex]
- const memberToAdd = membersToSwap[(includedMemberIndex + 1) % 2]
- await removeMember(groupWeb3, groupAddress, memberToRemove)
- await addMember(groupWeb3, groupAddress, memberToAdd)
- includedMemberIndex = (includedMemberIndex + 1) % 2
- const newMembers = await getValidatorGroupMembers()
- assert.include(newMembers, memberToAdd)
- assert.notInclude(newMembers, memberToRemove)
+ try {
+ blockNumbers.push(header.number)
+ // At the start of epoch N, perform actions so the validator set is different for epoch N + 1.
+ if (header.number % epoch === 1) {
+ // 1. Swap validator0 and validator1 so one is a member of the group and the other is not.
+ const memberToRemove = membersToSwap[index]
+ const memberToAdd = membersToSwap[(index + 1) % 2]
+ await removeMember(groupWeb3, memberToRemove)
+ await addMember(groupWeb3, memberToAdd)
+ const newMembers = await getValidatorGroupMembers()
+ assert.include(newMembers, memberToAdd)
+ assert.notInclude(newMembers, memberToRemove)
+ // 2. Rotate keys for validator 2 by authorizing a new validating key.
+ if (!doneAuthorizing) {
+ await authorizeValidatorSigner(validatorWeb3, authorizedWeb3s[index])
+ await updateValidatorBlsKey(
+ validatorWeb3,
+ authorizedWeb3s[index],
+ authorizedPrivateKeys[index]
+ )
+ }
+ doneAuthorizing = doneAuthorizing || index === 1
+ const signingKeys = await Promise.all(
+ newMembers.map((v: string) => getValidatorSigner(v))
+ )
+ // Confirm that authorizing signing keys worked.
+ // @ts-ignore Type does not include `notSameMembers`
+ assert.notSameMembers(newMembers, signingKeys)
+ index = (index + 1) % 2
+ }
+ } catch (e) {
+ console.error(e)
+ errorWhileChangingValidatorSet = e
}
}
@@ -210,12 +325,22 @@ describe('governance tests', () => {
;(subscription as any).unsubscribe()
// Wait for the current epoch to complete.
await sleep(epoch)
+ assert.equal(errorWhileChangingValidatorSet, '')
})
- const getValidatorSetAtBlock = async (blockNumber: number): Promise => {
+ const getValidatorSetSignersAtBlock = async (blockNumber: number): Promise => {
return election.methods.currentValidators().call({}, blockNumber)
}
+ const getValidatorSetAccountsAtBlock = async (blockNumber: number) => {
+ const signingKeys = await getValidatorSetSignersAtBlock(blockNumber)
+ return Promise.all(
+ signingKeys.map((address: string) =>
+ accounts.methods.signerToAccount(address).call({}, blockNumber)
+ )
+ )
+ }
+
const getLastEpochBlock = (blockNumber: number) => {
const epochNumber = Math.floor((blockNumber - 1) / epoch)
return epochNumber * epoch
@@ -232,20 +357,39 @@ describe('governance tests', () => {
}
})
- it('should always return a validator set equal to the group members at the end of the last epoch', async () => {
+ it('should always return a validator set equal to the signing keys of the group members at the end of the last epoch', async function(this: any) {
+ this.timeout(0)
for (const blockNumber of blockNumbers) {
const lastEpochBlock = getLastEpochBlock(blockNumber)
- const groupMembership = await getValidatorGroupMembers(lastEpochBlock)
- const validatorSet = await getValidatorSetAtBlock(blockNumber)
- assert.sameMembers(groupMembership, validatorSet)
+ const memberAccounts = await getValidatorGroupMembers(lastEpochBlock)
+ const memberSigners = await Promise.all(
+ memberAccounts.map((v: string) => getValidatorSigner(v, lastEpochBlock))
+ )
+ const validatorSetSigners = await getValidatorSetSignersAtBlock(blockNumber)
+ const validatorSetAccounts = await getValidatorSetAccountsAtBlock(blockNumber)
+ assert.sameMembers(memberSigners, validatorSetSigners)
+ assert.sameMembers(memberAccounts, validatorSetAccounts)
}
})
- it('should only have created blocks whose miner was in the current validator set', async () => {
+ it('should block propose in a round robin fashion', async () => {
+ let roundRobinOrder: string[] = []
for (const blockNumber of blockNumbers) {
- const validatorSet = await getValidatorSetAtBlock(blockNumber)
+ const lastEpochBlock = getLastEpochBlock(blockNumber)
+ // Fetch the round robin order if it hasn't already been set for this epoch.
+ if (roundRobinOrder.length === 0 || blockNumber === lastEpochBlock + 1) {
+ const validatorSet = await getValidatorSetSignersAtBlock(blockNumber)
+ roundRobinOrder = await Promise.all(
+ validatorSet.map(
+ async (_, i) => (await web3.eth.getBlock(lastEpochBlock + i + 1)).miner
+ )
+ )
+ assert.sameMembers(roundRobinOrder, validatorSet)
+ }
+ const indexInEpoch = blockNumber - lastEpochBlock - 1
+ const expectedProposer = roundRobinOrder[indexInEpoch % roundRobinOrder.length]
const block = await web3.eth.getBlock(blockNumber)
- assert.include(validatorSet.map((x) => x.toLowerCase()), block.miner.toLowerCase())
+ assert.equal(block.miner.toLowerCase(), expectedProposer.toLowerCase())
}
})
@@ -257,10 +401,10 @@ describe('governance tests', () => {
const assertScoreUnchanged = async (validator: string, blockNumber: number) => {
const score = new BigNumber(
- (await validators.methods.getValidator(validator).call({}, blockNumber))[2]
+ (await validators.methods.getValidator(validator).call({}, blockNumber)).score
)
const previousScore = new BigNumber(
- (await validators.methods.getValidator(validator).call({}, blockNumber - 1))[2]
+ (await validators.methods.getValidator(validator).call({}, blockNumber - 1)).score
)
assert.isFalse(score.isNaN())
assert.isFalse(previousScore.isNaN())
@@ -269,10 +413,10 @@ describe('governance tests', () => {
const assertScoreChanged = async (validator: string, blockNumber: number) => {
const score = new BigNumber(
- (await validators.methods.getValidator(validator).call({}, blockNumber))[2]
+ (await validators.methods.getValidator(validator).call({}, blockNumber)).score
)
const previousScore = new BigNumber(
- (await validators.methods.getValidator(validator).call({}, blockNumber - 1))[2]
+ (await validators.methods.getValidator(validator).call({}, blockNumber - 1)).score
)
assert.isFalse(score.isNaN())
assert.isFalse(previousScore.isNaN())
@@ -286,10 +430,10 @@ describe('governance tests', () => {
let expectUnchangedScores: string[]
let expectChangedScores: string[]
if (isLastBlockOfEpoch(blockNumber, epoch)) {
- expectChangedScores = await getValidatorSetAtBlock(blockNumber)
- expectUnchangedScores = allValidators.filter((x) => !expectChangedScores.includes(x))
+ expectChangedScores = await getValidatorSetAccountsAtBlock(blockNumber)
+ expectUnchangedScores = validatorAccounts.filter((x) => !expectChangedScores.includes(x))
} else {
- expectUnchangedScores = allValidators
+ expectUnchangedScores = validatorAccounts
expectChangedScores = []
}
@@ -316,9 +460,9 @@ describe('governance tests', () => {
const getExpectedTotalPayment = async (validator: string, blockNumber: number) => {
const score = new BigNumber(
- (await validators.methods.getValidator(validator).call({}, blockNumber))[2]
+ (await validators.methods.getValidator(validator).call({}, blockNumber)).score
)
- assert.isNotNaN(score)
+ assert.isFalse(score.isNaN())
// We need to calculate the rewards multiplier for the previous block, before
// the rewards actually are awarded.
const rewardsMultiplier = new BigNumber(
@@ -333,10 +477,12 @@ describe('governance tests', () => {
let expectUnchangedBalances: string[]
let expectChangedBalances: string[]
if (isLastBlockOfEpoch(blockNumber, epoch)) {
- expectChangedBalances = await getValidatorSetAtBlock(blockNumber)
- expectUnchangedBalances = allValidators.filter((x) => !expectChangedBalances.includes(x))
+ expectChangedBalances = await getValidatorSetAccountsAtBlock(blockNumber)
+ expectUnchangedBalances = validatorAccounts.filter(
+ (x) => !expectChangedBalances.includes(x)
+ )
} else {
- expectUnchangedBalances = allValidators
+ expectUnchangedBalances = validatorAccounts
expectChangedBalances = []
}
@@ -484,13 +630,13 @@ describe('governance tests', () => {
)
const difference = currentTarget.minus(previousTarget)
- // Assert equal to 10 decimal places due to rounding errors.
+ // Assert equal to 9 decimal places due to rounding errors.
assert.equal(
fromFixed(difference)
- .dp(10)
+ .dp(9)
.toFixed(),
fromFixed(expected)
- .dp(10)
+ .dp(9)
.toFixed()
)
}
diff --git a/packages/celotool/src/e2e-tests/utils.ts b/packages/celotool/src/e2e-tests/utils.ts
index 98f669af9e8..9aaaa882178 100644
--- a/packages/celotool/src/e2e-tests/utils.ts
+++ b/packages/celotool/src/e2e-tests/utils.ts
@@ -213,7 +213,7 @@ export async function init(gethBinaryPath: string, datadir: string, genesisPath:
}
export async function importPrivateKey(gethBinaryPath: string, instance: GethInstanceConfig) {
- const keyFile = '/tmp/key.txt'
+ const keyFile = `/${getDatadir(instance)}/key.txt`
fs.writeFileSync(keyFile, instance.privateKey)
console.info(`geth:${instance.name}: import account`)
await execCmdWithExitOnFailure(
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 371e03eab0c..3e7444c9503 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -38,6 +38,7 @@
"@oclif/plugin-help": "^2",
"bip32": "^1.0.2",
"bip39": "^2.5.0",
+ "bls12377js": "https://github.com/celo-org/bls12377js#cada1105f4a5e4c2ddd239c1874df3bf33144a10",
"chalk": "^2.4.2",
"cli-table": "^0.3.1",
"cli-ux": "^5.3.1",
diff --git a/packages/cli/src/commands/account/authorize.test.ts b/packages/cli/src/commands/account/authorize.test.ts
index 1127bd6afcf..fada8a235c7 100644
--- a/packages/cli/src/commands/account/authorize.test.ts
+++ b/packages/cli/src/commands/account/authorize.test.ts
@@ -8,14 +8,32 @@ process.env.NO_SYNCCHECK = 'true'
testWithGanache('account:authorize cmd', (web3: Web3) => {
test('can authorize account', async () => {
const accounts = await web3.eth.getAccounts()
- await Register.run(['--from', accounts[0], '--name', 'Chapulin Colorado'])
- await Authorize.run(['--from', accounts[0], '--role', 'validation', '--to', accounts[1]])
+ await Register.run(['--from', accounts[0]])
+ await Authorize.run([
+ '--from',
+ accounts[0],
+ '--role',
+ 'validator',
+ '--signer',
+ accounts[1],
+ '--pop',
+ '0x1b9fca4bbb5bfb1dbe69ef1cddbd9b4202dcb6b134c5170611e1e36ecfa468d7b46c85328d504934fce6c2a1571603a50ae224d2b32685e84d4d1a1eebad8452eb',
+ ])
})
test('fails if from is not an account', async () => {
const accounts = await web3.eth.getAccounts()
await expect(
- Authorize.run(['--from', accounts[0], '--role', 'validation', '--to', accounts[1]])
+ Authorize.run([
+ '--from',
+ accounts[0],
+ '--role',
+ 'validator',
+ '--signer',
+ accounts[1],
+ '--pop',
+ '0x1b9fca4bbb5bfb1dbe69ef1cddbd9b4202dcb6b134c5170611e1e36ecfa468d7b46c85328d504934fce6c2a1571603a50ae224d2b32685e84d4d1a1eebad8452eb',
+ ])
).rejects.toThrow()
})
})
diff --git a/packages/cli/src/commands/account/authorize.ts b/packages/cli/src/commands/account/authorize.ts
index f34239dae7c..4878f7e4013 100644
--- a/packages/cli/src/commands/account/authorize.ts
+++ b/packages/cli/src/commands/account/authorize.ts
@@ -5,40 +5,35 @@ import { displaySendTx } from '../../utils/cli'
import { Flags } from '../../utils/command'
export default class Authorize extends BaseCommand {
- static description = 'Authorize an attestation, validation or vote signing key'
+ static description = 'Authorize an attestation, validator, or vote signer'
static flags = {
...BaseCommand.flags,
from: Flags.address({ required: true }),
role: flags.string({
char: 'r',
- options: ['vote', 'validation', 'attestation'],
+ options: ['vote', 'validator', 'attestation'],
description: 'Role to delegate',
+ required: true,
}),
- to: Flags.address({ required: true }),
+ pop: flags.string({
+ description: 'Proof-of-possession of the signer key',
+ required: true,
+ }),
+ signer: Flags.address({ required: true }),
}
static args = []
static examples = [
- 'authorize --from 0x5409ED021D9299bf6814279A6A1411A7e866A631 --role vote --to 0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d',
+ 'authorize --from 0x5409ED021D9299bf6814279A6A1411A7e866A631 --role vote --signer 0x6ecbe1db9ef729cbe972c83fb886247691fb6beb --pop 0x1b9fca4bbb5bfb1dbe69ef1cddbd9b4202dcb6b134c5170611e1e36ecfa468d7b46c85328d504934fce6c2a1571603a50ae224d2b32685e84d4d1a1eebad8452eb',
]
async run() {
const res = this.parse(Authorize)
-
- if (!res.flags.role) {
- this.error(`Specify role with --role`)
- return
- }
-
- if (!res.flags.to) {
- this.error(`Specify authorized address with --to`)
- return
- }
-
this.kit.defaultAccount = res.flags.from
const accounts = await this.kit.contracts.getAccounts()
+ const sig = accounts.parseSignatureOfAddress(res.flags.from, res.flags.signer, res.flags.pop)
await newCheckBuilder(this)
.isAccount(res.flags.from)
@@ -46,11 +41,11 @@ export default class Authorize extends BaseCommand {
let tx: any
if (res.flags.role === 'vote') {
- tx = await accounts.authorizeVoteSigner(res.flags.from, res.flags.to)
- } else if (res.flags.role === 'validation') {
- tx = await accounts.authorizeValidationSigner(res.flags.from, res.flags.to)
+ tx = await accounts.authorizeVoteSigner(res.flags.signer, sig)
+ } else if (res.flags.role === 'validator') {
+ tx = await accounts.authorizeValidatorSigner(res.flags.signer, sig)
} else if (res.flags.role === 'attestation') {
- tx = await accounts.authorizeAttestationSigner(res.flags.from, res.flags.to)
+ tx = await accounts.authorizeAttestationSigner(res.flags.signer, sig)
} else {
this.error(`Invalid role provided`)
return
diff --git a/packages/cli/src/commands/account/claims.test.ts b/packages/cli/src/commands/account/claims.test.ts
index df2994703b3..6cb91fb8ce9 100644
--- a/packages/cli/src/commands/account/claims.test.ts
+++ b/packages/cli/src/commands/account/claims.test.ts
@@ -11,7 +11,7 @@ import CreateMetadata from './create-metadata'
import RegisterMetadata from './register-metadata'
process.env.NO_SYNCCHECK = 'true'
-testWithGanache('account:authorize cmd', (web3: Web3) => {
+testWithGanache('account metadata cmds', (web3: Web3) => {
let account: string
let accounts: string[]
beforeEach(async () => {
diff --git a/packages/cli/src/commands/account/proof-of-possession.ts b/packages/cli/src/commands/account/proof-of-possession.ts
new file mode 100644
index 00000000000..24b0ab206ba
--- /dev/null
+++ b/packages/cli/src/commands/account/proof-of-possession.ts
@@ -0,0 +1,28 @@
+import { serializeSignature } from '@celo/utils/lib/signatureUtils'
+import { BaseCommand } from '../../base'
+import { printValueMap } from '../../utils/cli'
+import { Flags } from '../../utils/command'
+
+export default class ProofOfPossession extends BaseCommand {
+ static description = 'Generate proof-of-possession to be used to authorize a signer'
+
+ static flags = {
+ ...BaseCommand.flags,
+ signer: Flags.address({ required: true }),
+ account: Flags.address({ required: true }),
+ }
+
+ static examples = [
+ 'proof-of-possession --account 0x5409ed021d9299bf6814279a6a1411a7e866a631 --signer 0x6ecbe1db9ef729cbe972c83fb886247691fb6beb',
+ ]
+
+ async run() {
+ const res = this.parse(ProofOfPossession)
+ const accounts = await this.kit.contracts.getAccounts()
+ const pop = await accounts.generateProofOfSigningKeyPossession(
+ res.flags.account,
+ res.flags.signer
+ )
+ printValueMap({ signature: serializeSignature(pop) })
+ }
+}
diff --git a/packages/cli/src/commands/account/register.ts b/packages/cli/src/commands/account/register.ts
index ea121e9cdfc..9dd445c5838 100644
--- a/packages/cli/src/commands/account/register.ts
+++ b/packages/cli/src/commands/account/register.ts
@@ -9,13 +9,16 @@ export default class Register extends BaseCommand {
static flags = {
...BaseCommand.flags,
- name: flags.string({ required: true }),
+ name: flags.string(),
from: Flags.address({ required: true }),
}
static args = []
- static examples = ['register']
+ static examples = [
+ 'register --from 0x5409ed021d9299bf6814279a6a1411a7e866a631',
+ 'register --from 0x5409ed021d9299bf6814279a6a1411a7e866a631 --name test-account',
+ ]
async run() {
const res = this.parse(Register)
@@ -26,6 +29,8 @@ export default class Register extends BaseCommand {
.isNotAccount(res.flags.from)
.runChecks()
await displaySendTx('register', accounts.createAccount())
- await displaySendTx('setName', accounts.setName(res.flags.name))
+ if (res.flags.name) {
+ await displaySendTx('setName', accounts.setName(res.flags.name))
+ }
}
}
diff --git a/packages/cli/src/commands/validator/publicKey.ts b/packages/cli/src/commands/validator/publicKey.ts
deleted file mode 100644
index cfe11ab125a..00000000000
--- a/packages/cli/src/commands/validator/publicKey.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import { BaseCommand } from '../../base'
-import { newCheckBuilder } from '../../utils/checks'
-import { displaySendTx } from '../../utils/cli'
-import { Flags } from '../../utils/command'
-import { getPubKeyFromAddrAndWeb3 } from '../../utils/helpers'
-
-export default class ValidatorPublicKey extends BaseCommand {
- static description = 'Manage BLS public key data for a validator'
-
- static flags = {
- ...BaseCommand.flags,
- from: Flags.address({ required: true, description: "Validator's address" }),
- publicKey: Flags.publicKey({ required: true }),
- }
-
- static examples = [
- 'publickey --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --publicKey 0xc52f3fab06e22a54915a8765c4f6826090cfac5e40282b43844bf1c0df83aaa632e55b67869758f2291d1aabe0ebecc7cbf4236aaa45e3e0cfbf997eda082ae19d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae30005d223780d785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d7405011220a66a6257562d0c26dabf64485a1d96bad27bb1c0fd6080a75b0ec9f75b50298a2a8e04b02b2688c8104fca61fb00',
- ]
- async run() {
- const res = this.parse(ValidatorPublicKey)
- this.kit.defaultAccount = res.flags.from
- const validators = await this.kit.contracts.getValidators()
- const accounts = await this.kit.contracts.getAccounts()
-
- await newCheckBuilder(this, res.flags.from)
- .isSignerOrAccount()
- .canSignValidatorTxs()
- .signerAccountIsValidator()
- .runChecks()
-
- await displaySendTx(
- 'updatePublicKeysData',
- validators.updatePublicKeysData(res.flags.publicKey as any)
- )
-
- // register encryption key on accounts contract
- // TODO: Use a different key data encryption
- const pubKey = await getPubKeyFromAddrAndWeb3(res.flags.from, this.web3)
- // TODO fix typing
- const setKeyTx = accounts.setAccountDataEncryptionKey(pubKey as any)
- await displaySendTx('Set encryption key', setKeyTx)
- }
-}
diff --git a/packages/cli/src/commands/validator/register.ts b/packages/cli/src/commands/validator/register.ts
index bc50dd33dd6..0753ae27088 100644
--- a/packages/cli/src/commands/validator/register.ts
+++ b/packages/cli/src/commands/validator/register.ts
@@ -1,8 +1,8 @@
+import { addressToPublicKey } from '@celo/utils/lib/signatureUtils'
import { BaseCommand } from '../../base'
import { newCheckBuilder } from '../../utils/checks'
import { displaySendTx } from '../../utils/cli'
import { Flags } from '../../utils/command'
-import { getPubKeyFromAddrAndWeb3 } from '../../utils/helpers'
export default class ValidatorRegister extends BaseCommand {
static description = 'Register a new Validator'
@@ -10,12 +10,15 @@ export default class ValidatorRegister extends BaseCommand {
static flags = {
...BaseCommand.flags,
from: Flags.address({ required: true, description: 'Address for the Validator' }),
- publicKey: Flags.publicKey({ required: true }),
+ ecdsaKey: Flags.ecdsaPublicKey({ required: true }),
+ blsKey: Flags.blsPublicKey({ required: true }),
+ blsPop: Flags.blsProofOfPossession({ required: true }),
}
static examples = [
- 'register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --publicKey 0xc52f3fab06e22a54915a8765c4f6826090cfac5e40282b43844bf1c0df83aaa632e55b67869758f2291d1aabe0ebecc7cbf4236aaa45e3e0cfbf997eda082ae19d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae30005d223780d785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d7405011220a66a6257562d0c26dabf64485a1d96bad27bb1c0fd6080a75b0ec9f75b50298a2a8e04b02b2688c8104fca61fb00',
+ 'register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --ecdsaKey 0xc52f3fab06e22a54915a8765c4f6826090cfac5e40282b43844bf1c0df83aaa632e55b67869758f2291d1aabe0ebecc7cbf4236aaa45e3e0cfbf997eda082ae1 --blsKey 0x9d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae300 --blsPop 0x05d223780d785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d7405011220a66a6257562d0c26dabf64485a1d96bad27bb1c0fd6080a75b0ec9f75b50298a2a8e04b02b2688c8104fca61fb00',
]
+
async run() {
const res = this.parse(ValidatorRegister)
this.kit.defaultAccount = res.flags.from
@@ -31,12 +34,16 @@ export default class ValidatorRegister extends BaseCommand {
await displaySendTx(
'registerValidator',
- validators.registerValidator(res.flags.publicKey as any)
+ validators.registerValidator(
+ res.flags.ecdsaKey as any,
+ res.flags.blsKey as any,
+ res.flags.blsPop as any
+ )
)
// register encryption key on accounts contract
// TODO: Use a different key data encryption
- const pubKey = await getPubKeyFromAddrAndWeb3(res.flags.from, this.web3)
+ const pubKey = await addressToPublicKey(res.flags.from, this.web3.eth.sign)
// TODO fix typing
const setKeyTx = accounts.setAccountDataEncryptionKey(pubKey as any)
await displaySendTx('Set encryption key', setKeyTx)
diff --git a/packages/cli/src/commands/validator/update-bls-public-key.ts b/packages/cli/src/commands/validator/update-bls-public-key.ts
new file mode 100644
index 00000000000..70521743dea
--- /dev/null
+++ b/packages/cli/src/commands/validator/update-bls-public-key.ts
@@ -0,0 +1,34 @@
+import { BaseCommand } from '../../base'
+import { newCheckBuilder } from '../../utils/checks'
+import { displaySendTx } from '../../utils/cli'
+import { Flags } from '../../utils/command'
+
+export default class ValidatorUpdateBlsPublicKey extends BaseCommand {
+ static description = 'Update BLS key for a validator'
+
+ static flags = {
+ ...BaseCommand.flags,
+ from: Flags.address({ required: true, description: "Validator's address" }),
+ blsKey: Flags.blsPublicKey({ required: true }),
+ blsPop: Flags.blsProofOfPossession({ required: true }),
+ }
+
+ static examples = [
+ 'update-bls-key --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --blsKey 0x9d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae300 --blsPop 0x05d223780d785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d7405011220a66a6257562d0c26dabf64485a1d96bad27bb1c0fd6080a75b0ec9f75b50298a2a8e04b02b2688c8104fca61fb00',
+ ]
+ async run() {
+ const res = this.parse(ValidatorUpdateBlsPublicKey)
+ this.kit.defaultAccount = res.flags.from
+ const validators = await this.kit.contracts.getValidators()
+ await newCheckBuilder(this, res.flags.from)
+ .isSignerOrAccount()
+ .canSignValidatorTxs()
+ .signerAccountIsValidator()
+ .runChecks()
+
+ await displaySendTx(
+ 'updateBlsPublicKey',
+ validators.updateBlsPublicKey(res.flags.blsKey as any, res.flags.blsPop as any)
+ )
+ }
+}
diff --git a/packages/cli/src/utils/checks.ts b/packages/cli/src/utils/checks.ts
index 0e79ab09874..d51d3b21df4 100644
--- a/packages/cli/src/utils/checks.ts
+++ b/packages/cli/src/utils/checks.ts
@@ -73,7 +73,7 @@ class CheckBuilder {
'Signer can sign Validator Txs',
this.withAccounts((lg) =>
lg
- .activeValidationSignerToAccount(this.signer!)
+ .validatorSignerToAccount(this.signer!)
.then(() => true)
.catch(() => false)
)
diff --git a/packages/cli/src/utils/command.ts b/packages/cli/src/utils/command.ts
index dee09ae0690..7268010b70c 100644
--- a/packages/cli/src/utils/command.ts
+++ b/packages/cli/src/utils/command.ts
@@ -1,3 +1,4 @@
+import { BLS_POP_SIZE, BLS_PUBLIC_KEY_SIZE } from '@celo/utils/lib/bls'
import { URL_REGEX } from '@celo/utils/lib/io'
import { flags } from '@oclif/command'
import { CLIError } from '@oclif/errors'
@@ -5,14 +6,24 @@ import { IArg, ParseFn } from '@oclif/parser/lib/args'
import { pathExistsSync } from 'fs-extra'
import Web3 from 'web3'
-const parsePublicKey: ParseFn = (input) => {
- // Check that the string starts with 0x and has byte length of ecdsa pub key (64 bytes) + bls pub key (48 bytes) + proof of pos (96 bytes)
- if (Web3.utils.isHex(input) && input.length === 418 && input.startsWith('0x')) {
+const parseBytes = (input: string, length: number, msg: string) => {
+ // Check that the string starts with 0x and has byte length of `length`.
+ if (Web3.utils.isHex(input) && input.length === length && input.startsWith('0x')) {
return input
} else {
- throw new CLIError(`${input} is not a public key`)
+ throw new CLIError(msg)
}
}
+
+const parseEcdsaPublicKey: ParseFn = (input) => {
+ return parseBytes(input, 64, `${input} is not an ECDSA public key`)
+}
+const parseBlsPublicKey: ParseFn = (input) => {
+ return parseBytes(input, BLS_PUBLIC_KEY_SIZE, `${input} is not a BLS public key`)
+}
+const parseBlsProofOfPossession: ParseFn = (input) => {
+ return parseBytes(input, BLS_POP_SIZE, `${input} is not a BLS proof-of-possession`)
+}
const parseAddress: ParseFn = (input) => {
if (Web3.utils.isAddress(input)) {
return input
@@ -54,9 +65,19 @@ export const Flags = {
description: 'Account Address',
helpValue: '0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d',
}),
- publicKey: flags.build({
- parse: parsePublicKey,
- description: 'Public Key',
+ ecdsaPublicKey: flags.build({
+ parse: parseEcdsaPublicKey,
+ description: 'ECDSA Public Key',
+ helpValue: '0x',
+ }),
+ blsPublicKey: flags.build({
+ parse: parseBlsPublicKey,
+ description: 'BLS Public Key',
+ helpValue: '0x',
+ }),
+ blsProofOfPossession: flags.build({
+ parse: parseBlsProofOfPossession,
+ description: 'BLS Proof-of-Possession',
helpValue: '0x',
}),
url: flags.build({
diff --git a/packages/cli/src/utils/helpers.ts b/packages/cli/src/utils/helpers.ts
index d95c1354c57..4d8c7c0f768 100644
--- a/packages/cli/src/utils/helpers.ts
+++ b/packages/cli/src/utils/helpers.ts
@@ -1,29 +1,7 @@
-import { eqAddress } from '@celo/utils/lib/address'
-import ethjsutil from 'ethereumjs-util'
import Web3 from 'web3'
import { Block } from 'web3/eth/types'
import { failWith } from './cli'
-import assert = require('assert')
-
-export async function getPubKeyFromAddrAndWeb3(addr: string, web3: Web3) {
- const msg = new Buffer('dummy_msg_data')
- const data = '0x' + msg.toString('hex')
- // Note: Eth.sign typing displays incorrect parameter order
- const sig = await web3.eth.sign(data, addr)
-
- const rawsig = ethjsutil.fromRpcSig(sig)
-
- const prefix = new Buffer('\x19Ethereum Signed Message:\n')
- const prefixedMsg = ethjsutil.sha3(Buffer.concat([prefix, new Buffer(String(msg.length)), msg]))
- const pubKey = ethjsutil.ecrecover(prefixedMsg, rawsig.v, rawsig.r, rawsig.s)
-
- const computedAddr = ethjsutil.pubToAddress(pubKey).toString('hex')
- assert(eqAddress(computedAddr, addr), 'computed address !== addr')
-
- return pubKey
-}
-
export async function nodeIsSynced(web3: Web3): Promise {
if (process.env.NO_SYNCCHECK) {
return true
diff --git a/packages/contractkit/src/wrappers/Accounts.test.ts b/packages/contractkit/src/wrappers/Accounts.test.ts
new file mode 100644
index 00000000000..95bd22d4a18
--- /dev/null
+++ b/packages/contractkit/src/wrappers/Accounts.test.ts
@@ -0,0 +1,74 @@
+import { addressToPublicKey, parseSignature } from '@celo/utils/lib/signatureUtils'
+import Web3 from 'web3'
+import { newKitFromWeb3 } from '../kit'
+import { testWithGanache } from '../test-utils/ganache-test'
+import { AccountsWrapper } from './Accounts'
+import { LockedGoldWrapper } from './LockedGold'
+import { ValidatorsWrapper } from './Validators'
+
+/*
+TEST NOTES:
+- In migrations: The only account that has cUSD is accounts[0]
+*/
+
+const minLockedGoldValue = Web3.utils.toWei('10', 'ether') // 10 gold
+
+// Random hex strings
+const blsPublicKey =
+ '0x4d23d8cd06f30b1fa7cf368e2f5399ab04bb6846c682f493a98a607d3dfb7e53a712bb79b475c57b0ac2785460f91301'
+const blsPoP =
+ '0x9d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae30005d223780d785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d740501'
+
+testWithGanache('Accounts Wrapper', (web3) => {
+ const kit = newKitFromWeb3(web3)
+ let accounts: string[] = []
+ let accountsInstance: AccountsWrapper
+ let validators: ValidatorsWrapper
+ let lockedGold: LockedGoldWrapper
+
+ const registerAccountWithLockedGold = async (account: string) => {
+ if (!(await accountsInstance.isAccount(account))) {
+ await accountsInstance.createAccount().sendAndWaitForReceipt({ from: account })
+ }
+ await lockedGold.lock().sendAndWaitForReceipt({ from: account, value: minLockedGoldValue })
+ }
+
+ const getParsedSignatureOfAddress = async (address: string, signer: string) => {
+ const addressHash = web3.utils.soliditySha3({ type: 'address', value: address })
+ const signature = await web3.eth.sign(addressHash, signer)
+ return parseSignature(addressHash, signature, signer)
+ }
+
+ beforeAll(async () => {
+ accounts = await web3.eth.getAccounts()
+ validators = await kit.contracts.getValidators()
+ lockedGold = await kit.contracts.getLockedGold()
+ accountsInstance = await kit.contracts.getAccounts()
+ })
+
+ const setupValidator = async (validatorAccount: string) => {
+ const publicKey = await addressToPublicKey(validatorAccount, web3.eth.sign)
+ await registerAccountWithLockedGold(validatorAccount)
+ await validators
+ // @ts-ignore
+ .registerValidator(publicKey, blsPublicKey, blsPoP)
+ .sendAndWaitForReceipt({ from: validatorAccount })
+ }
+
+ test('SBAT authorize validator key when not a validator', async () => {
+ const account = accounts[0]
+ const signer = accounts[1]
+ await accountsInstance.createAccount()
+ const sig = await getParsedSignatureOfAddress(account, signer)
+ await accountsInstance.authorizeValidatorSigner(signer, sig)
+ })
+
+ test('SBAT authorize validator key when a validator', async () => {
+ const account = accounts[0]
+ const signer = accounts[1]
+ await accountsInstance.createAccount()
+ await setupValidator(account)
+ const sig = await getParsedSignatureOfAddress(account, signer)
+ await accountsInstance.authorizeValidatorSigner(signer, sig)
+ })
+})
diff --git a/packages/contractkit/src/wrappers/Accounts.ts b/packages/contractkit/src/wrappers/Accounts.ts
index 01afb340612..a995bc664e6 100644
--- a/packages/contractkit/src/wrappers/Accounts.ts
+++ b/packages/contractkit/src/wrappers/Accounts.ts
@@ -1,3 +1,9 @@
+import {
+ hashMessageWithPrefix,
+ parseSignature,
+ Signature,
+ signedMessageToPublicKey,
+} from '@celo/utils/lib/signatureUtils'
import Web3 from 'web3'
import { Address } from '../base'
import { Accounts } from '../generated/types/Accounts'
@@ -9,11 +15,6 @@ import {
toTransactionObject,
} from '../wrappers/BaseWrapper'
-enum SignerRole {
- Attestation,
- Validation,
- Vote,
-}
/**
* Contract for handling deposits needed for voting.
*/
@@ -40,12 +41,12 @@ export class AccountsWrapper extends BaseWrapper {
this.contract.methods.getVoteSigner
)
/**
- * Returns the validation signere for the specified account.
+ * Returns the validator signer for the specified account.
* @param account The address of the account.
* @return The address with which the account can register a validator or group.
*/
- getValidationSigner: (account: string) => Promise = proxyCall(
- this.contract.methods.getValidationSigner
+ getValidatorSigner: (account: string) => Promise = proxyCall(
+ this.contract.methods.getValidatorSigner
)
/**
@@ -53,8 +54,8 @@ export class AccountsWrapper extends BaseWrapper {
* @param signer Address that is authorized to sign the tx as validator
* @return The Account address
*/
- activeValidationSignerToAccount: (signer: Address) => Promise = proxyCall(
- this.contract.methods.activeValidationSignerToAccount
+ validatorSignerToAccount: (signer: Address) => Promise = proxyCall(
+ this.contract.methods.validatorSignerToAccount
)
/**
@@ -69,44 +70,98 @@ export class AccountsWrapper extends BaseWrapper {
* @param address The address of the account
* @return Returns `true` if account exists. Returns `false` otherwise.
*/
- isSigner: (address: string) => Promise = proxyCall(this.contract.methods.isAuthorized)
+ isSigner: (address: string) => Promise = proxyCall(
+ this.contract.methods.isAuthorizedSigner
+ )
/**
* Authorize an attestation signing key on behalf of this account to another address.
- * @param account Address of the active account.
- * @param attestationSigner The address of the signing key to authorize.
+ * @param signer The address of the signing key to authorize.
+ * @param proofOfSigningKeyPossession The account address signed by the signer address.
* @return A CeloTransactionObject
*/
async authorizeAttestationSigner(
- account: Address,
- attestationSigner: Address
+ signer: Address,
+ proofOfSigningKeyPossession: Signature
): Promise> {
- return this.authorizeSigner(SignerRole.Attestation, account, attestationSigner)
+ return toTransactionObject(
+ this.kit,
+ this.contract.methods.authorizeAttestationSigner(
+ signer,
+ proofOfSigningKeyPossession.v,
+ proofOfSigningKeyPossession.r,
+ proofOfSigningKeyPossession.s
+ )
+ )
}
/**
* Authorizes an address to sign votes on behalf of the account.
- * @param account Address of the active account.
- * @param voteSigner The address of the vote signing key to authorize.
+ * @param signer The address of the vote signing key to authorize.
+ * @param proofOfSigningKeyPossession The account address signed by the signer address.
* @return A CeloTransactionObject
*/
async authorizeVoteSigner(
- account: Address,
- voteSigner: Address
+ signer: Address,
+ proofOfSigningKeyPossession: Signature
): Promise> {
- return this.authorizeSigner(SignerRole.Vote, account, voteSigner)
+ return toTransactionObject(
+ this.kit,
+ this.contract.methods.authorizeVoteSigner(
+ signer,
+ proofOfSigningKeyPossession.v,
+ proofOfSigningKeyPossession.r,
+ proofOfSigningKeyPossession.s
+ )
+ )
}
/**
* Authorizes an address to sign consensus messages on behalf of the account.
- * @param account Address of the active account.
- * @param validationSigner The address of the signing key to authorize.
+ * @param signer The address of the signing key to authorize.
+ * @param proofOfSigningKeyPossession The account address signed by the signer address.
* @return A CeloTransactionObject
*/
- async authorizeValidationSigner(
- account: Address,
- validationSigner: Address
+ async authorizeValidatorSigner(
+ signer: Address,
+ proofOfSigningKeyPossession: Signature
): Promise> {
- return this.authorizeSigner(SignerRole.Validation, account, validationSigner)
+ const validators = await this.kit.contracts.getValidators()
+ const account = this.kit.defaultAccount || (await this.kit.web3.eth.getAccounts())[0]
+ if (await validators.isValidator(account)) {
+ const message = this.kit.web3.utils.soliditySha3({ type: 'address', value: account })
+ const prefixedMsg = hashMessageWithPrefix(message)
+ const pubKey = signedMessageToPublicKey(
+ prefixedMsg,
+ proofOfSigningKeyPossession.v,
+ proofOfSigningKeyPossession.r,
+ proofOfSigningKeyPossession.s
+ )
+ return toTransactionObject(
+ this.kit,
+ this.contract.methods.authorizeValidatorSigner(
+ signer,
+ pubKey,
+ proofOfSigningKeyPossession.v,
+ proofOfSigningKeyPossession.r,
+ // @ts-ignore Typescript does not support overloading.
+ proofOfSigningKeyPossession.s
+ )
+ )
+ } else {
+ return toTransactionObject(
+ this.kit,
+ this.contract.methods.authorizeValidatorSigner(
+ signer,
+ proofOfSigningKeyPossession.v,
+ proofOfSigningKeyPossession.r,
+ proofOfSigningKeyPossession.s
+ )
+ )
+ }
+ }
+
+ async generateProofOfSigningKeyPossession(account: Address, signer: Address) {
+ return this.getParsedSignatureOfAddress(account, signer)
}
/**
@@ -168,25 +223,14 @@ export class AccountsWrapper extends BaseWrapper {
*/
setWalletAddress = proxySend(this.kit, this.contract.methods.setWalletAddress)
- private authorizeFns = {
- [SignerRole.Attestation]: this.contract.methods.authorizeAttestationSigner,
- [SignerRole.Validation]: this.contract.methods.authorizeValidationSigner,
- [SignerRole.Vote]: this.contract.methods.authorizeVoteSigner,
- }
-
- private async authorizeSigner(role: SignerRole, account: Address, signer: Address) {
- const sig = await this.getParsedSignatureOfAddress(account, signer)
- // TODO(asa): Pass default tx "from" argument.
- return toTransactionObject(this.kit, this.authorizeFns[role](signer, sig.v, sig.r, sig.s))
+ parseSignatureOfAddress(address: Address, signer: string, signature: string) {
+ const hash = Web3.utils.soliditySha3({ type: 'address', value: address })
+ return parseSignature(hash, signature, signer)
}
private async getParsedSignatureOfAddress(address: Address, signer: string) {
const hash = Web3.utils.soliditySha3({ type: 'address', value: address })
- const signature = (await this.kit.web3.eth.sign(hash, signer)).slice(2)
- return {
- r: `0x${signature.slice(0, 64)}`,
- s: `0x${signature.slice(64, 128)}`,
- v: Web3.utils.hexToNumber(signature.slice(128, 130)) + 27,
- }
+ const signature = await this.kit.web3.eth.sign(hash, signer)
+ return parseSignature(hash, signature, signer)
}
}
diff --git a/packages/contractkit/src/wrappers/Validators.test.ts b/packages/contractkit/src/wrappers/Validators.test.ts
index c8b0dfcde1c..e90aec9cdfe 100644
--- a/packages/contractkit/src/wrappers/Validators.test.ts
+++ b/packages/contractkit/src/wrappers/Validators.test.ts
@@ -1,3 +1,4 @@
+import { addressToPublicKey } from '@celo/utils/lib/signatureUtils'
import BigNumber from 'bignumber.js'
import Web3 from 'web3'
import { newKitFromWeb3 } from '../kit'
@@ -13,15 +14,10 @@ TEST NOTES:
const minLockedGoldValue = Web3.utils.toWei('10', 'ether') // 10 gold
-// A random 64 byte hex string.
-const publicKey =
- 'ea0733ad275e2b9e05541341a97ee82678c58932464fad26164657a111a7e37a9fa0300266fb90e2135a1f1512350cb4e985488a88809b14e3cbe415e76e82b2'
const blsPublicKey =
- '4d23d8cd06f30b1fa7cf368e2f5399ab04bb6846c682f493a98a607d3dfb7e53a712bb79b475c57b0ac2785460f91301'
+ '0x4d23d8cd06f30b1fa7cf368e2f5399ab04bb6846c682f493a98a607d3dfb7e53a712bb79b475c57b0ac2785460f91301'
const blsPoP =
- '9d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae30005d223780d785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d740501'
-
-const publicKeysData = '0x' + publicKey + blsPublicKey + blsPoP
+ '0x9d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae30005d223780d785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d740501'
testWithGanache('Validators Wrapper', (web3) => {
const kit = newKitFromWeb3(web3)
@@ -52,13 +48,12 @@ testWithGanache('Validators Wrapper', (web3) => {
}
const setupValidator = async (validatorAccount: string) => {
+ const publicKey = await addressToPublicKey(validatorAccount, web3.eth.sign)
await registerAccountWithLockedGold(validatorAccount)
// set account1 as the validator
await validators
- .registerValidator(
- // @ts-ignore
- publicKeysData
- )
+ // @ts-ignore
+ .registerValidator(publicKey, blsPublicKey, blsPoP)
.sendAndWaitForReceipt({ from: validatorAccount })
}
diff --git a/packages/contractkit/src/wrappers/Validators.ts b/packages/contractkit/src/wrappers/Validators.ts
index 548f877d823..0cd4d737365 100644
--- a/packages/contractkit/src/wrappers/Validators.ts
+++ b/packages/contractkit/src/wrappers/Validators.ts
@@ -18,7 +18,8 @@ import {
export interface Validator {
address: Address
- publicKey: string
+ ecdsaPublicKey: string
+ blsPublicKey: string
affiliation: string | null
score: BigNumber
}
@@ -57,7 +58,6 @@ export class ValidatorsWrapper extends BaseWrapper {
this.contract.methods.updateCommission(toFixed(commission).toFixed())
)
}
- updatePublicKeysData = proxySend(this.kit, this.contract.methods.updatePublicKeysData)
/**
* Returns the Locked Gold requirements for validators.
* @returns The Locked Gold requirements for validators.
@@ -100,9 +100,26 @@ export class ValidatorsWrapper extends BaseWrapper {
async signerToAccount(signerAddress: Address) {
const accounts = await this.kit.contracts.getAccounts()
- return accounts.activeValidationSignerToAccount(signerAddress)
+ return accounts.validatorSignerToAccount(signerAddress)
}
+ /**
+ * Updates a validator's BLS key.
+ * @param blsPublicKey The BLS public key that the validator is using for consensus, should pass proof
+ * of possession. 48 bytes.
+ * @param blsPop The BLS public key proof-of-possession, which consists of a signature on the
+ * account address. 96 bytes.
+ * @return True upon success.
+ */
+ updateBlsPublicKey: (
+ blsPublicKey: string,
+ blsPop: string
+ ) => CeloTransactionObject = proxySend(
+ this.kit,
+ this.contract.methods.updateBlsPublicKey,
+ tupleParser(parseBytes, parseBytes)
+ )
+
/**
* Returns whether a particular account has a registered validator.
* @param account The account.
@@ -147,9 +164,10 @@ export class ValidatorsWrapper extends BaseWrapper {
const res = await this.contract.methods.getValidator(address).call()
return {
address,
- publicKey: res[0] as any,
- affiliation: res[1],
- score: fromFixed(new BigNumber(res[2])),
+ ecdsaPublicKey: res[0] as any,
+ blsPublicKey: res[1] as any,
+ affiliation: res[2],
+ score: fromFixed(new BigNumber(res[3])),
}
}
@@ -214,20 +232,23 @@ export class ValidatorsWrapper extends BaseWrapper {
* Registers a validator unaffiliated with any validator group.
*
* Fails if the account is already a validator or validator group.
- * Fails if the account does not have sufficient weight.
*
- * @param publicKeysData Comprised of three tightly-packed elements:
- * - publicKey - The public key that the validator is using for consensus, should match
- * msg.sender. 64 bytes.
- * - blsPublicKey - The BLS public key that the validator is using for consensus, should pass
- * proof of possession. 48 bytes.
- * - blsPoP - The BLS public key proof of possession. 96 bytes.
+ * @param ecdsaPublicKey The ECDSA public key that the validator is using for consensus, should match
+ * the validator signer. 64 bytes.
+ * @param blsPublicKey The BLS public key that the validator is using for consensus, should pass proof
+ * of possession. 48 bytes.
+ * @param blsPop The BLS public key proof-of-possession, which consists of a signature on the
+ * account address. 96 bytes.
*/
- registerValidator: (publicKeysData: string) => CeloTransactionObject = proxySend(
+ registerValidator: (
+ ecdsaPublicKey: string,
+ blsPublicKey: string,
+ blsPop: string
+ ) => CeloTransactionObject = proxySend(
this.kit,
this.contract.methods.registerValidator,
- tupleParser(parseBytes)
+ tupleParser(parseBytes, parseBytes, parseBytes)
)
/**
diff --git a/packages/docs/command-line-interface/account.md b/packages/docs/command-line-interface/account.md
index 91f4cd511cf..c22e3daa1e3 100644
--- a/packages/docs/command-line-interface/account.md
+++ b/packages/docs/command-line-interface/account.md
@@ -6,20 +6,23 @@ description: Manage your account, send and receive Celo Gold and Celo Dollars
### Authorize
-Authorize an attestation, validation or vote signing key
+Authorize an attestation, validator, or vote signer
```
USAGE
$ celocli account:authorize
OPTIONS
- -r, --role=vote|validation|attestation Role to delegate
- --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Account Address
- --to=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Account Address
+ -r, --role=vote|validator|attestation (required) Role to delegate
+ --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Account Address
+ --pop=pop (required) Proof-of-possession of the signer key
+ --signer=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Account Address
EXAMPLE
- authorize --from 0x5409ED021D9299bf6814279A6A1411A7e866A631 --role vote --to
- 0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d
+ authorize --from 0x5409ED021D9299bf6814279A6A1411A7e866A631 --role vote --signer
+ 0x6ecbe1db9ef729cbe972c83fb886247691fb6beb --pop
+ 0x1b9fca4bbb5bfb1dbe69ef1cddbd9b4202dcb6b134c5170611e1e36ecfa468d7b46c85328d504934fce6c2a1571603a50ae224d2b32685e84d4d
+ 1a1eebad8452eb
```
_See code: [packages/cli/src/commands/account/authorize.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/account/authorize.ts)_
@@ -229,6 +232,25 @@ EXAMPLE
_See code: [packages/cli/src/commands/account/new.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/account/new.ts)_
+### Proof-of-possession
+
+Generate proof-of-possession to be used to authorize a signer
+
+```
+USAGE
+ $ celocli account:proof-of-possession
+
+OPTIONS
+ --account=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Account Address
+ --signer=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Account Address
+
+EXAMPLE
+ proof-of-possession --account 0x5409ed021d9299bf6814279a6a1411a7e866a631 --signer
+ 0x6ecbe1db9ef729cbe972c83fb886247691fb6beb
+```
+
+_See code: [packages/cli/src/commands/account/proof-of-possession.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/account/proof-of-possession.ts)_
+
### Register
Register an account
@@ -239,10 +261,11 @@ USAGE
OPTIONS
--from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Account Address
- --name=name (required)
+ --name=name
-EXAMPLE
- register
+EXAMPLES
+ register --from 0x5409ed021d9299bf6814279a6a1411a7e866a631
+ register --from 0x5409ed021d9299bf6814279a6a1411a7e866a631 --name test-account
```
_See code: [packages/cli/src/commands/account/register.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/account/register.ts)_
diff --git a/packages/docs/command-line-interface/validator.md b/packages/docs/command-line-interface/validator.md
index 308a79300f3..8bfdea3e57d 100644
--- a/packages/docs/command-line-interface/validator.md
+++ b/packages/docs/command-line-interface/validator.md
@@ -72,28 +72,6 @@ EXAMPLE
_See code: [packages/cli/src/commands/validator/list.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/validator/list.ts)_
-### PublicKey
-
-Manage BLS public key data for a validator
-
-```
-USAGE
- $ celocli validator:publicKey
-
-OPTIONS
- --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Validator's address
- --publicKey=0x (required) Public Key
-
-EXAMPLE
- publickey --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --publicKey
- 0xc52f3fab06e22a54915a8765c4f6826090cfac5e40282b43844bf1c0df83aaa632e55b67869758f2291d1aabe0ebecc7cbf4236aaa45e3e0cfbf
- 997eda082ae19d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae30005d223780d
- 785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d7405011220a66a6257562d0c26dabf64485a1d
- 96bad27bb1c0fd6080a75b0ec9f75b50298a2a8e04b02b2688c8104fca61fb00
-```
-
-_See code: [packages/cli/src/commands/validator/publicKey.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/validator/publicKey.ts)_
-
### Register
Register a new Validator
@@ -103,15 +81,18 @@ USAGE
$ celocli validator:register
OPTIONS
+ --blsKey=0x (required) BLS Public Key
+ --blsPop=0x (required) BLS Proof-of-Possession
+ --ecdsaKey=0x (required) ECDSA Public Key
--from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address for the Validator
- --publicKey=0x (required) Public Key
EXAMPLE
- register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --publicKey
+ register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --ecdsaKey
0xc52f3fab06e22a54915a8765c4f6826090cfac5e40282b43844bf1c0df83aaa632e55b67869758f2291d1aabe0ebecc7cbf4236aaa45e3e0cfbf
- 997eda082ae19d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae30005d223780d
- 785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d7405011220a66a6257562d0c26dabf64485a1d
- 96bad27bb1c0fd6080a75b0ec9f75b50298a2a8e04b02b2688c8104fca61fb00
+ 997eda082ae1 --blsKey
+ 0x9d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae300 --blsPop
+ 0x05d223780d785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d7405011220a66a6257562d0c26
+ dabf64485a1d96bad27bb1c0fd6080a75b0ec9f75b50298a2a8e04b02b2688c8104fca61fb00
```
_See code: [packages/cli/src/commands/validator/register.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/validator/register.ts)_
@@ -146,3 +127,25 @@ EXAMPLE
```
_See code: [packages/cli/src/commands/validator/show.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/validator/show.ts)_
+
+### Update-bls-public-key
+
+Update BLS key for a validator
+
+```
+USAGE
+ $ celocli validator:update-bls-public-key
+
+OPTIONS
+ --blsKey=0x (required) BLS Public Key
+ --blsPop=0x (required) BLS Proof-of-Possession
+ --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Validator's address
+
+EXAMPLE
+ update-bls-key --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --blsKey
+ 0x9d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae300 --blsPop
+ 0x05d223780d785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d7405011220a66a6257562d0c26
+ dabf64485a1d96bad27bb1c0fd6080a75b0ec9f75b50298a2a8e04b02b2688c8104fca61fb00
+```
+
+_See code: [packages/cli/src/commands/validator/update-bls-public-key.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/validator/update-bls-public-key.ts)_
diff --git a/packages/mobile/src/identity/commentKey.ts b/packages/mobile/src/identity/commentKey.ts
index 7dbae492aab..c54deaf486a 100644
--- a/packages/mobile/src/identity/commentKey.ts
+++ b/packages/mobile/src/identity/commentKey.ts
@@ -1,5 +1,5 @@
+import { stripHexLeader } from '@celo/utils/src/address'
import { encryptComment as encryptCommentRaw } from '@celo/utils/src/commentEncryption'
-import { stripHexLeader } from '@celo/utils/src/signatureUtils'
import { getAttestationsContract, getDataEncryptionKey } from '@celo/walletkit'
import { web3 } from 'src/web3/contracts'
diff --git a/packages/mobile/src/identity/verification.ts b/packages/mobile/src/identity/verification.ts
index 95405fd427d..3f1282a7455 100644
--- a/packages/mobile/src/identity/verification.ts
+++ b/packages/mobile/src/identity/verification.ts
@@ -1,6 +1,6 @@
+import { eqAddress } from '@celo/utils/src/address'
import { compressedPubKey } from '@celo/utils/src/commentEncryption'
import { getPhoneHash, isE164Number } from '@celo/utils/src/phoneNumbers'
-import { areAddressesEqual } from '@celo/utils/src/signatureUtils'
import {
ActionableAttestation,
extractAttestationCodeFromMessage,
@@ -487,10 +487,7 @@ function* setAccount(attestationsContract: AttestationsType, address: string, da
Logger.debug(TAG, 'Setting wallet address and public data encryption key')
const currentWalletAddress = yield call(getWalletAddress, attestationsContract, address)
const currentWalletDEK = yield call(getDataEncryptionKey, attestationsContract, address)
- if (
- !areAddressesEqual(currentWalletAddress, address) ||
- !areAddressesEqual(currentWalletDEK, dataKey)
- ) {
+ if (!eqAddress(currentWalletAddress, address) || !eqAddress(currentWalletDEK, dataKey)) {
const setAccountTx = makeSetAccountTx(attestationsContract, address, dataKey)
yield call(sendTransaction, setAccountTx, address, TAG, `Set Wallet Address & DEK`)
CeloAnalytics.track(CustomEventNames.verification_set_account)
diff --git a/packages/mobile/src/import/saga.ts b/packages/mobile/src/import/saga.ts
index f3d40c96680..5be02a1c3bf 100644
--- a/packages/mobile/src/import/saga.ts
+++ b/packages/mobile/src/import/saga.ts
@@ -1,4 +1,4 @@
-import { ensureHexLeader } from '@celo/utils/src/signatureUtils'
+import { ensureHexLeader } from '@celo/utils/src/address'
import BigNumber from 'bignumber.js'
import { validateMnemonic } from 'bip39'
import { mnemonicToSeedHex } from 'react-native-bip39'
diff --git a/packages/mobile/src/invite/saga.ts b/packages/mobile/src/invite/saga.ts
index b19bc5b3d95..e6a16fc9238 100644
--- a/packages/mobile/src/invite/saga.ts
+++ b/packages/mobile/src/invite/saga.ts
@@ -1,5 +1,5 @@
+import { stripHexLeader } from '@celo/utils/src/address'
import { getPhoneHash } from '@celo/utils/src/phoneNumbers'
-import { stripHexLeader } from '@celo/utils/src/signatureUtils'
import { getEscrowContract, getGoldTokenContract, getStableTokenContract } from '@celo/walletkit'
import BigNumber from 'bignumber.js'
import { Linking, Platform } from 'react-native'
diff --git a/packages/mobile/src/qrcode/utils.ts b/packages/mobile/src/qrcode/utils.ts
index a7f62532841..5e8adbc3e05 100644
--- a/packages/mobile/src/qrcode/utils.ts
+++ b/packages/mobile/src/qrcode/utils.ts
@@ -1,4 +1,4 @@
-import { isValidAddress } from '@celo/utils/src/signatureUtils'
+import { isValidAddress } from '@celo/utils/src/address'
import { isEmpty } from 'lodash'
import * as RNFS from 'react-native-fs'
import Share from 'react-native-share'
diff --git a/packages/mobile/src/recipients/RecipientPicker.tsx b/packages/mobile/src/recipients/RecipientPicker.tsx
index 9c0d72ce22d..b8fddb44e4e 100644
--- a/packages/mobile/src/recipients/RecipientPicker.tsx
+++ b/packages/mobile/src/recipients/RecipientPicker.tsx
@@ -8,8 +8,8 @@ import ForwardChevron from '@celo/react-components/icons/ForwardChevron'
import QRCode from '@celo/react-components/icons/QRCode'
import colors from '@celo/react-components/styles/colors'
import { fontStyles } from '@celo/react-components/styles/fonts'
+import { isValidAddress } from '@celo/utils/src/address'
import { parsePhoneNumber } from '@celo/utils/src/phoneNumbers'
-import { isValidAddress } from '@celo/utils/src/signatureUtils'
import { TranslationFunction } from 'i18next'
import * as React from 'react'
import { withNamespaces, WithNamespaces } from 'react-i18next'
diff --git a/packages/mobile/src/verify/VerificationCodeRow.tsx b/packages/mobile/src/verify/VerificationCodeRow.tsx
index 548220d959d..98614ab767f 100644
--- a/packages/mobile/src/verify/VerificationCodeRow.tsx
+++ b/packages/mobile/src/verify/VerificationCodeRow.tsx
@@ -3,7 +3,7 @@ import withTextInputPasteAware from '@celo/react-components/components/WithTextI
import Checkmark from '@celo/react-components/icons/Checkmark'
import colors from '@celo/react-components/styles/colors'
import fontStyles from '@celo/react-components/styles/fonts'
-import { stripHexLeader } from '@celo/utils/src/signatureUtils'
+import { stripHexLeader } from '@celo/utils/src/address'
import { extractAttestationCodeFromMessage } from '@celo/walletkit'
import * as React from 'react'
import { withNamespaces, WithNamespaces } from 'react-i18next'
diff --git a/packages/protocol/contracts/common/Accounts.sol b/packages/protocol/contracts/common/Accounts.sol
index 027eba383e8..9e04877b836 100644
--- a/packages/protocol/contracts/common/Accounts.sol
+++ b/packages/protocol/contracts/common/Accounts.sol
@@ -1,7 +1,8 @@
pragma solidity ^0.5.3;
-import "openzeppelin-solidity/contracts/utils/ReentrancyGuard.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
+import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
+import "openzeppelin-solidity/contracts/utils/ReentrancyGuard.sol";
import "./interfaces/IAccounts.sol";
@@ -9,21 +10,21 @@ import "../common/Initializable.sol";
import "../common/Signatures.sol";
import "../common/UsingRegistry.sol";
-contract Accounts is IAccounts, ReentrancyGuard, Initializable, UsingRegistry {
+contract Accounts is IAccounts, Ownable, ReentrancyGuard, Initializable, UsingRegistry {
using SafeMath for uint256;
struct Signers {
//The address that is authorized to vote in governance and validator elections on behalf of the
// account. The account can vote as well, whether or not an vote signing key has been specified.
- address voting;
+ address vote;
// The address that is authorized to manage a validator or validator group and sign consensus
// messages on behalf of the account. The account can manage the validator, whether or not an
- // validation signing key has been specified. However if an validation signing key has been
+ // validator signing key has been specified. However if an validator signing key has been
// specified, only that key may actually participate in consensus.
- address validating;
+ address validator;
// The address of the key with which this account wants to sign attestations on the Attestations
// contract
- address attesting;
+ address attestation;
}
struct Account {
@@ -44,18 +45,23 @@ contract Accounts is IAccounts, ReentrancyGuard, Initializable, UsingRegistry {
}
mapping(address => Account) private accounts;
- // Maps voting and validating keys to the account that provided the authorization.
+ // Maps authorized signers to the account that provided the authorization.
mapping(address => address) public authorizedBy;
event AttestationSignerAuthorized(address indexed account, address signer);
event VoteSignerAuthorized(address indexed account, address signer);
- event ValidationSignerAuthorized(address indexed account, address signer);
+ event ValidatorSignerAuthorized(address indexed account, address signer);
event AccountDataEncryptionKeySet(address indexed account, bytes dataEncryptionKey);
event AccountNameSet(address indexed account, string name);
event AccountMetadataURLSet(address indexed account, string metadataURL);
event AccountWalletAddressSet(address indexed account, address walletAddress);
event AccountCreated(address indexed account);
+ function initialize(address registryAddress) external initializer {
+ _transferOwnership(msg.sender);
+ setRegistry(registryAddress);
+ }
+
/**
* @notice Convenience Setter for the dataEncryptionKey and wallet address for an account
* @param name A string to set as the name of the account
@@ -73,6 +79,48 @@ contract Accounts is IAccounts, ReentrancyGuard, Initializable, UsingRegistry {
setWalletAddress(walletAddress);
}
+ /**
+ * @notice Creates an account.
+ * @return True if account creation succeeded.
+ */
+ function createAccount() public returns (bool) {
+ require(isNotAccount(msg.sender) && isNotAuthorizedSigner(msg.sender));
+ Account storage account = accounts[msg.sender];
+ account.exists = true;
+ emit AccountCreated(msg.sender);
+ return true;
+ }
+
+ /**
+ * @notice Setter for the name of an account.
+ * @param name The name to set.
+ */
+ function setName(string memory name) public {
+ require(isAccount(msg.sender));
+ accounts[msg.sender].name = name;
+ emit AccountNameSet(msg.sender, name);
+ }
+
+ /**
+ * @notice Setter for the wallet address for an account
+ * @param walletAddress The wallet address to set for the account
+ */
+ function setWalletAddress(address walletAddress) public {
+ require(isAccount(msg.sender));
+ accounts[msg.sender].walletAddress = walletAddress;
+ emit AccountWalletAddressSet(msg.sender, walletAddress);
+ }
+
+ /**
+ * @notice Setter for the data encryption key and version.
+ * @param dataEncryptionKey secp256k1 public key for data encryption. Preferably compressed.
+ */
+ function setAccountDataEncryptionKey(bytes memory dataEncryptionKey) public {
+ require(dataEncryptionKey.length >= 33, "data encryption key length <= 32");
+ accounts[msg.sender].dataEncryptionKey = dataEncryptionKey;
+ emit AccountDataEncryptionKeySet(msg.sender, dataEncryptionKey);
+ }
+
/**
* @notice Setter for the metadata of an account.
* @param metadataURL The URL to access the metadata.
@@ -85,102 +133,179 @@ contract Accounts is IAccounts, ReentrancyGuard, Initializable, UsingRegistry {
/**
* @notice Authorizes an address to sign votes on behalf of the account.
- * @param voter The address of the vote signing key to authorize.
+ * @param signer The address of the signing key to authorize.
* @param v The recovery id of the incoming ECDSA signature.
* @param r Output value r of the ECDSA signature.
* @param s Output value s of the ECDSA signature.
- * @dev v, r, s constitute `voter`'s signature on `msg.sender`.
+ * @dev v, r, s constitute `signer`'s signature on `msg.sender`.
*/
- function authorizeVoteSigner(address voter, uint8 v, bytes32 r, bytes32 s) external nonReentrant {
+ function authorizeVoteSigner(address signer, uint8 v, bytes32 r, bytes32 s)
+ external
+ nonReentrant
+ {
Account storage account = accounts[msg.sender];
- authorize(voter, account.signers.voting, v, r, s);
- account.signers.voting = voter;
- emit VoteSignerAuthorized(msg.sender, voter);
+ authorize(signer, v, r, s);
+ account.signers.vote = signer;
+ emit VoteSignerAuthorized(msg.sender, signer);
}
/**
* @notice Authorizes an address to sign consensus messages on behalf of the account.
- * @param validator The address of the signing key to authorize.
+ * @param signer The address of the signing key to authorize.
* @param v The recovery id of the incoming ECDSA signature.
* @param r Output value r of the ECDSA signature.
* @param s Output value s of the ECDSA signature.
- * @dev v, r, s constitute `validator`'s signature on `msg.sender`.
+ * @dev v, r, s constitute `signer`'s signature on `msg.sender`.
*/
- function authorizeValidationSigner(address validator, uint8 v, bytes32 r, bytes32 s)
+ function authorizeValidatorSigner(address signer, uint8 v, bytes32 r, bytes32 s)
external
nonReentrant
{
Account storage account = accounts[msg.sender];
- authorize(validator, account.signers.validating, v, r, s);
- account.signers.validating = validator;
- emit ValidationSignerAuthorized(msg.sender, validator);
+ authorize(signer, v, r, s);
+ account.signers.validator = signer;
+ require(!getValidators().isValidator(msg.sender));
+ emit ValidatorSignerAuthorized(msg.sender, signer);
}
/**
- * @notice Check if an address has been authorized by an account for voting or validating.
- * @param account The possibly authorized address.
- * @return Returns `true` if authorized. Returns `false` otherwise.
+ * @notice Authorizes an address to sign consensus messages on behalf of the account.
+ * @param signer The address of the signing key to authorize.
+ * @param ecdsaPublicKey The ECDSA public key corresponding to `signer`.
+ * @param v The recovery id of the incoming ECDSA signature.
+ * @param r Output value r of the ECDSA signature.
+ * @param s Output value s of the ECDSA signature.
+ * @dev v, r, s constitute `signer`'s signature on `msg.sender`.
*/
- function isAuthorized(address account) external view returns (bool) {
- return (authorizedBy[account] != address(0));
+ function authorizeValidatorSigner(
+ address signer,
+ bytes calldata ecdsaPublicKey,
+ uint8 v,
+ bytes32 r,
+ bytes32 s
+ ) external nonReentrant {
+ Account storage account = accounts[msg.sender];
+ authorize(signer, v, r, s);
+ account.signers.validator = signer;
+ require(getValidators().updateEcdsaPublicKey(msg.sender, signer, ecdsaPublicKey));
+ emit ValidatorSignerAuthorized(msg.sender, signer);
}
/**
- * @notice Returns the account associated with `accountOrAttestationSigner`.
- * @param accountOrAttestationSigner The address of the account or active authorized attestation
- signer.
- * @dev Fails if the `accountOrAttestationSigner` is not an account or active authorized
- attestation signer.
+ * @notice Authorizes an address to sign attestations on behalf of the account.
+ * @param signer The address of the signing key to authorize.
+ * @param v The recovery id of the incoming ECDSA signature.
+ * @param r Output value r of the ECDSA signature.
+ * @param s Output value s of the ECDSA signature.
+ * @dev v, r, s constitute `signer`'s signature on `msg.sender`.
+ */
+ function authorizeAttestationSigner(address signer, uint8 v, bytes32 r, bytes32 s) public {
+ Account storage account = accounts[msg.sender];
+ authorize(signer, v, r, s);
+ account.signers.attestation = signer;
+ emit AttestationSignerAuthorized(msg.sender, signer);
+ }
+
+ /**
+ * @notice Returns the account associated with `signer`.
+ * @param signer The address of the account or currently authorized attestation signer.
+ * @dev Fails if the `signer` is not an account or currently authorized attestation signer.
* @return The associated account.
*/
- function activeAttesttationSignerToAccount(address accountOrAttestationSigner)
- external
- view
- returns (address)
- {
- address authorizingAccount = authorizedBy[accountOrAttestationSigner];
+ function attestationSignerToAccount(address signer) external view returns (address) {
+ address authorizingAccount = authorizedBy[signer];
if (authorizingAccount != address(0)) {
- require(accounts[authorizingAccount].signers.attesting == accountOrAttestationSigner);
+ require(accounts[authorizingAccount].signers.attestation == signer);
return authorizingAccount;
} else {
- require(isAccount(accountOrAttestationSigner));
- return accountOrAttestationSigner;
+ require(isAccount(signer));
+ return signer;
}
}
/**
- * @notice Returns the account associated with `accountOrVoteSigner`.
- * @param accountOrVoteSigner The address of the account or active authorized vote signer.
- * @dev Fails if the `accountOrVoteSigner` is not an account or active authorized vote signer.
+ * @notice Returns the account associated with `signer`.
+ * @param signer The address of an account or currently authorized validator signer.
+ * @dev Fails if the `signer` is not an account or currently authorized validator.
* @return The associated account.
*/
- function activeVoteSignerToAccount(address accountOrVoteSigner) external view returns (address) {
- address authorizingAccount = authorizedBy[accountOrVoteSigner];
+ function validatorSignerToAccount(address signer) public view returns (address) {
+ address authorizingAccount = authorizedBy[signer];
if (authorizingAccount != address(0)) {
- require(accounts[authorizingAccount].signers.voting == accountOrVoteSigner);
+ require(accounts[authorizingAccount].signers.validator == signer);
return authorizingAccount;
} else {
- require(isAccount(accountOrVoteSigner));
- return accountOrVoteSigner;
+ require(isAccount(signer));
+ return signer;
}
}
/**
- * @notice Returns the account associated with `accountOrVoteSigner`.
- * @param accountOrVoteSigner The address of the account or previously authorized vote signer.
- * @dev Fails if the `accountOrVoteSigner` is not an account or previously authorized vote signer.
+ * @notice Returns the account associated with `signer`.
+ * @param signer The address of the account or currently authorized vote signer.
+ * @dev Fails if the `signer` is not an account or currently authorized vote signer.
* @return The associated account.
*/
- function voteSignerToAccount(address accountOrVoteSigner) external view returns (address) {
- address authorizingAccount = authorizedBy[accountOrVoteSigner];
+ function voteSignerToAccount(address signer) external view returns (address) {
+ address authorizingAccount = authorizedBy[signer];
if (authorizingAccount != address(0)) {
+ require(accounts[authorizingAccount].signers.vote == signer);
return authorizingAccount;
} else {
- require(isAccount(accountOrVoteSigner));
- return accountOrVoteSigner;
+ require(isAccount(signer));
+ return signer;
}
}
+ /**
+ * @notice Returns the account associated with `signer`.
+ * @param signer The address of the account or previously authorized signer.
+ * @dev Fails if the `signer` is not an account or previously authorized signer.
+ * @return The associated account.
+ */
+ function signerToAccount(address signer) external view returns (address) {
+ address authorizingAccount = authorizedBy[signer];
+ if (authorizingAccount != address(0)) {
+ return authorizingAccount;
+ } else {
+ require(isAccount(signer));
+ return signer;
+ }
+ }
+
+ /**
+ * @notice Returns the vote signer for the specified account.
+ * @param account The address of the account.
+ * @return The address with which the account can sign votes.
+ */
+ function getVoteSigner(address account) public view returns (address) {
+ require(isAccount(account));
+ address signer = accounts[account].signers.vote;
+ return signer == address(0) ? account : signer;
+ }
+
+ /**
+ * @notice Returns the validator signer for the specified account.
+ * @param account The address of the account.
+ * @return The address with which the account can register a validator or group.
+ */
+ function getValidatorSigner(address account) public view returns (address) {
+ require(isAccount(account));
+ address signer = accounts[account].signers.validator;
+ return signer == address(0) ? account : signer;
+ }
+
+ /**
+ * @notice Returns the attestation signer for the specified account.
+ * @param account The address of the account.
+ * @return The address with which the account can sign attestations.
+ */
+ function getAttestationSigner(address account) public view returns (address) {
+ require(isAccount(account));
+ address signer = accounts[account].signers.attestation;
+ return signer == address(0) ? account : signer;
+ }
+
/**
* @notice Getter for the name of an account.
* @param account The address of the account to get the name for.
@@ -247,160 +372,6 @@ contract Accounts is IAccounts, ReentrancyGuard, Initializable, UsingRegistry {
return accounts[account].walletAddress;
}
- /**
- * @notice Creates an account.
- * @return True if account creation succeeded.
- */
- function createAccount() public returns (bool) {
- require(isNotAccount(msg.sender) && isNotAuthorized(msg.sender));
- Account storage account = accounts[msg.sender];
- account.exists = true;
- emit AccountCreated(msg.sender);
- return true;
- }
-
- /**
- * @notice Setter for the name of an account.
- * @param name The name to set.
- */
- function setName(string memory name) public {
- require(isAccount(msg.sender));
- accounts[msg.sender].name = name;
- emit AccountNameSet(msg.sender, name);
- }
-
- /**
- * @notice Setter for the wallet address for an account
- * @param walletAddress The wallet address to set for the account
- */
- function setWalletAddress(address walletAddress) public {
- require(isAccount(msg.sender));
- accounts[msg.sender].walletAddress = walletAddress;
- emit AccountWalletAddressSet(msg.sender, walletAddress);
- }
-
- /**
- * @notice Setter for the data encryption key and version.
- * @param dataEncryptionKey secp256k1 public key for data encryption. Preferably compressed.
- */
- function setAccountDataEncryptionKey(bytes memory dataEncryptionKey) public {
- require(dataEncryptionKey.length >= 33, "data encryption key length <= 32");
- accounts[msg.sender].dataEncryptionKey = dataEncryptionKey;
- emit AccountDataEncryptionKeySet(msg.sender, dataEncryptionKey);
- }
-
- /**
- * @notice Authorizes an address to sign attestations on behalf of the account.
- * @param attestor The address of the signing key to authorize.
- * @param v The recovery id of the incoming ECDSA signature.
- * @param r Output value r of the ECDSA signature.
- * @param s Output value s of the ECDSA signature.
- * @dev v, r, s constitute `attestor`'s signature on `msg.sender`.
- */
- function authorizeAttestationSigner(address attestor, uint8 v, bytes32 r, bytes32 s) public {
- Account storage account = accounts[msg.sender];
- authorize(attestor, account.signers.attesting, v, r, s);
- account.signers.attesting = attestor;
- emit AttestationSignerAuthorized(msg.sender, attestor);
- }
-
- /**
- * @notice Returns the account associated with `accountOrAttestationSigner`.
- * @param accountOrAttestationSigner The address of the account or previously authorized
- * attestation signing key.
- * @dev Fails if the `accountOrAttestationSigner` is not an account or previously authorized
- * attestation signing key.
- * @return The associated account.
- */
- function attestationSignerToAccount(address accountOrAttestationSigner)
- public
- view
- returns (address)
- {
- address authorizingAccount = authorizedBy[accountOrAttestationSigner];
- if (authorizingAccount != address(0)) {
- return authorizingAccount;
- } else {
- require(isAccount(accountOrAttestationSigner));
- return accountOrAttestationSigner;
- }
- }
-
- /**
- * @notice Returns the account associated with `accountOrValidationSigner`.
- * @param accountOrValidationSigner The address of the account or active authorized validator.
- * @dev Fails if the `accountOrValidationSigner` is not an account or active authorized validator.
- * @return The associated account.
- */
- function activeValidationSignerToAccount(address accountOrValidationSigner)
- public
- view
- returns (address)
- {
- address authorizingAccount = authorizedBy[accountOrValidationSigner];
- if (authorizingAccount != address(0)) {
- require(accounts[authorizingAccount].signers.validating == accountOrValidationSigner);
- return authorizingAccount;
- } else {
- require(isAccount(accountOrValidationSigner));
- return accountOrValidationSigner;
- }
- }
-
- /**
- * @notice Returns the account associated with `accountOrValidationSigner`.
- * @param accountOrValidationSigner The address of the account or previously authorized validator.
- * @dev Fails if the `accountOrValidationSigner` is not an account or previously authorized
- validator.
- * @return The associated account.
- */
- function validationSignerToAccount(address accountOrValidationSigner)
- public
- view
- returns (address)
- {
- address authorizingAccount = authorizedBy[accountOrValidationSigner];
- if (authorizingAccount != address(0)) {
- return authorizingAccount;
- } else {
- require(isAccount(accountOrValidationSigner));
- return accountOrValidationSigner;
- }
- }
-
- /**
- * @notice Returns the vote signer for the specified account.
- * @param account The address of the account.
- * @return The address with which the account can sign votes.
- */
- function getVoteSigner(address account) public view returns (address) {
- require(isAccount(account));
- address voter = accounts[account].signers.voting;
- return voter == address(0) ? account : voter;
- }
-
- /**
- * @notice Returns the validation signer for the specified account.
- * @param account The address of the account.
- * @return The address with which the account can register a validator or group.
- */
- function getValidationSigner(address account) public view returns (address) {
- require(isAccount(account));
- address validator = accounts[account].signers.validating;
- return validator == address(0) ? account : validator;
- }
-
- /**
- * @notice Returns the attestation signer for the specified account.
- * @param account The address of the account.
- * @return The address with which the account can sign attestations.
- */
- function getAttestationSigner(address account) public view returns (address) {
- require(isAccount(account));
- address attestor = accounts[account].signers.attesting;
- return attestor == address(0) ? account : attestor;
- }
-
/**
* @notice Check if an account already exists.
* @param account The address of the account
@@ -420,31 +391,39 @@ contract Accounts is IAccounts, ReentrancyGuard, Initializable, UsingRegistry {
}
/**
- * @notice Check if an address has been authorized by an account for voting or validating.
- * @param account The possibly authorized address.
+ * @notice Check if an address has been an authorized signer for an account.
+ * @param signer The possibly authorized address.
+ * @return Returns `true` if authorized. Returns `false` otherwise.
+ */
+ function isAuthorizedSigner(address signer) external view returns (bool) {
+ return (authorizedBy[signer] != address(0));
+ }
+
+ /**
+ * @notice Check if an address has been an authorized signer for an account.
+ * @param signer The possibly authorized address.
* @return Returns `false` if authorized. Returns `true` otherwise.
*/
- function isNotAuthorized(address account) internal view returns (bool) {
- return (authorizedBy[account] == address(0));
+ function isNotAuthorizedSigner(address signer) internal view returns (bool) {
+ return (authorizedBy[signer] == address(0));
}
/**
- * @notice Authorizes voting or validating power of `msg.sender`'s account to another address.
- * @param current The address to authorize.
- * @param previous The previous authorized address.
+ * @notice Authorizes some role of of `msg.sender`'s account to another address.
+ * @param authorized The address to authorize.
* @param v The recovery id of the incoming ECDSA signature.
* @param r Output value r of the ECDSA signature.
* @param s Output value s of the ECDSA signature.
* @dev Fails if the address is already authorized or is an account.
+ * @dev Note that once an address is authorized, it may never be authorized again.
* @dev v, r, s constitute `current`'s signature on `msg.sender`.
*/
- function authorize(address current, address previous, uint8 v, bytes32 r, bytes32 s) private {
- require(isAccount(msg.sender) && isNotAccount(current) && isNotAuthorized(current));
+ function authorize(address authorized, uint8 v, bytes32 r, bytes32 s) private {
+ require(isAccount(msg.sender) && isNotAccount(authorized) && isNotAuthorizedSigner(authorized));
address signer = Signatures.getSignerOfAddress(msg.sender, v, r, s);
- require(signer == current);
+ require(signer == authorized);
- authorizedBy[previous] = address(0);
- authorizedBy[current] = msg.sender;
+ authorizedBy[authorized] = msg.sender;
}
}
diff --git a/packages/protocol/contracts/common/UsingPrecompiles.sol b/packages/protocol/contracts/common/UsingPrecompiles.sol
index 4b93a055c67..542846af9a6 100644
--- a/packages/protocol/contracts/common/UsingPrecompiles.sol
+++ b/packages/protocol/contracts/common/UsingPrecompiles.sol
@@ -123,16 +123,20 @@ contract UsingPrecompiles {
/**
* @notice Checks a BLS proof of possession.
- * @param proofOfPossessionBytes The public key and signature of the proof of possession.
+ * @param sender The address signed by the BLS key to generate the proof of possession.
+ * @param blsKey The BLS public key that the validator is using for consensus, should pass proof
+ * of possession. 48 bytes.
+ * @param blsPop The BLS public key proof-of-possession, which consists of a signature on the
+ * account address. 96 bytes.
* @return True upon success.
*/
- function checkProofOfPossession(address sender, bytes memory proofOfPossessionBytes)
+ function checkProofOfPossession(address sender, bytes memory blsKey, bytes memory blsPop)
public
returns (bool)
{
bool success;
(success, ) = PROOF_OF_POSSESSION.call.value(0).gas(gasleft())(
- abi.encodePacked(sender, proofOfPossessionBytes)
+ abi.encodePacked(sender, blsKey, blsPop)
);
return success;
}
diff --git a/packages/protocol/contracts/common/interfaces/IAccounts.sol b/packages/protocol/contracts/common/interfaces/IAccounts.sol
index b197c66b549..0360e66394e 100644
--- a/packages/protocol/contracts/common/interfaces/IAccounts.sol
+++ b/packages/protocol/contracts/common/interfaces/IAccounts.sol
@@ -2,13 +2,11 @@ pragma solidity ^0.5.3;
interface IAccounts {
function isAccount(address) external view returns (bool);
- function activeVoteSignerToAccount(address) external view returns (address);
function voteSignerToAccount(address) external view returns (address);
- function activeValidationSignerToAccount(address) external view returns (address);
- function validationSignerToAccount(address) external view returns (address);
- function getValidationSigner(address) external view returns (address);
- function activeAttesttationSignerToAccount(address) external view returns (address);
+ function validatorSignerToAccount(address) external view returns (address);
function attestationSignerToAccount(address) external view returns (address);
+ function signerToAccount(address) external view returns (address);
+ function getValidatorSigner(address) external view returns (address);
function getAttestationSigner(address) external view returns (address);
function setAccountDataEncryptionKey(bytes calldata) external;
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 2ee97bc0e8d..c24bb9b6cbd 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -209,7 +209,7 @@ contract Election is
require(votes.total.eligible.contains(group));
require(0 < value);
require(canReceiveVotes(group, value));
- address account = getAccounts().activeVoteSignerToAccount(msg.sender);
+ address account = getAccounts().voteSignerToAccount(msg.sender);
// Add group to the groups voted for by the account.
address[] storage groups = votes.groupsVotedFor[account];
@@ -233,7 +233,7 @@ contract Election is
* @dev Pending votes cannot be activated until an election has been held.
*/
function activate(address group) external nonReentrant returns (bool) {
- address account = getAccounts().activeVoteSignerToAccount(msg.sender);
+ address account = getAccounts().voteSignerToAccount(msg.sender);
PendingVote storage pendingVote = votes.pending.forGroup[group].byAccount[account];
require(pendingVote.epoch < getEpochNumber());
uint256 value = pendingVote.value;
@@ -263,7 +263,7 @@ contract Election is
uint256 index
) external nonReentrant returns (bool) {
require(group != address(0));
- address account = getAccounts().activeVoteSignerToAccount(msg.sender);
+ address account = getAccounts().voteSignerToAccount(msg.sender);
require(0 < value && value <= getPendingVotesForGroupByAccount(group, account));
decrementPendingVotes(group, account, value);
decrementTotalVotes(group, value, lesser, greater);
@@ -296,7 +296,7 @@ contract Election is
) external nonReentrant returns (bool) {
// TODO(asa): Dedup with revokePending.
require(group != address(0));
- address account = getAccounts().activeVoteSignerToAccount(msg.sender);
+ address account = getAccounts().voteSignerToAccount(msg.sender);
require(0 < value && value <= getActiveVotesForGroupByAccount(group, account));
decrementActiveVotes(group, account, value);
decrementTotalVotes(group, value, lesser, greater);
@@ -706,7 +706,7 @@ contract Election is
* @return The list of elected validators.
* @dev See https://en.wikipedia.org/wiki/D%27Hondt_method#Allocation for more information.
*/
- function electValidators() external view returns (address[] memory) {
+ function electValidatorSigners() external view returns (address[] memory) {
// Groups must have at least `electabilityThreshold` proportion of the total votes to be
// considered for the election.
uint256 requiredVotes = electabilityThreshold
diff --git a/packages/protocol/contracts/governance/Governance.sol b/packages/protocol/contracts/governance/Governance.sol
index 029aa8ee062..850a1dcb464 100644
--- a/packages/protocol/contracts/governance/Governance.sol
+++ b/packages/protocol/contracts/governance/Governance.sol
@@ -437,7 +437,7 @@ contract Governance is
nonReentrant
returns (bool)
{
- address account = getAccounts().activeVoteSignerToAccount(msg.sender);
+ address account = getAccounts().voteSignerToAccount(msg.sender);
// TODO(asa): When upvoting a proposal that will get dequeued, should we let the tx succeed
// and return false?
dequeueProposalsIfReady();
@@ -484,7 +484,7 @@ contract Governance is
*/
function revokeUpvote(uint256 lesser, uint256 greater) external nonReentrant returns (bool) {
dequeueProposalsIfReady();
- address account = getAccounts().activeVoteSignerToAccount(msg.sender);
+ address account = getAccounts().voteSignerToAccount(msg.sender);
Voter storage voter = voters[account];
uint256 proposalId = voter.upvote.proposalId;
Proposals.Proposal storage proposal = proposals[proposalId];
@@ -548,7 +548,7 @@ contract Governance is
nonReentrant
returns (bool)
{
- address account = getAccounts().activeVoteSignerToAccount(msg.sender);
+ address account = getAccounts().voteSignerToAccount(msg.sender);
dequeueProposalsIfReady();
Proposals.Proposal storage proposal = proposals[proposalId];
require(isDequeuedProposal(proposal, proposalId, index));
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index f5ef68f71b9..261fc914b8b 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -53,10 +53,6 @@ contract Validators is
uint256 duration;
}
- // If we knew what time the validator was last in a group, we could enforce that to deregister a
- // group, you need to have had 0 members for `duration`, and to deregister a validator, you need
- // to have been out of a group for `duration`...
-
struct ValidatorGroup {
bool exists;
LinkedList.List members;
@@ -85,8 +81,13 @@ contract Validators is
uint256 lastRemovedFromGroupTimestamp;
}
+ struct PublicKeys {
+ bytes ecdsa;
+ bytes bls;
+ }
+
struct Validator {
- bytes publicKeysData;
+ PublicKeys publicKeys;
address affiliation;
FixidityLib.Fraction score;
MembershipHistory membershipHistory;
@@ -114,11 +115,12 @@ contract Validators is
event GroupLockedGoldRequirementsSet(uint256 value, uint256 duration);
event ValidatorLockedGoldRequirementsSet(uint256 value, uint256 duration);
event MembershipHistoryLengthSet(uint256 length);
- event ValidatorRegistered(address indexed validator, bytes publicKeysData);
+ event ValidatorRegistered(address indexed validator, bytes ecdsaPublicKey, bytes blsPublicKey);
event ValidatorDeregistered(address indexed validator);
event ValidatorAffiliated(address indexed validator, address indexed group);
event ValidatorDeaffiliated(address indexed validator, address indexed group);
- event ValidatorPublicKeysDataUpdated(address indexed validator, bytes publicKeysData);
+ event ValidatorEcdsaPublicKeyUpdated(address indexed validator, bytes ecdsaPublicKey);
+ event ValidatorBlsPublicKeyUpdated(address indexed validator, bytes blsPublicKey);
event ValidatorGroupRegistered(address indexed group, uint256 commission);
event ValidatorGroupDeregistered(address indexed group);
event ValidatorGroupMemberAdded(address indexed group, address indexed validator);
@@ -258,27 +260,32 @@ contract Validators is
/**
* @notice Registers a validator unaffiliated with any validator group.
- * @param publicKeysData Comprised of three tightly-packed elements:
- * - publicKey - The public key that the validator is using for consensus, should match
- * msg.sender. 64 bytes.
- * - blsPublicKey - The BLS public key that the validator is using for consensus, should pass
- * proof of possession. 48 bytes.
- * - blsPoP - The BLS public key proof of possession. 96 bytes.
+ * @param ecdsaPublicKey The ECDSA public key that the validator is using for consensus, should
+ * match the validator signer. 64 bytes.
+ * @param blsPublicKey The BLS public key that the validator is using for consensus, should pass
+ * proof of possession. 48 bytes.
+ * @param blsPop The BLS public key proof-of-possession, which consists of a signature on the
+ * account address. 96 bytes.
* @return True upon success.
* @dev Fails if the account is already a validator or validator group.
* @dev Fails if the account does not have sufficient Locked Gold.
*/
- function registerValidator(bytes calldata publicKeysData) external nonReentrant returns (bool) {
- address account = getAccounts().activeValidationSignerToAccount(msg.sender);
+ function registerValidator(
+ bytes calldata ecdsaPublicKey,
+ bytes calldata blsPublicKey,
+ bytes calldata blsPop
+ ) external nonReentrant returns (bool) {
+ address account = getAccounts().signerToAccount(msg.sender);
require(!isValidator(account) && !isValidatorGroup(account));
uint256 lockedGoldBalance = getLockedGold().getAccountTotalLockedGold(account);
require(lockedGoldBalance >= validatorLockedGoldRequirements.value);
Validator storage validator = validators[account];
- _updatePublicKeysData(validator, publicKeysData);
- validator.publicKeysData = publicKeysData;
+ address signer = getAccounts().getValidatorSigner(account);
+ _updateEcdsaPublicKey(validator, signer, ecdsaPublicKey);
+ _updateBlsPublicKey(validator, account, blsPublicKey, blsPop);
registeredValidators.push(account);
updateMembershipHistory(account, address(0));
- emit ValidatorRegistered(account, publicKeysData);
+ emit ValidatorRegistered(account, ecdsaPublicKey, blsPublicKey);
return true;
}
@@ -313,23 +320,23 @@ contract Validators is
/**
* @notice Updates a validator's score based on its uptime for the epoch.
- * @param validator The address of the validator.
+ * @param signer The validator signer of the validator account whose score needs updating.
* @param uptime The Fixidity representation of the validator's uptime, between 0 and 1.
* @return True upon success.
*/
- function updateValidatorScore(address validator, uint256 uptime) external onlyVm() {
- _updateValidatorScore(validator, uptime);
+ function updateValidatorScoreFromSigner(address signer, uint256 uptime) external onlyVm() {
+ _updateValidatorScoreFromSigner(signer, uptime);
}
/**
* @notice Updates a validator's score based on its uptime for the epoch.
- * @param validator The address of the validator.
+ * @param signer The validator signer of the validator whose score needs updating.
* @param uptime The Fixidity representation of the validator's uptime, between 0 and 1.
* @dev new_score = uptime ** exponent * adjustmentSpeed + old_score * (1 - adjustmentSpeed)
* @return True upon success.
*/
- function _updateValidatorScore(address validator, uint256 uptime) internal {
- address account = getAccounts().validationSignerToAccount(validator);
+ function _updateValidatorScoreFromSigner(address signer, uint256 uptime) internal {
+ address account = getAccounts().signerToAccount(signer);
require(isValidator(account));
require(uptime <= FixidityLib.fixed1().unwrap());
@@ -361,32 +368,32 @@ contract Validators is
}
/**
- * @notice Distributes epoch payments to `validator` and its group.
- * @param validator The validator to distribute the epoch payment to.
+ * @notice Distributes epoch payments to the account associated with `signer` and its group.
+ * @param signer The validator signer of the account to distribute the epoch payment to.
* @param maxPayment The maximum payment to the validator. Actual payment is based on score and
* group commission.
* @return The total payment paid to the validator and their group.
*/
- function distributeEpochPayment(address validator, uint256 maxPayment)
+ function distributeEpochPaymentsFromSigner(address signer, uint256 maxPayment)
external
onlyVm()
returns (uint256)
{
- return _distributeEpochPayment(validator, maxPayment);
+ return _distributeEpochPaymentsFromSigner(signer, maxPayment);
}
/**
- * @notice Distributes epoch payments to `validator` and its group.
- * @param validator The validator to distribute the epoch payment to.
+ * @notice Distributes epoch payments to the account associated with `signer` and its group.
+ * @param signer The validator signer of the validator to distribute the epoch payment to.
* @param maxPayment The maximum payment to the validator. Actual payment is based on score and
* group commission.
* @return The total payment paid to the validator and their group.
*/
- function _distributeEpochPayment(address validator, uint256 maxPayment)
+ function _distributeEpochPaymentsFromSigner(address signer, uint256 maxPayment)
internal
returns (uint256)
{
- address account = getAccounts().validationSignerToAccount(validator);
+ address account = getAccounts().signerToAccount(signer);
require(isValidator(account));
// The group that should be paid is the group that the validator was a member of at the
// time it was elected.
@@ -414,7 +421,7 @@ contract Validators is
* @dev Fails if the account is not a validator.
*/
function deregisterValidator(uint256 index) external nonReentrant returns (bool) {
- address account = getAccounts().activeValidationSignerToAccount(msg.sender);
+ address account = getAccounts().signerToAccount(msg.sender);
require(isValidator(account));
// Require that the validator has not been a member of a validator group for
@@ -442,7 +449,7 @@ contract Validators is
* @dev De-affiliates with the previously affiliated group if present.
*/
function affiliate(address group) external nonReentrant returns (bool) {
- address account = getAccounts().activeValidationSignerToAccount(msg.sender);
+ address account = getAccounts().signerToAccount(msg.sender);
require(isValidator(account) && isValidatorGroup(group));
require(meetsAccountLockedGoldRequirements(account));
require(meetsAccountLockedGoldRequirements(group));
@@ -461,7 +468,7 @@ contract Validators is
* @dev Fails if the account is not a validator with non-zero affiliation.
*/
function deaffiliate() external nonReentrant returns (bool) {
- address account = getAccounts().activeValidationSignerToAccount(msg.sender);
+ address account = getAccounts().signerToAccount(msg.sender);
require(isValidator(account));
Validator storage validator = validators[account];
require(validator.affiliation != address(0));
@@ -470,46 +477,86 @@ contract Validators is
}
/**
- * @notice Updates a validator's public keys data.
- * @param publicKeysData Comprised of three tightly-packed elements:
- * - publicKey - The public key that the validator is using for consensus, should match
- * msg.sender. 64 bytes.
- * - blsPublicKey - The BLS public key that the validator is using for consensus, should pass
- * proof of possession. 48 bytes.
- * - blsPoP - The BLS public key proof of possession. 96 bytes.
+ * @notice Updates a validator's BLS key.
+ * @param blsPublicKey The BLS public key that the validator is using for consensus, should pass
+ * proof of possession. 48 bytes.
+ * @param blsPop The BLS public key proof-of-possession, which consists of a signature on the
+ * account address. 96 bytes.
* @return True upon success.
*/
- function updatePublicKeysData(bytes calldata publicKeysData) external returns (bool) {
- address account = getAccounts().activeValidationSignerToAccount(msg.sender);
+ function updateBlsPublicKey(bytes calldata blsPublicKey, bytes calldata blsPop)
+ external
+ returns (bool)
+ {
+ address account = getAccounts().signerToAccount(msg.sender);
require(isValidator(account));
Validator storage validator = validators[account];
- _updatePublicKeysData(validator, publicKeysData);
- emit ValidatorPublicKeysDataUpdated(account, publicKeysData);
+ _updateBlsPublicKey(validator, account, blsPublicKey, blsPop);
+ emit ValidatorBlsPublicKeyUpdated(account, blsPublicKey);
return true;
}
/**
- * @notice Updates a validator's public keys data.
- * @param validator The validator whose public keys data should be updated.
- * @param publicKeysData Comprised of three tightly-packed elements:
- * - publicKey - The public key that the validator is using for consensus, should match
- * msg.sender. 64 bytes.
- * - blsPublicKey - The BLS public key that the validator is using for consensus, should pass
- * proof of possession. 48 bytes.
- * - blsPoP - The BLS public key proof of possession. 96 bytes.
+ * @notice Updates a validator's BLS key.
+ * @param validator The validator whose BLS public key should be updated.
+ * @param account The address under which the validator is registered.
+ * @param blsPublicKey The BLS public key that the validator is using for consensus, should pass
+ * proof of possession. 48 bytes.
+ * @param blsPop The BLS public key proof-of-possession, which consists of a signature on the
+ * account address. 96 bytes.
* @return True upon success.
*/
- function _updatePublicKeysData(Validator storage validator, bytes memory publicKeysData)
- private
+ function _updateBlsPublicKey(
+ Validator storage validator,
+ address account,
+ bytes memory blsPublicKey,
+ bytes memory blsPop
+ ) private returns (bool) {
+ require(blsPublicKey.length == 48);
+ require(blsPop.length == 96);
+ require(checkProofOfPossession(account, blsPublicKey, blsPop));
+ validator.publicKeys.bls = blsPublicKey;
+ return true;
+ }
+
+ /**
+ * @notice Updates a validator's ECDSA key.
+ * @param account The address under which the validator is registered.
+ * @param signer The address which the validator is using to sign consensus messages.
+ * @param ecdsaPublicKey The ECDSA public key corresponding to `signer`.
+ * @return True upon success.
+ */
+ function updateEcdsaPublicKey(address account, address signer, bytes calldata ecdsaPublicKey)
+ external
+ onlyRegisteredContract(ACCOUNTS_REGISTRY_ID)
returns (bool)
{
+ require(isValidator(account));
+ Validator storage validator = validators[account];
+ require(_updateEcdsaPublicKey(validator, signer, ecdsaPublicKey));
+ emit ValidatorEcdsaPublicKeyUpdated(account, ecdsaPublicKey);
+ return true;
+ }
+
+ /**
+ * @notice Updates a validator's ECDSA key.
+ * @param validator The validator whose ECDSA public key should be updated.
+ * @param signer The address with which the validator is signing consensus messages.
+ * @param ecdsaPublicKey The ECDSA public key that the validator is using for consensus. Should
+ * match `signer`. 64 bytes.
+ * @return True upon success.
+ */
+ function _updateEcdsaPublicKey(
+ Validator storage validator,
+ address signer,
+ bytes memory ecdsaPublicKey
+ ) private returns (bool) {
+ require(ecdsaPublicKey.length == 64);
require(
- // secp256k1 public key + BLS public key + BLS proof of possession
- publicKeysData.length == (64 + 48 + 96)
+ address(uint160(uint256(keccak256(ecdsaPublicKey)))) == signer,
+ "ECDSA key does not match signer"
);
- // Use the proof of possession bytes
- require(checkProofOfPossession(msg.sender, publicKeysData.slice(64, 48 + 96)));
- validator.publicKeysData = publicKeysData;
+ validator.publicKeys.ecdsa = ecdsaPublicKey;
return true;
}
@@ -523,7 +570,7 @@ contract Validators is
*/
function registerValidatorGroup(uint256 commission) external nonReentrant returns (bool) {
require(commission <= FixidityLib.fixed1().unwrap(), "Commission can't be greater than 100%");
- address account = getAccounts().activeValidationSignerToAccount(msg.sender);
+ address account = getAccounts().signerToAccount(msg.sender);
require(!isValidator(account) && !isValidatorGroup(account));
uint256 lockedGoldBalance = getLockedGold().getAccountTotalLockedGold(account);
require(lockedGoldBalance >= groupLockedGoldRequirements.value);
@@ -542,7 +589,7 @@ contract Validators is
* @dev Fails if the account is not a validator group with no members.
*/
function deregisterValidatorGroup(uint256 index) external nonReentrant returns (bool) {
- address account = getAccounts().activeValidationSignerToAccount(msg.sender);
+ address account = getAccounts().signerToAccount(msg.sender);
// Only Validator Groups that have never had members or have been empty for at least
// `groupLockedGoldRequirements.duration` seconds can be deregistered.
require(isValidatorGroup(account) && groups[account].members.numElements == 0);
@@ -564,7 +611,7 @@ contract Validators is
* @dev Fails if the group has zero members.
*/
function addMember(address validator) external nonReentrant returns (bool) {
- address account = getAccounts().activeValidationSignerToAccount(msg.sender);
+ address account = getAccounts().signerToAccount(msg.sender);
require(groups[account].members.numElements > 0);
return _addMember(account, validator, address(0), address(0));
}
@@ -583,7 +630,7 @@ contract Validators is
nonReentrant
returns (bool)
{
- address account = getAccounts().activeValidationSignerToAccount(msg.sender);
+ address account = getAccounts().signerToAccount(msg.sender);
require(groups[account].members.numElements == 0);
return _addMember(account, validator, lesser, greater);
}
@@ -626,7 +673,7 @@ contract Validators is
* @dev Fails if `validator` is not a member of the account's group.
*/
function removeMember(address validator) external nonReentrant returns (bool) {
- address account = getAccounts().activeValidationSignerToAccount(msg.sender);
+ address account = getAccounts().signerToAccount(msg.sender);
require(isValidatorGroup(account) && isValidator(validator), "is not group and validator");
return _removeMember(account, validator);
}
@@ -646,7 +693,7 @@ contract Validators is
nonReentrant
returns (bool)
{
- address account = getAccounts().activeValidationSignerToAccount(msg.sender);
+ address account = getAccounts().signerToAccount(msg.sender);
require(isValidatorGroup(account) && isValidator(validator));
ValidatorGroup storage group = groups[account];
require(group.members.contains(validator));
@@ -662,7 +709,7 @@ contract Validators is
* @return True upon success.
*/
function updateCommission(uint256 commission) external returns (bool) {
- address account = getAccounts().activeValidationSignerToAccount(msg.sender);
+ address account = getAccounts().signerToAccount(msg.sender);
require(isValidatorGroup(account));
ValidatorGroup storage group = groups[account];
require(commission <= FixidityLib.fixed1().unwrap(), "Commission can't be greater than 100%");
@@ -706,19 +753,44 @@ contract Validators is
return balance >= getAccountLockedGoldRequirement(account);
}
+ /**
+ * @notice Returns the validator BLS key.
+ * @param signer The account that registered the validator or its authorized signing address.
+ * @return The validator BLS key.
+ */
+ function getValidatorBlsPublicKeyFromSigner(address signer)
+ external
+ view
+ returns (bytes memory blsPublicKey)
+ {
+ address account = getAccounts().signerToAccount(signer);
+ require(isValidator(account));
+ return validators[account].publicKeys.bls;
+ }
+
/**
* @notice Returns validator information.
* @param account The account that registered the validator.
* @return The unpacked validator struct.
*/
function getValidator(address account)
- external
+ public
view
- returns (bytes memory publicKeysData, address affiliation, uint256 score)
+ returns (
+ bytes memory ecdsaPublicKey,
+ bytes memory blsPublicKey,
+ address affiliation,
+ uint256 score
+ )
{
require(isValidator(account));
Validator storage validator = validators[account];
- return (validator.publicKeysData, validator.affiliation, validator.score.unwrap());
+ return (
+ validator.publicKeys.ecdsa,
+ validator.publicKeys.bls,
+ validator.affiliation,
+ validator.score.unwrap()
+ );
}
/**
@@ -760,7 +832,7 @@ contract Validators is
address[] memory topAccounts = groups[account].members.headN(n);
address[] memory topValidators = new address[](n);
for (uint256 i = 0; i < n; i = i.add(1)) {
- topValidators[i] = getAccounts().getValidationSigner(topAccounts[i]);
+ topValidators[i] = getAccounts().getValidatorSigner(topAccounts[i]);
}
return topValidators;
}
@@ -814,6 +886,19 @@ contract Validators is
return registeredValidators;
}
+ /**
+ * @notice Returns the list of signers for the registered validator accounts.
+ * @return The list of signers for registered validator accounts.
+ */
+ function getRegisteredValidatorSigners() external view returns (address[] memory) {
+ IAccounts accounts = getAccounts();
+ address[] memory signers = new address[](registeredValidators.length);
+ for (uint256 i = 0; i < signers.length; i = i.add(1)) {
+ signers[i] = accounts.getValidatorSigner(registeredValidators[i]);
+ }
+ return signers;
+ }
+
/**
* @notice Returns the list of registered validator group accounts.
* @return The list of registered validator group addresses.
@@ -837,7 +922,7 @@ contract Validators is
* @return Whether a particular address is a registered validator.
*/
function isValidator(address account) public view returns (bool) {
- return validators[account].publicKeysData.length > 0;
+ return validators[account].publicKeys.bls.length > 0;
}
/**
@@ -921,6 +1006,12 @@ contract Validators is
return true;
}
+ /**
+ * @notice Updates the size history of a validator group.
+ * @param group The account whose group size has changed.
+ * @param size The new size of the group.
+ * @dev Used to determine how much gold an account needs to keep locked.
+ */
function updateSizeHistory(address group, uint256 size) private {
uint256[] storage sizeHistory = groups[group].sizeHistory;
if (size == sizeHistory.length) {
@@ -932,6 +1023,17 @@ contract Validators is
}
}
+ /**
+ * @notice Returns the group that `account` was a member of at the end of the last epoch.
+ * @param signer The signer of the account whose group membership should be returned.
+ * @return The group that `account` was a member of at the end of the last epoch.
+ */
+ function getMembershipInLastEpochFromSigner(address signer) external view returns (address) {
+ address account = getAccounts().signerToAccount(signer);
+ require(isValidator(account));
+ return getMembershipInLastEpoch(account);
+ }
+
/**
* @notice Returns the group that `account` was a member of at the end of the last epoch.
* @param account The account whose group membership should be returned.
diff --git a/packages/protocol/contracts/governance/interfaces/IElection.sol b/packages/protocol/contracts/governance/interfaces/IElection.sol
index 55c545b0d21..ea20aa76978 100644
--- a/packages/protocol/contracts/governance/interfaces/IElection.sol
+++ b/packages/protocol/contracts/governance/interfaces/IElection.sol
@@ -6,5 +6,5 @@ interface IElection {
function getTotalVotesByAccount(address) external view returns (uint256);
function markGroupIneligible(address) external;
function markGroupEligible(address, address, address) external;
- function electValidators() external view returns (address[] memory);
+ function electValidatorSigners() external view returns (address[] memory);
}
diff --git a/packages/protocol/contracts/governance/interfaces/IValidators.sol b/packages/protocol/contracts/governance/interfaces/IValidators.sol
index 80906504a03..fcb6e3cd1ef 100644
--- a/packages/protocol/contracts/governance/interfaces/IValidators.sol
+++ b/packages/protocol/contracts/governance/interfaces/IValidators.sol
@@ -7,4 +7,6 @@ interface IValidators {
function getGroupsNumMembers(address[] calldata) external view returns (uint256[] memory);
function getNumRegisteredValidators() external view returns (uint256);
function getTopGroupValidators(address, uint256) external view returns (address[] memory);
+ function updateEcdsaPublicKey(address, address, bytes calldata) external returns (bool);
+ function isValidator(address) external view returns (bool);
}
diff --git a/packages/protocol/contracts/governance/test/MockElection.sol b/packages/protocol/contracts/governance/test/MockElection.sol
index ddc157abe79..f39b470ba0f 100644
--- a/packages/protocol/contracts/governance/test/MockElection.sol
+++ b/packages/protocol/contracts/governance/test/MockElection.sol
@@ -44,7 +44,7 @@ contract MockElection is IElection {
electedValidators = _electedValidators;
}
- function electValidators() external view returns (address[] memory) {
+ function electValidatorSigners() external view returns (address[] memory) {
return electedValidators;
}
}
diff --git a/packages/protocol/contracts/governance/test/MockValidators.sol b/packages/protocol/contracts/governance/test/MockValidators.sol
index 0eb1c902fb3..aa2406f8e0f 100644
--- a/packages/protocol/contracts/governance/test/MockValidators.sol
+++ b/packages/protocol/contracts/governance/test/MockValidators.sol
@@ -6,42 +6,33 @@ import "../interfaces/IValidators.sol";
* @title Holds a list of addresses of validators
*/
contract MockValidators is IValidators {
- mapping(address => bool) private _isValidating;
- mapping(address => bool) private _isVoting;
+ mapping(address => bool) public isValidator;
mapping(address => uint256) private numGroupMembers;
mapping(address => uint256) private lockedGoldRequirements;
mapping(address => bool) private doesNotMeetAccountLockedGoldRequirements;
mapping(address => address[]) private members;
uint256 private numRegisteredValidators;
- function setDoesNotMeetAccountLockedGoldRequirements(address account) external {
- doesNotMeetAccountLockedGoldRequirements[account] = true;
+ function updateEcdsaPublicKey(address, address, bytes calldata) external returns (bool) {
+ return true;
}
- function meetsAccountLockedGoldRequirements(address account) external view returns (bool) {
- return !doesNotMeetAccountLockedGoldRequirements[account];
+ function setValidator(address account) external {
+ isValidator[account] = true;
}
- function isValidating(address account) external view returns (bool) {
- return _isValidating[account];
+ function setDoesNotMeetAccountLockedGoldRequirements(address account) external {
+ doesNotMeetAccountLockedGoldRequirements[account] = true;
}
- function isVoting(address account) external view returns (bool) {
- return _isVoting[account];
+ function meetsAccountLockedGoldRequirements(address account) external view returns (bool) {
+ return !doesNotMeetAccountLockedGoldRequirements[account];
}
function getGroupNumMembers(address group) public view returns (uint256) {
return members[group].length;
}
- function setValidating(address account) external {
- _isValidating[account] = true;
- }
-
- function setVoting(address account) external {
- _isVoting[account] = true;
- }
-
function setNumRegisteredValidators(uint256 value) external {
numRegisteredValidators = value;
}
diff --git a/packages/protocol/contracts/governance/test/ValidatorsTest.sol b/packages/protocol/contracts/governance/test/ValidatorsTest.sol
index a049ddc6efb..86af7f26a3b 100644
--- a/packages/protocol/contracts/governance/test/ValidatorsTest.sol
+++ b/packages/protocol/contracts/governance/test/ValidatorsTest.sol
@@ -7,14 +7,14 @@ import "../../common/FixidityLib.sol";
* @title A wrapper around Validators that exposes onlyVm functions for testing.
*/
contract ValidatorsTest is Validators {
- function updateValidatorScore(address validator, uint256 uptime) external {
- return _updateValidatorScore(validator, uptime);
+ function updateValidatorScoreFromSigner(address signer, uint256 uptime) external {
+ return _updateValidatorScoreFromSigner(signer, uptime);
}
- function distributeEpochPayment(address validator, uint256 maxPayment)
+ function distributeEpochPaymentsFromSigner(address signer, uint256 maxPayment)
external
returns (uint256)
{
- return _distributeEpochPayment(validator, maxPayment);
+ return _distributeEpochPaymentsFromSigner(signer, maxPayment);
}
}
diff --git a/packages/protocol/contracts/identity/Attestations.sol b/packages/protocol/contracts/identity/Attestations.sol
index c3758e906b0..fc4287a77e6 100644
--- a/packages/protocol/contracts/identity/Attestations.sol
+++ b/packages/protocol/contracts/identity/Attestations.sol
@@ -501,7 +501,7 @@ contract Attestations is
) public view returns (address) {
bytes32 codehash = keccak256(abi.encodePacked(identifier, account));
address signer = Signatures.getSignerOfMessageHash(codehash, v, r, s);
- address issuer = getAccounts().activeAttesttationSignerToAccount(signer);
+ address issuer = getAccounts().attestationSignerToAccount(signer);
Attestation storage attestation = identifiers[identifier].attestations[account]
.issuedAttestations[issuer];
@@ -570,7 +570,7 @@ contract Attestations is
while (currentIndex < unselectedRequest.attestationsRequested) {
seed = keccak256(abi.encodePacked(seed));
validator = validatorAddressFromCurrentSet(uint256(seed) % numberValidators);
- issuer = getAccounts().activeValidationSignerToAccount(validator);
+ issuer = getAccounts().validatorSignerToAccount(validator);
Attestation storage attestation = state.issuedAttestations[issuer];
// Attestation issuers can only be added if they haven't been already.
diff --git a/packages/protocol/lib/test-utils.ts b/packages/protocol/lib/test-utils.ts
index 812669b0f5a..0605852eaed 100644
--- a/packages/protocol/lib/test-utils.ts
+++ b/packages/protocol/lib/test-utils.ts
@@ -34,30 +34,6 @@ export function assertContainSubset(superset: any, subset: any) {
return assert2.containSubset(superset, subset)
}
-export async function advanceBlockNum(numBlocks: number, web3: Web3) {
- let returnValue: any
- for (let i: number = 0; i < numBlocks; i++) {
- returnValue = new Promise((resolve, reject) => {
- web3.currentProvider.send(
- {
- jsonrpc: '2.0',
- method: 'evm_mine',
- params: [],
- id: new Date().getTime(),
- },
- // @ts-ignore
- (err: any, result: any) => {
- if (err) {
- return reject(err)
- }
- return resolve(result)
- }
- )
- })
- }
- return returnValue
-}
-
export async function jsonRpc(web3: Web3, method: string, params: any[] = []): Promise {
return new Promise((resolve, reject) => {
web3.currentProvider.send(
@@ -342,7 +318,6 @@ export const matchAny = () => {
}
export default {
- advanceBlockNum,
assertContainSubset,
assertRevert,
timeTravel,
diff --git a/packages/protocol/lib/web3-utils.ts b/packages/protocol/lib/web3-utils.ts
index d5b96f21e8e..b7dc2624564 100644
--- a/packages/protocol/lib/web3-utils.ts
+++ b/packages/protocol/lib/web3-utils.ts
@@ -3,41 +3,20 @@
import { setAndInitializeImplementation } from '@celo/protocol/lib/proxy-utils'
import { CeloContractName } from '@celo/protocol/lib/registry-utils'
import { signTransaction } from '@celo/protocol/lib/signing-utils'
+import { privateKeyToAddress } from '@celo/utils/lib/address'
import { BigNumber } from 'bignumber.js'
-import { ec as EC } from 'elliptic'
import { EscrowInstance, GoldTokenInstance, MultiSigInstance, OwnableInstance, ProxyContract, ProxyInstance, RegistryInstance, StableTokenInstance } from 'types'
import { TransactionObject } from 'web3/eth/types'
import Web3 = require('web3')
-const ec = new EC('secp256k1')
-const cachedWeb3 = new Web3()
-
-export function add0x(str: string) {
- return '0x' + str
-}
-
-export function generatePublicKeyFromPrivateKey(privateKey: string) {
- const ecPrivateKey = ec.keyFromPrivate(Buffer.from(privateKey, 'hex'))
- const ecPublicKey: string = ecPrivateKey.getPublic('hex')
- return ecPublicKey.slice(2)
-}
-
-export function generateAccountAddressFromPrivateKey(privateKey: string) {
- if (!privateKey.startsWith('0x')) {
- privateKey = '0x' + privateKey
- }
- // @ts-ignore-next-line
- return cachedWeb3.eth.accounts.privateKeyToAccount(privateKey).address
-}
-
export async function sendTransactionWithPrivateKey(
web3: Web3,
tx: TransactionObject,
privateKey: string,
txArgs: any
) {
- const address = generateAccountAddressFromPrivateKey(privateKey.slice(2))
+ const address = privateKeyToAddress(privateKey)
const encodedTxData = tx.encodeABI()
const estimatedGas = await tx.estimateGas({
...txArgs,
diff --git a/packages/protocol/migrations/10_accounts.ts b/packages/protocol/migrations/10_accounts.ts
index 4a9542cdc76..592087734c8 100644
--- a/packages/protocol/migrations/10_accounts.ts
+++ b/packages/protocol/migrations/10_accounts.ts
@@ -1,9 +1,21 @@
import { CeloContractName } from '@celo/protocol/lib/registry-utils'
-import { deploymentForCoreContract } from '@celo/protocol/lib/web3-utils'
-import { AccountsInstance } from 'types'
+import {
+ deploymentForCoreContract,
+ getDeployedProxiedContract,
+} from '@celo/protocol/lib/web3-utils'
+import { AccountsInstance, RegistryInstance } from 'types'
+
+const initializeArgs = async (): Promise<[string]> => {
+ const registry: RegistryInstance = await getDeployedProxiedContract(
+ 'Registry',
+ artifacts
+ )
+ return [registry.address]
+}
module.exports = deploymentForCoreContract(
web3,
artifacts,
- CeloContractName.Accounts
+ CeloContractName.Accounts,
+ initializeArgs
)
diff --git a/packages/protocol/migrations/20_elect_validators.ts b/packages/protocol/migrations/20_elect_validators.ts
index a42077e0ba2..24baec893e4 100644
--- a/packages/protocol/migrations/20_elect_validators.ts
+++ b/packages/protocol/migrations/20_elect_validators.ts
@@ -1,17 +1,14 @@
/* tslint:disable:no-console */
import { NULL_ADDRESS } from '@celo/protocol/lib/test-utils'
import {
- add0x,
- generateAccountAddressFromPrivateKey,
- generatePublicKeyFromPrivateKey,
getDeployedProxiedContract,
sendTransactionWithPrivateKey,
} from '@celo/protocol/lib/web3-utils'
import { config } from '@celo/protocol/migrationsConfig'
-import { blsPrivateKeyToProcessedPrivateKey } from '@celo/utils/lib/bls'
+import { privateKeyToAddress, privateKeyToPublicKey } from '@celo/utils/lib/address'
+import { getBlsPoP, getBlsPublicKey } from '@celo/utils/lib/bls'
import { toFixed } from '@celo/utils/lib/fixidity'
import { BigNumber } from 'bignumber.js'
-import * as bls12377js from 'bls12377js'
import { AccountsInstance, ElectionInstance, LockedGoldInstance, ValidatorsInstance } from 'types'
const Web3 = require('web3')
@@ -66,7 +63,7 @@ async function registerValidatorGroup(
)
await web3.eth.sendTransaction({
- from: generateAccountAddressFromPrivateKey(privateKey.slice(0)),
+ from: privateKeyToAddress(privateKey),
to: account.address,
value: lockedGoldValue.times(1.01).toFixed(), // Add a premium to cover tx fees
})
@@ -102,21 +99,6 @@ async function registerValidator(
index: number,
networkName: string
) {
- const validatorPrivateKeyHexStripped = validatorPrivateKey.slice(2)
- const address = generateAccountAddressFromPrivateKey(validatorPrivateKey)
- const publicKey = generatePublicKeyFromPrivateKey(validatorPrivateKeyHexStripped)
- const blsValidatorPrivateKeyBytes = blsPrivateKeyToProcessedPrivateKey(
- validatorPrivateKeyHexStripped
- )
- const blsPublicKey = bls12377js.BLS.privateToPublicBytes(blsValidatorPrivateKeyBytes).toString(
- 'hex'
- )
- const blsPoP = bls12377js.BLS.signPoP(
- blsValidatorPrivateKeyBytes,
- Buffer.from(address.slice(2), 'hex')
- ).toString('hex')
- const publicKeysData = publicKey + blsPublicKey + blsPoP
-
await lockGold(
accounts,
lockedGold,
@@ -130,8 +112,12 @@ async function registerValidator(
to: accounts.address,
})
+ const publicKey = privateKeyToPublicKey(validatorPrivateKey)
+ const blsPublicKey = getBlsPublicKey(validatorPrivateKey)
+ const blsPoP = getBlsPoP(privateKeyToAddress(validatorPrivateKey), validatorPrivateKey)
+
// @ts-ignore
- const registerTx = validators.contract.methods.registerValidator(add0x(publicKeysData))
+ const registerTx = validators.contract.methods.registerValidator(publicKey, blsPublicKey, blsPoP)
await sendTransactionWithPrivateKey(web3, registerTx, validatorPrivateKey, {
to: validators.address,
@@ -146,7 +132,7 @@ async function registerValidator(
// @ts-ignore
const registerDataEncryptionKeyTx = accounts.contract.methods.setAccountDataEncryptionKey(
- add0x(publicKey)
+ privateKeyToPublicKey(validatorPrivateKey)
)
await sendTransactionWithPrivateKey(web3, registerDataEncryptionKeyTx, validatorPrivateKey, {
@@ -216,7 +202,7 @@ module.exports = async (_deployer: any, networkName: string) => {
console.info(' Adding Validators to Validator Group ...')
for (let i = 0; i < valKeys.length; i++) {
const key = valKeys[i]
- const address = generateAccountAddressFromPrivateKey(key.slice(2))
+ const address = privateKeyToAddress(key)
if (i === 0) {
// @ts-ignore
const addTx = validators.contract.methods.addFirstMember(address, NULL_ADDRESS, NULL_ADDRESS)
diff --git a/packages/protocol/package.json b/packages/protocol/package.json
index fb42227a875..1e3b6884ec9 100644
--- a/packages/protocol/package.json
+++ b/packages/protocol/package.json
@@ -43,7 +43,6 @@
"@0x/subproviders": "^5.0.0",
"@celo/utils": "^0.1.0",
"apollo-client": "^2.4.13",
- "bls12377js": "https://github.com/celo-org/bls12377js#cada1105f4a5e4c2ddd239c1874df3bf33144a10",
"chai-subset": "^1.6.0",
"csv-parser": "^2.0.0",
"csv-stringify": "^4.3.1",
diff --git a/packages/protocol/test/common/accounts.ts b/packages/protocol/test/common/accounts.ts
index 6e409ed4543..86c65839056 100644
--- a/packages/protocol/test/common/accounts.ts
+++ b/packages/protocol/test/common/accounts.ts
@@ -1,14 +1,23 @@
import { parseSolidityStringArray } from '@celo/utils/lib/parsing'
import { upperFirst } from 'lodash'
-import { AccountsInstance } from 'types'
-import { getParsedSignatureOfAddress } from '../../lib/signing-utils'
+import { CeloContractName } from '@celo/protocol/lib/registry-utils'
+import { getParsedSignatureOfAddress } from '@celo/protocol/lib/signing-utils'
import {
assertLogMatches,
assertLogMatches2,
assertRevert,
NULL_ADDRESS,
-} from '../../lib/test-utils'
-const Accounts: Truffle.Contract = artifacts.require('Accounts')
+} from '@celo/protocol/lib/test-utils'
+import {
+ AccountsContract,
+ AccountsInstance,
+ MockValidatorsContract,
+ MockValidatorsInstance,
+ RegistryContract,
+} from 'types'
+const Accounts: AccountsContract = artifacts.require('Accounts')
+const Registry: RegistryContract = artifacts.require('Registry')
+const MockValidators: MockValidatorsContract = artifacts.require('MockValidators')
const authorizationTests: any = {}
const authorizationTestDescriptions = {
voting: {
@@ -16,8 +25,8 @@ const authorizationTestDescriptions = {
subject: 'voteSigner',
},
validating: {
- me: 'validation signing key',
- subject: 'validationSigner',
+ me: 'validator signing key',
+ subject: 'validatorSigner',
},
attesting: {
me: 'attestation signing key',
@@ -27,6 +36,7 @@ const authorizationTestDescriptions = {
contract('Accounts', (accounts: string[]) => {
let accountsInstance: AccountsInstance
+ let mockValidators: MockValidatorsInstance
const account = accounts[0]
const caller = accounts[0]
@@ -39,27 +49,28 @@ contract('Accounts', (accounts: string[]) => {
beforeEach(async () => {
accountsInstance = await Accounts.new({ from: account })
+ mockValidators = await MockValidators.new()
+ const registry = await Registry.new()
+ await registry.setAddressFor(CeloContractName.Validators, mockValidators.address)
+ await accountsInstance.initialize(registry.address)
authorizationTests.voting = {
fn: accountsInstance.authorizeVoteSigner,
eventName: 'VoteSignerAuthorized',
getAuthorizedFromAccount: accountsInstance.getVoteSigner,
- getAccountFromAuthorized: accountsInstance.voteSignerToAccount,
- getAccountFromActiveAuthorized: accountsInstance.activeVoteSignerToAccount,
+ authorizedSignerToAccount: accountsInstance.voteSignerToAccount,
}
authorizationTests.validating = {
- fn: accountsInstance.authorizeValidationSigner,
- eventName: 'ValidationSignerAuthorized',
- getAuthorizedFromAccount: accountsInstance.getValidationSigner,
- getAccountFromAuthorized: accountsInstance.validationSignerToAccount,
- getAccountFromActiveAuthorized: accountsInstance.activeValidationSignerToAccount,
+ fn: accountsInstance.authorizeValidatorSigner,
+ eventName: 'ValidatorSignerAuthorized',
+ getAuthorizedFromAccount: accountsInstance.getValidatorSigner,
+ authorizedSignerToAccount: accountsInstance.validatorSignerToAccount,
}
authorizationTests.attesting = {
fn: accountsInstance.authorizeAttestationSigner,
eventName: 'AttestationSignerAuthorized',
getAuthorizedFromAccount: accountsInstance.getAttestationSigner,
- getAccountFromAuthorized: accountsInstance.attestationSignerToAccount,
- getAccountFromActiveAuthorized: accountsInstance.activeAttesttationSignerToAccount,
+ authorizedSignerToAccount: accountsInstance.attestationSignerToAccount,
}
})
@@ -359,7 +370,7 @@ contract('Accounts', (accounts: string[]) => {
await authorizationTest.fn(authorized, sig.v, sig.r, sig.s)
assert.equal(await accountsInstance.authorizedBy(authorized), account)
assert.equal(await authorizationTest.getAuthorizedFromAccount(account), authorized)
- assert.equal(await authorizationTest.getAccountFromActiveAuthorized(authorized), account)
+ assert.equal(await authorizationTest.authorizedSignerToAccount(authorized), account)
})
it(`should emit the right event`, async () => {
@@ -409,14 +420,11 @@ contract('Accounts', (accounts: string[]) => {
it(`should set the new authorized ${authorizationTestDescriptions[key].me}`, async () => {
assert.equal(await accountsInstance.authorizedBy(newAuthorized), account)
assert.equal(await authorizationTest.getAuthorizedFromAccount(account), newAuthorized)
- assert.equal(
- await authorizationTest.getAccountFromActiveAuthorized(newAuthorized),
- account
- )
+ assert.equal(await authorizationTest.authorizedSignerToAccount(newAuthorized), account)
})
- it('should reset the previous authorization', async () => {
- assert.equal(await accountsInstance.authorizedBy(authorized), NULL_ADDRESS)
+ it('should preserve the previous authorization', async () => {
+ assert.equal(await accountsInstance.authorizedBy(authorized), account)
})
})
})
@@ -426,11 +434,11 @@ contract('Accounts', (accounts: string[]) => {
authorizationTestDescriptions[key].me
}`, () => {
it('should return the account when passed the account', async () => {
- assert.equal(await authorizationTest.getAccountFromActiveAuthorized(account), account)
+ assert.equal(await authorizationTest.authorizedSignerToAccount(account), account)
})
it('should revert when passed an address that is not an account', async () => {
- await assertRevert(authorizationTest.getAccountFromActiveAuthorized(accounts[1]))
+ await assertRevert(authorizationTest.authorizedSignerToAccount(accounts[1]))
})
})
@@ -444,16 +452,13 @@ contract('Accounts', (accounts: string[]) => {
})
it('should return the account when passed the account', async () => {
- assert.equal(await authorizationTest.getAccountFromActiveAuthorized(account), account)
+ assert.equal(await authorizationTest.authorizedSignerToAccount(account), account)
})
it(`should return the account when passed the ${
authorizationTestDescriptions[key].me
}`, async () => {
- assert.equal(
- await authorizationTest.getAccountFromActiveAuthorized(authorized),
- account
- )
+ assert.equal(await authorizationTest.authorizedSignerToAccount(authorized), account)
})
})
})
diff --git a/packages/protocol/test/governance/election.ts b/packages/protocol/test/governance/election.ts
index b4469376c56..c0b98704a9b 100644
--- a/packages/protocol/test/governance/election.ts
+++ b/packages/protocol/test/governance/election.ts
@@ -751,7 +751,7 @@ contract('Election', (accounts: string[]) => {
})
})
- describe('#electValidators', () => {
+ describe('#electValidatorSigners', () => {
let random: MockRandomInstance
let totalLockedGold: number
const group1 = accounts[0]
@@ -812,7 +812,7 @@ contract('Election', (accounts: string[]) => {
it("should return that group's member list", async () => {
await setRandomness(hash1)
- assertSameAddresses(await election.electValidators(), [
+ assertSameAddresses(await election.electValidatorSigners(), [
validator1,
validator2,
validator3,
@@ -830,7 +830,7 @@ contract('Election', (accounts: string[]) => {
it('should return maxElectableValidators elected validators', async () => {
await setRandomness(hash1)
- assertSameAddresses(await election.electValidators(), [
+ assertSameAddresses(await election.electValidatorSigners(), [
validator1,
validator2,
validator3,
@@ -850,9 +850,9 @@ contract('Election', (accounts: string[]) => {
it('should return different results', async () => {
await setRandomness(hash1)
- const valsWithHash1 = (await election.electValidators()).map((x) => x.toLowerCase())
+ const valsWithHash1 = (await election.electValidatorSigners()).map((x) => x.toLowerCase())
await setRandomness(hash2)
- const valsWithHash2 = (await election.electValidators()).map((x) => x.toLowerCase())
+ const valsWithHash2 = (await election.electValidatorSigners()).map((x) => x.toLowerCase())
assert.sameMembers(valsWithHash1, valsWithHash2)
assert.notDeepEqual(valsWithHash1, valsWithHash2)
})
@@ -872,7 +872,7 @@ contract('Election', (accounts: string[]) => {
it('should elect only n members from that group', async () => {
await setRandomness(hash1)
- assertSameAddresses(await election.electValidators(), [
+ assertSameAddresses(await election.electValidatorSigners(), [
validator7,
validator1,
validator2,
@@ -894,7 +894,7 @@ contract('Election', (accounts: string[]) => {
it('should not elect any members from that group', async () => {
await setRandomness(hash1)
- assertSameAddresses(await election.electValidators(), [
+ assertSameAddresses(await election.electValidatorSigners(), [
validator1,
validator2,
validator3,
@@ -913,7 +913,7 @@ contract('Election', (accounts: string[]) => {
it('should revert', async () => {
await setRandomness(hash1)
- await assertRevert(election.electValidators())
+ await assertRevert(election.electValidatorSigners())
})
})
})
diff --git a/packages/protocol/test/governance/validators.ts b/packages/protocol/test/governance/validators.ts
index 749a8581df3..387b5fdbd62 100644
--- a/packages/protocol/test/governance/validators.ts
+++ b/packages/protocol/test/governance/validators.ts
@@ -1,4 +1,5 @@
import { CeloContractName } from '@celo/protocol/lib/registry-utils'
+import { getParsedSignatureOfAddress } from '@celo/protocol/lib/signing-utils'
import {
assertContainSubset,
assertEqualBN,
@@ -10,6 +11,7 @@ import {
timeTravel,
} from '@celo/protocol/lib/test-utils'
import { fixed1, fromFixed, toFixed } from '@celo/utils/lib/fixidity'
+import { addressToPublicKey } from '@celo/utils/lib/signatureUtils'
import BigNumber from 'bignumber.js'
import {
AccountsContract,
@@ -25,8 +27,8 @@ import {
ValidatorsTestContract,
ValidatorsTestInstance,
} from 'types'
-const Accounts: AccountsContract = artifacts.require('Accounts')
+const Accounts: AccountsContract = artifacts.require('Accounts')
const Validators: ValidatorsTestContract = artifacts.require('ValidatorsTest')
const MockElection: MockElectionContract = artifacts.require('MockElection')
const MockLockedGold: MockLockedGoldContract = artifacts.require('MockLockedGold')
@@ -39,9 +41,10 @@ Validators.numberFormat = 'BigNumber'
const parseValidatorParams = (validatorParams: any) => {
return {
- publicKeysData: validatorParams[0],
- affiliation: validatorParams[1],
- score: validatorParams[2],
+ ecdsaPublicKey: validatorParams[0],
+ blsPublicKey: validatorParams[1],
+ affiliation: validatorParams[2],
+ score: validatorParams[3],
}
}
@@ -90,24 +93,26 @@ contract('Validators', (accounts: string[]) => {
const maxGroupSize = new BigNumber(5)
// A random 64 byte hex string.
- const publicKey =
- 'ea0733ad275e2b9e05541341a97ee82678c58932464fad26164657a111a7e37a9fa0300266fb90e2135a1f1512350cb4e985488a88809b14e3cbe415e76e82b2'
const blsPublicKey =
- '4d23d8cd06f30b1fa7cf368e2f5399ab04bb6846c682f493a98a607d3dfb7e53a712bb79b475c57b0ac2785460f91301'
+ '0x4d23d8cd06f30b1fa7cf368e2f5399ab04bb6846c682f493a98a607d3dfb7e53a712bb79b475c57b0ac2785460f91301'
const blsPoP =
- '9d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae30005d223780d785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d740501'
- const publicKeysData = '0x' + publicKey + blsPublicKey + blsPoP
+ '0x9d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae30005d223780d785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d740501'
const commission = toFixed(1 / 100)
beforeEach(async () => {
accountsInstance = await Accounts.new()
- await Promise.all(accounts.map((account) => accountsInstance.createAccount({ from: account })))
+ // Do not register an account for the last address so it can be used as an authorized validator signer.
+ await Promise.all(
+ accounts.slice(0, -1).map((account) => accountsInstance.createAccount({ from: account }))
+ )
mockElection = await MockElection.new()
mockLockedGold = await MockLockedGold.new()
registry = await Registry.new()
validators = await Validators.new()
+ await accountsInstance.initialize(registry.address)
await registry.setAddressFor(CeloContractName.Accounts, accountsInstance.address)
await registry.setAddressFor(CeloContractName.Election, mockElection.address)
await registry.setAddressFor(CeloContractName.LockedGold, mockLockedGold.address)
+ await registry.setAddressFor(CeloContractName.Validators, validators.address)
await validators.initialize(
registry.address,
groupLockedGoldRequirements.value,
@@ -123,9 +128,14 @@ contract('Validators', (accounts: string[]) => {
const registerValidator = async (validator: string) => {
await mockLockedGold.setAccountTotalLockedGold(validator, validatorLockedGoldRequirements.value)
+ const publicKey = await addressToPublicKey(validator, web3.eth.sign)
await validators.registerValidator(
// @ts-ignore bytes type
- publicKeysData,
+ publicKey,
+ // @ts-ignore bytes type
+ blsPublicKey,
+ // @ts-ignore bytes type
+ blsPoP,
{ from: validator }
)
}
@@ -519,78 +529,110 @@ contract('Validators', (accounts: string[]) => {
const validator = accounts[0]
let resp: any
describe('when the account is not a registered validator', () => {
- let validatorRegistrationEpochNumber: number
beforeEach(async () => {
await mockLockedGold.setAccountTotalLockedGold(
validator,
validatorLockedGoldRequirements.value
)
- resp = await validators.registerValidator(
- // @ts-ignore bytes type
- publicKeysData
- )
- const blockNumber = (await web3.eth.getBlock('latest')).number
- validatorRegistrationEpochNumber = Math.floor(blockNumber / EPOCH)
})
- it('should mark the account as a validator', async () => {
- assert.isTrue(await validators.isValidator(validator))
- })
+ describe('when the account has authorized a validator signer', () => {
+ let validatorRegistrationEpochNumber: number
+ let publicKey: string
+ beforeEach(async () => {
+ const signer = accounts[9]
+ const sig = await getParsedSignatureOfAddress(web3, validator, signer)
+ await accountsInstance.authorizeValidatorSigner(signer, sig.v, sig.r, sig.s)
+ publicKey = await addressToPublicKey(signer, web3.eth.sign)
+ resp = await validators.registerValidator(
+ // @ts-ignore bytes type
+ publicKey,
+ // @ts-ignore bytes type
+ blsPublicKey,
+ // @ts-ignore bytes type
+ blsPoP
+ )
+ const blockNumber = await web3.eth.getBlockNumber()
+ validatorRegistrationEpochNumber = Math.floor(blockNumber / EPOCH)
+ })
- it('should add the account to the list of validators', async () => {
- assert.deepEqual(await validators.getRegisteredValidators(), [validator])
- })
+ it('should mark the account as a validator', async () => {
+ assert.isTrue(await validators.isValidator(validator))
+ })
- it('should set the validator public key', async () => {
- const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
- assert.equal(parsedValidator.publicKeysData, publicKeysData)
- })
+ it('should add the account to the list of validators', async () => {
+ assert.deepEqual(await validators.getRegisteredValidators(), [validator])
+ })
- it('should set account locked gold requirements', async () => {
- const requirement = await validators.getAccountLockedGoldRequirement(validator)
- assertEqualBN(requirement, validatorLockedGoldRequirements.value)
- })
+ it('should set the validator ecdsa public key', async () => {
+ const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
+ assert.equal(parsedValidator.ecdsaPublicKey, publicKey)
+ })
- it('should set the validator membership history', async () => {
- const membershipHistory = await validators.getMembershipHistory(validator)
- assertEqualBNArray(membershipHistory[0], [validatorRegistrationEpochNumber])
- assert.deepEqual(membershipHistory[1], [NULL_ADDRESS])
- })
+ it('should set the validator bls public key', async () => {
+ const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
+ assert.equal(parsedValidator.blsPublicKey, blsPublicKey)
+ })
- it('should set the validator membership history', async () => {
- const membershipHistory = await validators.getMembershipHistory(validator)
- assertEqualBNArray(membershipHistory[0], [validatorRegistrationEpochNumber])
- assert.deepEqual(membershipHistory[1], [NULL_ADDRESS])
- })
+ it('should set account locked gold requirements', async () => {
+ const requirement = await validators.getAccountLockedGoldRequirement(validator)
+ assertEqualBN(requirement, validatorLockedGoldRequirements.value)
+ })
- it('should emit the ValidatorRegistered event', async () => {
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorRegistered',
- args: {
- validator,
- publicKeysData,
- },
+ it('should set the validator membership history', async () => {
+ const membershipHistory = await validators.getMembershipHistory(validator)
+ assertEqualBNArray(membershipHistory[0], [validatorRegistrationEpochNumber])
+ assert.deepEqual(membershipHistory[1], [NULL_ADDRESS])
+ })
+
+ it('should set the validator membership history', async () => {
+ const membershipHistory = await validators.getMembershipHistory(validator)
+ assertEqualBNArray(membershipHistory[0], [validatorRegistrationEpochNumber])
+ assert.deepEqual(membershipHistory[1], [NULL_ADDRESS])
+ })
+
+ it('should emit the ValidatorRegistered event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorRegistered',
+ args: {
+ validator,
+ ecdsaPublicKey: publicKey,
+ blsPublicKey: blsPublicKey,
+ },
+ })
})
})
})
describe('when the account is already a registered validator ', () => {
+ let publicKey: string
beforeEach(async () => {
await mockLockedGold.setAccountTotalLockedGold(
validator,
validatorLockedGoldRequirements.value
)
- // @ts-ignore bytes type
- await validators.registerValidator(publicKeysData)
})
it('should revert', async () => {
+ publicKey = await addressToPublicKey(validator, web3.eth.sign)
+ await validators.registerValidator(
+ // @ts-ignore bytes type
+ publicKey,
+ // @ts-ignore bytes type
+ blsPublicKey,
+ // @ts-ignore bytes type
+ blsPoP
+ )
await assertRevert(
validators.registerValidator(
// @ts-ignore bytes type
- publicKeysData
+ publicKey,
+ // @ts-ignore bytes type
+ blsPublicKey,
+ // @ts-ignore bytes type
+ blsPoP
)
)
})
@@ -603,10 +645,15 @@ contract('Validators', (accounts: string[]) => {
})
it('should revert', async () => {
+ const publicKey = await addressToPublicKey(validator, web3.eth.sign)
await assertRevert(
validators.registerValidator(
// @ts-ignore bytes type
- publicKeysData
+ publicKey,
+ // @ts-ignore bytes type
+ blsPublicKey,
+ // @ts-ignore bytes type
+ blsPoP
)
)
})
@@ -621,10 +668,15 @@ contract('Validators', (accounts: string[]) => {
})
it('should revert', async () => {
+ const publicKey = await addressToPublicKey(validator, web3.eth.sign)
await assertRevert(
validators.registerValidator(
// @ts-ignore bytes type
- publicKeysData
+ publicKey,
+ // @ts-ignore bytes type
+ blsPublicKey,
+ // @ts-ignore bytes type
+ blsPoP
)
)
})
@@ -750,7 +802,7 @@ contract('Validators', (accounts: string[]) => {
describe('when the account has a registered validator', () => {
beforeEach(async () => {
await registerValidator(validator)
- registrationEpoch = Math.floor((await web3.eth.getBlock('latest')).number / EPOCH)
+ registrationEpoch = Math.floor((await web3.eth.getBlockNumber()) / EPOCH)
})
describe('when affiliating with a registered validator group', () => {
beforeEach(async () => {
@@ -831,9 +883,9 @@ contract('Validators', (accounts: string[]) => {
await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS, {
from: group,
})
- additionEpoch = Math.floor((await web3.eth.getBlock('latest')).number / EPOCH)
+ additionEpoch = Math.floor((await web3.eth.getBlockNumber()) / EPOCH)
resp = await validators.affiliate(otherGroup)
- affiliationEpoch = Math.floor((await web3.eth.getBlock('latest')).number / EPOCH)
+ affiliationEpoch = Math.floor((await web3.eth.getBlockNumber()) / EPOCH)
})
it('should remove the validator from the group membership list', async () => {
@@ -931,7 +983,7 @@ contract('Validators', (accounts: string[]) => {
let registrationEpoch: number
beforeEach(async () => {
await registerValidator(validator)
- registrationEpoch = Math.floor((await web3.eth.getBlock('latest')).number / EPOCH)
+ registrationEpoch = Math.floor((await web3.eth.getBlockNumber()) / EPOCH)
await registerValidatorGroup(group)
await validators.affiliate(group)
})
@@ -961,9 +1013,9 @@ contract('Validators', (accounts: string[]) => {
let resp: any
beforeEach(async () => {
await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS, { from: group })
- additionEpoch = Math.floor((await web3.eth.getBlock('latest')).number / EPOCH)
+ additionEpoch = Math.floor((await web3.eth.getBlockNumber()) / EPOCH)
resp = await validators.deaffiliate()
- deaffiliationEpoch = Math.floor((await web3.eth.getBlock('latest')).number / EPOCH)
+ deaffiliationEpoch = Math.floor((await web3.eth.getBlockNumber()) / EPOCH)
})
it('should remove the validator from the group membership list', async () => {
@@ -1016,53 +1068,116 @@ contract('Validators', (accounts: string[]) => {
})
})
- describe('#updatePublicKeysData()', () => {
- const newPublicKey = web3.utils.randomHex(64).slice(2)
- const newBlsPublicKey = web3.utils.randomHex(48).slice(2)
- const newBlsPoP = web3.utils.randomHex(96).slice(2)
- const newPublicKeysData = '0x' + newPublicKey + newBlsPublicKey + newBlsPoP
+ describe('#updateEcdsaPublicKey()', () => {
+ describe('when called by a registered validator', () => {
+ const validator = accounts[0]
+ beforeEach(async () => {
+ await registerValidator(validator)
+ })
+
+ describe('when called by the registered `Accounts` contract', () => {
+ beforeEach(async () => {
+ await registry.setAddressFor(CeloContractName.Accounts, accounts[0])
+ })
+
+ describe('when the public key matches the signer', () => {
+ let resp: any
+ let newPublicKey: string
+ const signer = accounts[9]
+ beforeEach(async () => {
+ newPublicKey = await addressToPublicKey(signer, web3.eth.sign)
+ // @ts-ignore Broken typechain typing for bytes
+ resp = await validators.updateEcdsaPublicKey(validator, signer, newPublicKey)
+ })
+
+ it('should set the validator ecdsa public key', async () => {
+ const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
+ assert.equal(parsedValidator.ecdsaPublicKey, newPublicKey)
+ })
+
+ it('should emit the ValidatorEcdsaPublicKeyUpdated event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorEcdsaPublicKeyUpdated',
+ args: {
+ validator,
+ ecdsaPublicKey: newPublicKey,
+ },
+ })
+ })
+ })
+
+ describe('when the public key does not match the signer', () => {
+ let newPublicKey: string
+ const signer = accounts[9]
+ it('should revert', async () => {
+ newPublicKey = await addressToPublicKey(accounts[8], web3.eth.sign)
+ // @ts-ignore Broken typechain typing for bytes
+ await assertRevert(validators.updateEcdsaPublicKey(validator, signer, newPublicKey))
+ })
+ })
+ })
+
+ describe('when not called by the registered `Accounts` contract', () => {
+ describe('when the public key matches the signer', () => {
+ let newPublicKey: string
+ const signer = accounts[9]
+ it('should revert', async () => {
+ newPublicKey = await addressToPublicKey(signer, web3.eth.sign)
+ // @ts-ignore Broken typechain typing for bytes
+ await assertRevert(validators.updateEcdsaPublicKey(validator, signer, newPublicKey))
+ })
+ })
+ })
+ })
+ })
+
+ describe('#updateBlsPublicKey()', () => {
+ const newBlsPublicKey = web3.utils.randomHex(48)
+ const newBlsPoP = web3.utils.randomHex(96)
describe('when called by a registered validator', () => {
const validator = accounts[0]
beforeEach(async () => {
await registerValidator(validator)
})
- describe('when the public keys data is the right length', () => {
+ describe('when the keys are the right length', () => {
let resp: any
beforeEach(async () => {
// @ts-ignore Broken typechain typing for bytes
- resp = await validators.updatePublicKeysData(newPublicKeysData)
+ resp = await validators.updateBlsPublicKey(newBlsPublicKey, newBlsPoP)
})
- it('should set the validator public keys data', async () => {
+ it('should set the validator bls public key', async () => {
const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
- assert.equal(parsedValidator.publicKeysData, newPublicKeysData)
+ assert.equal(parsedValidator.blsPublicKey, newBlsPublicKey)
})
- it('should emit the ValidatorPublicKeysDataUpdated event', async () => {
+ it('should emit the ValidatorBlsPublicKeyUpdated event', async () => {
assert.equal(resp.logs.length, 1)
const log = resp.logs[0]
assertContainSubset(log, {
- event: 'ValidatorPublicKeysDataUpdated',
+ event: 'ValidatorBlsPublicKeyUpdated',
args: {
validator,
- publicKeysData: newPublicKeysData,
+ blsPublicKey: newBlsPublicKey,
},
})
})
})
- describe('when the public keys data is too long', () => {
+ describe('when the public key is not 48 bytes', () => {
it('should revert', async () => {
// @ts-ignore Broken typechain typing for bytes
- await assertRevert(validators.updatePublicKeysData(newPublicKeysData + '00'))
+ await assertRevert(validators.updateBlsPublicKey(newBlsPublicKey + '01', newBlsPoP))
})
})
- describe('when the public keys data is too short', () => {
+ describe('when the proof of possession is not 96 bytes', () => {
it('should revert', async () => {
// @ts-ignore Broken typechain typing for bytes
- await assertRevert(validators.updatePublicKeysData(newPublicKeysData.slice(0, -2)))
+ await assertRevert(validators.updateBlsPublicKey(newBlsPublicKey, newBlsPoP + '01'))
})
})
})
@@ -1276,15 +1391,19 @@ contract('Validators', (accounts: string[]) => {
await registerValidatorGroup(group)
})
describe('when adding a validator affiliated with the group', () => {
+ let registrationEpoch: number
beforeEach(async () => {
await registerValidator(validator)
+ registrationEpoch = Math.floor((await web3.eth.getBlockNumber()) / EPOCH)
await validators.affiliate(group, { from: validator })
})
describe('when the group meets the locked gold requirements', () => {
describe('when the validator meets the locked gold requirements', () => {
+ let additionEpoch: number
beforeEach(async () => {
resp = await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS)
+ additionEpoch = Math.floor((await web3.eth.getBlockNumber()) / EPOCH)
})
it('should add the member to the list of members', async () => {
@@ -1306,14 +1425,14 @@ contract('Validators', (accounts: string[]) => {
})
it("should update the member's membership history", async () => {
- const membershipHistory = await validators.getMembershipHistory(validator)
- const expectedEpoch = new BigNumber(
- Math.floor((await web3.eth.getBlock('latest')).number / EPOCH)
+ const expectedEntries = registrationEpoch == additionEpoch ? 1 : 2
+ const membershipHistory = parseMembershipHistory(
+ await validators.getMembershipHistory(validator)
)
- assert.equal(membershipHistory[0].length, 1)
- assertEqualBN(membershipHistory[0][0], expectedEpoch)
- assert.equal(membershipHistory[1].length, 1)
- assertSameAddress(membershipHistory[1][0], group)
+ assert.equal(membershipHistory.epochs.length, expectedEntries)
+ assertEqualBN(membershipHistory.epochs[expectedEntries - 1], additionEpoch)
+ assert.equal(membershipHistory.groups.length, expectedEntries)
+ assertSameAddress(membershipHistory.groups[expectedEntries - 1], group)
})
it('should mark the group as eligible', async () => {
@@ -1602,7 +1721,7 @@ contract('Validators', (accounts: string[]) => {
})
})
- describe('#updateValidatorScore', () => {
+ describe('#updateValidatorScoreFromSigner', () => {
const validator = accounts[0]
beforeEach(async () => {
await registerValidator(validator)
@@ -1614,7 +1733,7 @@ contract('Validators', (accounts: string[]) => {
const epochScore = uptime.pow(validatorScoreParameters.exponent)
const adjustmentSpeed = fromFixed(validatorScoreParameters.adjustmentSpeed)
beforeEach(async () => {
- await validators.updateValidatorScore(validator, toFixed(uptime))
+ await validators.updateValidatorScoreFromSigner(validator, toFixed(uptime))
})
it('should update the validator score', async () => {
@@ -1625,7 +1744,7 @@ contract('Validators', (accounts: string[]) => {
describe('when the validator already has a non-zero score', () => {
beforeEach(async () => {
- await validators.updateValidatorScore(validator, toFixed(uptime))
+ await validators.updateValidatorScoreFromSigner(validator, toFixed(uptime))
})
it('should update the validator score', async () => {
@@ -1643,18 +1762,18 @@ contract('Validators', (accounts: string[]) => {
describe('when uptime > 1.0', () => {
const uptime = 1.01
it('should revert', async () => {
- await assertRevert(validators.updateValidatorScore(validator, toFixed(uptime)))
+ await assertRevert(validators.updateValidatorScoreFromSigner(validator, toFixed(uptime)))
})
})
})
describe('#updateMembershipHistory', () => {
const validator = accounts[0]
- const groups = accounts.slice(1)
+ const groups = accounts.slice(1, -1)
let validatorRegistrationEpochNumber: number
beforeEach(async () => {
await registerValidator(validator)
- const blockNumber = (await web3.eth.getBlock('latest')).number
+ const blockNumber = await web3.eth.getBlockNumber()
validatorRegistrationEpochNumber = Math.floor(blockNumber / EPOCH)
for (const group of groups) {
await registerValidatorGroup(group)
@@ -1668,7 +1787,7 @@ contract('Validators', (accounts: string[]) => {
const expectedMembershipHistoryGroups = [NULL_ADDRESS]
const expectedMembershipHistoryEpochs = [new BigNumber(validatorRegistrationEpochNumber)]
for (let i = 0; i < numTests; i++) {
- const blockNumber = (await web3.eth.getBlock('latest')).number
+ const blockNumber = await web3.eth.getBlockNumber()
const epochNumber = Math.floor(blockNumber / EPOCH)
const blocksUntilNextEpoch = (epochNumber + 1) * EPOCH - blockNumber
await mineBlocks(blocksUntilNextEpoch, web3)
@@ -1707,7 +1826,7 @@ contract('Validators', (accounts: string[]) => {
const expectedMembershipHistoryGroups = [NULL_ADDRESS]
const expectedMembershipHistoryEpochs = [new BigNumber(validatorRegistrationEpochNumber)]
for (let i = 0; i < membershipHistoryLength.plus(1).toNumber(); i++) {
- const blockNumber = (await web3.eth.getBlock('latest')).number
+ const blockNumber = await web3.eth.getBlockNumber()
const epochNumber = Math.floor(blockNumber / EPOCH)
const blocksUntilNextEpoch = (epochNumber + 1) * EPOCH - blockNumber
await mineBlocks(blocksUntilNextEpoch, web3)
@@ -1732,7 +1851,7 @@ contract('Validators', (accounts: string[]) => {
describe('#getMembershipInLastEpoch', () => {
const validator = accounts[0]
- const groups = accounts.slice(1)
+ const groups = accounts.slice(1, -1)
beforeEach(async () => {
await registerValidator(validator)
for (const group of groups) {
@@ -1743,7 +1862,7 @@ contract('Validators', (accounts: string[]) => {
describe('when changing groups more times than membership history length', () => {
it('should always return the correct membership for the last epoch', async () => {
for (let i = 0; i < membershipHistoryLength.plus(1).toNumber(); i++) {
- const blockNumber = (await web3.eth.getBlock('latest')).number
+ const blockNumber = await web3.eth.getBlockNumber()
const epochNumber = Math.floor(blockNumber / EPOCH)
const blocksUntilNextEpoch = (epochNumber + 1) * EPOCH - blockNumber
await mineBlocks(blocksUntilNextEpoch, web3)
@@ -1835,7 +1954,7 @@ contract('Validators', (accounts: string[]) => {
})
})
- describe('#distributeEpochPayment', () => {
+ describe('#distributeEpochPaymentsFromSigner', () => {
const validator = accounts[0]
const group = accounts[1]
const maxPayment = new BigNumber(20122394876)
@@ -1844,6 +1963,10 @@ contract('Validators', (accounts: string[]) => {
await registerValidatorGroupWithMembers(group, [validator])
mockStableToken = await MockStableToken.new()
await registry.setAddressFor(CeloContractName.StableToken, mockStableToken.address)
+ // Fast-forward to the next epoch, so that the getMembershipInLastEpoch(validator) == group
+ const blockNumber = await web3.eth.getBlockNumber()
+ const epochNumber = Math.floor(blockNumber / EPOCH)
+ await mineBlocks((epochNumber + 1) * EPOCH - blockNumber, web3)
})
describe('when the validator score is non-zero', () => {
@@ -1857,14 +1980,15 @@ contract('Validators', (accounts: string[]) => {
.times(fromFixed(commission))
.dp(0, BigNumber.ROUND_FLOOR)
const expectedValidatorPayment = expectedTotalPayment.minus(expectedGroupPayment)
+
beforeEach(async () => {
- await validators.updateValidatorScore(validator, toFixed(uptime))
+ await validators.updateValidatorScoreFromSigner(validator, toFixed(uptime))
})
describe('when the validator and group meet the balance requirements', () => {
beforeEach(async () => {
- ret = await validators.distributeEpochPayment.call(validator, maxPayment)
- await validators.distributeEpochPayment(validator, maxPayment)
+ ret = await validators.distributeEpochPaymentsFromSigner.call(validator, maxPayment)
+ await validators.distributeEpochPaymentsFromSigner(validator, maxPayment)
})
it('should pay the validator', async () => {
@@ -1886,8 +2010,8 @@ contract('Validators', (accounts: string[]) => {
validator,
validatorLockedGoldRequirements.value.minus(1)
)
- ret = await validators.distributeEpochPayment.call(validator, maxPayment)
- await validators.distributeEpochPayment(validator, maxPayment)
+ ret = await validators.distributeEpochPaymentsFromSigner.call(validator, maxPayment)
+ await validators.distributeEpochPaymentsFromSigner(validator, maxPayment)
})
it('should not pay the validator', async () => {
@@ -1909,8 +2033,8 @@ contract('Validators', (accounts: string[]) => {
group,
groupLockedGoldRequirements.value.minus(1)
)
- ret = await validators.distributeEpochPayment.call(validator, maxPayment)
- await validators.distributeEpochPayment(validator, maxPayment)
+ ret = await validators.distributeEpochPaymentsFromSigner.call(validator, maxPayment)
+ await validators.distributeEpochPaymentsFromSigner(validator, maxPayment)
})
it('should not pay the validator', async () => {
diff --git a/packages/protocol/test/identity/attestations.ts b/packages/protocol/test/identity/attestations.ts
index ebed62fd29a..4add2b99b64 100644
--- a/packages/protocol/test/identity/attestations.ts
+++ b/packages/protocol/test/identity/attestations.ts
@@ -2,11 +2,11 @@ import Web3 = require('web3')
import { CeloContractName } from '@celo/protocol/lib/registry-utils'
import {
- advanceBlockNum,
assertEqualBN,
assertLogMatches2,
assertRevert,
assertSameAddress,
+ mineBlocks,
NULL_ADDRESS,
} from '@celo/protocol/lib/test-utils'
import { attestToIdentifier } from '@celo/utils'
@@ -26,6 +26,7 @@ import {
MockRandomInstance,
MockStableTokenContract,
MockStableTokenInstance,
+ MockValidatorsContract,
RegistryContract,
RegistryInstance,
TestAttestationsContract,
@@ -43,6 +44,7 @@ const Attestations: TestAttestationsContract = artifacts.require('TestAttestatio
const MockStableToken: MockStableTokenContract = artifacts.require('MockStableToken')
const MockElection: MockElectionContract = artifacts.require('MockElection')
const MockLockedGold: MockLockedGoldContract = artifacts.require('MockLockedGold')
+const MockValidators: MockValidatorsContract = artifacts.require('MockValidators')
const Random: MockRandomContract = artifacts.require('MockRandom')
const Registry: RegistryContract = artifacts.require('Registry')
@@ -131,17 +133,21 @@ contract('Attestations', (accounts: string[]) => {
accountsInstance = await Accounts.new()
mockStableToken = await MockStableToken.new()
otherMockStableToken = await MockStableToken.new()
+ const mockValidators = await MockValidators.new()
attestations = await Attestations.new()
random = await Random.new()
random.addTestRandomness(0, '0x00')
mockLockedGold = await MockLockedGold.new()
+ registry = await Registry.new()
+ await accountsInstance.initialize(registry.address)
+ await registry.setAddressFor(CeloContractName.Validators, mockValidators.address)
await Promise.all(
accounts.map(async (account) => {
await accountsInstance.createAccount({ from: account })
await unlockAndAuthorizeKey(
KeyOffsets.VALIDATING_KEY_OFFSET,
- accountsInstance.authorizeValidationSigner,
+ accountsInstance.authorizeValidatorSigner,
account
)
})
@@ -153,7 +159,6 @@ contract('Attestations', (accounts: string[]) => {
privateKeyToAddress(getDerivedKey(KeyOffsets.VALIDATING_KEY_OFFSET, account))
)
)
- registry = await Registry.new()
await registry.setAddressFor(CeloContractName.Accounts, accountsInstance.address)
await registry.setAddressFor(CeloContractName.Random, random.address)
await registry.setAddressFor(CeloContractName.Election, mockElection.address)
@@ -348,7 +353,7 @@ contract('Attestations', (accounts: string[]) => {
describe('when the original request has expired', () => {
it('should allow to request more attestations', async () => {
- await advanceBlockNum(attestationExpiryBlocks, web3)
+ await mineBlocks(attestationExpiryBlocks, web3)
await attestations.request(phoneHash, 1, mockStableToken.address)
})
})
@@ -480,7 +485,7 @@ contract('Attestations', (accounts: string[]) => {
describe('after attestationExpiryBlocks', () => {
beforeEach(async () => {
await attestations.selectIssuers(phoneHash)
- await advanceBlockNum(attestationExpiryBlocks, web3)
+ await mineBlocks(attestationExpiryBlocks, web3)
})
it('should no longer list the attestations in getCompletableAttestations', async () => {
@@ -551,7 +556,7 @@ contract('Attestations', (accounts: string[]) => {
})
it('should set the time of the successful completion', async () => {
- await advanceBlockNum(1, web3)
+ await mineBlocks(1, web3)
await attestations.complete(phoneHash, v, r, s)
const expectedBlock = await web3.eth.getBlock('latest')
@@ -647,7 +652,7 @@ contract('Attestations', (accounts: string[]) => {
})
it('does not let you verify beyond the window', async () => {
- await advanceBlockNum(attestationExpiryBlocks, web3)
+ await mineBlocks(attestationExpiryBlocks, web3)
await assertRevert(attestations.complete(phoneHash, v, r, s))
})
})
diff --git a/packages/utils/src/address.ts b/packages/utils/src/address.ts
index dcaf9fd424b..92f846ebbf1 100644
--- a/packages/utils/src/address.ts
+++ b/packages/utils/src/address.ts
@@ -3,21 +3,47 @@ import { privateToAddress, privateToPublic, pubToAddress, toChecksumAddress } fr
export type Address = string
export function eqAddress(a: Address, b: Address) {
- return a.replace('0x', '').toLowerCase() === b.replace('0x', '').toLowerCase()
+ return stripHexLeader(a).toLowerCase() === stripHexLeader(b).toLowerCase()
}
export const privateKeyToAddress = (privateKey: string) => {
return toChecksumAddress(
- '0x' + privateToAddress(Buffer.from(privateKey.slice(2), 'hex')).toString('hex')
+ ensureHexLeader(
+ privateToAddress(Buffer.from(stripHexLeader(privateKey), 'hex')).toString('hex')
+ )
+ )
+}
+
+export const privateKeyToPublicKey = (privateKey: string) => {
+ return toChecksumAddress(
+ ensureHexLeader(privateToPublic(Buffer.from(stripHexLeader(privateKey), 'hex')).toString('hex'))
)
}
export const publicKeyToAddress = (publicKey: string) => {
- return '0x' + pubToAddress(Buffer.from(publicKey.slice(2), 'hex')).toString('hex')
+ return toChecksumAddress(
+ ensureHexLeader(pubToAddress(Buffer.from(stripHexLeader(publicKey), 'hex')).toString('hex'))
+ )
}
-export const privateKeyToPublicKey = (privateKey: string) => {
- return '0x' + privateToPublic(Buffer.from(privateKey.slice(2), 'hex')).toString('hex')
+/**
+ * Strips out the leading '0x' from a hex string. Does not fail on a string that does not
+ * contain a leading '0x'
+ *
+ * @param hexString Hex string that may have '0x' prepended to it.
+ * @returns hexString with no leading '0x'.
+ */
+export function stripHexLeader(hexString: string): string {
+ return hexString.indexOf('0x') === 0 ? hexString.slice(2) : hexString
+}
+
+/**
+ * Returns a hex string with 0x prepended if it's not already starting with 0x
+ */
+export function ensureHexLeader(hexString: string): string {
+ return '0x' + stripHexLeader(hexString)
}
+export { isValidAddress } from 'ethereumjs-util'
export { toChecksumAddress } from 'ethereumjs-util'
+export { isValidChecksumAddress } from 'ethereumjs-util'
diff --git a/packages/utils/src/bls.ts b/packages/utils/src/bls.ts
index b6e3889ee36..287d69f6fe1 100644
--- a/packages/utils/src/bls.ts
+++ b/packages/utils/src/bls.ts
@@ -2,10 +2,14 @@
const keccak256 = require('keccak256')
const BigInteger = require('bigi')
const reverse = require('buffer-reverse')
+import * as bls12377js from 'bls12377js'
+import { isValidAddress } from './address'
const n = BigInteger.fromHex('12ab655e9a2ca55660b44d1e5c37b00159aa76fed00000010a11800000000001', 16)
const MODULUSMASK = 31
+export const BLS_PUBLIC_KEY_SIZE = 48
+export const BLS_POP_SIZE = 96
export const blsPrivateKeyToProcessedPrivateKey = (privateKeyHex: string) => {
for (let i = 0; i < 256; i++) {
@@ -35,3 +39,24 @@ export const blsPrivateKeyToProcessedPrivateKey = (privateKeyHex: string) => {
throw new Error("couldn't derive BLS key from ECDSA key")
}
+
+const getBlsPrivateKey = (privateKeyHex: string) => {
+ const blsPrivateKeyBytes = blsPrivateKeyToProcessedPrivateKey(privateKeyHex.slice(2))
+ return blsPrivateKeyBytes
+}
+
+export const getBlsPublicKey = (privateKeyHex: string) => {
+ const blsPrivateKeyBytes = getBlsPrivateKey(privateKeyHex)
+ return '0x' + bls12377js.BLS.privateToPublicBytes(blsPrivateKeyBytes).toString('hex')
+}
+
+export const getBlsPoP = (address: string, privateKeyHex: string) => {
+ if (!isValidAddress(address)) {
+ throw new Error('Invalid checksum address for generating BLS proof-of-possession')
+ }
+ const blsPrivateKeyBytes = getBlsPrivateKey(privateKeyHex)
+ return (
+ '0x' +
+ bls12377js.BLS.signPoP(blsPrivateKeyBytes, Buffer.from(address.slice(2), 'hex')).toString('hex')
+ )
+}
diff --git a/packages/utils/src/io.ts b/packages/utils/src/io.ts
index 2d01ee0573d..14708ff1ee8 100644
--- a/packages/utils/src/io.ts
+++ b/packages/utils/src/io.ts
@@ -1,8 +1,8 @@
import { isValidPublic, toChecksumAddress } from 'ethereumjs-util'
import { either } from 'fp-ts/lib/Either'
import * as t from 'io-ts'
+import { isValidAddress } from './address'
import { isE164NumberStrict } from './phoneNumbers'
-import { isValidAddress } from './signatureUtils'
// from http://urlregex.com/
export const URL_REGEX = new RegExp(
diff --git a/packages/utils/src/signatureUtils.ts b/packages/utils/src/signatureUtils.ts
index 177874535ed..e05f5427b7c 100644
--- a/packages/utils/src/signatureUtils.ts
+++ b/packages/utils/src/signatureUtils.ts
@@ -1,7 +1,9 @@
+import assert = require('assert')
+
const ethjsutil = require('ethereumjs-util')
import * as Web3Utils from 'web3-utils'
-import { privateKeyToAddress } from './address'
+import { eqAddress, privateKeyToAddress } from './address'
// If messages is a hex, the length of it should be the number of bytes
function messageLength(message: string) {
@@ -25,6 +27,30 @@ export interface Signer {
sign: (message: string) => Promise
}
+export async function addressToPublicKey(
+ signer: string,
+ signFn: (message: string, signer: string) => Promise
+) {
+ const msg = new Buffer('dummy_msg_data')
+ const data = '0x' + msg.toString('hex')
+ // Note: Eth.sign typing displays incorrect parameter order
+ const sig = await signFn(data, signer)
+
+ const rawsig = ethjsutil.fromRpcSig(sig)
+ const prefixedMsg = hashMessageWithPrefix(data)
+ const pubKey = ethjsutil.ecrecover(
+ Buffer.from(prefixedMsg.slice(2), 'hex'),
+ rawsig.v,
+ rawsig.r,
+ rawsig.s
+ )
+
+ const computedAddr = ethjsutil.pubToAddress(pubKey).toString('hex')
+ assert(eqAddress(computedAddr, signer), 'computed address !== signer')
+
+ return '0x' + pubKey.toString('hex')
+}
+
// Uses a native function to sign (as signFn), most commonly `web.eth.sign`
export function NativeSigner(
signFn: (message: string, signer: string) => Promise,
@@ -45,6 +71,16 @@ export function LocalSigner(privateKey: string): Signer {
}
}
+export function signedMessageToPublicKey(message: string, v: number, r: string, s: string) {
+ const pubKeyBuf = ethjsutil.ecrecover(
+ Buffer.from(message.slice(2), 'hex'),
+ v,
+ Buffer.from(r.slice(2), 'hex'),
+ Buffer.from(s.slice(2), 'hex')
+ )
+ return '0x' + pubKeyBuf.toString('hex')
+}
+
export function signMessage(message: string, privateKey: string, address: string) {
return signMessageWithoutPrefix(hashMessageWithPrefix(message), privateKey, address)
}
@@ -137,42 +173,6 @@ function isValidSignature(signer: string, message: string, v: number, r: string,
}
}
-/**
- * Strips out the leading '0x' from a hex string. Does not fail on a string that does not
- * contain a leading '0x'
- *
- * @param hexString Hex string that may have '0x' prepended to it.
- * @returns hexString with no leading '0x'.
- */
-export function stripHexLeader(hexString: string): string {
- return hexString.indexOf('0x') === 0 ? hexString.slice(2) : hexString
-}
-
-/**
- * Returns a hex string with 0x prepended if it's not already starting with 0x
- */
-export function ensureHexLeader(hexString: string): string {
- return '0x' + stripHexLeader(hexString)
-}
-
-export function isValidAddress(address: string) {
- return (
- typeof address === 'string' &&
- !ethjsutil.isZeroAddress(address) &&
- ethjsutil.isValidAddress(address)
- )
-}
-
-export function areAddressesEqual(address1: string | null, address2: string | null) {
- if (address1) {
- address1 = stripHexLeader(address1.toLowerCase())
- }
- if (address2) {
- address2 = stripHexLeader(address2.toLowerCase())
- }
- return address1 === address2
-}
-
export const SignatureUtils = {
NativeSigner,
LocalSigner,
@@ -180,9 +180,5 @@ export const SignatureUtils = {
signMessageWithoutPrefix,
parseSignature,
parseSignatureWithoutPrefix,
- stripHexLeader,
- ensureHexLeader,
serializeSignature,
- isValidAddress,
- areAddressesEqual,
}