New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Crypto-economic Aggregate Signatures #34
Closed
Closed
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,7 +65,7 @@ contract ParsecBridge { | |
uint32 parentIndex; // the position of this node in the Parent's children list | ||
uint8 slot; | ||
uint32 timestamp; | ||
uint64 reward; | ||
uint256 sigs; | ||
bytes32[] children; // unordered list of children below this node | ||
} | ||
mapping(bytes32 => Period) public periods; | ||
|
@@ -86,6 +86,14 @@ contract ParsecBridge { | |
} | ||
mapping(bytes32 => Exit) public exits; | ||
|
||
struct Challenge { | ||
uint64 stake; | ||
address signer; | ||
uint32 finalizationEpoch; | ||
} | ||
|
||
mapping(bytes32 => mapping(uint256 => Challenge)) challenges; | ||
|
||
|
||
constructor(uint256 _epochLength, uint256 _maxReward, uint256 _parentBlockInterval, uint256 _exitDuration) public { | ||
// init genesis preiod | ||
|
@@ -256,13 +264,21 @@ contract ParsecBridge { | |
averageGasPrice = averageGasPrice - (averageGasPrice / 15) + (tx.gasprice / 15); | ||
} | ||
|
||
function submitPeriod(uint256 _slotId, bytes32 _prevHash, bytes32 _root) public { | ||
function countSigs(uint256 _sigs, uint256 _epochLength) internal pure returns (uint256 count) { | ||
for (uint i = 256; i >= 256 - _epochLength; i--) { | ||
count += uint8(_sigs >> i) & 0x01; | ||
} | ||
} | ||
|
||
function submitPeriod(uint256 _slotId, bytes32 _prevHash, bytes32 _root, uint256 _sigs) public { | ||
// check parent node exists | ||
require(periods[_prevHash].parent > 0); | ||
// check that same root not submitted yet | ||
require(periods[_root].height == 0); | ||
// check slot | ||
require(_slotId < epochLength); | ||
// count sigs | ||
require(countSigs(_sigs, epochLength) > epochLength.mul(2).div(3)); | ||
Slot storage slot = slots[_slotId]; | ||
require(slot.signer == msg.sender); | ||
if (slot.activationEpoch > 0) { | ||
|
@@ -285,6 +301,7 @@ contract ParsecBridge { | |
// store the period | ||
Period memory newPeriod; | ||
newPeriod.parent = _prevHash; | ||
newPeriod.sigs = _sigs; | ||
newPeriod.height = uint32(newHeight); | ||
newPeriod.slot = uint8(_slotId); | ||
newPeriod.timestamp = uint32(block.timestamp); | ||
|
@@ -310,6 +327,93 @@ contract ParsecBridge { | |
} | ||
} | ||
|
||
function getChallenge(bytes32 _period, uint256 _slotId) public constant returns (uint256, address, uint256) { | ||
return (challenges[_period][_slotId].stake, challenges[_period][_slotId].signer, challenges[_period][_slotId].finalizationEpoch); | ||
|
||
} | ||
|
||
function challengeSig(bytes32 _period, uint256 _slotId) public { | ||
require(_slotId < epochLength); | ||
// check that sig was actually 1 | ||
require(uint8(periods[_period].sigs >> _slotId) & 0x01 == 1); | ||
// check that challenge doesn't exist yet | ||
require(challenges[_period][_slotId].signer == 0x0); | ||
// check that challenge submitted for previous epoche, and current epoch has not progressed more than 1/3 | ||
uint256 finalizationEpoch; | ||
if (periods[_period].height <= lastEpochBlockHeight) { | ||
// check that challenge not older than 4/3 epoch | ||
require(periods[tipHash].height - periods[_period].height < epochLength.mul(32).mul(4).div(3)); | ||
// check that current epoch not older than 1/3 epochLength | ||
require(periods[tipHash].height - lastEpochBlockHeight < epochLength.mul(32).div(3)); | ||
finalizationEpoch = lastCompleteEpoch; | ||
} else { | ||
// challenge submitted for current epoch, nothing to check | ||
finalizationEpoch = lastCompleteEpoch + 1; | ||
} | ||
// transfer funds for bond | ||
tokens[0].addr.transferFrom(msg.sender, this, 100); | ||
// create challenge object | ||
challenges[_period][_slotId] = Challenge(100, msg.sender, uint32(finalizationEpoch)); | ||
} | ||
|
||
function answerSig(bytes32 _period, uint256 _slotId, uint8 _v, bytes32 _r, bytes32 _s) public { | ||
// check that challenge does exist | ||
require(challenges[_period][_slotId].stake > 0); | ||
// check signature | ||
require(ecrecover(_period, _v, _r, _s) == slots[_slotId].signer); | ||
// slash challenger | ||
slots[_slotId].stake += challenges[_period][_slotId].stake / 2; | ||
// delete challenge | ||
delete challenges[_period][_slotId]; | ||
} | ||
|
||
function slashSig(bytes32 _period, uint256 _slotId) public { | ||
Challenge memory challenge = challenges[_period][_slotId]; | ||
// check that challenge does exist | ||
require(challenge.stake > 0); | ||
// check that we are in next epoch | ||
require(periods[_period].height <= lastEpochBlockHeight); | ||
// check that 2/3 of next epoch passed | ||
require(periods[tipHash].height - lastEpochBlockHeight >= epochLength.mul(32).mul(2).div(3)); | ||
// check that challenge not older than 2 epochs | ||
require(periods[tipHash].height - periods[_period].height < epochLength.mul(32).mul(2)); | ||
// slash validator | ||
slash(periods[_period].slot, challenge.stake); | ||
// pay out challenger | ||
tokens[0].addr.transfer(challenge.signer, challenge.stake / 2); | ||
// TODO burn rest of tokens in total supply | ||
// delete challenge | ||
delete challenges[_period][_slotId]; | ||
} | ||
|
||
function slashDoubleSig(uint256 _slotId, bytes32 _root1, uint8 _v1, bytes32 _r1, bytes32 _s1, bytes32 _root2, uint8 _v2, bytes32 _r2, bytes32 _s2) public { | ||
// check roots are different | ||
require(_root1 != _root2); | ||
// check _roots exist and have same height | ||
require(periods[_root1].height == periods[_root2].height); | ||
// check that signer has slot | ||
address signer = ecrecover(_root1, _v1, _r1, _s1); | ||
require(slots[_slotId].signer == signer); | ||
// check that second signature also mathches signer | ||
require(ecrecover(_root2, _v2, _r2, _s2) == signer); | ||
// slash signer | ||
slash(_slotId, 100); | ||
} | ||
|
||
function slash(uint256 _slotId, uint256 _value) public { | ||
require(_slotId < epochLength); | ||
Slot storage slot = slots[_slotId]; | ||
require(slot.stake > 0); | ||
uint256 prevStake = slot.stake; | ||
slot.stake = (_value >= slot.stake) ? 0 : slot.stake - uint64(_value); | ||
// if slot became empty by slashing | ||
if (prevStake > 0 && slot.stake == 0) { | ||
//emit ValidatorLeave(slot.signer, _slotId, slot.tendermint, lastCompleteEpoch + 1); | ||
slot.activationEpoch = 0; | ||
// activate next guy | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe emit Slash event here? |
||
|
||
function deletePeriod(bytes32 hash) internal { | ||
Period storage parent = periods[periods[hash].parent]; | ||
uint256 i = periods[hash].parentIndex; | ||
|
@@ -336,7 +440,6 @@ contract ParsecBridge { | |
signer = address(_txData[2]); | ||
} | ||
|
||
|
||
/* | ||
* _txData = [ 32b periodHash, (1b Proofoffset, 8b pos, ..00.., 1b txData), 32b txData, 32b proof, 32b proof ] | ||
* | ||
|
@@ -408,29 +511,6 @@ contract ParsecBridge { | |
slash(p.slot, 50); | ||
} | ||
|
||
function slash(uint256 _slotId, uint256 _value) public { | ||
require(_slotId < epochLength); | ||
Slot storage slot = slots[_slotId]; | ||
require(slot.stake > 0); | ||
uint256 prevStake = slot.stake; | ||
slot.stake = (_value >= slot.stake) ? 0 : slot.stake - uint64(_value); | ||
// if slot became empty by slashing | ||
if (prevStake > 0 && slot.stake == 0) { | ||
emit ValidatorLeave(slot.signer, _slotId, slot.tendermint, lastCompleteEpoch + 1); | ||
slot.activationEpoch = 0; | ||
if (slot.newStake > 0) { | ||
// someone in queue | ||
activate(_slotId); | ||
} else { | ||
// clean out account | ||
slot.owner = 0; | ||
slot.signer = 0; | ||
slot.tendermint = 0x0; | ||
slot.stake = 0; | ||
} | ||
} | ||
} | ||
|
||
/* | ||
* Add funds | ||
*/ | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tokens come out of thin air?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what happens to the second half of the slashed stake?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
second half should be burned. so that the slash function is not used as a function to unstake faster than allowed.