impl spec (WIP)

This commit is contained in:
Ricardo Guilherme Schmidt 2020-01-30 11:11:52 -02:00
parent 7801ece88a
commit 73da535f65
No known key found for this signature in database
GPG Key ID: 1FD1630B93893608
4 changed files with 216 additions and 29 deletions

View File

@ -23,7 +23,7 @@ contract MultisigRecovery {
//just used offchain
mapping(address => uint256) public nonce;
//flag approvals
mapping(bytes32 => mapping(bytes32=>bool)) public approved;
mapping(bytes32 => mapping(bytes32 => bool)) public approved;
//storage for pending setup
mapping(address => RecoverySet) public pending;
//storage for active recovery
@ -50,15 +50,6 @@ contract MultisigRecovery {
{
ens = _ens;
}
/**
* @notice Cancels a pending setup to change the recovery parameters
*/
function cancelSetup()
external
{
delete pending[msg.sender];
emit SetupRequested(msg.sender, 0);
}
/**
* @notice Configure recovery parameters of `msg.sender`. `emit Activated(msg.sender)` if there was no previous setup, or `emit SetupRequested(msg.sender, now()+setupDelay)` when reconfiguring.
@ -100,6 +91,16 @@ contract MultisigRecovery {
emit Activated(_who);
}
/**
* @notice Cancels a pending setup to change the recovery parameters
*/
function cancelSetup()
external
{
delete pending[msg.sender];
emit SetupRequested(msg.sender, 0);
}
/**
* @notice Approves a recovery. This method is important for when the address is an contract and dont implements EIP1271.
* @param _approveHash Hash of the recovery call
@ -214,7 +215,7 @@ contract MultisigRecovery {
),
"Invalid ENS entry"
);
bytes32 leaf = keccak256(abi.encodePacked(isENS, isENS ? _ensNode : bytes32(uint256(_signer))));
bytes32 leaf = keccak256(isENS ? abi.encodePacked(byte(0x01), _ensNode) : abi.encodePacked(byte(0x00), bytes32(uint256(_signer))));
approved[leaf][_approveHash] = true;
emit Approved(_approveHash, leaf);
}

View File

@ -30,6 +30,7 @@ config({
});
contract('MultiMerkleProof', function () {
describe('calculateMultiMerkleRoot', function () {
it('display cost of deploy MerkleMultiProofWrapper', async function () {
await MerkleMultiProofWrapper.methods.foo().send();
@ -96,8 +97,25 @@ contract('MultiMerkleProof', function () {
}
assert(invalid);
});
it(`cost of calculate Tree A root for ${leafsA.length} leafs using ${proofA.length} proofs and ${flagsA.length} flags`, async function () {
await MerkleMultiProofWrapper.methods.calculateMultiMerkleRoot(
leafsA,
proofA,
flagsA,
).send()
});
it('calculate merkle root from leafs, proofs and flags (fuzzy)', async function () {
it(`cost of calculate Tree B root for ${leafsB.length} leafs using ${proofB.length} proofs and ${flagsB.length} flags`, async function () {
await MerkleMultiProofWrapper.methods.calculateMultiMerkleRoot(
leafsB,
proofB,
flagsB,
).send()
});
it(`calculate ${fuzzyProofChecks} merkle root from leafs, proofs and flags (fuzzy)`, async function () {
this.timeout(500*fuzzyProofChecks);
for(let j = 0; j < fuzzyProofChecks; j++){
const leafsFuzzy = merkleTreeA.getElements(
Array.from({length: leafsSize}, () => elementsA[Math.floor(Math.random()*elementsA.length)] ).filter((value, index, self) => self.indexOf(value) === index)
@ -113,7 +131,8 @@ contract('MultiMerkleProof', function () {
}
});
it('return wrong root for invalid Merkle proof (fuzzy)', async function () {
it(`calculate ${fuzzyProofChecks} wrong merkle root from wrong proofs and flags (fuzzy)`, async function () {
this.timeout(500*fuzzyProofChecks);
for(let j = 0; j < fuzzyProofChecks; j++){
const leafsFuzzy = merkleTreeB.getElements(
Array.from({length: leafsSize}, () => elementsB[Math.floor(Math.random()*elementsB.length)] ).filter((value, index, self) => self.indexOf(value) === index)
@ -133,21 +152,5 @@ contract('MultiMerkleProof', function () {
assert(invalid);
}
});
it(`cost of calculate Tree A root for ${leafsA.length} leafs using ${proofA.length} proofs and ${flagsA.length} flags`, async function () {
await MerkleMultiProofWrapper.methods.calculateMultiMerkleRoot(
leafsA,
proofA,
flagsA,
).send()
});
it(`cost of calculate Tree B root for ${leafsB.length} leafs using ${proofB.length} proofs and ${flagsB.length} flags`, async function () {
await MerkleMultiProofWrapper.methods.calculateMultiMerkleRoot(
leafsB,
proofB,
flagsB,
).send()
});
});
});

View File

@ -0,0 +1,113 @@
const { SecretMultisig } = require('../utils/secretMultisig.js');
const EmbarkJS = require('Embark/EmbarkJS');
const MultisigRecovery = require('Embark/contracts/MultisigRecovery')
let accounts;
let ms;
config({
blockchain: {
accounts: [
{
mnemonic: "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat",
addressIndex: "0",
numAddresses: "102",
balance: "5 ether"
}
]
},
namesystem: {
enabled: true,
register: {
rootDomain: "eth",
subdomains: {
'test': '0x627306090abaB3A6e1400e9345bC60c78a8BEf57'
}
}
},
contracts: {
deploy: {
"MultisigRecovery": {
args: [ "$ENSRegistry" ]
}
}
}
}, (_err, web3_accounts) => {
accounts = web3_accounts;
ms = new SecretMultisig(accounts[0], "0x0011223344556677889900112233445566778899001122334455667788990011", accounts.slice(1));
});
contract('SecretMultisigRecovery', function () {
describe('setup', function () {
it('first time activates immediately', async function () {
});
it('pending setup', async function () {
});
});
describe('activate', function () {
it('does not activate during delay time', async function () {
});
it('activates a pending setup', async function () {
});
});
describe('cancelSetup', function () {
it('cancels when not reached', async function () {
});
it('does not cancel when reached', async function () {
});
});
describe('approve', function () {
it('using address', async function () {
});
it('using ENS', async function () {
let address = await EmbarkJS.Names.resolve('test.eth')
console.log('ENS address', address);
});
});
describe('approvePreSigned', function () {
it('using address', async function () {
});
it('using ENS', async function () {
let address = await EmbarkJS.Names.resolve('test.eth')
console.log('ENS address', address);
});
});
describe('execute', function () {
it('executes approved', async function () {
});
it('cant execute with low threshold', async function () {
});
it('cant execute with different calldest', async function () {
});
it('cant execute with different calldata', async function () {
});
});
});

70
utils/secretMultisig.js Normal file
View File

@ -0,0 +1,70 @@
const { MerkleTree } = require('./merkleTree.js');
const { keccak256, bufferToHex, isValidAddress, setLengthLeft } = require('ethereumjs-util');
const namehash = require('eth-ens-namehash');
const MINIMUM_LIST_SIZE = 4096;
const THRESHOLD = 100 * 10**18;
const RECOVERY_ADDRESS = "0x2429242924292429242924292429242924292429";
const ERC2429 = require('Embark/contracts/MultisigRecovery');
export default class SecretMultisig {
constructor(userAddress, privateHash, addressList) {
if (addressList.length == 0){
throw new Error("Invalid Address List")
}
this.elements = addressList.map((v) => this.hashLeaf(v.address, v.weight));
if(this.elements.length < MINIMUM_LIST_SIZE) {
this.elements.push(... Array.from({length: MINIMUM_LIST_SIZE-this.elements.length}, (v,k) => hashFakeLeaf(privateHash, k)))
}
this.merkleTree = new MerkleTree(this.elements);
this.executeHash = this.hashExecute(privateHash, userAddress);
this.partialReveal = keccak256(this.executeHash);
this.publicHash = keccak256(Buffer.concat(
this.partialReveal,
keccak256(this.merkleTree.getRoot())
));
}
hashExecute = async (privateHash, userAddress) => keccak256(Buffer.concat(
privateHash,
Buffer,from(ERC2429.address, 'hex'),
setLengthLeft(Buffer.from(Number.toString(await ERC2429.methods.nonce(userAddress).call(), 16), 'hex'), 32)
));
hashLeaf = (ethereumAddress, weight) => keccak256(Buffer.concat(
this.hashAddress(ethereumAddress),
setLengthLeft(Buffer.from(Number.toString(weight, 16), 'hex'), 32)
));
hashFakeLeaf = (privateHash, position) => keccak256(Buffer.concat(
privateHash,
setLengthLeft(Buffer.from(Number.toString(position, 16), 'hex'), 32)
));
hashAddress = (ethereumAddress) => keccak256(
isValidAddress(ethereumAddress) ? Buffer.concat(
Buffer.from('0x00', 'hex'),
setLengthLeft(Buffer.from(ethereumAddress, 'hex'), 32)
) : Buffer.concat(
Buffer.from('0x01', 'hex'),
namehash(ethereumAddress)
)
);
hashApproval = (approver_address, calldest, calldata) => keccak256(Buffer.concat(
this.hashAddress(approver_address),
this.hashCall(calldest, calldata)
));
hashCall = (calldest, calldata) => keccak256(Buffer.concat(
this.partialReveal,
Buffer.from(calldest, 'hex'),
Buffer.from(calldata, 'hex')
));
}