use block hash instead of nonce

This commit is contained in:
Michele Balistreri 2019-12-13 13:09:59 +01:00
parent a282bc9d13
commit 111a3c4dff
No known key found for this signature in database
GPG Key ID: E9567DA33A4F791A
3 changed files with 139 additions and 43 deletions

View File

@ -3,31 +3,38 @@ pragma experimental ABIEncoderV2;
contract KeycardWallet {
event TopUp(address from, uint256 value);
event NewPaymentRequest(uint256 nonce, address to, uint256 value);
event NewPaymentRequest(uint256 blockNumber, address to, uint256 amount);
event NewWithdrawal(address to, uint256 value);
//TODO: replace with chainid opcode
uint256 constant chainId = 1;
// must be less than 256, because the hash of older blocks cannot be retrieved
uint256 constant maxTxDelayInBlocks = 10;
struct Payment {
uint256 nonce;
uint256 blockNumber;
bytes32 blockHash;
uint256 amount;
address to;
}
bytes32 constant EIP712DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
bytes32 constant PAYMENT_TYPEHASH = keccak256("Payment(uint256 nonce,uint256 amount,address to)");
bytes32 constant PAYMENT_TYPEHASH = keccak256("Payment(uint256 blockNumber,bytes32 blockHash,uint256 amount,address to)");
bytes32 DOMAIN_SEPARATOR;
address public factory;
address public owner;
bytes3 public name;
address public keycard;
uint256 public nonce;
Settings public settings;
mapping(address => uint) public pendingWithdrawals;
uint256 public totalPendingWithdrawals;
uint256 public lastUsedBlockNum;
struct Settings {
uint256 maxTxValue;
uint256 minBlockDistance;
}
modifier onlyOwner() {
@ -40,21 +47,23 @@ contract KeycardWallet {
emit TopUp(msg.sender, msg.value);
}
constructor(bytes3 _name, address _keycard, uint256 _maxTxValue) public {
constructor(bytes3 _name, address _keycard, uint256 _maxTxValue, address _factory) public {
DOMAIN_SEPARATOR = keccak256(abi.encode(
EIP712DOMAIN_TYPEHASH,
keccak256("KeycardWallet"),
keccak256("1"),
chainId,
address(this)
_factory
));
owner = msg.sender;
name = _name;
keycard = _keycard;
factory = _factory;
settings.maxTxValue = _maxTxValue;
nonce = 0;
settings.minBlockDistance = 1;
totalPendingWithdrawals = 0;
lastUsedBlockNum = block.number;
}
function setKeycard(address _keycard) public onlyOwner {
@ -68,7 +77,8 @@ contract KeycardWallet {
function hash(Payment memory _payment) internal pure returns (bytes32) {
return keccak256(abi.encode(
PAYMENT_TYPEHASH,
_payment.nonce,
_payment.blockNumber,
_payment.blockHash,
_payment.amount,
_payment.to
));
@ -109,8 +119,17 @@ contract KeycardWallet {
// verify the signer
require(verify(_payment, _signature), "signer is not the keycard");
// check that the nonce is valid
require(nonce == _payment.nonce, "invalid nonce");
// check that the block number used for signing is less than the block number
require(_payment.blockNumber < block.number, "transaction cannot be in the future");
// check that the block number used is not too old
require(_payment.blockNumber >= (block.number - maxTxDelayInBlocks), "transaction too old");
// check that the block number is not too near to the last one in which a tx has been processed
require(_payment.blockNumber >= (lastUsedBlockNum + settings.minBlockDistance), "cooldown period not expired yet");
// check that the blockHash is valid
require(_payment.blockHash == blockhash(_payment.blockNumber), "invalid block hash");
// check that _payment.amount is not greater than settings.maxTxValue
require(_payment.amount <= settings.maxTxValue, "amount not allowed");
@ -119,14 +138,14 @@ contract KeycardWallet {
// check that balance is enough for this payment
require(availableBalance >= 0, "balance is not enough");
// increment nonce
nonce++;
// set new baseline block for checks
lastUsedBlockNum = block.number;
// add pendingWithdrawal
totalPendingWithdrawals += _payment.amount;
pendingWithdrawals[_payment.to] += _payment.amount;
emit NewPaymentRequest(_payment.nonce, _payment.to, _payment.amount);
emit NewPaymentRequest(_payment.blockNumber, _payment.to, _payment.amount);
}
function withdraw() public {

View File

@ -12,7 +12,7 @@ contract KeycardWalletFactory {
);
function create(bytes3 name, address keycard, uint256 maxTxValue) public {
KeycardWallet wallet = new KeycardWallet(name, keycard, maxTxValue);
KeycardWallet wallet = new KeycardWallet(name, keycard, maxTxValue, address(this));
ownersWallets[msg.sender].push(address(wallet));
keycardsWallets[keycard] = address(wallet);
emit NewWallet(wallet, name);

View File

@ -20,7 +20,8 @@ async function signPaymentRequest(signer, message) {
];
let payment = [
{ name: "nonce", type: "uint256" },
{ name: "blockNumber", type: "uint256" },
{ name: "blockHash", type: "bytes32" },
{ name: "amount", type: "uint256" },
{ name: "to", type: "address" }
];
@ -29,7 +30,7 @@ async function signPaymentRequest(signer, message) {
name: "KeycardWallet",
version: "1",
chainId: 1,
verifyingContract: KeycardWallet.address
verifyingContract: "0x0000000000000000000000000000000000000001"
};
let data = {
@ -51,7 +52,7 @@ let owner,
config({
contracts: {
KeycardWallet: {
args: ["0x000000", "0x0000000000000000000000000000000000000000", 0]
args: ["0x000000", "0x0000000000000000000000000000000000000000", 0, "0x0000000000000000000000000000000000000001"]
}
}
}, (err, _accounts) => {
@ -99,7 +100,8 @@ contract('KeycardWallet', () => {
});
it('requestPayment without setting a keycard address', async () => {
const requestPayment = KeycardWallet.methods.requestPayment({nonce: 0, amount: 0, to: merchant}, "0x00");
const block = await web3.eth.getBlock("latest");
const requestPayment = KeycardWallet.methods.requestPayment({blockNumber: block.number, blockHash: block.hash, amount: 0, to: merchant}, "0x00");
try {
const estimatedGas = await requestPayment.estimateGas();
await requestPayment.send({
@ -135,11 +137,11 @@ contract('KeycardWallet', () => {
});
it('requestPayment with bad signature', async () => {
const nonce = await KeycardWallet.methods.nonce().call();
const block = await web3.eth.getBlock("latest");
const to = merchant;
const value = 10;
const message = {nonce: nonce, amount: value, to: to};
const message = {blockNumber: block.number, blockHash: block.hash, amount: value, to: to};
// message is signed by the merchant
const sig = await signPaymentRequest(merchant, message)
const requestPayment = KeycardWallet.methods.requestPayment(message, sig);
@ -159,15 +161,13 @@ contract('KeycardWallet', () => {
assert.equal(pendingWithdrawal, 0);
});
it('requestPayment with bad nonce', async () => {
let nonce = await KeycardWallet.methods.nonce().call();
// increment nonce making it invalid
nonce++;
it('requestPayment with block in the future', async () => {
const block = await web3.eth.getBlock("latest");
const to = merchant;
const value = 10;
const message = {nonce: nonce, amount: value, to: to};
const message = {blockNumber: block.number + 1, blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000", amount: value, to: to};
const sig = await signPaymentRequest(keycard, message)
const requestPayment = KeycardWallet.methods.requestPayment(message, sig);
@ -179,7 +179,32 @@ contract('KeycardWallet', () => {
});
assert.fail("requestPayment should have failed");
} catch (err) {
assert.equal(getErrorReason(err), "invalid nonce");
assert.equal(getErrorReason(err), "transaction cannot be in the future");
}
const pendingWithdrawal = await KeycardWallet.methods.pendingWithdrawals(merchant).call();
assert.equal(pendingWithdrawal, 0);
});
it('requestPayment with wrong block hash', async () => {
const block = await web3.eth.getBlock("latest");
const to = merchant;
const value = 10;
const message = {blockNumber: block.number, blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000", amount: value, to: to};
const sig = await signPaymentRequest(keycard, message)
const requestPayment = KeycardWallet.methods.requestPayment(message, sig);
try {
const estimatedGas = await requestPayment.estimateGas();
await requestPayment.send({
from: merchant,
gas: estimatedGas
});
assert.fail("requestPayment should have failed");
} catch (err) {
assert.equal(getErrorReason(err), "invalid block hash");
}
const pendingWithdrawal = await KeycardWallet.methods.pendingWithdrawals(merchant).call();
@ -187,13 +212,13 @@ contract('KeycardWallet', () => {
});
it('requestPayment with params different from signed params', async () => {
const nonce = await KeycardWallet.methods.nonce().call();
const block = await web3.eth.getBlock("latest");
const to = merchant;
const value = 10;
const badMessage = {nonce: nonce, amount: value + 1, to: to};
const badMessage = {blockNumber: block.number, blockHash: block.hash, amount: value + 1, to: to};
const message = {nonce: nonce, amount: value, to: to};
const message = {blockNumber: block.number, blockHash: block.hash, amount: value, to: to};
const sig = await signPaymentRequest(keycard, message)
const requestPayment = KeycardWallet.methods.requestPayment(badMessage, sig);
@ -227,7 +252,7 @@ contract('KeycardWallet', () => {
it('setSettings called by the owner', async () => {
const settingsBefore = await KeycardWallet.methods.settings().call();
assert.equal(settingsBefore, 0);
assert.equal(settingsBefore.maxTxValue, 0);
const setSettings = KeycardWallet.methods.setSettings(999);
await setSettings.send({
@ -235,15 +260,15 @@ contract('KeycardWallet', () => {
});
const currentSettings = await KeycardWallet.methods.settings().call();
assert.equal(currentSettings, 999);
assert.equal(currentSettings.maxTxValue, 999);
});
it('requestPayment with value greater than maxTxValue', async () => {
const nonce = await KeycardWallet.methods.nonce().call();
const block = await web3.eth.getBlock("latest");
const to = merchant;
const value = 1000;
const message = {nonce: nonce, amount: value, to: to};
const message = {blockNumber: block.number, blockHash: block.hash, amount: value, to: to};
const sig = await signPaymentRequest(keycard, message)
const requestPayment = KeycardWallet.methods.requestPayment(message, sig);
@ -260,11 +285,11 @@ contract('KeycardWallet', () => {
});
it('requestPayment with value greater than balance', async () => {
const nonce = await KeycardWallet.methods.nonce().call();
const block = await web3.eth.getBlock("latest");
const to = merchant;
const value = 101;
const message = {nonce: nonce, amount: value, to: to};
const message = {blockNumber: block.number, blockHash: block.hash, amount: value, to: to};
const sig = await signPaymentRequest(keycard, message)
const requestPayment = KeycardWallet.methods.requestPayment(message, sig);
@ -281,11 +306,11 @@ contract('KeycardWallet', () => {
});
it('requestPayment', async () => {
const nonce = await KeycardWallet.methods.nonce().call();
const block = await web3.eth.getBlock("latest");
const to = merchant;
const value = 10;
const message = {nonce: nonce, amount: value, to: to};
const message = {blockNumber: block.number, blockHash: block.hash, amount: value, to: to};
const sig = await signPaymentRequest(keycard, message)
const requestPayment = KeycardWallet.methods.requestPayment(message, sig);
@ -296,9 +321,9 @@ contract('KeycardWallet', () => {
});
const event = receipt.events.NewPaymentRequest;
assert.equal(event.returnValues.nonce, nonce);
assert.equal(event.returnValues.to, merchant);
assert.equal(event.returnValues.value, value);
assert.equal(event.returnValues.blockNumber, block.number);
assert.equal(event.returnValues.to, to);
assert.equal(event.returnValues.amount, value);
const pendingWithdrawal = await KeycardWallet.methods.pendingWithdrawals(merchant).call();
assert.equal(pendingWithdrawal, value);
@ -307,8 +332,34 @@ contract('KeycardWallet', () => {
assert.equal(totalPendingWithdrawal, value);
});
it('requestPayment without waiting for cooldown', async () => {
const block = await web3.eth.getBlock("latest");
const to = merchant;
const value = 1;
const message = {blockNumber: block.number, blockHash: block.hash, amount: value, to: to};
const sig = await signPaymentRequest(keycard, message)
const requestPayment = KeycardWallet.methods.requestPayment(message, sig);
try {
const estimatedGas = await requestPayment.estimateGas();
const receipt = await requestPayment.send({
from: merchant,
gas: estimatedGas
});
assert.fail("requestPayment should have failed");
} catch (err) {
assert.equal(getErrorReason(err), "cooldown period not expired yet");
}
// skip a block for next test
const setSettings = KeycardWallet.methods.setSettings(999);
await setSettings.send({from: owner});
});
it('requestPayment with value greater than available balance', async () => {
const nonce = await KeycardWallet.methods.nonce().call();
const block = await web3.eth.getBlock("latest");
const to = merchant;
const value = 100;
@ -318,7 +369,7 @@ contract('KeycardWallet', () => {
const totalPendingWithdrawal = await KeycardWallet.methods.totalPendingWithdrawals().call();
assert.equal(totalPendingWithdrawal, 10);
const message = {nonce: nonce, amount: value, to: to};
const message = {blockNumber: block.number, blockHash: block.hash, amount: value, to: to};
const sig = await signPaymentRequest(keycard, message)
const requestPayment = KeycardWallet.methods.requestPayment(message, sig);
@ -335,8 +386,34 @@ contract('KeycardWallet', () => {
}
});
it('requestPayment with old block', async () => {
const currentBlock = await web3.eth.getBlock("latest");
const block = await web3.eth.getBlock(currentBlock.number - 10);
const to = merchant;
const value = 1;
const message = {blockNumber: block.number, blockHash: block.hash, amount: value, to: to};
const sig = await signPaymentRequest(keycard, message)
const requestPayment = KeycardWallet.methods.requestPayment(message, sig);
try {
const estimatedGas = await requestPayment.estimateGas();
const receipt = await requestPayment.send({
from: merchant,
gas: estimatedGas
});
assert.fail("requestPayment should have failed");
} catch (err) {
assert.equal(getErrorReason(err), "transaction too old");
}
// skip a block for next test
const setSettings = KeycardWallet.methods.setSettings(999);
await setSettings.send({from: owner});
});
it('withdraw from address without pendingWithdrawal', async () => {
const withdrawalValue = 1;
const pendingWithdrawalBefore = await KeycardWallet.methods.pendingWithdrawals(thief).call();
assert.equal(pendingWithdrawalBefore, 0);