mirror of https://github.com/nucypher/nucypher.git
Merge pull request #507 from nucypher/vodka
VODKA: Slashing for incorrect re-encryptionpull/811/head
commit
ec6b754ad8
3
Pipfile
3
Pipfile
|
@ -21,8 +21,9 @@ sqlalchemy = "*"
|
|||
maya = "*"
|
||||
flask = "*"
|
||||
# Third-Party Ethereum
|
||||
py-evm = "*"
|
||||
py-evm = "==0.2.0a39"
|
||||
eth-tester = "*"
|
||||
coincurve = "*"
|
||||
web3 = "*"
|
||||
# CLI / Configuration
|
||||
sentry-sdk = "==0.5.2"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,58 +1,109 @@
|
|||
-i https://pypi.python.org/simple
|
||||
-e .
|
||||
-e git+https://github.com/nucypher/py-solc.git@391b8da1a6bac5816877197bda25527c6b0b8c15#egg=py-solc
|
||||
alabaster==0.7.12
|
||||
ansible==2.7.7
|
||||
apipkg==1.5
|
||||
appdirs==1.4.3
|
||||
argh==0.26.2
|
||||
asn1crypto==0.24.0
|
||||
atomicwrites==1.3.0
|
||||
attrdict==2.0.1
|
||||
attrs==18.2.0
|
||||
autobahn==19.2.1
|
||||
automat==0.7.0
|
||||
aws-xray-sdk==0.95
|
||||
babel==2.6.0
|
||||
bcrypt==3.1.6
|
||||
boto3==1.9.91
|
||||
boto3==1.9.96
|
||||
boto==2.49.0
|
||||
botocore==1.12.91
|
||||
botocore==1.12.96
|
||||
bumpversion==0.5.3
|
||||
bytestring-splitter==1.0.0a4
|
||||
certifi==2018.11.29
|
||||
cffi==1.11.5
|
||||
cffi==1.12.0
|
||||
chardet==3.0.4
|
||||
click==7.0
|
||||
coincurve==11.0.0
|
||||
colorama==0.4.1
|
||||
commonmark==0.8.1
|
||||
constant-sorrow==0.1.0a8
|
||||
constantly==15.1.0
|
||||
coverage==4.0.3
|
||||
cryptography==2.5
|
||||
cytoolz==0.9.0.1 ; implementation_name == 'cpython'
|
||||
dateparser==0.7.1
|
||||
decorator==4.3.2
|
||||
docker-pycreds==0.4.0
|
||||
docker==3.7.0
|
||||
docutils==0.14
|
||||
ecdsa==0.13
|
||||
eth-abi==2.0.0b5
|
||||
eth-account==0.3.0
|
||||
eth-bloom==1.0.3
|
||||
eth-hash[pycryptodome,pysha3]==0.2.0
|
||||
eth-keyfile==0.5.1
|
||||
eth-keys==0.2.1
|
||||
eth-rlp==0.1.2
|
||||
eth-tester==0.1.0b37
|
||||
eth-typing==2.0.0
|
||||
eth-utils==1.4.1
|
||||
ethpm==0.1.4a12
|
||||
execnet==1.5.0
|
||||
flask==1.0.2
|
||||
future==0.17.1
|
||||
-e git://github.com/nucypher/py-solc.git@v5.0.0-eol.0#egg=py-solc
|
||||
greenlet==0.4.15
|
||||
hendrix==3.2.2
|
||||
hexbytes==0.1.0
|
||||
humanize==0.5.1
|
||||
hyperlink==18.0.0
|
||||
idna==2.8
|
||||
imagesize==1.1.0
|
||||
incremental==17.5.0
|
||||
ipfsapi==0.4.3
|
||||
itsdangerous==1.1.0
|
||||
jinja2==2.10
|
||||
jmespath==0.9.3
|
||||
jsondiff==1.1.1
|
||||
jsonpickle==1.1
|
||||
jsonschema==2.6.0
|
||||
lru-dict==1.1.6
|
||||
markupsafe==1.1.0
|
||||
maya==0.6.1
|
||||
mock==2.0.0
|
||||
more-itertools==5.0.0
|
||||
more-itertools==6.0.0
|
||||
moto==1.3.7
|
||||
msgpack-python==0.5.6
|
||||
mypy-extensions==0.4.1
|
||||
mypy==0.670
|
||||
packaging==19.0
|
||||
paramiko==2.4.2
|
||||
parsimonious==0.8.1
|
||||
pathtools==0.1.2
|
||||
pbr==5.1.2
|
||||
pendulum==2.0.4
|
||||
pluggy==0.8.1
|
||||
protobuf==3.7.0rc2
|
||||
py-ecc==1.4.7
|
||||
py-evm==0.2.0a39
|
||||
py-geth==2.0.1
|
||||
py==1.7.0
|
||||
pyaml==18.11.0
|
||||
pyasn1-modules==0.2.4
|
||||
pyasn1==0.4.5
|
||||
pychalk==2.0.1
|
||||
pycparser==2.19
|
||||
pycryptodome==3.7.3
|
||||
pyethash==0.1.27
|
||||
pygments==2.3.1
|
||||
pyhamcrest==1.9.0
|
||||
pynacl==1.3.0
|
||||
pyopenssl==19.0.0
|
||||
pyparsing==2.3.1
|
||||
pysha3==1.0.2
|
||||
pytest-cov==2.5.1
|
||||
pytest-forked==1.0.1
|
||||
pytest-ethereum==0.1.3a6
|
||||
pytest-forked==1.0.2
|
||||
pytest-mock==1.10.1
|
||||
pytest-mypy==0.3.2
|
||||
pytest-twisted==1.9
|
||||
|
@ -62,20 +113,37 @@ python-coveralls==2.9.1
|
|||
python-dateutil==2.8.0 ; python_version >= '2.7'
|
||||
python-jose==2.0.2
|
||||
pytz==2018.9
|
||||
pytzdata==2018.9
|
||||
pyyaml==4.2b4
|
||||
recommonmark==0.5.0
|
||||
regex==2019.2.7
|
||||
requests==2.21.0
|
||||
responses==0.10.5
|
||||
rlp==1.1.0
|
||||
s3transfer==0.2.0
|
||||
semantic-version==2.6.0
|
||||
sentry-sdk==0.5.2
|
||||
service-identity==18.1.0
|
||||
six==1.12.0
|
||||
snaptime==0.2.4
|
||||
snowballstemmer==1.2.1
|
||||
sphinx-rtd-theme==0.4.2
|
||||
sphinx-rtd-theme==0.4.3
|
||||
sphinx==1.8.4
|
||||
sphinxcontrib-websupport==1.1.0
|
||||
sqlalchemy==1.3.0b3
|
||||
toolz==0.9.0
|
||||
trie==1.3.8
|
||||
twisted==18.9.0
|
||||
txaio==18.8.1
|
||||
typed-ast==1.3.1
|
||||
tzlocal==2.0.0b1
|
||||
umbral==0.1.3a0
|
||||
urllib3==1.24.1 ; python_version >= '3.4'
|
||||
watchdog==0.9.0
|
||||
web3[tester]==5.0.0a5
|
||||
websocket-client==0.54.0
|
||||
websockets==7.0
|
||||
werkzeug==0.14.1
|
||||
wrapt==1.11.1
|
||||
xmltodict==0.11.0
|
||||
xmltodict==0.12.0
|
||||
zope.interface==4.6.0
|
||||
|
|
|
@ -143,12 +143,12 @@ class MinerAgent(EthereumContractAgent):
|
|||
return self.contract.functions.minerInfo(address).call()[0]
|
||||
|
||||
def get_stake_info(self, miner_address: str, stake_index: int):
|
||||
first_period, *others, locked_value = self.contract.functions.getStakeInfo(miner_address, stake_index).call()
|
||||
last_period = self.contract.functions.getLastPeriodOfStake(miner_address, stake_index).call()
|
||||
first_period, *others, locked_value = self.contract.functions.getSubStakeInfo(miner_address, stake_index).call()
|
||||
last_period = self.contract.functions.getLastPeriodOfSubStake(miner_address, stake_index).call()
|
||||
return first_period, last_period, locked_value
|
||||
|
||||
def get_all_stakes(self, miner_address: str):
|
||||
stakes_length = self.contract.functions.getStakesLength(miner_address).call()
|
||||
stakes_length = self.contract.functions.getSubStakesLength(miner_address).call()
|
||||
for stake_index in range(stakes_length):
|
||||
yield self.get_stake_info(miner_address=miner_address, stake_index=stake_index)
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
|||
"""Nucypher Token and Miner constants."""
|
||||
|
||||
ONE_YEAR_IN_SECONDS = 31540000
|
||||
NUCYPHER_GAS_LIMIT = 6000000 # TODO: move elsewhere?
|
||||
NUCYPHER_GAS_LIMIT = 6500000 # TODO: move elsewhere?
|
||||
|
||||
#
|
||||
# Dispatcher
|
||||
|
|
|
@ -24,7 +24,7 @@ contract Issuer is Upgradeable {
|
|||
uint32 public secondsPerPeriod;
|
||||
uint16 public rewardedPeriods;
|
||||
|
||||
uint16 public lastMintedPeriod;
|
||||
uint16 public currentMintingPeriod;
|
||||
uint256 public totalSupply;
|
||||
/**
|
||||
* Current supply is used in the mining formula and is stored to prevent different calculation
|
||||
|
@ -89,7 +89,7 @@ contract Issuer is Upgradeable {
|
|||
**/
|
||||
function initialize() public onlyOwner {
|
||||
require(currentSupply1 == 0);
|
||||
lastMintedPeriod = getCurrentPeriod();
|
||||
currentMintingPeriod = getCurrentPeriod();
|
||||
totalSupply = token.totalSupply();
|
||||
uint256 reservedReward = token.balanceOf(address(this));
|
||||
uint256 currentTotalSupply = totalSupply.sub(reservedReward);
|
||||
|
@ -100,26 +100,26 @@ contract Issuer is Upgradeable {
|
|||
|
||||
/**
|
||||
* @notice Function to mint tokens for one period.
|
||||
* @param _period Period number.
|
||||
* @param _currentPeriod Current period number.
|
||||
* @param _lockedValue The amount of tokens that were locked by user in specified period.
|
||||
* @param _totalLockedValue The amount of tokens that were locked by all users in specified period.
|
||||
* @param _allLockedPeriods The max amount of periods during which tokens will be locked after specified period.
|
||||
* @return Amount of minted tokens.
|
||||
*/
|
||||
function mint(
|
||||
uint16 _period,
|
||||
uint16 _currentPeriod,
|
||||
uint256 _lockedValue,
|
||||
uint256 _totalLockedValue,
|
||||
uint16 _allLockedPeriods
|
||||
)
|
||||
internal returns (uint256 amount)
|
||||
{
|
||||
uint256 currentSupply = _period <= lastMintedPeriod ?
|
||||
Math.min(currentSupply1, currentSupply2) :
|
||||
Math.max(currentSupply1, currentSupply2);
|
||||
if (currentSupply == totalSupply) {
|
||||
if (currentSupply1 == totalSupply || currentSupply2 == totalSupply) {
|
||||
return 0;
|
||||
}
|
||||
uint256 currentSupply = _currentPeriod <= currentMintingPeriod ?
|
||||
Math.min(currentSupply1, currentSupply2) :
|
||||
Math.max(currentSupply1, currentSupply2);
|
||||
|
||||
//totalSupply * lockedValue * (k1 + allLockedPeriods) / (totalLockedValue * k2) -
|
||||
//currentSupply * lockedValue * (k1 + allLockedPeriods) / (totalLockedValue * k2)
|
||||
|
@ -141,14 +141,14 @@ contract Issuer is Upgradeable {
|
|||
amount = 1;
|
||||
}
|
||||
|
||||
if (_period <= lastMintedPeriod) {
|
||||
if (_currentPeriod <= currentMintingPeriod) {
|
||||
if (currentSupply1 > currentSupply2) {
|
||||
currentSupply1 = currentSupply1.add(amount);
|
||||
} else {
|
||||
currentSupply2 = currentSupply2.add(amount);
|
||||
}
|
||||
} else {
|
||||
lastMintedPeriod = _period;
|
||||
currentMintingPeriod = _currentPeriod;
|
||||
if (currentSupply1 > currentSupply2) {
|
||||
currentSupply2 = currentSupply1.add(amount);
|
||||
} else {
|
||||
|
@ -157,13 +157,29 @@ contract Issuer is Upgradeable {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Return tokens for future minting
|
||||
* @param _amount Amount of tokens
|
||||
**/
|
||||
function unMint(uint256 _amount) internal {
|
||||
currentSupply1 = currentSupply1.sub(_amount);
|
||||
currentSupply2 = currentSupply2.sub(_amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns the number of tokens that can be mined
|
||||
**/
|
||||
function getReservedReward() public view returns (uint256) {
|
||||
return totalSupply - Math.max(currentSupply1, currentSupply2);
|
||||
}
|
||||
|
||||
function verifyState(address _testTarget) public onlyOwner {
|
||||
require(address(uint160(delegateGet(_testTarget, "token()"))) == address(token));
|
||||
require(delegateGet(_testTarget, "miningCoefficient()") == miningCoefficient);
|
||||
require(delegateGet(_testTarget, "lockedPeriodsCoefficient()") == lockedPeriodsCoefficient);
|
||||
require(uint32(delegateGet(_testTarget, "secondsPerPeriod()")) == secondsPerPeriod);
|
||||
require(uint16(delegateGet(_testTarget, "rewardedPeriods()")) == rewardedPeriods);
|
||||
require(uint16(delegateGet(_testTarget, "lastMintedPeriod()")) == lastMintedPeriod);
|
||||
require(uint16(delegateGet(_testTarget, "currentMintingPeriod()")) == currentMintingPeriod);
|
||||
require(delegateGet(_testTarget, "currentSupply1()") == currentSupply1);
|
||||
require(delegateGet(_testTarget, "currentSupply2()") == currentSupply2);
|
||||
require(delegateGet(_testTarget, "totalSupply()") == totalSupply);
|
||||
|
|
|
@ -15,6 +15,14 @@ contract PolicyManagerInterface {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notice MiningAdjudicator interface
|
||||
**/
|
||||
contract MiningAdjudicatorInterface {
|
||||
function escrow() public view returns (address);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notice Contract holds and locks miners tokens.
|
||||
* Each miner that locks their tokens will receive some compensation
|
||||
|
@ -36,8 +44,9 @@ contract MinersEscrow is Issuer {
|
|||
event Withdrawn(address indexed miner, uint256 value);
|
||||
event ActivityConfirmed(address indexed miner, uint16 indexed period, uint256 value);
|
||||
event Mined(address indexed miner, uint16 indexed period, uint256 value);
|
||||
event Slashed(address indexed miner, uint256 penalty, address indexed investigator, uint256 reward);
|
||||
|
||||
struct StakeInfo {
|
||||
struct SubStakeInfo {
|
||||
uint16 firstPeriod;
|
||||
uint16 lastPeriod;
|
||||
uint16 periods;
|
||||
|
@ -65,7 +74,7 @@ contract MinersEscrow is Issuer {
|
|||
// downtime
|
||||
uint16 lastActivePeriod;
|
||||
Downtime[] pastDowntime;
|
||||
StakeInfo[] stakes;
|
||||
SubStakeInfo[] subStakes;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -74,9 +83,13 @@ contract MinersEscrow is Issuer {
|
|||
* but increases gas usage in mint() method. In both cases confirmActivity()
|
||||
* with one execution of mint() method consume the same amount of gas
|
||||
*/
|
||||
uint16 constant EMPTY_CONFIRMED_PERIOD = 0;
|
||||
uint16 public constant EMPTY_CONFIRMED_PERIOD = 0;
|
||||
// used only for upgrading
|
||||
uint16 constant RESERVED_PERIOD = 0;
|
||||
uint16 constant MAX_CHECKED_VALUES = 5;
|
||||
// to prevent high gas consumption in loops for slashing
|
||||
uint16 public constant MAX_SUB_STAKES = 30;
|
||||
uint16 constant MAX_UINT16 = 65535;
|
||||
|
||||
mapping (address => MinerInfo) public minerInfo;
|
||||
address[] public miners;
|
||||
|
@ -86,6 +99,7 @@ contract MinersEscrow is Issuer {
|
|||
uint256 public minAllowableLockedTokens;
|
||||
uint256 public maxAllowableLockedTokens;
|
||||
PolicyManagerInterface public policyManager;
|
||||
MiningAdjudicatorInterface public miningAdjudicator;
|
||||
|
||||
/**
|
||||
* @notice Constructor sets address of token contract and coefficients for mining
|
||||
|
@ -133,8 +147,32 @@ contract MinersEscrow is Issuer {
|
|||
_;
|
||||
}
|
||||
|
||||
//------------------------Initialization------------------------
|
||||
/**
|
||||
* @notice Get the start period. Use in the calculation of the last period of the stake
|
||||
* @notice Set policy manager address
|
||||
**/
|
||||
function setPolicyManager(PolicyManagerInterface _policyManager) external onlyOwner {
|
||||
require(address(policyManager) == address(0) &&
|
||||
address(_policyManager) != address(0) &&
|
||||
_policyManager.escrow() == address(this));
|
||||
policyManager = _policyManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Set mining adjudicator address
|
||||
**/
|
||||
function setMiningAdjudicator(MiningAdjudicatorInterface _miningAdjudicator) external onlyOwner {
|
||||
require(address(miningAdjudicator) == address(0) &&
|
||||
address(_miningAdjudicator) != address(0) &&
|
||||
_miningAdjudicator.escrow() == address(this));
|
||||
miningAdjudicator = _miningAdjudicator;
|
||||
}
|
||||
|
||||
//------------------------Main getters------------------------
|
||||
/**
|
||||
* @notice Get the start period. Use in the calculation of the last period of the sub stake
|
||||
* @param _info Miner structure
|
||||
* @param _currentPeriod Current period
|
||||
**/
|
||||
function getStartPeriod(MinerInfo storage _info, uint16 _currentPeriod)
|
||||
internal view returns (uint16)
|
||||
|
@ -147,49 +185,79 @@ contract MinersEscrow is Issuer {
|
|||
}
|
||||
|
||||
/**
|
||||
* @notice Get the last period of the stake
|
||||
* @notice Get the last period of the sub stake
|
||||
* @param _subStake Sub stake structure
|
||||
* @param _startPeriod Pre-calculated start period
|
||||
**/
|
||||
function getLastPeriodOfStake(StakeInfo storage _stake, uint16 _startPeriod)
|
||||
function getLastPeriodOfSubStake(SubStakeInfo storage _subStake, uint16 _startPeriod)
|
||||
internal view returns (uint16)
|
||||
{
|
||||
return _stake.lastPeriod != 0 ? _stake.lastPeriod : _startPeriod.add16(_stake.periods);
|
||||
return _subStake.lastPeriod != 0 ? _subStake.lastPeriod : _startPeriod.add16(_subStake.periods);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Get the last period of the stake
|
||||
* @notice Get the last period of the sub stake
|
||||
* @param _miner Miner
|
||||
* @param _index Stake index
|
||||
**/
|
||||
function getLastPeriodOfStake(address _miner, uint256 _index)
|
||||
function getLastPeriodOfSubStake(address _miner, uint256 _index)
|
||||
public view returns (uint16)
|
||||
{
|
||||
MinerInfo storage info = minerInfo[_miner];
|
||||
require(_index < info.stakes.length);
|
||||
StakeInfo storage stake = info.stakes[_index];
|
||||
require(_index < info.subStakes.length);
|
||||
SubStakeInfo storage subStake = info.subStakes[_index];
|
||||
uint16 startPeriod = getStartPeriod(info, getCurrentPeriod());
|
||||
return getLastPeriodOfStake(stake, startPeriod);
|
||||
return getLastPeriodOfSubStake(subStake, startPeriod);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notice Get the value of locked tokens for a miner in a specified period
|
||||
* @dev Information may be incorrect for mined or unconfirmed surpassed period
|
||||
* @param _info Miner structure
|
||||
* @param _currentPeriod Current period
|
||||
* @param _period Next period
|
||||
**/
|
||||
function getLockedTokens(MinerInfo storage _info, uint16 _currentPeriod, uint16 _period)
|
||||
internal view returns (uint256 lockedValue)
|
||||
{
|
||||
uint16 startPeriod = getStartPeriod(_info, _currentPeriod);
|
||||
for (uint256 i = 0; i < _info.subStakes.length; i++) {
|
||||
SubStakeInfo storage subStake = _info.subStakes[i];
|
||||
if (subStake.firstPeriod <= _period &&
|
||||
getLastPeriodOfSubStake(subStake, startPeriod) >= _period) {
|
||||
lockedValue = lockedValue.add(subStake.lockedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Get the value of locked tokens for a miner in a future period
|
||||
* @param _miner Miner
|
||||
* @param _periods Amount of periods to calculate locked tokens
|
||||
* @param _periods Amount of periods that will be added to the current period
|
||||
**/
|
||||
function getLockedTokens(address _miner, uint16 _periods)
|
||||
public view returns (uint256 lockedValue)
|
||||
{
|
||||
uint16 startPeriod = getCurrentPeriod();
|
||||
uint16 nextPeriod = startPeriod.add16(_periods);
|
||||
MinerInfo storage info = minerInfo[_miner];
|
||||
startPeriod = getStartPeriod(info, startPeriod);
|
||||
uint16 currentPeriod = getCurrentPeriod();
|
||||
uint16 nextPeriod = currentPeriod.add16(_periods);
|
||||
return getLockedTokens(info, currentPeriod, nextPeriod);
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < info.stakes.length; i++) {
|
||||
StakeInfo storage stake = info.stakes[i];
|
||||
if (stake.firstPeriod <= nextPeriod &&
|
||||
getLastPeriodOfStake(stake, startPeriod) >= nextPeriod) {
|
||||
lockedValue = lockedValue.add(stake.lockedValue);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @notice Get the value of locked tokens for a miner in a previous period
|
||||
* @dev Information may be incorrect for mined or unconfirmed surpassed period
|
||||
* @param _miner Miner
|
||||
* @param _periods Amount of periods that will be subtracted from the current period
|
||||
**/
|
||||
function getLockedTokensInPast(address _miner, uint16 _periods)
|
||||
public view returns (uint256 lockedValue)
|
||||
{
|
||||
MinerInfo storage info = minerInfo[_miner];
|
||||
uint16 currentPeriod = getCurrentPeriod();
|
||||
uint16 previousPeriod = currentPeriod.sub16(_periods);
|
||||
return getLockedTokens(info, currentPeriod, previousPeriod);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -202,6 +270,41 @@ contract MinersEscrow is Issuer {
|
|||
return getLockedTokens(_miner, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Get the last active miner's period
|
||||
* @param _miner Miner
|
||||
**/
|
||||
function getLastActivePeriod(address _miner) public view returns (uint16) {
|
||||
MinerInfo storage info = minerInfo[_miner];
|
||||
if (info.confirmedPeriod1 != EMPTY_CONFIRMED_PERIOD ||
|
||||
info.confirmedPeriod2 != EMPTY_CONFIRMED_PERIOD) {
|
||||
return AdditionalMath.max16(info.confirmedPeriod1, info.confirmedPeriod2);
|
||||
}
|
||||
return info.lastActivePeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Get the value of locked tokens for active miners in (getCurrentPeriod() + _periods) period
|
||||
* @param _periods Amount of periods for locked tokens calculation
|
||||
**/
|
||||
function getAllLockedTokens(uint16 _periods)
|
||||
external view returns (uint256 lockedTokens)
|
||||
{
|
||||
require(_periods > 0);
|
||||
uint16 currentPeriod = getCurrentPeriod();
|
||||
uint16 nextPeriod = currentPeriod.add16(_periods);
|
||||
for (uint256 i = 0; i < miners.length; i++) {
|
||||
address miner = miners[i];
|
||||
MinerInfo storage info = minerInfo[miner];
|
||||
if (info.confirmedPeriod1 != currentPeriod &&
|
||||
info.confirmedPeriod2 != currentPeriod) {
|
||||
continue;
|
||||
}
|
||||
lockedTokens = lockedTokens.add(getLockedTokens(info, currentPeriod, nextPeriod));
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------Main methods------------------------
|
||||
/**
|
||||
* @notice Pre-deposit tokens
|
||||
* @param _miners Miners
|
||||
|
@ -222,14 +325,14 @@ contract MinersEscrow is Issuer {
|
|||
uint256 value = _values[i];
|
||||
uint16 periods = _periods[i];
|
||||
MinerInfo storage info = minerInfo[miner];
|
||||
require(info.stakes.length == 0 &&
|
||||
require(info.subStakes.length == 0 &&
|
||||
value >= minAllowableLockedTokens &&
|
||||
value <= maxAllowableLockedTokens &&
|
||||
periods >= minLockedPeriods);
|
||||
miners.push(miner);
|
||||
policyManager.register(miner, currentPeriod);
|
||||
info.value = value;
|
||||
info.stakes.push(StakeInfo(currentPeriod.add16(1), 0, periods, value));
|
||||
info.subStakes.push(SubStakeInfo(currentPeriod.add16(1), 0, periods, value));
|
||||
allValue = allValue.add(value);
|
||||
emit Deposited(miner, value, periods);
|
||||
}
|
||||
|
@ -237,32 +340,19 @@ contract MinersEscrow is Issuer {
|
|||
token.safeTransferFrom(msg.sender, address(this), allValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Get the last active miner's period
|
||||
* @param _miner Miner
|
||||
**/
|
||||
function getLastActivePeriod(address _miner) public view returns (uint16) {
|
||||
MinerInfo storage info = minerInfo[_miner];
|
||||
if (info.confirmedPeriod1 != EMPTY_CONFIRMED_PERIOD ||
|
||||
info.confirmedPeriod2 != EMPTY_CONFIRMED_PERIOD) {
|
||||
return AdditionalMath.max16(info.confirmedPeriod1, info.confirmedPeriod2);
|
||||
}
|
||||
return info.lastActivePeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Implementation of the receiveApproval(address,uint256,address,bytes) method
|
||||
* (see NuCypherToken contract). Deposit all tokens that were approved to transfer
|
||||
* @param _from Miner
|
||||
* @param _value Amount of tokens to deposit
|
||||
* @param _tokenContract Token contract address
|
||||
* @param _extraData Amount of periods during which tokens will be locked
|
||||
* @notice (param _extraData) Amount of periods during which tokens will be locked
|
||||
**/
|
||||
function receiveApproval(
|
||||
address _from,
|
||||
uint256 _value,
|
||||
address _tokenContract,
|
||||
bytes calldata _extraData
|
||||
bytes calldata /* _extraData */
|
||||
)
|
||||
external
|
||||
{
|
||||
|
@ -299,7 +389,7 @@ contract MinersEscrow is Issuer {
|
|||
require(_value != 0);
|
||||
MinerInfo storage info = minerInfo[_miner];
|
||||
// initial stake of the miner
|
||||
if (info.stakes.length == 0) {
|
||||
if (info.subStakes.length == 0) {
|
||||
miners.push(_miner);
|
||||
policyManager.register(_miner, getCurrentPeriod());
|
||||
}
|
||||
|
@ -331,16 +421,17 @@ contract MinersEscrow is Issuer {
|
|||
uint16 lastActivePeriod = getLastActivePeriod(_miner);
|
||||
mint(_miner);
|
||||
|
||||
uint256 lockedTokens = getLockedTokens(_miner, 1);
|
||||
uint16 currentPeriod = getCurrentPeriod();
|
||||
uint16 nextPeriod = currentPeriod.add16(1);
|
||||
MinerInfo storage info = minerInfo[_miner];
|
||||
uint256 lockedTokens = getLockedTokens(info, currentPeriod, nextPeriod);
|
||||
require(_value.add(lockedTokens) <= info.value &&
|
||||
_value.add(lockedTokens) <= maxAllowableLockedTokens);
|
||||
|
||||
uint16 nextPeriod = getCurrentPeriod().add16(1);
|
||||
if (info.confirmedPeriod1 != nextPeriod && info.confirmedPeriod2 != nextPeriod) {
|
||||
info.stakes.push(StakeInfo(nextPeriod, 0, _periods, _value));
|
||||
saveSubStake(info, nextPeriod, 0, _periods, _value);
|
||||
} else {
|
||||
info.stakes.push(StakeInfo(nextPeriod, 0, _periods - 1, _value));
|
||||
saveSubStake(info, nextPeriod, 0, _periods - 1, _value);
|
||||
}
|
||||
|
||||
confirmActivity(_miner, _value + lockedTokens, _value, lastActivePeriod);
|
||||
|
@ -348,10 +439,47 @@ contract MinersEscrow is Issuer {
|
|||
}
|
||||
|
||||
/**
|
||||
* @notice Divide stake into two parts
|
||||
* @param _index Index of the stake
|
||||
* @param _newValue New stake value
|
||||
* @param _periods Amount of periods for extending stake
|
||||
* @notice Save sub stake. First tries to override inactive sub stake
|
||||
* @dev Inactive sub stake means that last period of sub stake has been surpassed and already mined
|
||||
* @param _info Miner structure
|
||||
* @param _firstPeriod First period of the sub stake
|
||||
* @param _lastPeriod Last period of the sub stake
|
||||
* @param _periods Duration of the sub stake in periods
|
||||
* @param _lockedValue Amount of locked tokens
|
||||
**/
|
||||
function saveSubStake(
|
||||
MinerInfo storage _info,
|
||||
uint16 _firstPeriod,
|
||||
uint16 _lastPeriod,
|
||||
uint16 _periods,
|
||||
uint256 _lockedValue
|
||||
)
|
||||
internal
|
||||
{
|
||||
for (uint256 i = 0; i < _info.subStakes.length; i++) {
|
||||
SubStakeInfo storage subStake = _info.subStakes[i];
|
||||
if (subStake.lastPeriod != 0 &&
|
||||
(_info.confirmedPeriod1 == EMPTY_CONFIRMED_PERIOD ||
|
||||
subStake.lastPeriod < _info.confirmedPeriod1) &&
|
||||
(_info.confirmedPeriod2 == EMPTY_CONFIRMED_PERIOD ||
|
||||
subStake.lastPeriod < _info.confirmedPeriod2))
|
||||
{
|
||||
subStake.firstPeriod = _firstPeriod;
|
||||
subStake.lastPeriod = _lastPeriod;
|
||||
subStake.periods = _periods;
|
||||
subStake.lockedValue = _lockedValue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
require(_info.subStakes.length < MAX_SUB_STAKES);
|
||||
_info.subStakes.push(SubStakeInfo(_firstPeriod, _lastPeriod, _periods, _lockedValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Divide sub stake into two parts
|
||||
* @param _index Index of the sub stake
|
||||
* @param _newValue New sub stake value
|
||||
* @param _periods Amount of periods for extending sub stake
|
||||
**/
|
||||
function divideStake(
|
||||
uint256 _index,
|
||||
|
@ -363,23 +491,24 @@ contract MinersEscrow is Issuer {
|
|||
MinerInfo storage info = minerInfo[msg.sender];
|
||||
require(_newValue >= minAllowableLockedTokens &&
|
||||
_periods > 0 &&
|
||||
_index < info.stakes.length);
|
||||
StakeInfo storage stake = info.stakes[_index];
|
||||
_index < info.subStakes.length);
|
||||
SubStakeInfo storage subStake = info.subStakes[_index];
|
||||
uint16 currentPeriod = getCurrentPeriod();
|
||||
uint16 startPeriod = getStartPeriod(info, currentPeriod);
|
||||
uint16 lastPeriod = getLastPeriodOfStake(stake, startPeriod);
|
||||
uint16 lastPeriod = getLastPeriodOfSubStake(subStake, startPeriod);
|
||||
require(lastPeriod >= currentPeriod);
|
||||
|
||||
uint256 oldValue = stake.lockedValue;
|
||||
stake.lockedValue = oldValue.sub(_newValue);
|
||||
require(stake.lockedValue >= minAllowableLockedTokens);
|
||||
info.stakes.push(StakeInfo(stake.firstPeriod, 0, stake.periods.add16(_periods), _newValue));
|
||||
// if the next period is confirmed and old stake is finishing in the current period then rerun confirmActivity
|
||||
uint256 oldValue = subStake.lockedValue;
|
||||
subStake.lockedValue = oldValue.sub(_newValue);
|
||||
require(subStake.lockedValue >= minAllowableLockedTokens);
|
||||
saveSubStake(info, subStake.firstPeriod, 0, subStake.periods.add16(_periods), _newValue);
|
||||
// if the next period is confirmed and
|
||||
// old sub stake is finishing in the current period then rerun confirmActivity
|
||||
if (lastPeriod == currentPeriod && startPeriod > currentPeriod) {
|
||||
confirmActivity(msg.sender, _newValue, _newValue, 0);
|
||||
}
|
||||
emit Divided(msg.sender, oldValue, lastPeriod, _newValue, _periods);
|
||||
emit Locked(msg.sender, _newValue, stake.firstPeriod, stake.periods + _periods);
|
||||
emit Locked(msg.sender, _newValue, subStake.firstPeriod, subStake.periods + _periods);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -387,11 +516,13 @@ contract MinersEscrow is Issuer {
|
|||
* @param _value Amount of tokens to withdraw
|
||||
**/
|
||||
function withdraw(uint256 _value) public onlyMiner {
|
||||
uint16 currentPeriod = getCurrentPeriod();
|
||||
uint16 nextPeriod = currentPeriod.add16(1);
|
||||
MinerInfo storage info = minerInfo[msg.sender];
|
||||
// the max locked tokens in most cases will be in the current period
|
||||
// but when the miner stakes more then we should use the next period
|
||||
uint256 lockedTokens = Math.max(getLockedTokens(msg.sender, 1),
|
||||
getLockedTokens(msg.sender, 0));
|
||||
uint256 lockedTokens = Math.max(getLockedTokens(info, currentPeriod, nextPeriod),
|
||||
getLockedTokens(info, currentPeriod, currentPeriod));
|
||||
require(_value <= token.balanceOf(address(this)) &&
|
||||
_value <= info.value.sub(lockedTokens));
|
||||
info.value -= _value;
|
||||
|
@ -436,13 +567,13 @@ contract MinersEscrow is Issuer {
|
|||
info.confirmedPeriod2 = nextPeriod;
|
||||
}
|
||||
|
||||
for (uint256 index = 0; index < info.stakes.length; index++) {
|
||||
StakeInfo storage stake = info.stakes[index];
|
||||
if (stake.periods > 1) {
|
||||
stake.periods--;
|
||||
} else if (stake.periods == 1) {
|
||||
stake.periods = 0;
|
||||
stake.lastPeriod = nextPeriod;
|
||||
for (uint256 index = 0; index < info.subStakes.length; index++) {
|
||||
SubStakeInfo storage subStake = info.subStakes[index];
|
||||
if (subStake.lastPeriod == 0 && subStake.periods > 1) {
|
||||
subStake.periods--;
|
||||
} else if (subStake.lastPeriod == 0 && subStake.periods == 1) {
|
||||
subStake.periods = 0;
|
||||
subStake.lastPeriod = nextPeriod;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -461,7 +592,7 @@ contract MinersEscrow is Issuer {
|
|||
mint(msg.sender);
|
||||
MinerInfo storage info = minerInfo[msg.sender];
|
||||
uint16 currentPeriod = getCurrentPeriod();
|
||||
uint16 nextPeriod = currentPeriod + 1;
|
||||
uint16 nextPeriod = currentPeriod.add16(1);
|
||||
|
||||
// the period has already been confirmed
|
||||
if (info.confirmedPeriod1 == nextPeriod ||
|
||||
|
@ -469,7 +600,7 @@ contract MinersEscrow is Issuer {
|
|||
return;
|
||||
}
|
||||
|
||||
uint256 lockedTokens = getLockedTokens(msg.sender, 1);
|
||||
uint256 lockedTokens = getLockedTokens(info, currentPeriod, nextPeriod);
|
||||
confirmActivity(msg.sender, lockedTokens, 0, lastActivePeriod);
|
||||
}
|
||||
|
||||
|
@ -493,8 +624,8 @@ contract MinersEscrow is Issuer {
|
|||
* @param _miner Miner
|
||||
**/
|
||||
function mint(address _miner) internal {
|
||||
uint16 startPeriod = getCurrentPeriod();
|
||||
uint16 previousPeriod = startPeriod.sub16(1);
|
||||
uint16 currentPeriod = getCurrentPeriod();
|
||||
uint16 previousPeriod = currentPeriod.sub16(1);
|
||||
MinerInfo storage info = minerInfo[_miner];
|
||||
|
||||
if (info.confirmedPeriod1 > previousPeriod &&
|
||||
|
@ -518,24 +649,24 @@ contract MinersEscrow is Issuer {
|
|||
last = info.confirmedPeriod2;
|
||||
}
|
||||
|
||||
startPeriod = getStartPeriod(info, startPeriod);
|
||||
uint16 startPeriod = getStartPeriod(info, currentPeriod);
|
||||
uint256 reward = 0;
|
||||
if (info.confirmedPeriod1 != EMPTY_CONFIRMED_PERIOD &&
|
||||
info.confirmedPeriod1 < info.confirmedPeriod2) {
|
||||
reward = reward.add(mint(_miner, info, info.confirmedPeriod1, previousPeriod, startPeriod));
|
||||
reward = reward.add(mint(_miner, info, info.confirmedPeriod1, currentPeriod, startPeriod));
|
||||
info.confirmedPeriod1 = EMPTY_CONFIRMED_PERIOD;
|
||||
} else if (info.confirmedPeriod2 != EMPTY_CONFIRMED_PERIOD &&
|
||||
info.confirmedPeriod2 < info.confirmedPeriod1) {
|
||||
reward = reward.add(mint(_miner, info, info.confirmedPeriod2, previousPeriod, startPeriod));
|
||||
reward = reward.add(mint(_miner, info, info.confirmedPeriod2, currentPeriod, startPeriod));
|
||||
info.confirmedPeriod2 = EMPTY_CONFIRMED_PERIOD;
|
||||
}
|
||||
if (info.confirmedPeriod2 <= previousPeriod &&
|
||||
info.confirmedPeriod2 > info.confirmedPeriod1) {
|
||||
reward = reward.add(mint(_miner, info, info.confirmedPeriod2, previousPeriod, startPeriod));
|
||||
reward = reward.add(mint(_miner, info, info.confirmedPeriod2, currentPeriod, startPeriod));
|
||||
info.confirmedPeriod2 = EMPTY_CONFIRMED_PERIOD;
|
||||
} else if (info.confirmedPeriod1 <= previousPeriod &&
|
||||
info.confirmedPeriod1 > info.confirmedPeriod2) {
|
||||
reward = reward.add(mint(_miner, info, info.confirmedPeriod1, previousPeriod, startPeriod));
|
||||
reward = reward.add(mint(_miner, info, info.confirmedPeriod1, currentPeriod, startPeriod));
|
||||
info.confirmedPeriod1 = EMPTY_CONFIRMED_PERIOD;
|
||||
}
|
||||
|
||||
|
@ -545,48 +676,33 @@ contract MinersEscrow is Issuer {
|
|||
|
||||
/**
|
||||
* @notice Calculate reward for one period
|
||||
* @param _miner Miner's address
|
||||
* @param _info Miner structure
|
||||
* @param _mintingPeriod Period when minting occurs
|
||||
* @param _currentPeriod Current period
|
||||
* @param _startPeriod Pre-calculated start period
|
||||
**/
|
||||
function mint(
|
||||
address _miner,
|
||||
MinerInfo storage _info,
|
||||
uint16 _period,
|
||||
uint16 _previousPeriod,
|
||||
uint16 _mintingPeriod,
|
||||
uint16 _currentPeriod,
|
||||
uint16 _startPeriod
|
||||
)
|
||||
internal returns (uint256 reward)
|
||||
{
|
||||
for (uint256 i = 0; i < _info.stakes.length; i++) {
|
||||
StakeInfo storage stake = _info.stakes[i];
|
||||
uint16 lastPeriod = getLastPeriodOfStake(stake, _startPeriod);
|
||||
if (stake.firstPeriod <= _period && lastPeriod >= _period) {
|
||||
for (uint256 i = 0; i < _info.subStakes.length; i++) {
|
||||
SubStakeInfo storage subStake = _info.subStakes[i];
|
||||
uint16 lastPeriod = getLastPeriodOfSubStake(subStake, _startPeriod);
|
||||
if (subStake.firstPeriod <= _mintingPeriod && lastPeriod >= _mintingPeriod) {
|
||||
reward = reward.add(mint(
|
||||
_previousPeriod,
|
||||
stake.lockedValue,
|
||||
lockedPerPeriod[_period],
|
||||
lastPeriod.sub16(_period)));
|
||||
_currentPeriod,
|
||||
subStake.lockedValue,
|
||||
lockedPerPeriod[_mintingPeriod],
|
||||
lastPeriod.sub16(_mintingPeriod)));
|
||||
}
|
||||
}
|
||||
policyManager.updateReward(_miner, _period);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Get the value of locked tokens for active miners in (getCurrentPeriod() + _periods) period
|
||||
* @param _periods Amount of periods for locked tokens calculation
|
||||
**/
|
||||
function getAllLockedTokens(uint16 _periods)
|
||||
external view returns (uint256 lockedTokens)
|
||||
{
|
||||
require(_periods > 0);
|
||||
uint16 currentPeriod = getCurrentPeriod();
|
||||
for (uint256 i = 0; i < miners.length; i++) {
|
||||
address miner = miners[i];
|
||||
MinerInfo storage info = minerInfo[miner];
|
||||
if (info.confirmedPeriod1 != currentPeriod &&
|
||||
info.confirmedPeriod2 != currentPeriod) {
|
||||
continue;
|
||||
}
|
||||
lockedTokens = lockedTokens.add(getLockedTokens(miner, _periods));
|
||||
}
|
||||
policyManager.updateReward(_miner, _mintingPeriod);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -611,6 +727,7 @@ contract MinersEscrow is Issuer {
|
|||
{
|
||||
require(_periods > 0 && _points.length > 0);
|
||||
uint16 currentPeriod = getCurrentPeriod();
|
||||
uint16 nextPeriod = currentPeriod.add16(_periods);
|
||||
result = new address[](_points.length);
|
||||
|
||||
uint256 pointIndex = 0;
|
||||
|
@ -628,7 +745,7 @@ contract MinersEscrow is Issuer {
|
|||
continue;
|
||||
}
|
||||
if (addMoreTokens) {
|
||||
sumOfLockedTokens = sumOfLockedTokens.add(getLockedTokens(currentMiner, _periods));
|
||||
sumOfLockedTokens = sumOfLockedTokens.add(getLockedTokens(info, currentPeriod, nextPeriod));
|
||||
}
|
||||
if (sumOfLockedTokens > point) {
|
||||
result[pointIndex] = currentMiner;
|
||||
|
@ -642,15 +759,268 @@ contract MinersEscrow is Issuer {
|
|||
}
|
||||
}
|
||||
|
||||
//-------------------------Slashing-------------------------
|
||||
/**
|
||||
* @notice Set policy manager address
|
||||
* @notice Slash the miner's stake and reward the investigator
|
||||
* @param _miner Miner's address
|
||||
* @param _penalty Penalty
|
||||
* @param _investigator Investigator
|
||||
* @param _reward Reward for the investigator
|
||||
**/
|
||||
function setPolicyManager(PolicyManagerInterface _policyManager) external onlyOwner {
|
||||
require(address(policyManager) == address(0) &&
|
||||
_policyManager.escrow() == address(this));
|
||||
policyManager = _policyManager;
|
||||
function slashMiner(
|
||||
address _miner,
|
||||
uint256 _penalty,
|
||||
address _investigator,
|
||||
uint256 _reward
|
||||
)
|
||||
public
|
||||
{
|
||||
require(msg.sender == address(miningAdjudicator));
|
||||
require(_penalty > 0);
|
||||
MinerInfo storage info = minerInfo[_miner];
|
||||
if (info.value <= _penalty) {
|
||||
_penalty = info.value;
|
||||
}
|
||||
info.value -= _penalty;
|
||||
if (_reward > _penalty) {
|
||||
_reward = _penalty;
|
||||
}
|
||||
|
||||
uint16 currentPeriod = getCurrentPeriod();
|
||||
uint16 nextPeriod = currentPeriod.add16(1);
|
||||
uint16 startPeriod = getStartPeriod(info, currentPeriod);
|
||||
|
||||
(uint256 currentLock, uint256 nextLock, uint256 currentAndNextLock, uint256 shortestSubStakeIndex) =
|
||||
getLockedTokensAndShortestSubStake(info, currentPeriod, nextPeriod, startPeriod);
|
||||
|
||||
// Decrease the stake if amount of locked tokens in the current period more than miner has
|
||||
uint256 lockedTokens = currentLock + currentAndNextLock;
|
||||
if (info.value < lockedTokens) {
|
||||
decreaseSubStakes(info, lockedTokens - info.value, currentPeriod, startPeriod, shortestSubStakeIndex);
|
||||
}
|
||||
// Decrease the stake if amount of locked tokens in the next period more than miner has
|
||||
if (nextLock > 0) {
|
||||
lockedTokens = nextLock + currentAndNextLock -
|
||||
(currentAndNextLock > info.value ? currentAndNextLock - info.value : 0);
|
||||
if (info.value < lockedTokens) {
|
||||
decreaseSubStakes(info, lockedTokens - info.value, nextPeriod, startPeriod, MAX_SUB_STAKES);
|
||||
}
|
||||
}
|
||||
|
||||
emit Slashed(_miner, _penalty, _investigator, _reward);
|
||||
_penalty -= _reward;
|
||||
if (_penalty > 0) {
|
||||
unMint(_penalty);
|
||||
}
|
||||
// TODO change to withdrawal pattern
|
||||
if (_reward > 0) {
|
||||
token.safeTransfer(_investigator, _reward);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Get the value of locked tokens for a miner in the current and the next period
|
||||
* and find the shortest sub stake
|
||||
* @param _info Miner structure
|
||||
* @param _currentPeriod Current period
|
||||
* @param _nextPeriod Next period
|
||||
* @param _startPeriod Pre-calculated start period
|
||||
* @return currentLock Amount of tokens that locked in the current period and unlocked in the next period
|
||||
* @return nextLock Amount of tokens that locked in the next period and not locked in the current period
|
||||
* @return currentAndNextLock Amount of tokens that locked in the current period and in the next period
|
||||
* @return shortestSubStakeIndex Index of the shortest sub stake
|
||||
**/
|
||||
function getLockedTokensAndShortestSubStake(
|
||||
MinerInfo storage _info,
|
||||
uint16 _currentPeriod,
|
||||
uint16 _nextPeriod,
|
||||
uint16 _startPeriod
|
||||
)
|
||||
internal view returns (
|
||||
uint256 currentLock,
|
||||
uint256 nextLock,
|
||||
uint256 currentAndNextLock,
|
||||
uint256 shortestSubStakeIndex
|
||||
)
|
||||
{
|
||||
uint16 minDuration = MAX_UINT16;
|
||||
uint16 minLastPeriod = MAX_UINT16;
|
||||
shortestSubStakeIndex = MAX_SUB_STAKES;
|
||||
for (uint256 i = 0; i < _info.subStakes.length; i++) {
|
||||
SubStakeInfo storage subStake = _info.subStakes[i];
|
||||
uint16 lastPeriod = getLastPeriodOfSubStake(subStake, _startPeriod);
|
||||
if (lastPeriod < subStake.firstPeriod) {
|
||||
continue;
|
||||
}
|
||||
if (subStake.firstPeriod <= _currentPeriod &&
|
||||
lastPeriod >= _nextPeriod) {
|
||||
currentAndNextLock = currentAndNextLock.add(subStake.lockedValue);
|
||||
} else if (subStake.firstPeriod <= _currentPeriod &&
|
||||
lastPeriod >= _currentPeriod) {
|
||||
currentLock = currentLock.add(subStake.lockedValue);
|
||||
} else if (subStake.firstPeriod <= _nextPeriod &&
|
||||
lastPeriod >= _nextPeriod) {
|
||||
nextLock = nextLock.add(subStake.lockedValue);
|
||||
}
|
||||
uint16 duration = lastPeriod.sub16(subStake.firstPeriod);
|
||||
if (subStake.firstPeriod <= _currentPeriod &&
|
||||
lastPeriod >= _currentPeriod &&
|
||||
(lastPeriod < minLastPeriod ||
|
||||
lastPeriod == minLastPeriod && duration < minDuration))
|
||||
{
|
||||
shortestSubStakeIndex = i;
|
||||
minDuration = duration;
|
||||
minLastPeriod = lastPeriod;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Decrease short sub stakes
|
||||
* @param _info Miner structure
|
||||
* @param _penalty Penalty rate
|
||||
* @param _decreasePeriod The period when the decrease begins
|
||||
* @param _startPeriod Pre-calculated start period
|
||||
* @param _shortestSubStakeIndex Index of the shortest period
|
||||
**/
|
||||
function decreaseSubStakes(
|
||||
MinerInfo storage _info,
|
||||
uint256 _penalty,
|
||||
uint16 _decreasePeriod,
|
||||
uint16 _startPeriod,
|
||||
uint256 _shortestSubStakeIndex
|
||||
)
|
||||
internal
|
||||
{
|
||||
SubStakeInfo storage shortestSubStake = _info.subStakes[0];
|
||||
uint16 minSubStakeLastPeriod = MAX_UINT16;
|
||||
uint16 minSubStakeDuration = MAX_UINT16;
|
||||
while(_penalty > 0) {
|
||||
if (_shortestSubStakeIndex < MAX_SUB_STAKES) {
|
||||
shortestSubStake = _info.subStakes[_shortestSubStakeIndex];
|
||||
minSubStakeLastPeriod = getLastPeriodOfSubStake(shortestSubStake, _startPeriod);
|
||||
minSubStakeDuration = minSubStakeLastPeriod.sub16(shortestSubStake.firstPeriod);
|
||||
_shortestSubStakeIndex = MAX_SUB_STAKES;
|
||||
} else {
|
||||
(shortestSubStake, minSubStakeDuration, minSubStakeLastPeriod) =
|
||||
getShortestSubStake(_info, _decreasePeriod, _startPeriod);
|
||||
}
|
||||
if (minSubStakeDuration == MAX_UINT16) {
|
||||
break;
|
||||
}
|
||||
uint256 appliedPenalty = _penalty;
|
||||
if (_penalty < shortestSubStake.lockedValue) {
|
||||
shortestSubStake.lockedValue -= _penalty;
|
||||
saveOldSubStake(_info, shortestSubStake.firstPeriod, _penalty, _decreasePeriod);
|
||||
_penalty = 0;
|
||||
} else {
|
||||
shortestSubStake.lastPeriod = _decreasePeriod.sub16(1);
|
||||
_penalty -= shortestSubStake.lockedValue;
|
||||
appliedPenalty = shortestSubStake.lockedValue;
|
||||
}
|
||||
if (_info.confirmedPeriod1 >= _decreasePeriod &&
|
||||
_info.confirmedPeriod1 <= minSubStakeLastPeriod)
|
||||
{
|
||||
lockedPerPeriod[_info.confirmedPeriod1] -= appliedPenalty;
|
||||
}
|
||||
if (_info.confirmedPeriod2 >= _decreasePeriod &&
|
||||
_info.confirmedPeriod2 <= minSubStakeLastPeriod)
|
||||
{
|
||||
lockedPerPeriod[_info.confirmedPeriod2] -= appliedPenalty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Get the shortest sub stake
|
||||
* @param _info Miner structure
|
||||
* @param _currentPeriod Current period
|
||||
* @param _startPeriod Pre-calculated start period
|
||||
* @return shortestSubStake The shortest sub stake
|
||||
* @return minSubStakeDuration Duration of the shortest sub stake
|
||||
* @return minSubStakeLastPeriod Last period of the shortest sub stake
|
||||
**/
|
||||
function getShortestSubStake(
|
||||
MinerInfo storage _info,
|
||||
uint16 _currentPeriod,
|
||||
uint16 _startPeriod
|
||||
)
|
||||
internal view returns (
|
||||
SubStakeInfo storage shortestSubStake,
|
||||
uint16 minSubStakeDuration,
|
||||
uint16 minSubStakeLastPeriod
|
||||
)
|
||||
{
|
||||
shortestSubStake = shortestSubStake;
|
||||
minSubStakeDuration = MAX_UINT16;
|
||||
minSubStakeLastPeriod = MAX_UINT16;
|
||||
for (uint256 i = 0; i < _info.subStakes.length; i++) {
|
||||
SubStakeInfo storage subStake = _info.subStakes[i];
|
||||
uint16 lastPeriod = getLastPeriodOfSubStake(subStake, _startPeriod);
|
||||
if (lastPeriod < subStake.firstPeriod) {
|
||||
continue;
|
||||
}
|
||||
uint16 duration = lastPeriod.sub16(subStake.firstPeriod);
|
||||
if (subStake.firstPeriod <= _currentPeriod &&
|
||||
lastPeriod >= _currentPeriod &&
|
||||
(lastPeriod < minSubStakeLastPeriod ||
|
||||
lastPeriod == minSubStakeLastPeriod && duration < minSubStakeDuration))
|
||||
{
|
||||
shortestSubStake = subStake;
|
||||
minSubStakeDuration = duration;
|
||||
minSubStakeLastPeriod = lastPeriod;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Save the old sub stake values to prevent decreasing reward for the previous period
|
||||
* @dev Saving happens only if the previous period is confirmed
|
||||
* @param _info Miner structure
|
||||
* @param _firstPeriod First period of the old sub stake
|
||||
* @param _lockedValue Locked value of the old sub stake
|
||||
* @param _currentPeriod Current period, when the old sub stake is already unlocked
|
||||
**/
|
||||
function saveOldSubStake(
|
||||
MinerInfo storage _info,
|
||||
uint16 _firstPeriod,
|
||||
uint256 _lockedValue,
|
||||
uint16 _currentPeriod
|
||||
)
|
||||
internal
|
||||
{
|
||||
// Check that the old sub stake should be saved
|
||||
bool oldConfirmedPeriod1 = _info.confirmedPeriod1 != EMPTY_CONFIRMED_PERIOD &&
|
||||
_info.confirmedPeriod1 < _currentPeriod;
|
||||
bool oldConfirmedPeriod2 = _info.confirmedPeriod2 != EMPTY_CONFIRMED_PERIOD &&
|
||||
_info.confirmedPeriod2 < _currentPeriod;
|
||||
bool crossConfirmedPeriod1 = oldConfirmedPeriod1 && _info.confirmedPeriod1 >= _firstPeriod;
|
||||
bool crossConfirmedPeriod2 = oldConfirmedPeriod2 && _info.confirmedPeriod2 >= _firstPeriod;
|
||||
if (!crossConfirmedPeriod1 && !crossConfirmedPeriod2) {
|
||||
return;
|
||||
}
|
||||
// Try to find already existent proper old sub stake
|
||||
uint16 previousPeriod = _currentPeriod.sub16(1);
|
||||
bool createNew = true;
|
||||
for (uint256 i = 0; i < _info.subStakes.length; i++) {
|
||||
SubStakeInfo storage subStake = _info.subStakes[i];
|
||||
if (subStake.lastPeriod == previousPeriod &&
|
||||
((crossConfirmedPeriod1 ==
|
||||
(oldConfirmedPeriod1 && _info.confirmedPeriod1 >= subStake.firstPeriod)) &&
|
||||
(crossConfirmedPeriod2 ==
|
||||
(oldConfirmedPeriod2 && _info.confirmedPeriod2 >= subStake.firstPeriod))))
|
||||
{
|
||||
subStake.lockedValue += _lockedValue;
|
||||
createNew = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (createNew) {
|
||||
saveSubStake(_info, _firstPeriod, previousPeriod, 0, _lockedValue);
|
||||
}
|
||||
}
|
||||
|
||||
//-------------Additional getters for miners info-------------
|
||||
/**
|
||||
* @notice Return the length of the array of miners
|
||||
**/
|
||||
|
@ -659,21 +1029,21 @@ contract MinersEscrow is Issuer {
|
|||
}
|
||||
|
||||
/**
|
||||
* @notice Return the length of the array of stakes
|
||||
* @notice Return the length of the array of sub stakes
|
||||
**/
|
||||
function getStakesLength(address _miner) public view returns (uint256) {
|
||||
return minerInfo[_miner].stakes.length;
|
||||
function getSubStakesLength(address _miner) public view returns (uint256) {
|
||||
return minerInfo[_miner].subStakes.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Return the information about stake
|
||||
* @notice Return the information about sub stake
|
||||
**/
|
||||
function getStakeInfo(address _miner, uint256 _index)
|
||||
function getSubStakeInfo(address _miner, uint256 _index)
|
||||
// TODO change to structure when ABIEncoderV2 is released
|
||||
// public view returns (StakeInfo)
|
||||
// public view returns (SubStakeInfo)
|
||||
public view returns (uint16 firstPeriod, uint16 lastPeriod, uint16 periods, uint256 lockedValue)
|
||||
{
|
||||
StakeInfo storage info = minerInfo[_miner].stakes[_index];
|
||||
SubStakeInfo storage info = minerInfo[_miner].subStakes[_index];
|
||||
firstPeriod = info.firstPeriod;
|
||||
lastPeriod = info.lastPeriod;
|
||||
periods = info.periods;
|
||||
|
@ -700,6 +1070,8 @@ contract MinersEscrow is Issuer {
|
|||
endPeriod = downtime.endPeriod;
|
||||
}
|
||||
|
||||
|
||||
//------------------------Upgradeable------------------------
|
||||
/**
|
||||
* @dev Get MinerInfo structure by delegatecall
|
||||
**/
|
||||
|
@ -713,13 +1085,13 @@ contract MinersEscrow is Issuer {
|
|||
}
|
||||
|
||||
/**
|
||||
* @dev Get StakeInfo structure by delegatecall
|
||||
* @dev Get SubStakeInfo structure by delegatecall
|
||||
**/
|
||||
function delegateGetStakeInfo(address _target, bytes32 _miner, uint256 _index)
|
||||
internal returns (StakeInfo memory result)
|
||||
function delegateGetSubStakeInfo(address _target, bytes32 _miner, uint256 _index)
|
||||
internal returns (SubStakeInfo memory result)
|
||||
{
|
||||
bytes32 memoryAddress = delegateGetData(
|
||||
_target, "getStakeInfo(address,uint256)", 2, _miner, bytes32(_index));
|
||||
_target, "getSubStakeInfo(address,uint256)", 2, _miner, bytes32(_index));
|
||||
assembly {
|
||||
result := memoryAddress
|
||||
}
|
||||
|
@ -747,6 +1119,7 @@ contract MinersEscrow is Issuer {
|
|||
require(delegateGet(_testTarget, "maxAllowableLockedTokens()") ==
|
||||
maxAllowableLockedTokens);
|
||||
require(address(uint160(delegateGet(_testTarget, "policyManager()"))) == address(policyManager));
|
||||
require(address(delegateGet(_testTarget, "miningAdjudicator()")) == address(miningAdjudicator));
|
||||
require(delegateGet(_testTarget, "lockedPerPeriod(uint16)",
|
||||
bytes32(bytes2(RESERVED_PERIOD))) == lockedPerPeriod[RESERVED_PERIOD]);
|
||||
|
||||
|
@ -773,14 +1146,14 @@ contract MinersEscrow is Issuer {
|
|||
downtimeToCheck.endPeriod == downtime.endPeriod);
|
||||
}
|
||||
|
||||
require(delegateGet(_testTarget, "getStakesLength(address)", miner) == info.stakes.length);
|
||||
for (uint256 i = 0; i < info.stakes.length && i < MAX_CHECKED_VALUES; i++) {
|
||||
StakeInfo storage stakeInfo = info.stakes[i];
|
||||
StakeInfo memory stakeInfoToCheck = delegateGetStakeInfo(_testTarget, miner, i);
|
||||
require(stakeInfoToCheck.firstPeriod == stakeInfo.firstPeriod &&
|
||||
stakeInfoToCheck.lastPeriod == stakeInfo.lastPeriod &&
|
||||
stakeInfoToCheck.periods == stakeInfo.periods &&
|
||||
stakeInfoToCheck.lockedValue == stakeInfo.lockedValue);
|
||||
require(delegateGet(_testTarget, "getSubStakesLength(address)", miner) == info.subStakes.length);
|
||||
for (uint256 i = 0; i < info.subStakes.length && i < MAX_CHECKED_VALUES; i++) {
|
||||
SubStakeInfo storage subStakeInfo = info.subStakes[i];
|
||||
SubStakeInfo memory subStakeInfoToCheck = delegateGetSubStakeInfo(_testTarget, miner, i);
|
||||
require(subStakeInfoToCheck.firstPeriod == subStakeInfo.firstPeriod &&
|
||||
subStakeInfoToCheck.lastPeriod == subStakeInfo.lastPeriod &&
|
||||
subStakeInfoToCheck.periods == subStakeInfo.periods &&
|
||||
subStakeInfoToCheck.lockedValue == subStakeInfo.lockedValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,487 @@
|
|||
pragma solidity ^0.5.3;
|
||||
|
||||
|
||||
import "contracts/lib/UmbralDeserializer.sol";
|
||||
import "contracts/lib/SignatureVerifier.sol";
|
||||
import "contracts/lib/Numerology.sol";
|
||||
import "contracts/MinersEscrow.sol";
|
||||
import "contracts/proxy/Upgradeable.sol";
|
||||
import "zeppelin/math/SafeMath.sol";
|
||||
import "zeppelin/math/Math.sol";
|
||||
|
||||
|
||||
/**
|
||||
* @notice Supervises miners' behavior and punishes when something's wrong.
|
||||
**/
|
||||
contract MiningAdjudicator is Upgradeable {
|
||||
using UmbralDeserializer for bytes;
|
||||
using SafeMath for uint256;
|
||||
|
||||
event CFragEvaluated(
|
||||
bytes32 indexed evaluationHash,
|
||||
address indexed miner,
|
||||
address indexed investigator,
|
||||
bool correctness
|
||||
);
|
||||
|
||||
// See parameter `u` of `UmbralParameters` class in pyUmbral
|
||||
// https://github.com/nucypher/pyUmbral/blob/master/umbral/params.py
|
||||
uint8 public constant UMBRAL_PARAMETER_U_SIGN = 0x02;
|
||||
uint256 public constant UMBRAL_PARAMETER_U_XCOORD = 0x03c98795773ff1c241fc0b1cced85e80f8366581dda5c9452175ebd41385fa1f;
|
||||
uint256 public constant UMBRAL_PARAMETER_U_YCOORD = 0x7880ed56962d7c0ae44d6f14bb53b5fe64b31ea44a41d0316f3a598778f0f936;
|
||||
|
||||
// used only for upgrading
|
||||
bytes32 constant RESERVED_CAPSULE_AND_CFRAG_BYTES = bytes32(0);
|
||||
address constant RESERVED_ADDRESS = address(0);
|
||||
|
||||
MinersEscrow public escrow;
|
||||
SignatureVerifier.HashAlgorithm public hashAlgorithm;
|
||||
uint256 public basePenalty;
|
||||
uint256 public penaltyHistoryCoefficient;
|
||||
uint256 public percentagePenaltyCoefficient;
|
||||
uint256 public rewardCoefficient;
|
||||
mapping (address => uint256) public penaltyHistory;
|
||||
mapping (bytes32 => bool) public evaluatedCFrags;
|
||||
|
||||
/**
|
||||
* @param _escrow Escrow contract
|
||||
* @param _hashAlgorithm Hashing algorithm
|
||||
* @param _basePenalty Base for the penalty calculation
|
||||
* @param _penaltyHistoryCoefficient Coefficient for calculating the penalty depending on the history
|
||||
* @param _percentagePenaltyCoefficient Coefficient for calculating the percentage penalty
|
||||
* @param _rewardCoefficient Coefficient for calculating the reward
|
||||
**/
|
||||
constructor(
|
||||
MinersEscrow _escrow,
|
||||
SignatureVerifier.HashAlgorithm _hashAlgorithm,
|
||||
uint256 _basePenalty,
|
||||
uint256 _penaltyHistoryCoefficient,
|
||||
uint256 _percentagePenaltyCoefficient,
|
||||
uint256 _rewardCoefficient
|
||||
)
|
||||
public
|
||||
{
|
||||
require(address(_escrow) != address(0) &&
|
||||
_percentagePenaltyCoefficient != 0 &&
|
||||
_rewardCoefficient != 0);
|
||||
escrow = _escrow;
|
||||
hashAlgorithm = _hashAlgorithm;
|
||||
basePenalty = _basePenalty;
|
||||
percentagePenaltyCoefficient = _percentagePenaltyCoefficient;
|
||||
penaltyHistoryCoefficient = _penaltyHistoryCoefficient;
|
||||
rewardCoefficient = _rewardCoefficient;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Submit proof that miner created wrong CFrag
|
||||
* @param _capsuleBytes Serialized capsule
|
||||
* @param _capsuleSignatureByRequester Signature of Capsule by requester
|
||||
* @param _capsuleSignatureByRequesterAndMiner Signature of Capsule by requester and miner
|
||||
* @param _cFragBytes Serialized CFrag
|
||||
* @param _cFragSignatureByMiner Signature of CFrag by miner
|
||||
* @param _requesterPublicKey Requester's public key that was used to sign Capsule
|
||||
* @param _minerPublicKey Miner's public key that was used to sign Capsule and CFrag
|
||||
* @param _minerPublicKeySignature Signature of public key by miner's eth-key
|
||||
* @param _preComputedData Pre computed data for CFrag correctness verification
|
||||
**/
|
||||
// TODO add way to slash owner of UserEscrow contract
|
||||
function evaluateCFrag(
|
||||
bytes memory _capsuleBytes,
|
||||
bytes memory _capsuleSignatureByRequester,
|
||||
bytes memory _capsuleSignatureByRequesterAndMiner,
|
||||
bytes memory _cFragBytes,
|
||||
bytes memory _cFragSignatureByMiner,
|
||||
bytes memory _requesterPublicKey,
|
||||
bytes memory _minerPublicKey,
|
||||
bytes memory _minerPublicKeySignature,
|
||||
bytes memory _preComputedData
|
||||
)
|
||||
public
|
||||
{
|
||||
require(_minerPublicKey.length == 65 && _requesterPublicKey.length == 65);
|
||||
|
||||
// Check that CFrag is not evaluated yet
|
||||
bytes32 evaluationHash = SignatureVerifier.hash(
|
||||
abi.encodePacked(_capsuleBytes, _cFragBytes), hashAlgorithm);
|
||||
require(!evaluatedCFrags[evaluationHash]);
|
||||
|
||||
// Verify requester's signature of Capsule
|
||||
bytes memory preparedPublicKey = new bytes(64);
|
||||
preparePublicKey(preparedPublicKey, _requesterPublicKey);
|
||||
require(SignatureVerifier.verify(
|
||||
_capsuleBytes, _capsuleSignatureByRequester, preparedPublicKey, hashAlgorithm));
|
||||
|
||||
// Verify miner's signatures of capsule and CFrag
|
||||
preparePublicKey(preparedPublicKey, _minerPublicKey);
|
||||
require(SignatureVerifier.verify(
|
||||
_capsuleSignatureByRequester, _capsuleSignatureByRequesterAndMiner, preparedPublicKey, hashAlgorithm));
|
||||
require(SignatureVerifier.verify(
|
||||
_cFragBytes, _cFragSignatureByMiner, preparedPublicKey, hashAlgorithm));
|
||||
|
||||
// Extract miner's address and check that is real miner
|
||||
address miner = SignatureVerifier.recover(
|
||||
SignatureVerifier.hash(_minerPublicKey, hashAlgorithm), _minerPublicKeySignature);
|
||||
// Check that miner can be slashed
|
||||
(uint256 minerValue,,,) = escrow.minerInfo(miner);
|
||||
require(minerValue > 0);
|
||||
|
||||
// Verify correctness of re-encryption
|
||||
evaluatedCFrags[evaluationHash] = true;
|
||||
if (!isCapsuleFragCorrect(_capsuleBytes, _cFragBytes, _preComputedData)) {
|
||||
(uint256 penalty, uint256 reward) = calculatePenaltyAndReward(miner, minerValue);
|
||||
escrow.slashMiner(miner, penalty, msg.sender, reward);
|
||||
emit CFragEvaluated(evaluationHash, miner, msg.sender, false);
|
||||
} else {
|
||||
emit CFragEvaluated(evaluationHash, miner, msg.sender, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Calculate penalty to the miner and reward to the investigator
|
||||
* @param _miner Miner's address
|
||||
* @param _minerValue Amount of tokens that belong to the miner
|
||||
**/
|
||||
function calculatePenaltyAndReward(address _miner, uint256 _minerValue)
|
||||
internal returns (uint256 penalty, uint256 reward)
|
||||
{
|
||||
penalty = basePenalty.add(penaltyHistoryCoefficient.mul(penaltyHistory[_miner]));
|
||||
penalty = Math.min(penalty, _minerValue.div(percentagePenaltyCoefficient));
|
||||
reward = penalty.div(rewardCoefficient);
|
||||
// TODO add maximum condition or other overflow protection or other penalty condition
|
||||
penaltyHistory[_miner] = penaltyHistory[_miner].add(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Prepare public key before verification (cut the first byte)
|
||||
**/
|
||||
function preparePublicKey(bytes memory _preparedPublicKey, bytes memory _publicKey)
|
||||
public pure
|
||||
{
|
||||
assembly {
|
||||
let destination := add(_preparedPublicKey, 32) // skip array length
|
||||
let source := add(_publicKey, 33) // skip array length and first byte in the array
|
||||
mstore(destination, mload(source))
|
||||
mstore(add(destination, 32), mload(add(source, 32)))
|
||||
}
|
||||
}
|
||||
|
||||
// This function was introduced just to facilitate debugging and testing
|
||||
// of Alice's address extraction from her signature
|
||||
// TODO: Consider moving this somewhere else, or even removing it
|
||||
function aliceAddress(
|
||||
bytes memory _cFragBytes,
|
||||
bytes memory _precomputedBytes
|
||||
)
|
||||
public pure
|
||||
returns (address)
|
||||
{
|
||||
UmbralDeserializer.CapsuleFrag memory _cFrag = _cFragBytes.toCapsuleFrag();
|
||||
UmbralDeserializer.PreComputedData memory _precomputed = _precomputedBytes.toPreComputedData();
|
||||
|
||||
// Extract Alice's address and check that it corresponds to the one provided
|
||||
address alicesAddress = SignatureVerifier.recover(
|
||||
_precomputed.hashedKFragValidityMessage,
|
||||
abi.encodePacked(_cFrag.proof.kFragSignature, _precomputed.kfragSignatureV)
|
||||
);
|
||||
return alicesAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Check correctness of re-encryption
|
||||
* @param _capsuleBytes Capsule
|
||||
* @param _cFragBytes Capsule frag
|
||||
* @param _precomputedBytes Additional precomputed data
|
||||
**/
|
||||
function isCapsuleFragCorrect(
|
||||
bytes memory _capsuleBytes,
|
||||
bytes memory _cFragBytes,
|
||||
bytes memory _precomputedBytes
|
||||
)
|
||||
public pure returns (bool)
|
||||
{
|
||||
UmbralDeserializer.Capsule memory _capsule = _capsuleBytes.toCapsule();
|
||||
UmbralDeserializer.CapsuleFrag memory _cFrag = _cFragBytes.toCapsuleFrag();
|
||||
UmbralDeserializer.PreComputedData memory _precomputed = _precomputedBytes.toPreComputedData();
|
||||
|
||||
// Extract Alice's address and check that it corresponds to the one provided
|
||||
address alicesAddress = SignatureVerifier.recover(
|
||||
_precomputed.hashedKFragValidityMessage,
|
||||
abi.encodePacked(_cFrag.proof.kFragSignature, _precomputed.kfragSignatureV)
|
||||
);
|
||||
require(alicesAddress == _precomputed.alicesKeyAsAddress, "Bad KFrag signature");
|
||||
|
||||
// Compute proof's challenge scalar h, used in all ZKP verification equations
|
||||
uint256 h = computeProofChallengeScalar(_capsuleBytes, _cFragBytes);
|
||||
|
||||
//////
|
||||
// Verifying 1st equation: z*E == h*E_1 + E_2
|
||||
//////
|
||||
|
||||
// Input validation: E
|
||||
require(Numerology.check_compressed_point(
|
||||
_capsule.pointE.sign,
|
||||
_capsule.pointE.xCoord,
|
||||
_precomputed.pointEyCoord
|
||||
));
|
||||
|
||||
// Input validation: z*E
|
||||
require(Numerology.is_on_curve(_precomputed.pointEZxCoord, _precomputed.pointEZyCoord));
|
||||
bool left_hand_element_is_correct = Numerology.ecmulVerify(
|
||||
_capsule.pointE.xCoord, // E_x
|
||||
_precomputed.pointEyCoord, // E_y
|
||||
_cFrag.proof.bnSig, // z
|
||||
_precomputed.pointEZxCoord, // zE_x
|
||||
_precomputed.pointEZyCoord // zE_y
|
||||
);
|
||||
|
||||
// Input validation: E1
|
||||
require(Numerology.check_compressed_point(
|
||||
_cFrag.pointE1.sign, // E1_sign
|
||||
_cFrag.pointE1.xCoord, // E1_x
|
||||
_precomputed.pointE1yCoord // E1_y
|
||||
));
|
||||
|
||||
// Input validation: h*E_1
|
||||
require(Numerology.is_on_curve(_precomputed.pointE1HxCoord, _precomputed.pointE1HyCoord));
|
||||
bool rhs_element_is_correct = Numerology.ecmulVerify(
|
||||
_cFrag.pointE1.xCoord, // E1_x
|
||||
_precomputed.pointE1yCoord, // E1_y
|
||||
h,
|
||||
_precomputed.pointE1HxCoord, // hE1_x
|
||||
_precomputed.pointE1HyCoord // hE1_y
|
||||
);
|
||||
|
||||
// Input validation: E_2
|
||||
require(Numerology.check_compressed_point(
|
||||
_cFrag.proof.pointE2.sign, // E2_sign
|
||||
_cFrag.proof.pointE2.xCoord, // E2_x
|
||||
_precomputed.pointE2yCoord // E2_y
|
||||
));
|
||||
|
||||
bool equation_holds = Numerology.eqAffineJacobian(
|
||||
[_precomputed.pointEZxCoord, _precomputed.pointEZyCoord],
|
||||
Numerology.addAffineJacobian(
|
||||
[_cFrag.proof.pointE2.xCoord, _precomputed.pointE2yCoord],
|
||||
[_precomputed.pointE1HxCoord, _precomputed.pointE1HyCoord]
|
||||
)
|
||||
);
|
||||
|
||||
if (!(left_hand_element_is_correct && rhs_element_is_correct && equation_holds)){
|
||||
return false;
|
||||
}
|
||||
|
||||
//////
|
||||
// Verifying 2nd equation: z*V == h*V_1 + V_2
|
||||
//////
|
||||
|
||||
// Input validation: V
|
||||
require(Numerology.check_compressed_point(
|
||||
_capsule.pointV.sign,
|
||||
_capsule.pointV.xCoord,
|
||||
_precomputed.pointVyCoord
|
||||
));
|
||||
|
||||
// Input validation: z*V
|
||||
require(Numerology.is_on_curve(_precomputed.pointVZxCoord, _precomputed.pointVZyCoord));
|
||||
left_hand_element_is_correct = Numerology.ecmulVerify(
|
||||
_capsule.pointV.xCoord, // V_x
|
||||
_precomputed.pointVyCoord, // V_y
|
||||
_cFrag.proof.bnSig, // z
|
||||
_precomputed.pointVZxCoord, // zV_x
|
||||
_precomputed.pointVZyCoord // zV_y
|
||||
);
|
||||
|
||||
// Input validation: V1
|
||||
require(Numerology.check_compressed_point(
|
||||
_cFrag.pointV1.sign, // V1_sign
|
||||
_cFrag.pointV1.xCoord, // V1_x
|
||||
_precomputed.pointV1yCoord // V1_y
|
||||
));
|
||||
|
||||
// Input validation: h*V_1
|
||||
require(Numerology.is_on_curve(_precomputed.pointV1HxCoord, _precomputed.pointV1HyCoord));
|
||||
rhs_element_is_correct = Numerology.ecmulVerify(
|
||||
_cFrag.pointV1.xCoord, // V1_x
|
||||
_precomputed.pointV1yCoord, // V1_y
|
||||
h,
|
||||
_precomputed.pointV1HxCoord, // h*V1_x
|
||||
_precomputed.pointV1HyCoord // h*V1_y
|
||||
);
|
||||
|
||||
// Input validation: V_2
|
||||
require(Numerology.check_compressed_point(
|
||||
_cFrag.proof.pointV2.sign, // V2_sign
|
||||
_cFrag.proof.pointV2.xCoord, // V2_x
|
||||
_precomputed.pointV2yCoord // V2_y
|
||||
));
|
||||
|
||||
equation_holds = Numerology.eqAffineJacobian(
|
||||
[_precomputed.pointVZxCoord, _precomputed.pointVZyCoord],
|
||||
Numerology.addAffineJacobian(
|
||||
[_cFrag.proof.pointV2.xCoord, _precomputed.pointV2yCoord],
|
||||
[_precomputed.pointV1HxCoord, _precomputed.pointV1HyCoord]
|
||||
)
|
||||
);
|
||||
|
||||
if (!(left_hand_element_is_correct && rhs_element_is_correct && equation_holds)){
|
||||
return false;
|
||||
}
|
||||
|
||||
//////
|
||||
// Verifying 3rd equation: z*U == h*U_1 + U_2
|
||||
//////
|
||||
|
||||
// We don't have to validate U since it's fixed and hard-coded
|
||||
|
||||
// Input validation: z*U
|
||||
require(Numerology.is_on_curve(_precomputed.pointUZxCoord, _precomputed.pointUZyCoord));
|
||||
left_hand_element_is_correct = Numerology.ecmulVerify(
|
||||
UMBRAL_PARAMETER_U_XCOORD, // U_x
|
||||
UMBRAL_PARAMETER_U_YCOORD, // U_y
|
||||
_cFrag.proof.bnSig, // z
|
||||
_precomputed.pointUZxCoord, // zU_x
|
||||
_precomputed.pointUZyCoord // zU_y
|
||||
);
|
||||
|
||||
// Input validation: U_1 (a.k.a. KFragCommitment)
|
||||
require(Numerology.check_compressed_point(
|
||||
_cFrag.proof.pointKFragCommitment.sign, // U1_sign
|
||||
_cFrag.proof.pointKFragCommitment.xCoord, // U1_x
|
||||
_precomputed.pointU1yCoord // U1_y
|
||||
));
|
||||
|
||||
// Input validation: h*U_1
|
||||
require(Numerology.is_on_curve(_precomputed.pointU1HxCoord, _precomputed.pointU1HyCoord));
|
||||
rhs_element_is_correct = Numerology.ecmulVerify(
|
||||
_cFrag.proof.pointKFragCommitment.xCoord, // U1_x
|
||||
_precomputed.pointU1yCoord, // U1_y
|
||||
h,
|
||||
_precomputed.pointU1HxCoord, // h*V1_x
|
||||
_precomputed.pointU1HyCoord // h*V1_y
|
||||
);
|
||||
|
||||
// Input validation: U_2 (a.k.a. KFragPok ("proof of knowledge"))
|
||||
require(Numerology.check_compressed_point(
|
||||
_cFrag.proof.pointKFragPok.sign, // U2_sign
|
||||
_cFrag.proof.pointKFragPok.xCoord, // U2_x
|
||||
_precomputed.pointU2yCoord // U2_y
|
||||
));
|
||||
|
||||
equation_holds = Numerology.eqAffineJacobian(
|
||||
[_precomputed.pointUZxCoord, _precomputed.pointUZyCoord],
|
||||
Numerology.addAffineJacobian(
|
||||
[_cFrag.proof.pointKFragPok.xCoord, _precomputed.pointU2yCoord],
|
||||
[_precomputed.pointU1HxCoord, _precomputed.pointU1HyCoord]
|
||||
)
|
||||
);
|
||||
|
||||
return left_hand_element_is_correct && rhs_element_is_correct && equation_holds;
|
||||
}
|
||||
|
||||
|
||||
function computeProofChallengeScalar(
|
||||
bytes memory _capsuleBytes,
|
||||
bytes memory _cFragBytes
|
||||
) public pure returns (uint256) {
|
||||
|
||||
UmbralDeserializer.Capsule memory _capsule = _capsuleBytes.toCapsule();
|
||||
UmbralDeserializer.CapsuleFrag memory _cFrag = _cFragBytes.toCapsuleFrag();
|
||||
|
||||
// Compute h = hash_to_bignum(e, e1, e2, v, v1, v2, u, u1, u2, metadata)
|
||||
bytes memory hashInput = abi.encodePacked(
|
||||
// Point E
|
||||
_capsule.pointE.sign,
|
||||
_capsule.pointE.xCoord,
|
||||
// Point E1
|
||||
_cFrag.pointE1.sign,
|
||||
_cFrag.pointE1.xCoord,
|
||||
// Point E2
|
||||
_cFrag.proof.pointE2.sign,
|
||||
_cFrag.proof.pointE2.xCoord
|
||||
);
|
||||
|
||||
hashInput = abi.encodePacked(
|
||||
hashInput,
|
||||
// Point V
|
||||
_capsule.pointV.sign,
|
||||
_capsule.pointV.xCoord,
|
||||
// Point V1
|
||||
_cFrag.pointV1.sign,
|
||||
_cFrag.pointV1.xCoord,
|
||||
// Point V2
|
||||
_cFrag.proof.pointV2.sign,
|
||||
_cFrag.proof.pointV2.xCoord
|
||||
);
|
||||
|
||||
hashInput = abi.encodePacked(
|
||||
hashInput,
|
||||
// Point U
|
||||
bytes1(UMBRAL_PARAMETER_U_SIGN),
|
||||
bytes32(UMBRAL_PARAMETER_U_XCOORD),
|
||||
// Point U1
|
||||
_cFrag.proof.pointKFragCommitment.sign,
|
||||
_cFrag.proof.pointKFragCommitment.xCoord,
|
||||
// Point U2
|
||||
_cFrag.proof.pointKFragPok.sign,
|
||||
_cFrag.proof.pointKFragPok.xCoord,
|
||||
// Re-encryption metadata
|
||||
_cFrag.proof.metadata
|
||||
);
|
||||
|
||||
uint256 h = extendedKeccakToBN(hashInput);
|
||||
return h;
|
||||
|
||||
}
|
||||
|
||||
// TODO: Consider changing to internal
|
||||
function extendedKeccakToBN (bytes memory _data) public pure returns (uint256) {
|
||||
|
||||
bytes32 upper;
|
||||
bytes32 lower;
|
||||
|
||||
// Umbral prepends to the data a customization string of 64-bytes.
|
||||
// In the case of hash_to_curvebn is 'hash_to_curvebn', padded with zeroes.
|
||||
bytes memory input = abi.encodePacked(bytes32("hash_to_curvebn"), bytes32(0x00), _data);
|
||||
|
||||
(upper, lower) = (keccak256(abi.encodePacked(uint8(0x00), input)),
|
||||
keccak256(abi.encodePacked(uint8(0x01), input)));
|
||||
|
||||
// Let n be the order of secp256k1's group (n = 2^256 - 0x1000003D1)
|
||||
// n_minus_1 = n - 1
|
||||
// delta = 2^256 mod n_minus_1
|
||||
uint256 delta = 0x14551231950b75fc4402da1732fc9bec0;
|
||||
uint256 n_minus_1 = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140;
|
||||
|
||||
uint256 upper_half = mulmod(uint256(upper), delta, n_minus_1);
|
||||
return 1 + addmod(upper_half, uint256(lower), n_minus_1);
|
||||
}
|
||||
|
||||
function verifyState(address _testTarget) public onlyOwner {
|
||||
require(address(delegateGet(_testTarget, "escrow()")) == address(escrow));
|
||||
require(SignatureVerifier.HashAlgorithm(uint256(delegateGet(_testTarget, "hashAlgorithm()"))) == hashAlgorithm);
|
||||
require(delegateGet(_testTarget, "basePenalty()") == basePenalty);
|
||||
require(delegateGet(_testTarget, "penaltyHistoryCoefficient()") == penaltyHistoryCoefficient);
|
||||
require(delegateGet(_testTarget, "percentagePenaltyCoefficient()") == percentagePenaltyCoefficient);
|
||||
require(delegateGet(_testTarget, "rewardCoefficient()") == rewardCoefficient);
|
||||
require(delegateGet(_testTarget, "penaltyHistory(address)", bytes32(bytes20(RESERVED_ADDRESS))) ==
|
||||
penaltyHistory[RESERVED_ADDRESS]);
|
||||
bytes32 evaluationCFragHash = SignatureVerifier.hash(
|
||||
abi.encodePacked(RESERVED_CAPSULE_AND_CFRAG_BYTES), hashAlgorithm);
|
||||
require(delegateGet(_testTarget, "evaluatedCFrags(bytes32)", evaluationCFragHash) != 0);
|
||||
}
|
||||
|
||||
function finishUpgrade(address _target) public onlyOwner {
|
||||
MiningAdjudicator targetContract = MiningAdjudicator(_target);
|
||||
escrow = targetContract.escrow();
|
||||
hashAlgorithm = targetContract.hashAlgorithm();
|
||||
basePenalty = targetContract.basePenalty();
|
||||
penaltyHistoryCoefficient = targetContract.penaltyHistoryCoefficient();
|
||||
percentagePenaltyCoefficient = targetContract.percentagePenaltyCoefficient();
|
||||
rewardCoefficient = targetContract.rewardCoefficient();
|
||||
// preparation for the verifyState method
|
||||
bytes32 evaluationCFragHash = SignatureVerifier.hash(
|
||||
abi.encodePacked(RESERVED_CAPSULE_AND_CFRAG_BYTES), hashAlgorithm);
|
||||
evaluatedCFrags[evaluationCFragHash] = true;
|
||||
penaltyHistory[RESERVED_ADDRESS] = 123;
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ import "zeppelin/token/ERC20/ERC20Detailed.sol";
|
|||
|
||||
/**
|
||||
* @title NuCypher token
|
||||
* @notice ERC20 token which can be burned by their owners
|
||||
* @notice ERC20 token
|
||||
* @dev Optional approveAndCall() functionality to notify a contract if an approve() has occurred.
|
||||
**/
|
||||
contract NuCypherToken is ERC20, ERC20Detailed('NuCypher', 'NU', 18) {
|
||||
|
|
|
@ -193,7 +193,7 @@ contract PolicyManager is Upgradeable {
|
|||
return;
|
||||
}
|
||||
for (uint16 i = node.lastMinedPeriod + 1; i <= _period; i++) {
|
||||
node.rewardRate = node.rewardRate.add(node.rewardDelta[i]);
|
||||
node.rewardRate = node.rewardRate.addSigned(node.rewardDelta[i]);
|
||||
}
|
||||
node.lastMinedPeriod = _period;
|
||||
node.reward = node.reward.add(node.rewardRate);
|
||||
|
@ -320,9 +320,6 @@ contract PolicyManager is Upgradeable {
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (refundValue > 0) {
|
||||
msg.sender.transfer(refundValue);
|
||||
}
|
||||
if (_node == RESERVED_NODE) {
|
||||
if (numberOfActive == 0) {
|
||||
policy.disabled = true;
|
||||
|
@ -334,6 +331,9 @@ contract PolicyManager is Upgradeable {
|
|||
// arrangement not found
|
||||
require(i < policy.arrangements.length);
|
||||
}
|
||||
if (refundValue > 0) {
|
||||
msg.sender.transfer(refundValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -551,4 +551,4 @@ contract PolicyManager is Upgradeable {
|
|||
nodeInfo.rewardDelta[11] = 55;
|
||||
nodeInfo.minRewardRate = 777;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -129,4 +129,4 @@ contract UserEscrow is Ownable {
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ library AdditionalMath {
|
|||
/**
|
||||
* @dev Adds signed value to unsigned value, throws on overflow.
|
||||
*/
|
||||
function add(uint256 a, int256 b) internal pure returns (uint256) {
|
||||
function addSigned(uint256 a, int256 b) internal pure returns (uint256) {
|
||||
if (b >= 0) {
|
||||
return a.add(uint256(b));
|
||||
} else {
|
||||
|
@ -57,7 +57,7 @@ library AdditionalMath {
|
|||
/**
|
||||
* @dev Subtracts signed value from unsigned value, throws on overflow.
|
||||
*/
|
||||
function sub(uint256 a, int256 b) internal pure returns (uint256) {
|
||||
function subSigned(uint256 a, int256 b) internal pure returns (uint256) {
|
||||
if (b >= 0) {
|
||||
return a.sub(uint256(b));
|
||||
} else {
|
||||
|
@ -93,4 +93,26 @@ library AdditionalMath {
|
|||
assert(b <= a);
|
||||
return a - b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Adds signed value to unsigned value, throws on overflow.
|
||||
*/
|
||||
function addSigned16(uint16 a, int16 b) internal pure returns (uint16) {
|
||||
if (b >= 0) {
|
||||
return add16(a, uint16(b));
|
||||
} else {
|
||||
return sub16(a, uint16(-b));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Subtracts signed value from unsigned value, throws on overflow.
|
||||
*/
|
||||
function subSigned16(uint16 a, int16 b) internal pure returns (uint16) {
|
||||
if (b >= 0) {
|
||||
return sub16(a, uint16(b));
|
||||
} else {
|
||||
return add16(a, uint16(-b));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,472 @@
|
|||
pragma solidity ^0.5.3;
|
||||
|
||||
|
||||
/// @title Numerology: A Solidity library for fast ECC arithmetics using curve secp256k1
|
||||
/// @author David Nuñez (david@nucypher.com)
|
||||
library Numerology {
|
||||
|
||||
uint256 constant fieldOrder = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F;
|
||||
|
||||
/// @notice Equality test of two points in Jacobian coordinates
|
||||
/// @param P An EC point in Jacobian coordinates
|
||||
/// @param Q An EC point in Jacobian coordinates
|
||||
/// @return true if P and Q represent the same point in affine coordinates; false otherwise
|
||||
function eqJacobian(
|
||||
uint256[3] memory P,
|
||||
uint256[3] memory Q
|
||||
) internal pure returns(bool) {
|
||||
uint256 p = fieldOrder;
|
||||
|
||||
uint256 Qz = Q[2];
|
||||
uint256 Pz = P[2];
|
||||
if(Pz == 0){
|
||||
return Qz == 0; // P and Q are both zero.
|
||||
} else if(Qz == 0){
|
||||
return false; // Q is zero but P isn't.
|
||||
}
|
||||
|
||||
// Now we're sure none of them is zero
|
||||
|
||||
uint256 Q_z_squared = mulmod(Qz, Qz, p);
|
||||
uint256 P_z_squared = mulmod(Pz, Pz, p);
|
||||
if (mulmod(P[0], Q_z_squared, p) != mulmod(Q[0], P_z_squared, p)){
|
||||
return false;
|
||||
}
|
||||
|
||||
uint256 Q_z_cubed = mulmod(Q_z_squared, Qz, p);
|
||||
uint256 P_z_cubed = mulmod(P_z_squared, Pz, p);
|
||||
return mulmod(P[1], Q_z_cubed, p) == mulmod(Q[1], P_z_cubed, p);
|
||||
|
||||
}
|
||||
|
||||
/// @notice Equality test of two points, in affine and Jacobian coordinates respectively
|
||||
/// @param P An EC point in affine coordinates
|
||||
/// @param Q An EC point in Jacobian coordinates
|
||||
/// @return true if P and Q represent the same point in affine coordinates; false otherwise
|
||||
function eqAffineJacobian(
|
||||
uint256[2] memory P,
|
||||
uint256[3] memory Q
|
||||
) internal pure returns(bool){
|
||||
uint256 Qz = Q[2];
|
||||
if(Qz == 0){
|
||||
return false; // Q is zero but P isn't.
|
||||
}
|
||||
|
||||
uint256 p = fieldOrder;
|
||||
uint256 Q_z_squared = mulmod(Qz, Qz, p);
|
||||
return mulmod(P[0], Q_z_squared, p) == Q[0] && mulmod(P[1], mulmod(Q_z_squared, Qz, p), p) == Q[1];
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// @notice Addition of two points in Jacobian coordinates
|
||||
/// @dev Based on the addition formulas from http://www.hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/addition/add-2001-b.op3
|
||||
/// @param P An EC point in Jacobian coordinates
|
||||
/// @param Q An EC point in Jacobian coordinates
|
||||
/// @return An EC point in Jacobian coordinates with the sum, represented by an array of 3 uint256
|
||||
function addJac(
|
||||
uint256[3] memory P,
|
||||
uint256[3] memory Q
|
||||
) internal pure returns (uint256[3] memory R) {
|
||||
|
||||
if(P[2] == 0){
|
||||
return Q;
|
||||
} else if(Q[2] == 0){
|
||||
return P;
|
||||
}
|
||||
|
||||
uint256 p = fieldOrder;
|
||||
uint256 zz1 = mulmod(P[2], P[2], p);
|
||||
uint256 zz2 = mulmod(Q[2], Q[2], p);
|
||||
uint256 a = mulmod(P[0], zz2, p);
|
||||
uint256 c = mulmod(P[1], mulmod(Q[2], zz2, p), p);
|
||||
uint256 t0 = mulmod(Q[0], zz1, p);
|
||||
uint256 t1 = mulmod(Q[1], mulmod(P[2], zz1, p), p);
|
||||
|
||||
if ((a == t0) && (c == t1)){
|
||||
return doubleJacobian(P);
|
||||
}
|
||||
uint256 d = addmod(t1, p-c, p); // d = t1 - c
|
||||
uint256[3] memory b;
|
||||
b[0] = addmod(t0, p-a, p); // b = t0 - a
|
||||
b[1] = mulmod(b[0], b[0], p); // e = b^2
|
||||
b[2] = mulmod(b[1], b[0], p); // f = b^3
|
||||
uint256 g = mulmod(a, b[1], p);
|
||||
R[0] = addmod(mulmod(d, d, p), p-addmod(mulmod(2, g, p), b[2], p), p);
|
||||
R[1] = addmod(mulmod(d, addmod(g, p-R[0], p), p), p-mulmod(c, b[2], p), p);
|
||||
R[2] = mulmod(b[0], mulmod(P[2], Q[2], p), p);
|
||||
}
|
||||
|
||||
/// @notice Addition of two points in Jacobian coordinates, placing the result in the first point
|
||||
/// @dev Based on the addition formulas from http://www.hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/addition/add-2001-b.op3
|
||||
/// @param P An EC point in Jacobian coordinates. The result is returned here.
|
||||
/// @param Q An EC point in Jacobian coordinates
|
||||
function addJacobianMutates(
|
||||
uint[3] memory P,
|
||||
uint[3] memory Q
|
||||
) pure internal {
|
||||
|
||||
uint256 Pz = P[2];
|
||||
uint256 Qz = Q[2];
|
||||
|
||||
if(Pz == 0){
|
||||
P[0] = Q[0];
|
||||
P[1] = Q[1];
|
||||
P[2] = Qz;
|
||||
return;
|
||||
} else if(Qz == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
uint256 p = fieldOrder;
|
||||
|
||||
uint256 zz = mulmod(Pz, Pz, p);
|
||||
uint256 t0 = mulmod(Q[0], zz, p);
|
||||
uint256 t1 = mulmod(Q[1], mulmod(Pz, zz, p), p);
|
||||
|
||||
zz = mulmod(Qz, Qz, p);
|
||||
uint256 a = mulmod(P[0], zz, p);
|
||||
uint256 c = mulmod(P[1], mulmod(Qz, zz, p), p);
|
||||
|
||||
|
||||
if ((a == t0) && (c == t1)){
|
||||
doubleMutates(P);
|
||||
return;
|
||||
}
|
||||
|
||||
t1 = addmod(t1, p-c, p); // d = t1 - c
|
||||
uint256 b = addmod(t0, p-a, p); // b = t0 - a
|
||||
uint256 e = mulmod(b, b, p); // e = b^2
|
||||
t0 = mulmod(a, e, p); // t0 is actually "g"
|
||||
e = mulmod(e, b, p); // f = b^3 (we will re-use the variable e )
|
||||
uint256 temp = addmod(mulmod(t1, t1, p), p-addmod(mulmod(2, t0, p), e, p), p);
|
||||
P[0] = temp;
|
||||
temp = mulmod(t1, addmod(t0, p-temp, p), p);
|
||||
P[1] = addmod(temp, p-mulmod(c, e, p), p);
|
||||
P[2] = mulmod(b, mulmod(Pz, Qz, p), p);
|
||||
}
|
||||
|
||||
/// @notice Subtraction of two points in Jacobian coordinates, placing the result in the first point
|
||||
/// @dev Based on the addition formulas from http://www.hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/addition/add-2001-b.op3
|
||||
/// @param P An EC point in Jacobian coordinates. The result is returned here.
|
||||
/// @param Q An EC point in Jacobian coordinates
|
||||
function subJacobianMutates(
|
||||
uint[3] memory P,
|
||||
uint[3] memory Q
|
||||
) pure internal {
|
||||
uint256 Pz = P[2];
|
||||
uint256 Qz = Q[2];
|
||||
uint256 p = fieldOrder;
|
||||
|
||||
if(Pz == 0){
|
||||
P[0] = Q[0];
|
||||
P[1] = p - Q[1];
|
||||
P[2] = Qz;
|
||||
return;
|
||||
} else if(Qz == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
uint256 zz = mulmod(Pz, Pz, p);
|
||||
uint256 t0 = mulmod(Q[0], zz, p);
|
||||
uint256 t1 = mulmod(p - Q[1], mulmod(Pz, zz, p), p);
|
||||
|
||||
zz = mulmod(Qz, Qz, p);
|
||||
uint256 a = mulmod(P[0], zz, p);
|
||||
uint256 c = mulmod(P[1], mulmod(Qz, zz, p), p);
|
||||
|
||||
if ((a == t0) && (c == t1)){
|
||||
P[2] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
t1 = addmod(t1, p-c, p); // d = t1 - c
|
||||
uint256 b = addmod(t0, p-a, p); // b = t0 - a
|
||||
uint256 e = mulmod(b, b, p); // e = b^2
|
||||
t0 = mulmod(a, e, p); // t0 is actually "g"
|
||||
e = mulmod(e, b, p); // f = b^3 (we will re-use the variable e )
|
||||
uint256 temp = addmod(mulmod(t1, t1, p), p-addmod(mulmod(2, t0, p), e, p), p);
|
||||
P[0] = temp;
|
||||
temp = mulmod(t1, addmod(t0, p-temp, p), p);
|
||||
P[1] = addmod(temp, p-mulmod(c, e, p), p);
|
||||
P[2] = mulmod(b, mulmod(Pz, Qz, p), p);
|
||||
}
|
||||
|
||||
/// @notice Adds two points in affine coordinates, with the result in Jacobian
|
||||
/// @dev Based on the addition formulas from http://www.hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/addition/add-2001-b.op3
|
||||
/// @param P An EC point in affine coordinates
|
||||
/// @param Q An EC point in affine coordinates
|
||||
/// @return An EC point in Jacobian coordinates with the sum, represented by an array of 3 uint256
|
||||
function addAffineJacobian(
|
||||
uint[2] memory P,
|
||||
uint[2] memory Q
|
||||
) internal pure returns (uint[3] memory R) {
|
||||
|
||||
uint256 p = fieldOrder;
|
||||
uint256 a = P[0];
|
||||
uint256 c = P[1];
|
||||
uint256 t0 = Q[0];
|
||||
uint256 t1 = Q[1];
|
||||
|
||||
if ((a == t0) && (c == t1)){
|
||||
return doubleJacobian([a, c, 1]);
|
||||
}
|
||||
uint256 d = addmod(t1, p-c, p); // d = t1 - c
|
||||
uint256 b = addmod(t0, p-a, p); // b = t0 - a
|
||||
uint256 e = mulmod(b, b, p); // e = b^2
|
||||
uint256 f = mulmod(e, b, p); // f = b^3
|
||||
uint256 g = mulmod(a, e, p);
|
||||
R[0] = addmod(mulmod(d, d, p), p-addmod(mulmod(2, g, p), f, p), p);
|
||||
R[1] = addmod(mulmod(d, addmod(g, p-R[0], p), p), p-mulmod(c, f, p), p);
|
||||
R[2] = b;
|
||||
}
|
||||
|
||||
/// @notice Point doubling in Jacobian coordinates
|
||||
/// @param P An EC point in Jacobian coordinates.
|
||||
/// @return An EC point in Jacobian coordinates
|
||||
function doubleJacobian(uint[3] memory P) internal pure returns (uint[3] memory Q) {
|
||||
uint256 z = P[2];
|
||||
if (z == 0)
|
||||
return Q;
|
||||
uint256 p = fieldOrder;
|
||||
uint256 x = P[0];
|
||||
uint256 _2y = mulmod(2, P[1], p);
|
||||
uint256 _4yy = mulmod(_2y, _2y, p);
|
||||
uint256 s = mulmod(_4yy, x, p);
|
||||
uint256 m = mulmod(3, mulmod(x, x, p), p);
|
||||
uint256 t = addmod(mulmod(m, m, p), mulmod(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2d, s, p),p);
|
||||
Q[0] = t;
|
||||
Q[1] = addmod(mulmod(m, addmod(s, p - t, p), p), mulmod(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffe17, mulmod(_4yy, _4yy, p), p), p);
|
||||
Q[2] = mulmod(_2y, z, p);
|
||||
}
|
||||
|
||||
/// @notice Point doubling in Jacobian coordinates, placing the result in the first point
|
||||
/// @param P An EC point in Jacobian coordinates. The result is also stored here.
|
||||
function doubleMutates(uint[3] memory P) internal pure {
|
||||
uint256 z = P[2];
|
||||
if (z == 0)
|
||||
return;
|
||||
uint256 p = fieldOrder;
|
||||
uint256 x = P[0];
|
||||
uint256 _2y = mulmod(2, P[1], p);
|
||||
uint256 _4yy = mulmod(_2y, _2y, p);
|
||||
uint256 s = mulmod(_4yy, x, p);
|
||||
uint256 m = mulmod(3, mulmod(x, x, p), p);
|
||||
uint256 t = addmod(mulmod(m, m, p), mulmod(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2d, s, p),p);
|
||||
P[0] = t;
|
||||
P[1] = addmod(mulmod(m, addmod(s, p - t, p), p), mulmod(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffe17, mulmod(_4yy, _4yy, p), p), p);
|
||||
P[2] = mulmod(_2y, z, p);
|
||||
}
|
||||
|
||||
function _lookup_sim_mul(
|
||||
uint256[3][4][4] memory iP,
|
||||
uint256[4] memory P_Q
|
||||
) internal pure {
|
||||
uint256 p = fieldOrder;
|
||||
uint256 beta = 0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee;
|
||||
|
||||
uint256[3][4] memory iPj;
|
||||
uint256[3] memory double;
|
||||
|
||||
// P1 Lookup Table
|
||||
iPj = iP[0];
|
||||
iPj[0] = [P_Q[0], P_Q[1], 1]; // P1
|
||||
|
||||
double = doubleJacobian(iPj[0]);
|
||||
iPj[1] = addJac(double, iPj[0]);
|
||||
iPj[2] = addJac(double, iPj[1]);
|
||||
iPj[3] = addJac(double, iPj[2]);
|
||||
|
||||
// P2 Lookup Table
|
||||
iP[1][0] = [mulmod(beta, P_Q[0], p), P_Q[1], 1]; // P2
|
||||
|
||||
iP[1][1] = [mulmod(beta, iPj[1][0], p), iPj[1][1], iPj[1][2]];
|
||||
iP[1][2] = [mulmod(beta, iPj[2][0], p), iPj[2][1], iPj[2][2]];
|
||||
iP[1][3] = [mulmod(beta, iPj[3][0], p), iPj[3][1], iPj[3][2]];
|
||||
|
||||
// Q1 Lookup Table
|
||||
iPj = iP[2];
|
||||
iPj[0] = [P_Q[2], P_Q[3], 1]; // Q1
|
||||
|
||||
double = doubleJacobian(iPj[0]);
|
||||
iPj[1] = addJac(double, iPj[0]);
|
||||
iPj[2] = addJac(double, iPj[1]);
|
||||
iPj[3] = addJac(double, iPj[2]);
|
||||
|
||||
// Q2 Lookup Table
|
||||
iP[3][0] = [mulmod(beta, P_Q[2], p), P_Q[3], 1]; // P2
|
||||
|
||||
iP[3][1] = [mulmod(beta, iPj[1][0], p), iPj[1][1], iPj[1][2]];
|
||||
iP[3][2] = [mulmod(beta, iPj[2][0], p), iPj[2][1], iPj[2][2]];
|
||||
iP[3][3] = [mulmod(beta, iPj[3][0], p), iPj[3][1], iPj[3][2]];
|
||||
}
|
||||
|
||||
/// @notice Computes the WNAF representation of an integer, and puts the resulting array of coefficients in memory
|
||||
/// @param d A 256-bit integer
|
||||
/// @return (ptr, length) The pointer to the first coefficient, and the total length of the array
|
||||
function _wnaf(int256 d) internal pure returns (uint256 ptr, uint256 length){
|
||||
|
||||
int sign = d < 0 ? -1 : int(1);
|
||||
uint256 k = uint256(sign * d);
|
||||
|
||||
length = 0;
|
||||
assembly
|
||||
{
|
||||
let ki := 0
|
||||
ptr := mload(0x40) // Get free memory pointer
|
||||
mstore(0x40, add(ptr, 300)) // Updates free memory pointer to +300 bytes offset
|
||||
for { } gt(k, 0) { } { // while k > 0
|
||||
if and(k, 1) { // if k is odd:
|
||||
ki := mod(k, 16)
|
||||
k := add(sub(k, ki), mul(gt(ki, 8), 16))
|
||||
// if sign = 1, store ki; if sign = -1, store 16 - ki
|
||||
mstore8(add(ptr, length), add(mul(ki, sign), sub(8, mul(sign, 8))))
|
||||
}
|
||||
length := add(length, 1)
|
||||
k := div(k, 2)
|
||||
}
|
||||
//log3(ptr, 1, 0xfabadaacabada, d, length)
|
||||
}
|
||||
|
||||
return (ptr, length);
|
||||
}
|
||||
|
||||
/// @notice Simultaneous multiplication of the form kP + lQ.
|
||||
/// @dev Scalars k and l are expected to be decomposed such that k = k1 + k2 λ, and l = l1 + l2 λ,
|
||||
/// where λ is specific to the endomorphism of the curve
|
||||
/// @param k_l An array with the decomposition of k and l values, i.e., [k1, k2, l1, l2]
|
||||
/// @param P_Q An array with the affine coordinates of both P and Q, i.e., [P1, P2, Q1, Q2]
|
||||
function _sim_mul(
|
||||
int256[4] memory k_l,
|
||||
uint256[4] memory P_Q
|
||||
) internal pure returns (uint[3] memory Q) {
|
||||
|
||||
require(
|
||||
is_on_curve(P_Q[0], P_Q[1]) && is_on_curve(P_Q[2], P_Q[3]),
|
||||
"Invalid points"
|
||||
);
|
||||
|
||||
uint256[4] memory wnaf;
|
||||
uint256 max_count = 0;
|
||||
uint256 count = 0;
|
||||
|
||||
for(uint j=0; j<4; j++){
|
||||
(wnaf[j], count) = _wnaf(k_l[j]);
|
||||
if(count > max_count){
|
||||
max_count = count;
|
||||
}
|
||||
}
|
||||
|
||||
Q = _sim_mul_wnaf(wnaf, max_count, P_Q);
|
||||
}
|
||||
|
||||
|
||||
function _sim_mul_wnaf(
|
||||
uint256[4] memory wnaf_ptr,
|
||||
uint256 length,
|
||||
uint256[4] memory P_Q
|
||||
) internal pure returns (uint[3] memory Q) {
|
||||
uint256[3][4][4] memory iP;
|
||||
_lookup_sim_mul(iP, P_Q);
|
||||
|
||||
// LOOP
|
||||
uint256 i = length;
|
||||
uint256 ki;
|
||||
uint256 ptr;
|
||||
while(i > 0) {
|
||||
i--;
|
||||
|
||||
doubleMutates(Q);
|
||||
|
||||
ptr = wnaf_ptr[0] + i;
|
||||
assembly {
|
||||
ki := byte(0, mload(ptr))
|
||||
}
|
||||
|
||||
if (ki > 8) {
|
||||
subJacobianMutates(Q, iP[0][(15 - ki) / 2]);
|
||||
} else if (ki > 0) {
|
||||
addJacobianMutates(Q, iP[0][(ki - 1) / 2]);
|
||||
}
|
||||
|
||||
ptr = wnaf_ptr[1] + i;
|
||||
assembly {
|
||||
ki := byte(0, mload(ptr))
|
||||
}
|
||||
|
||||
if (ki > 8) {
|
||||
subJacobianMutates(Q, iP[1][(15 - ki) / 2]);
|
||||
} else if (ki > 0) {
|
||||
addJacobianMutates(Q, iP[1][(ki - 1) / 2]);
|
||||
}
|
||||
|
||||
ptr = wnaf_ptr[2] + i;
|
||||
assembly {
|
||||
ki := byte(0, mload(ptr))
|
||||
}
|
||||
|
||||
if (ki > 8) {
|
||||
subJacobianMutates(Q, iP[2][(15 - ki) / 2]);
|
||||
} else if (ki > 0) {
|
||||
addJacobianMutates(Q, iP[2][(ki - 1) / 2]);
|
||||
}
|
||||
|
||||
ptr = wnaf_ptr[3] + i;
|
||||
assembly {
|
||||
ki := byte(0, mload(ptr))
|
||||
}
|
||||
|
||||
if (ki > 8) {
|
||||
subJacobianMutates(Q, iP[3][(15 - ki) / 2]);
|
||||
} else if (ki > 0) {
|
||||
addJacobianMutates(Q, iP[3][(ki - 1) / 2]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice Tests if a point is on the secp256k1 curve
|
||||
/// @param Px The X coordinate of an EC point in affine representation
|
||||
/// @param Py The Y coordinate of an EC point in affine representation
|
||||
/// @return true if (Px, Py) is a valid secp256k1 point; false otherwise
|
||||
function is_on_curve(uint256 Px, uint256 Py) internal pure returns (bool) {
|
||||
uint256 p = fieldOrder;
|
||||
|
||||
if (Px >= p || Py >= p){
|
||||
return false;
|
||||
}
|
||||
|
||||
uint256 y2 = mulmod(Py, Py, p);
|
||||
uint256 x3_plus_7 = addmod(mulmod(mulmod(Px, Px, p), Px, p), 7, p);
|
||||
return y2 == x3_plus_7;
|
||||
}
|
||||
|
||||
// https://ethresear.ch/t/you-can-kinda-abuse-ecrecover-to-do-ecmul-in-secp256k1-today/2384/4
|
||||
function ecmulVerify(
|
||||
uint256 x1,
|
||||
uint256 y1,
|
||||
uint256 scalar,
|
||||
uint256 qx,
|
||||
uint256 qy
|
||||
) internal pure returns(bool) {
|
||||
uint256 curve_order = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;
|
||||
address signer = ecrecover(0, uint8(27 + (y1 % 2)), bytes32(x1), bytes32(mulmod(scalar, x1, curve_order)));
|
||||
address xyAddress = address(uint256(keccak256(abi.encodePacked(qx, qy))) & 0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
|
||||
return xyAddress == signer;
|
||||
}
|
||||
|
||||
/// @notice Tests if a compressed point is valid, wrt to its corresponding Y coordinate
|
||||
/// @param _pointSign The sign byte from the compressed notation: 0x02 if the Y coord is even; 0x03 otherwise
|
||||
/// @param _pointX The X coordinate of an EC point in affine representation
|
||||
/// @param _pointY The Y coordinate of an EC point in affine representation
|
||||
/// @return true iff _pointSign and _pointX are the compressed representation of (_pointX, _pointY)
|
||||
function check_compressed_point(
|
||||
uint8 _pointSign,
|
||||
uint256 _pointX,
|
||||
uint256 _pointY
|
||||
) internal pure returns(bool) {
|
||||
bool correct_sign = _pointY % 2 == _pointSign - 2;
|
||||
return correct_sign && is_on_curve(_pointX, _pointY);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
pragma solidity ^0.5.3;
|
||||
|
||||
|
||||
/**
|
||||
* @notice Library to recover address and verify signatures
|
||||
* @dev Simple wrapper for `ecrecover`
|
||||
**/
|
||||
library SignatureVerifier {
|
||||
|
||||
enum HashAlgorithm {KECCAK256, SHA256, RIPEMD160}
|
||||
|
||||
/**
|
||||
* @notice Recover signer address from hash and signature
|
||||
* @param _hash 32 bytes message hash
|
||||
* @param _signature Signature of hash - 32 bytes r + 32 bytes s + 1 byte v (could be 0, 1, 27, 28)
|
||||
**/
|
||||
function recover(bytes32 _hash, bytes memory _signature)
|
||||
internal
|
||||
pure
|
||||
returns (address)
|
||||
{
|
||||
require(_signature.length == 65);
|
||||
|
||||
bytes32 r;
|
||||
bytes32 s;
|
||||
uint8 v;
|
||||
assembly {
|
||||
r := mload(add(_signature, 32))
|
||||
s := mload(add(_signature, 64))
|
||||
v := byte(0, mload(add(_signature, 96)))
|
||||
}
|
||||
|
||||
// Version of signature should be 27 or 28, but 0 and 1 are also possible versions
|
||||
if (v < 27) {
|
||||
v += 27;
|
||||
}
|
||||
require(v == 27 || v == 28);
|
||||
return ecrecover(_hash, v, r, s);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Transform public key to address
|
||||
* @param _publicKey secp256k1 public key
|
||||
**/
|
||||
function toAddress(bytes memory _publicKey) internal pure returns (address) {
|
||||
return address(uint160(uint256(keccak256(_publicKey))));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Hash using one of pre built hashing algorithm
|
||||
* @param _message Signed message
|
||||
* @param _algorithm Hashing algorithm
|
||||
**/
|
||||
function hash(bytes memory _message, HashAlgorithm _algorithm)
|
||||
internal
|
||||
pure
|
||||
returns (bytes32 result)
|
||||
{
|
||||
if (_algorithm == HashAlgorithm.KECCAK256) {
|
||||
result = keccak256(_message);
|
||||
} else if (_algorithm == HashAlgorithm.SHA256) {
|
||||
result = sha256(_message);
|
||||
} else {
|
||||
result = ripemd160(_message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Verify ECDSA signature
|
||||
* @dev Uses one of pre built hashing algorithm
|
||||
* @param _message Signed message
|
||||
* @param _signature Signature of message hash
|
||||
* @param _publicKey secp256k1 public key
|
||||
* @param _algorithm Hashing algorithm
|
||||
**/
|
||||
function verify(
|
||||
bytes memory _message,
|
||||
bytes memory _signature,
|
||||
bytes memory _publicKey,
|
||||
HashAlgorithm _algorithm
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bool)
|
||||
{
|
||||
return toAddress(_publicKey) == recover(hash(_message, _algorithm), _signature);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,305 @@
|
|||
pragma solidity ^0.5.3;
|
||||
|
||||
|
||||
/**
|
||||
* @notice Deserialization library for Umbral objects
|
||||
**/
|
||||
library UmbralDeserializer {
|
||||
|
||||
struct Point {
|
||||
uint8 sign;
|
||||
uint256 xCoord;
|
||||
}
|
||||
|
||||
struct Capsule {
|
||||
Point pointE;
|
||||
Point pointV;
|
||||
uint256 bnSig;
|
||||
}
|
||||
|
||||
struct CorrectnessProof {
|
||||
Point pointE2;
|
||||
Point pointV2;
|
||||
Point pointKFragCommitment;
|
||||
Point pointKFragPok;
|
||||
uint256 bnSig;
|
||||
bytes kFragSignature; // 64 bytes
|
||||
bytes metadata; // any length
|
||||
}
|
||||
|
||||
struct CapsuleFrag {
|
||||
Point pointE1;
|
||||
Point pointV1;
|
||||
bytes32 kFragId;
|
||||
Point pointPrecursor;
|
||||
CorrectnessProof proof;
|
||||
}
|
||||
|
||||
struct PreComputedData {
|
||||
uint256 pointEyCoord;
|
||||
uint256 pointEZxCoord;
|
||||
uint256 pointEZyCoord;
|
||||
uint256 pointE1yCoord;
|
||||
uint256 pointE1HxCoord;
|
||||
uint256 pointE1HyCoord;
|
||||
uint256 pointE2yCoord;
|
||||
uint256 pointVyCoord;
|
||||
uint256 pointVZxCoord;
|
||||
uint256 pointVZyCoord;
|
||||
uint256 pointV1yCoord;
|
||||
uint256 pointV1HxCoord;
|
||||
uint256 pointV1HyCoord;
|
||||
uint256 pointV2yCoord;
|
||||
uint256 pointUZxCoord;
|
||||
uint256 pointUZyCoord;
|
||||
uint256 pointU1yCoord;
|
||||
uint256 pointU1HxCoord;
|
||||
uint256 pointU1HyCoord;
|
||||
uint256 pointU2yCoord;
|
||||
bytes32 hashedKFragValidityMessage;
|
||||
address alicesKeyAsAddress;
|
||||
byte kfragSignatureV;
|
||||
}
|
||||
|
||||
uint256 constant BIGNUM_SIZE = 32;
|
||||
uint256 constant POINT_SIZE = 33;
|
||||
uint256 constant SIGNATURE_SIZE = 64;
|
||||
uint256 constant CAPSULE_SIZE = 2 * POINT_SIZE + BIGNUM_SIZE;
|
||||
uint256 constant CORRECTNESS_PROOF_SIZE = 4 * POINT_SIZE + BIGNUM_SIZE + SIGNATURE_SIZE;
|
||||
uint256 constant CAPSULE_FRAG_SIZE = 3 * POINT_SIZE + BIGNUM_SIZE;
|
||||
uint256 constant FULL_CAPSULE_FRAG_SIZE = CAPSULE_FRAG_SIZE + CORRECTNESS_PROOF_SIZE;
|
||||
uint256 constant PRECOMPUTED_DATA_SIZE = (20 * BIGNUM_SIZE) + 32 + 20 + 1;
|
||||
|
||||
/**
|
||||
* @notice Deserialize to capsule (not activated)
|
||||
**/
|
||||
function toCapsule(bytes memory _capsuleBytes)
|
||||
internal pure returns (Capsule memory capsule)
|
||||
{
|
||||
require(_capsuleBytes.length == CAPSULE_SIZE);
|
||||
uint256 pointer = getPointer(_capsuleBytes);
|
||||
pointer = copyPoint(pointer, capsule.pointE);
|
||||
pointer = copyPoint(pointer, capsule.pointV);
|
||||
capsule.bnSig = uint256(getBytes32(pointer));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Deserialize to correctness proof
|
||||
* @param _pointer Proof bytes memory pointer
|
||||
* @param _proofBytesLength Proof bytes length
|
||||
**/
|
||||
function toCorrectnessProof(uint256 _pointer, uint256 _proofBytesLength)
|
||||
internal pure returns (CorrectnessProof memory proof)
|
||||
{
|
||||
require(_proofBytesLength >= CORRECTNESS_PROOF_SIZE);
|
||||
|
||||
_pointer = copyPoint(_pointer, proof.pointE2);
|
||||
_pointer = copyPoint(_pointer, proof.pointV2);
|
||||
_pointer = copyPoint(_pointer, proof.pointKFragCommitment);
|
||||
_pointer = copyPoint(_pointer, proof.pointKFragPok);
|
||||
proof.bnSig = uint256(getBytes32(_pointer));
|
||||
_pointer += BIGNUM_SIZE;
|
||||
|
||||
proof.kFragSignature = new bytes(SIGNATURE_SIZE);
|
||||
// TODO optimize, just two mload->mstore
|
||||
_pointer = copyBytes(_pointer, proof.kFragSignature, SIGNATURE_SIZE);
|
||||
if (_proofBytesLength > CORRECTNESS_PROOF_SIZE) {
|
||||
proof.metadata = new bytes(_proofBytesLength - CORRECTNESS_PROOF_SIZE);
|
||||
copyBytes(_pointer, proof.metadata, proof.metadata.length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Deserialize to correctness proof
|
||||
**/
|
||||
function toCorrectnessProof(bytes memory _proofBytes)
|
||||
internal pure returns (CorrectnessProof memory proof)
|
||||
{
|
||||
uint256 pointer = getPointer(_proofBytes);
|
||||
return toCorrectnessProof(pointer, _proofBytes.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Deserialize to CapsuleFrag
|
||||
**/
|
||||
function toCapsuleFrag(bytes memory _cFragBytes)
|
||||
internal pure returns (CapsuleFrag memory cFrag)
|
||||
{
|
||||
uint256 cFragBytesLength = _cFragBytes.length;
|
||||
require(cFragBytesLength >= FULL_CAPSULE_FRAG_SIZE);
|
||||
|
||||
uint256 pointer = getPointer(_cFragBytes);
|
||||
pointer = copyPoint(pointer, cFrag.pointE1);
|
||||
pointer = copyPoint(pointer, cFrag.pointV1);
|
||||
cFrag.kFragId = getBytes32(pointer);
|
||||
pointer += BIGNUM_SIZE;
|
||||
pointer = copyPoint(pointer, cFrag.pointPrecursor);
|
||||
|
||||
cFrag.proof = toCorrectnessProof(pointer, cFragBytesLength - CAPSULE_FRAG_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Deserialize to precomputed data
|
||||
**/
|
||||
function toPreComputedData(bytes memory _preComputedData)
|
||||
internal pure returns (PreComputedData memory data)
|
||||
{
|
||||
require(_preComputedData.length == PRECOMPUTED_DATA_SIZE);
|
||||
uint256 initial_pointer = getPointer(_preComputedData);
|
||||
uint256 pointer = initial_pointer;
|
||||
|
||||
data.pointEyCoord = uint256(getBytes32(pointer));
|
||||
pointer += BIGNUM_SIZE;
|
||||
|
||||
data.pointEZxCoord = uint256(getBytes32(pointer));
|
||||
pointer += BIGNUM_SIZE;
|
||||
|
||||
data.pointEZyCoord = uint256(getBytes32(pointer));
|
||||
pointer += BIGNUM_SIZE;
|
||||
|
||||
data.pointE1yCoord = uint256(getBytes32(pointer));
|
||||
pointer += BIGNUM_SIZE;
|
||||
|
||||
data.pointE1HxCoord = uint256(getBytes32(pointer));
|
||||
pointer += BIGNUM_SIZE;
|
||||
|
||||
data.pointE1HyCoord = uint256(getBytes32(pointer));
|
||||
pointer += BIGNUM_SIZE;
|
||||
|
||||
data.pointE2yCoord = uint256(getBytes32(pointer));
|
||||
pointer += BIGNUM_SIZE;
|
||||
|
||||
data.pointVyCoord = uint256(getBytes32(pointer));
|
||||
pointer += BIGNUM_SIZE;
|
||||
|
||||
data.pointVZxCoord = uint256(getBytes32(pointer));
|
||||
pointer += BIGNUM_SIZE;
|
||||
|
||||
data.pointVZyCoord = uint256(getBytes32(pointer));
|
||||
pointer += BIGNUM_SIZE;
|
||||
|
||||
data.pointV1yCoord = uint256(getBytes32(pointer));
|
||||
pointer += BIGNUM_SIZE;
|
||||
|
||||
data.pointV1HxCoord = uint256(getBytes32(pointer));
|
||||
pointer += BIGNUM_SIZE;
|
||||
|
||||
data.pointV1HyCoord = uint256(getBytes32(pointer));
|
||||
pointer += BIGNUM_SIZE;
|
||||
|
||||
data.pointV2yCoord = uint256(getBytes32(pointer));
|
||||
pointer += BIGNUM_SIZE;
|
||||
|
||||
data.pointUZxCoord = uint256(getBytes32(pointer));
|
||||
pointer += BIGNUM_SIZE;
|
||||
|
||||
data.pointUZyCoord = uint256(getBytes32(pointer));
|
||||
pointer += BIGNUM_SIZE;
|
||||
|
||||
data.pointU1yCoord = uint256(getBytes32(pointer));
|
||||
pointer += BIGNUM_SIZE;
|
||||
|
||||
data.pointU1HxCoord = uint256(getBytes32(pointer));
|
||||
pointer += BIGNUM_SIZE;
|
||||
|
||||
data.pointU1HyCoord = uint256(getBytes32(pointer));
|
||||
pointer += BIGNUM_SIZE;
|
||||
|
||||
data.pointU2yCoord = uint256(getBytes32(pointer));
|
||||
pointer += BIGNUM_SIZE;
|
||||
|
||||
data.hashedKFragValidityMessage = getBytes32(pointer);
|
||||
pointer += 32;
|
||||
|
||||
data.alicesKeyAsAddress = address(bytes20(getBytes32(pointer)));
|
||||
pointer += 20;
|
||||
|
||||
data.kfragSignatureV = getByte(pointer);
|
||||
pointer += 1;
|
||||
|
||||
require(pointer == initial_pointer + PRECOMPUTED_DATA_SIZE);
|
||||
}
|
||||
|
||||
// TODO extract to external library if needed
|
||||
/**
|
||||
* @notice Get the memory pointer for start of array
|
||||
**/
|
||||
function getPointer(bytes memory _bytes) internal pure returns (uint256 pointer) {
|
||||
assembly {
|
||||
pointer := add(_bytes, 32) // skip array length
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Copy point data from memory in the pointer position
|
||||
**/
|
||||
function copyPoint(uint256 _pointer, Point memory _point)
|
||||
internal pure returns (uint256 resultPointer)
|
||||
{
|
||||
// TODO optimize, copy to point memory directly
|
||||
uint8 temp;
|
||||
uint256 xCoord;
|
||||
assembly {
|
||||
temp := byte(0, mload(_pointer))
|
||||
xCoord := mload(add(_pointer, 1))
|
||||
}
|
||||
_point.sign = temp;
|
||||
_point.xCoord = xCoord;
|
||||
resultPointer = _pointer + POINT_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Read 1 byte from memory in the pointer position
|
||||
**/
|
||||
function getByte(uint256 _pointer) internal pure returns (byte result) {
|
||||
bytes32 word;
|
||||
assembly {
|
||||
word := mload(_pointer)
|
||||
}
|
||||
result = word[0];
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Read 32 bytes from memory in the pointer position
|
||||
**/
|
||||
function getBytes32(uint256 _pointer) internal pure returns (bytes32 result) {
|
||||
assembly {
|
||||
result := mload(_pointer)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Copy bytes from the source pointer to the target array
|
||||
* @dev Assumes that enough memory has been allocated to store in target.
|
||||
* Also assumes that '_target' was the last thing that was allocated
|
||||
* @param _bytesPointer Source memory pointer
|
||||
* @param _target Target array
|
||||
* @param _bytesLength Number of bytes to copy
|
||||
**/
|
||||
function copyBytes(uint256 _bytesPointer, bytes memory _target, uint256 _bytesLength)
|
||||
internal
|
||||
pure
|
||||
returns (uint256 resultPointer)
|
||||
{
|
||||
// Exploiting the fact that '_target' was the last thing to be allocated,
|
||||
// we can write entire words, and just overwrite any excess.
|
||||
assembly {
|
||||
// evm operations on words
|
||||
let words := div(add(_bytesLength, 31), 32)
|
||||
let source := _bytesPointer
|
||||
let destination := add(_target, 32)
|
||||
for
|
||||
{ let i := 0 } // start at arr + 32 -> first byte corresponds to length
|
||||
lt(i, words)
|
||||
{ i := add(i, 1) }
|
||||
{
|
||||
let offset := mul(i, 32)
|
||||
mstore(add(destination, offset), mload(add(source, offset)))
|
||||
}
|
||||
mstore(add(_target, add(32, mload(_target))), 0)
|
||||
}
|
||||
resultPointer = _bytesPointer + _bytesLength;
|
||||
}
|
||||
|
||||
}
|
|
@ -7,7 +7,7 @@ import "zeppelin/ownership/Ownable.sol";
|
|||
/**
|
||||
* @notice Base contract for upgradeable contract
|
||||
* @dev Inherited contract should implement verifyState(address) method by checking storage variables
|
||||
* (see verifyState(address) in Dispatcher). Also contract should implement finishUpgrade(address)
|
||||
* (see verifyState(address) in Dispatcher). Also contract should implement finishUpgrade(address)
|
||||
* if it is using constructor parameters by coping this parameters to the dispatcher storage
|
||||
**/
|
||||
contract Upgradeable is Ownable {
|
||||
|
|
|
@ -41,6 +41,7 @@ from typing import Set
|
|||
from umbral.keys import UmbralPublicKey
|
||||
from umbral.signing import Signature
|
||||
from typing import Tuple
|
||||
from umbral.pre import UmbralCorrectnessError
|
||||
|
||||
from bytestring_splitter import BytestringKwargifier, BytestringSplittingError
|
||||
from bytestring_splitter import BytestringSplitter, VariableLengthBytestring
|
||||
|
@ -57,6 +58,7 @@ from nucypher.crypto.api import keccak_digest, encrypt_and_sign
|
|||
from nucypher.crypto.constants import PUBLIC_KEY_LENGTH, PUBLIC_ADDRESS_LENGTH
|
||||
from nucypher.crypto.kits import UmbralMessageKit
|
||||
from nucypher.crypto.powers import SigningPower, DecryptingPower, DelegatingPower, BlockchainPower, PowerUpError
|
||||
from nucypher.crypto.signing import InvalidSignature
|
||||
from nucypher.keystore.keypairs import HostingKeypair
|
||||
from nucypher.network.exceptions import NodeSeemsToBeDown
|
||||
from nucypher.network.middleware import RestMiddleware, UnexpectedResponse, NotFound
|
||||
|
@ -157,10 +159,10 @@ class Alice(Character, PolicyAuthor):
|
|||
good_to_go = self.block_until_number_of_known_nodes_is(n, learn_on_this_thread=True, timeout=timeout)
|
||||
if not good_to_go:
|
||||
raise ValueError(
|
||||
"To make a Policy in federated mode, you need to know about\
|
||||
all the Ursulas you need (in this case, {}); there's no other way to\
|
||||
know which nodes to use. Either pass them here or when you make\
|
||||
the Policy, or run the learning loop on a network with enough Ursulas.".format(self.n))
|
||||
"To make a Policy in federated mode, you need to know about "
|
||||
"all the Ursulas you need (in this case, {}); there's no other way to "
|
||||
"know which nodes to use. Either pass them here or when you make the Policy, "
|
||||
"or run the learning loop on a network with enough Ursulas.".format(self.n))
|
||||
|
||||
if len(handpicked_ursulas) < n:
|
||||
number_of_ursulas_needed = n - len(handpicked_ursulas)
|
||||
|
@ -318,12 +320,29 @@ class Alice(Character, PolicyAuthor):
|
|||
class Bob(Character):
|
||||
_default_crypto_powerups = [SigningPower, DecryptingPower]
|
||||
|
||||
class IncorrectCFragReceived(Exception):
|
||||
"""
|
||||
Raised when Bob detects an incorrect CFrag returned by some Ursula
|
||||
"""
|
||||
def __init__(self, evidence):
|
||||
self.evidence = evidence
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
from nucypher.policy.models import WorkOrderHistory # Need a bigger strategy to avoid circulars.
|
||||
self._saved_work_orders = WorkOrderHistory()
|
||||
|
||||
def _pick_treasure_map(self, treasure_map=None, map_id=None):
|
||||
if not treasure_map:
|
||||
if map_id:
|
||||
treasure_map = self.treasure_maps[map_id]
|
||||
else:
|
||||
raise ValueError("You need to pass either treasure_map or map_id.")
|
||||
elif map_id:
|
||||
raise ValueError("Don't pass both treasure_map and map_id - pick one or the other.")
|
||||
return treasure_map
|
||||
|
||||
def peek_at_treasure_map(self, treasure_map=None, map_id=None):
|
||||
"""
|
||||
Take a quick gander at the TreasureMap matching map_id to see which
|
||||
|
@ -334,14 +353,7 @@ class Bob(Character):
|
|||
|
||||
Return two sets: nodes that are unknown to us, nodes that are known to us.
|
||||
"""
|
||||
if not treasure_map:
|
||||
if map_id:
|
||||
treasure_map = self.treasure_maps[map_id]
|
||||
else:
|
||||
raise ValueError("You need to pass either treasure_map or map_id.")
|
||||
else:
|
||||
if map_id:
|
||||
raise ValueError("Don't pass both treasure_map and map_id - pick one or the other.")
|
||||
treasure_map = self._pick_treasure_map(treasure_map, map_id)
|
||||
|
||||
# The intersection of the map and our known nodes will be the known Ursulas...
|
||||
known_treasure_ursulas = treasure_map.destinations.keys() & self.known_nodes.addresses()
|
||||
|
@ -373,14 +385,7 @@ class Bob(Character):
|
|||
|
||||
# TODO: Check if nodes are up, declare them phantom if not.
|
||||
"""
|
||||
if not treasure_map:
|
||||
if map_id:
|
||||
treasure_map = self.treasure_maps[map_id]
|
||||
else:
|
||||
raise ValueError("You need to pass either treasure_map or map_id.")
|
||||
else:
|
||||
if map_id:
|
||||
raise ValueError("Don't pass both treasure_map and map_id - pick one or the other.")
|
||||
treasure_map = self._pick_treasure_map(treasure_map, map_id)
|
||||
|
||||
unknown_ursulas, known_ursulas = self.peek_at_treasure_map(treasure_map=treasure_map)
|
||||
|
||||
|
@ -437,9 +442,8 @@ class Bob(Character):
|
|||
|
||||
def get_treasure_map_from_known_ursulas(self, network_middleware, map_id):
|
||||
"""
|
||||
Iterate through swarm, asking for the TreasureMap.
|
||||
Iterate through the nodes we know, asking for the TreasureMap.
|
||||
Return the first one who has it.
|
||||
TODO: What if a node gives a bunk TreasureMap?
|
||||
"""
|
||||
from nucypher.policy.models import TreasureMap
|
||||
for node in self.known_nodes.shuffled():
|
||||
|
@ -449,7 +453,11 @@ class Bob(Character):
|
|||
continue
|
||||
|
||||
if response.status_code == 200 and response.content:
|
||||
treasure_map = TreasureMap.from_bytes(response.content)
|
||||
try:
|
||||
treasure_map = TreasureMap.from_bytes(response.content)
|
||||
except InvalidSignature:
|
||||
# TODO: What if a node gives a bunk TreasureMap?
|
||||
raise
|
||||
break
|
||||
else:
|
||||
continue # TODO: Actually, handle error case here.
|
||||
|
@ -487,11 +495,11 @@ class Bob(Character):
|
|||
work_order = WorkOrder.construct_by_bob(
|
||||
arrangement_id, capsules_to_include, ursula, self)
|
||||
generated_work_orders[node_id] = work_order
|
||||
# TODO: Fix this. It's always taking the last capsule
|
||||
self._saved_work_orders[node_id][capsule] = work_order
|
||||
|
||||
if num_ursulas is not None:
|
||||
if num_ursulas == len(generated_work_orders):
|
||||
break
|
||||
if num_ursulas == len(generated_work_orders):
|
||||
break
|
||||
|
||||
return generated_work_orders
|
||||
|
||||
|
@ -503,9 +511,6 @@ class Bob(Character):
|
|||
work_orders_by_ursula[capsule] = work_order
|
||||
return cfrags
|
||||
|
||||
def get_ursula(self, ursula_id):
|
||||
return self._ursulas[ursula_id]
|
||||
|
||||
def join_policy(self, label, alice_pubkey_sig, node_list=None, block=False):
|
||||
if node_list:
|
||||
self._node_ids_to_learn_about_immediately.update(node_list)
|
||||
|
@ -514,7 +519,8 @@ class Bob(Character):
|
|||
|
||||
def retrieve(self, message_kit, data_source, alice_verifying_key, label):
|
||||
|
||||
message_kit.capsule.set_correctness_keys(
|
||||
capsule = message_kit.capsule # TODO: generalize for WorkOrders with more than one capsule
|
||||
capsule.set_correctness_keys(
|
||||
delegating=data_source.policy_pubkey,
|
||||
receiving=self.public_keys(DecryptingPower),
|
||||
verifying=alice_verifying_key)
|
||||
|
@ -524,18 +530,26 @@ class Bob(Character):
|
|||
|
||||
# TODO: Consider blocking until map is done being followed.
|
||||
|
||||
work_orders = self.generate_work_orders(map_id, message_kit.capsule)
|
||||
work_orders = self.generate_work_orders(map_id, capsule)
|
||||
|
||||
cleartexts = []
|
||||
|
||||
for work_order in work_orders.values():
|
||||
try:
|
||||
cfrags = self.get_reencrypted_cfrags(work_order)
|
||||
message_kit.capsule.attach_cfrag(cfrags[0])
|
||||
if len(message_kit.capsule._attached_cfrags) >= m:
|
||||
break
|
||||
except requests.exceptions.ConnectTimeout:
|
||||
continue
|
||||
|
||||
cfrag = cfrags[0] # TODO: generalize for WorkOrders with more than one capsule
|
||||
try:
|
||||
message_kit.capsule.attach_cfrag(cfrag)
|
||||
if len(message_kit.capsule._attached_cfrags) >= m:
|
||||
break
|
||||
except UmbralCorrectnessError:
|
||||
evidence = self.collect_evidence(capsule=capsule,
|
||||
cfrag=cfrag,
|
||||
ursula=work_order.ursula)
|
||||
# TODO: Here's the evidence of Ursula misbehavior. Now what? #500
|
||||
raise self.IncorrectCFragReceived(evidence)
|
||||
else:
|
||||
raise Ursula.NotEnoughUrsulas("Unable to snag m cfrags.")
|
||||
|
||||
|
@ -547,6 +561,10 @@ class Bob(Character):
|
|||
cleartexts.append(delivered_cleartext)
|
||||
return cleartexts
|
||||
|
||||
def collect_evidence(self, capsule, cfrag, ursula):
|
||||
from nucypher.policy.models import IndisputableEvidence
|
||||
return IndisputableEvidence(capsule, cfrag, ursula)
|
||||
|
||||
def make_wsgi_app(drone_bob, start_learning=True):
|
||||
bob_control = Flask('bob-control')
|
||||
drone_bob.start_learning_loop(now=start_learning)
|
||||
|
|
|
@ -14,7 +14,12 @@ GNU General Public License for more details.
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from coincurve import PublicKey
|
||||
from eth_keys import KeyAPI as EthKeyAPI
|
||||
from typing import Any
|
||||
from umbral.keys import UmbralPublicKey
|
||||
from umbral.signing import Signature
|
||||
|
||||
from nucypher.crypto.api import keccak_digest
|
||||
|
||||
|
@ -25,3 +30,48 @@ def fingerprint_from_key(public_key: Any):
|
|||
:return: Hexdigest fingerprint of key (keccak-256) in bytes
|
||||
"""
|
||||
return keccak_digest(bytes(public_key)).hex().encode()
|
||||
|
||||
|
||||
def canonical_address_from_umbral_key(public_key: UmbralPublicKey) -> bytes:
|
||||
pubkey_raw_bytes = public_key.to_bytes(is_compressed=False)[1:]
|
||||
eth_pubkey = EthKeyAPI.PublicKey(pubkey_raw_bytes)
|
||||
canonical_address = eth_pubkey.to_canonical_address()
|
||||
return canonical_address
|
||||
|
||||
|
||||
def recover_pubkey_from_signature(prehashed_message, signature, v_value_to_try=None) -> bytes:
|
||||
"""
|
||||
Recovers a serialized, compressed public key from a signature.
|
||||
It allows to specify a potential v value, in which case it assumes the signature
|
||||
has the traditional (r,s) raw format. If a v value is not present, it assumes
|
||||
the signature has the recoverable format (r, s, v).
|
||||
|
||||
:param prehashed_message: Prehashed message
|
||||
:param signature: The signature from which the pubkey is recovered
|
||||
:param v_value_to_try: A potential v value to try
|
||||
:return: The compressed byte-serialized representation of the recovered public key
|
||||
"""
|
||||
|
||||
signature = bytes(signature)
|
||||
ecdsa_signature_size = Signature.expected_bytes_length()
|
||||
|
||||
if not v_value_to_try:
|
||||
expected_signature_size = ecdsa_signature_size + 1
|
||||
if not len(signature) == expected_signature_size:
|
||||
raise ValueError(f"When not passing a v value, "
|
||||
f"the signature size should be {expected_signature_size} B.")
|
||||
elif v_value_to_try in (0, 1, 27, 28):
|
||||
expected_signature_size = ecdsa_signature_size
|
||||
if not len(signature) == expected_signature_size:
|
||||
raise ValueError(f"When passing a v value, "
|
||||
f"the signature size should be {expected_signature_size} B.")
|
||||
if v_value_to_try >= 27:
|
||||
v_value_to_try -= 27
|
||||
signature = signature + v_value_to_try.to_bytes(1, 'big')
|
||||
else:
|
||||
raise ValueError("Wrong v value. It should be 0, 1, 27 or 28.")
|
||||
|
||||
pubkey = PublicKey.from_signature_and_message(serialized_sig=signature,
|
||||
message=prehashed_message,
|
||||
hasher=None)
|
||||
return pubkey.format(compressed=True)
|
||||
|
|
|
@ -37,14 +37,14 @@ from nucypher.config.storages import ForgetfulNodeStorage
|
|||
from nucypher.crypto.api import keccak_digest
|
||||
from nucypher.crypto.kits import UmbralMessageKit
|
||||
from nucypher.crypto.powers import SigningPower, KeyPairBasedPower, PowerUpError
|
||||
from nucypher.crypto.signing import InvalidSignature
|
||||
from nucypher.crypto.signing import SignatureStamp
|
||||
from nucypher.crypto.signing import InvalidSignature, SignatureStamp, Signature
|
||||
from nucypher.crypto.utils import canonical_address_from_umbral_key
|
||||
from nucypher.keystore.keypairs import HostingKeypair
|
||||
from nucypher.keystore.keystore import NotFound
|
||||
from nucypher.keystore.threading import ThreadedSession
|
||||
from nucypher.network import LEARNING_LOOP_VERSION
|
||||
from nucypher.network.middleware import RestMiddleware
|
||||
from nucypher.network.protocols import InterfaceInfo
|
||||
from nucypher.network.protocols import InterfaceInfo, SuspiciousActivity
|
||||
|
||||
HERE = BASE_DIR = os.path.abspath(os.path.dirname(__file__))
|
||||
TEMPLATES_DIR = os.path.join(HERE, "templates")
|
||||
|
@ -307,24 +307,39 @@ def make_rest_app(
|
|||
with ThreadedSession(db_engine) as session:
|
||||
policy_arrangement = datastore.get_policy_arrangement(arrangement_id=id_as_hex.encode(),
|
||||
session=session)
|
||||
|
||||
kfrag_bytes = policy_arrangement.kfrag # Careful! :-)
|
||||
verifying_key_bytes = policy_arrangement.alice_pubkey_sig.key_data
|
||||
|
||||
# TODO: Push this to a lower level.
|
||||
# TODO: Push this to a lower level. Perhaps to Ursula character? #619
|
||||
kfrag = KFrag.from_bytes(kfrag_bytes)
|
||||
alices_verifying_key = UmbralPublicKey.from_bytes(verifying_key_bytes)
|
||||
cfrag_byte_stream = b""
|
||||
|
||||
alices_address = canonical_address_from_umbral_key(alices_verifying_key)
|
||||
if not alices_address == work_order.alice_address:
|
||||
message = f"This Bob ({work_order.bob}) sent an Alice's ETH address " \
|
||||
f"({work_order.alice_address}) that doesn't match " \
|
||||
f"the one I have ({alices_address})."
|
||||
raise SuspiciousActivity(message)
|
||||
|
||||
bob_pubkey = work_order.bob.stamp.as_umbral_pubkey()
|
||||
if not work_order.alice_address_signature.verify(message=alices_address,
|
||||
verifying_key=bob_pubkey):
|
||||
message = f"This Bob ({work_order.bob}) sent an invalid signature of Alice's ETH address"
|
||||
raise InvalidSignature(message)
|
||||
|
||||
# This is Bob's signature of Alice's verifying key as ETH address.
|
||||
alice_address_signature = bytes(work_order.alice_address_signature)
|
||||
|
||||
for capsule, capsule_signature in zip(work_order.capsules, work_order.capsule_signatures):
|
||||
# This is the capsule signed by Bob
|
||||
capsule_signature = bytes(capsule_signature)
|
||||
# Ursula signs on top of it. Now both are committed to the same capsule.
|
||||
capsule_signed_by_both = bytes(stamp(capsule_signature))
|
||||
|
||||
# She signs Alice's address too.
|
||||
ursula_signature = stamp(capsule_signature + alice_address_signature)
|
||||
capsule.set_correctness_keys(verifying=alices_verifying_key)
|
||||
cfrag = pre.reencrypt(kfrag, capsule, metadata=capsule_signed_by_both)
|
||||
log.info("Re-encrypting for {}, made {}.".format(capsule, cfrag))
|
||||
cfrag = pre.reencrypt(kfrag, capsule, metadata=bytes(ursula_signature))
|
||||
log.info(f"Re-encrypting for {capsule}, made {cfrag}.")
|
||||
signature = stamp(bytes(cfrag) + bytes(capsule))
|
||||
cfrag_byte_stream += VariableLengthBytestring(cfrag) + signature
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@ You should have received a copy of the GNU General Public License
|
|||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
import binascii
|
||||
import os
|
||||
from abc import abstractmethod
|
||||
from collections import OrderedDict
|
||||
|
||||
|
@ -23,20 +22,28 @@ import maya
|
|||
import msgpack
|
||||
import uuid
|
||||
from bytestring_splitter import BytestringSplitter, VariableLengthBytestring
|
||||
from constant_sorrow import constants
|
||||
from constant_sorrow.constants import UNKNOWN_KFRAG, NO_DECRYPTION_PERFORMED, NOT_SIGNED
|
||||
from cryptography.hazmat.backends.openssl import backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from eth_utils import to_canonical_address, to_checksum_address
|
||||
from typing import Generator, List, Set
|
||||
from typing import Generator, List, Set, Optional
|
||||
|
||||
from umbral.cfrags import CapsuleFrag
|
||||
from umbral.config import default_params
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.keys import UmbralPublicKey
|
||||
from umbral.kfrags import KFrag
|
||||
from umbral.point import Point
|
||||
from umbral.pre import Capsule
|
||||
|
||||
from nucypher.characters.lawful import Alice
|
||||
from nucypher.characters.lawful import Bob, Ursula, Character
|
||||
from nucypher.characters.lawful import Alice, Bob, Ursula, Character
|
||||
from nucypher.crypto.api import keccak_digest, encrypt_and_sign, secure_random
|
||||
from nucypher.crypto.constants import PUBLIC_ADDRESS_LENGTH, KECCAK_DIGEST_LENGTH
|
||||
from nucypher.crypto.kits import UmbralMessageKit, RevocationKit
|
||||
from nucypher.crypto.powers import SigningPower, DecryptingPower
|
||||
from nucypher.crypto.signing import Signature, InvalidSignature
|
||||
from nucypher.crypto.splitters import key_splitter
|
||||
from nucypher.crypto.utils import canonical_address_from_umbral_key, recover_pubkey_from_signature
|
||||
from nucypher.network.exceptions import NodeSeemsToBeDown
|
||||
from nucypher.network.middleware import RestMiddleware, NotFound
|
||||
|
||||
|
@ -51,8 +58,8 @@ class Arrangement:
|
|||
splitter = key_splitter + BytestringSplitter((bytes, ID_LENGTH),
|
||||
(bytes, 27))
|
||||
|
||||
def __init__(self, alice, expiration, ursula=None, id=None,
|
||||
kfrag=constants.UNKNOWN_KFRAG, value=None, alices_signature=None) -> None:
|
||||
def __init__(self, alice, expiration, ursula=None, arrangement_id=None,
|
||||
kfrag=UNKNOWN_KFRAG, value=None, alices_signature=None) -> None:
|
||||
"""
|
||||
:param deposit: Funds which will pay for the timeframe of this Arrangement (not the actual re-encryptions);
|
||||
a portion will be locked for each Ursula that accepts.
|
||||
|
@ -60,7 +67,7 @@ class Arrangement:
|
|||
|
||||
Other params are hopefully self-evident.
|
||||
"""
|
||||
self.id = id or secure_random(self.ID_LENGTH)
|
||||
self.id = arrangement_id or secure_random(self.ID_LENGTH)
|
||||
self.expiration = expiration
|
||||
self.alice = alice
|
||||
self.uuid = uuid.uuid4()
|
||||
|
@ -79,10 +86,10 @@ class Arrangement:
|
|||
@classmethod
|
||||
def from_bytes(cls, arrangement_as_bytes):
|
||||
# Still unclear how to arrive at the correct number of bytes to represent a deposit. See #148.
|
||||
alice_pubkey_sig, id, expiration_bytes = cls.splitter(arrangement_as_bytes)
|
||||
alice_pubkey_sig, arrangement_id, expiration_bytes = cls.splitter(arrangement_as_bytes)
|
||||
expiration = maya.parse(expiration_bytes.decode())
|
||||
alice = Alice.from_public_keys({SigningPower: alice_pubkey_sig})
|
||||
return cls(alice=alice, id=id, expiration=expiration)
|
||||
return cls(alice=alice, arrangement_id=arrangement_id, expiration=expiration)
|
||||
|
||||
def encrypt_payload_for_ursula(self):
|
||||
"""Craft an offer to send to Ursula."""
|
||||
|
@ -105,7 +112,7 @@ class Arrangement:
|
|||
@abstractmethod
|
||||
def revoke(self):
|
||||
"""
|
||||
Publish arrangement.
|
||||
Revoke arrangement.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
@ -127,10 +134,10 @@ class Policy:
|
|||
alice,
|
||||
label,
|
||||
bob=None,
|
||||
kfrags=(constants.UNKNOWN_KFRAG,),
|
||||
kfrags=(UNKNOWN_KFRAG,),
|
||||
public_key=None,
|
||||
m: int = None,
|
||||
alices_signature=constants.NOT_SIGNED) -> None:
|
||||
alices_signature=NOT_SIGNED) -> None:
|
||||
|
||||
"""
|
||||
:param kfrags: A list of KFrags to distribute per this Policy.
|
||||
|
@ -260,7 +267,7 @@ class Policy:
|
|||
if publish is True:
|
||||
return self.publish(network_middleware)
|
||||
|
||||
def consider_arrangement(self, network_middleware, ursula, arrangement):
|
||||
def consider_arrangement(self, network_middleware, ursula, arrangement) -> bool:
|
||||
try:
|
||||
ursula.verify_node(network_middleware,
|
||||
accept_federated_only=arrangement.federated)
|
||||
|
@ -275,12 +282,12 @@ class Policy:
|
|||
negotiation_response = network_middleware.consider_arrangement(arrangement=arrangement)
|
||||
|
||||
# TODO: check out the response: need to assess the result and see if we're actually good to go.
|
||||
negotiation_result = negotiation_response.status_code == 200
|
||||
arrangement_is_accepted = negotiation_response.status_code == 200
|
||||
|
||||
bucket = self._accepted_arrangements if negotiation_result is True else self._rejected_arrangements
|
||||
bucket = self._accepted_arrangements if arrangement_is_accepted else self._rejected_arrangements
|
||||
bucket.add(arrangement)
|
||||
|
||||
return negotiation_result
|
||||
return arrangement_is_accepted
|
||||
|
||||
@abstractmethod
|
||||
def make_arrangements(self,
|
||||
|
@ -289,7 +296,7 @@ class Policy:
|
|||
expiration: maya.MayaDT,
|
||||
ursulas: Set[Ursula] = None) -> None:
|
||||
"""
|
||||
Create and consider n Arangement objects.
|
||||
Create and consider n Arrangement objects.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
@ -370,9 +377,9 @@ class TreasureMap:
|
|||
def __init__(self,
|
||||
m: int = None,
|
||||
destinations=None,
|
||||
message_kit: UmbralMessageKit= None,
|
||||
message_kit: UmbralMessageKit = None,
|
||||
public_signature: Signature = None,
|
||||
hrac=None) -> None:
|
||||
hrac: Optional[bytes] = None) -> None:
|
||||
|
||||
if m is not None:
|
||||
if m > 255:
|
||||
|
@ -381,11 +388,10 @@ class TreasureMap:
|
|||
|
||||
self._destinations = destinations or {}
|
||||
else:
|
||||
self._m = constants.NO_DECRYPTION_PERFORMED
|
||||
self._destinations = constants.NO_DECRYPTION_PERFORMED
|
||||
self._m = NO_DECRYPTION_PERFORMED
|
||||
self._destinations = NO_DECRYPTION_PERFORMED
|
||||
|
||||
self.message_kit = message_kit
|
||||
self._signature_for_bob = None
|
||||
self._public_signature = public_signature
|
||||
self._hrac = hrac
|
||||
self._payload = None
|
||||
|
@ -435,24 +441,27 @@ class TreasureMap:
|
|||
|
||||
@property
|
||||
def m(self):
|
||||
if self._m == constants.NO_DECRYPTION_PERFORMED:
|
||||
if self._m == NO_DECRYPTION_PERFORMED:
|
||||
raise TypeError("The TreasureMap is probably encrypted. You must decrypt it first.")
|
||||
return self._m
|
||||
|
||||
@property
|
||||
def destinations(self):
|
||||
if self._destinations == constants.NO_DECRYPTION_PERFORMED:
|
||||
if self._destinations == NO_DECRYPTION_PERFORMED:
|
||||
raise TypeError("The TreasureMap is probably encrypted. You must decrypt it first.")
|
||||
return self._destinations
|
||||
|
||||
def nodes_as_bytes(self):
|
||||
if self.destinations == constants.NO_DECRYPTION_PERFORMED:
|
||||
return constants.NO_DECRYPTION_PERFORMED
|
||||
if self.destinations == NO_DECRYPTION_PERFORMED:
|
||||
return NO_DECRYPTION_PERFORMED
|
||||
else:
|
||||
return bytes().join(to_canonical_address(ursula_id) + arrangement_id for ursula_id, arrangement_id in self.destinations.items())
|
||||
nodes_as_bytes = b""
|
||||
for ursula_id, arrangement_id in self.destinations.items():
|
||||
nodes_as_bytes += to_canonical_address(ursula_id) + arrangement_id
|
||||
return nodes_as_bytes
|
||||
|
||||
def add_arrangement(self, arrangement):
|
||||
if self.destinations == constants.NO_DECRYPTION_PERFORMED:
|
||||
if self.destinations == NO_DECRYPTION_PERFORMED:
|
||||
raise TypeError("This TreasureMap is encrypted. You can't add another node without decrypting it.")
|
||||
self.destinations[arrangement.ursula.checksum_public_address] = arrangement.id
|
||||
|
||||
|
@ -466,8 +475,7 @@ class TreasureMap:
|
|||
|
||||
@classmethod
|
||||
def from_bytes(cls, bytes_representation, verify=True):
|
||||
signature, hrac, tmap_message_kit = \
|
||||
cls.splitter(bytes_representation)
|
||||
signature, hrac, tmap_message_kit = cls.splitter(bytes_representation)
|
||||
|
||||
treasure_map = cls(
|
||||
message_kit=tmap_message_kit,
|
||||
|
@ -514,12 +522,19 @@ class TreasureMap:
|
|||
|
||||
|
||||
class WorkOrder:
|
||||
|
||||
class NotFromBob(InvalidSignature):
|
||||
def __init__(self):
|
||||
super().__init__("This doesn't appear to be from Bob.")
|
||||
|
||||
def __init__(self,
|
||||
bob,
|
||||
bob: Bob,
|
||||
arrangement_id,
|
||||
capsules,
|
||||
capsule_signatures,
|
||||
receipt_bytes,
|
||||
capsules: List[Capsule],
|
||||
capsule_signatures: List[Signature],
|
||||
alice_address: bytes,
|
||||
alice_address_signature: bytes,
|
||||
receipt_bytes: bytes,
|
||||
receipt_signature,
|
||||
ursula=None,
|
||||
) -> None:
|
||||
|
@ -527,6 +542,8 @@ class WorkOrder:
|
|||
self.arrangement_id = arrangement_id
|
||||
self.capsules = capsules
|
||||
self.capsule_signatures = capsule_signatures
|
||||
self.alice_address = alice_address
|
||||
self.alice_address_signature = alice_address_signature
|
||||
self.receipt_bytes = receipt_bytes
|
||||
self.receipt_signature = receipt_signature
|
||||
self.ursula = ursula # TODO: We may still need a more elegant system for ID'ing Ursula. See #136.
|
||||
|
@ -547,51 +564,96 @@ class WorkOrder:
|
|||
|
||||
@classmethod
|
||||
def construct_by_bob(cls, arrangement_id, capsules, ursula, bob):
|
||||
capsules_bytes = [bytes(c) for c in capsules]
|
||||
alice_verifying_key = capsules[0].get_correctness_keys()["verifying"]
|
||||
|
||||
capsules_bytes = []
|
||||
capsule_signatures = []
|
||||
for capsule in capsules:
|
||||
if alice_verifying_key != capsule.get_correctness_keys()["verifying"]:
|
||||
raise ValueError("Capsules in this work order are inconsistent.")
|
||||
capsule_bytes = bytes(capsule)
|
||||
capsules_bytes.append(capsule_bytes)
|
||||
capsule_signatures.append(bob.stamp(capsule_bytes))
|
||||
|
||||
alice_address = canonical_address_from_umbral_key(alice_verifying_key)
|
||||
alice_address_signature = bytes(bob.stamp(alice_address))
|
||||
|
||||
receipt_bytes = b"wo:" + ursula.canonical_public_address
|
||||
receipt_bytes += msgpack.dumps(capsules_bytes)
|
||||
receipt_signature = bob.stamp(receipt_bytes)
|
||||
capsule_signatures = [bob.stamp(c) for c in capsules_bytes]
|
||||
return cls(bob, arrangement_id, capsules, capsule_signatures, receipt_bytes, receipt_signature,
|
||||
ursula)
|
||||
|
||||
return cls(bob, arrangement_id, capsules, capsule_signatures,
|
||||
alice_address, alice_address_signature,
|
||||
receipt_bytes, receipt_signature, ursula)
|
||||
|
||||
@classmethod
|
||||
def from_rest_payload(cls, arrangement_id, rest_payload):
|
||||
|
||||
# TODO: Use JSON instead? This is a mess.
|
||||
payload_splitter = BytestringSplitter(Signature) + key_splitter
|
||||
signature, bob_pubkey_sig, \
|
||||
(receipt_bytes, packed_capsules, packed_signatures) = payload_splitter(rest_payload,
|
||||
msgpack_remainder=True)
|
||||
signature, bob_pubkey_sig, remainder = payload_splitter(rest_payload,
|
||||
msgpack_remainder=True)
|
||||
|
||||
receipt_bytes, *remainder = remainder
|
||||
packed_capsules, packed_signatures, *remainder = remainder
|
||||
alice_address, alice_address_signature = remainder
|
||||
alice_address_signature = Signature.from_bytes(alice_address_signature)
|
||||
if not alice_address_signature.verify(alice_address, bob_pubkey_sig):
|
||||
raise cls.NotFromBob()
|
||||
|
||||
capsules, capsule_signatures = list(), list()
|
||||
for capsule_bytes, signed_capsule in zip(msgpack.loads(packed_capsules), msgpack.loads(packed_signatures)):
|
||||
for capsule_bytes, capsule_signature in zip(msgpack.loads(packed_capsules), msgpack.loads(packed_signatures)):
|
||||
capsules.append(Capsule.from_bytes(capsule_bytes, params=default_params()))
|
||||
signed_capsule = Signature.from_bytes(signed_capsule)
|
||||
capsule_signatures.append(signed_capsule)
|
||||
if not signed_capsule.verify(capsule_bytes, bob_pubkey_sig):
|
||||
raise ValueError("This doesn't appear to be from Bob.")
|
||||
capsule_signature = Signature.from_bytes(capsule_signature)
|
||||
capsule_signatures.append(capsule_signature)
|
||||
if not capsule_signature.verify(capsule_bytes, bob_pubkey_sig):
|
||||
raise cls.NotFromBob()
|
||||
|
||||
verified = signature.verify(receipt_bytes, bob_pubkey_sig)
|
||||
if not verified:
|
||||
raise ValueError("This doesn't appear to be from Bob.")
|
||||
raise cls.NotFromBob()
|
||||
bob = Bob.from_public_keys({SigningPower: bob_pubkey_sig})
|
||||
return cls(bob, arrangement_id, capsules, capsule_signatures, receipt_bytes, signature)
|
||||
return cls(bob, arrangement_id, capsules, capsule_signatures,
|
||||
alice_address, alice_address_signature,
|
||||
receipt_bytes, signature)
|
||||
|
||||
def payload(self):
|
||||
capsules_as_bytes = [bytes(p) for p in self.capsules]
|
||||
capsule_signatures_as_bytes = [bytes(s) for s in self.capsule_signatures]
|
||||
packed_receipt_and_capsules = msgpack.dumps(
|
||||
(self.receipt_bytes, msgpack.dumps(capsules_as_bytes), msgpack.dumps(capsule_signatures_as_bytes)))
|
||||
(self.receipt_bytes,
|
||||
msgpack.dumps(capsules_as_bytes),
|
||||
msgpack.dumps(capsule_signatures_as_bytes),
|
||||
self.alice_address,
|
||||
self.alice_address_signature,
|
||||
)
|
||||
)
|
||||
return bytes(self.receipt_signature) + self.bob.stamp + packed_receipt_and_capsules
|
||||
|
||||
def complete(self, cfrags_and_signatures):
|
||||
good_cfrags = []
|
||||
if not len(self) == len(cfrags_and_signatures):
|
||||
raise ValueError("Ursula gave back the wrong number of cfrags. She's up to something.")
|
||||
raise ValueError("Ursula gave back the wrong number of cfrags. "
|
||||
"She's up to something.")
|
||||
|
||||
alice_address_signature = bytes(self.alice_address_signature)
|
||||
ursula_verifying_key = self.ursula.stamp.as_umbral_pubkey()
|
||||
|
||||
for counter, capsule in enumerate(self.capsules):
|
||||
cfrag, signature = cfrags_and_signatures[counter]
|
||||
if signature.verify(bytes(cfrag) + bytes(capsule), self.ursula.stamp.as_umbral_pubkey()):
|
||||
|
||||
# Validate CFrag metadata
|
||||
capsule_signature = bytes(self.capsule_signatures[counter])
|
||||
metadata_input = capsule_signature + alice_address_signature
|
||||
metadata_as_signature = Signature.from_bytes(cfrag.proof.metadata)
|
||||
if not metadata_as_signature.verify(metadata_input, ursula_verifying_key):
|
||||
raise InvalidSignature("Invalid metadata for {}.".format(cfrag))
|
||||
|
||||
# Validate work order response signatures
|
||||
if signature.verify(bytes(cfrag) + bytes(capsule), ursula_verifying_key):
|
||||
good_cfrags.append(cfrag)
|
||||
else:
|
||||
raise self.ursula.InvalidSignature("This CFrag is not properly signed by Ursula.")
|
||||
raise InvalidSignature("{} is not properly signed by Ursula.".format(cfrag))
|
||||
else:
|
||||
self.completed = maya.now()
|
||||
return good_cfrags
|
||||
|
@ -618,7 +680,7 @@ class WorkOrderHistory:
|
|||
def ursulas(self):
|
||||
return self.by_ursula.keys()
|
||||
|
||||
def by_capsule(self, capsule):
|
||||
def by_capsule(self, capsule: Capsule):
|
||||
ursulas_by_capsules = {} # type: dict
|
||||
for ursula, capsules in self.by_ursula.items():
|
||||
for saved_capsule, work_order in capsules.items():
|
||||
|
@ -674,3 +736,145 @@ class Revocation:
|
|||
raise InvalidSignature(
|
||||
"Revocation has an invalid signature: {}".format(self.signature))
|
||||
return True
|
||||
|
||||
|
||||
class IndisputableEvidence:
|
||||
|
||||
def __init__(self,
|
||||
capsule: Capsule,
|
||||
cfrag: CapsuleFrag,
|
||||
ursula,
|
||||
delegating_pubkey: UmbralPublicKey = None,
|
||||
receiving_pubkey: UmbralPublicKey = None,
|
||||
verifying_pubkey: UmbralPublicKey = None,
|
||||
) -> None:
|
||||
self.capsule = capsule
|
||||
self.cfrag = cfrag
|
||||
self.ursula = ursula
|
||||
|
||||
keys = capsule.get_correctness_keys()
|
||||
key_types = ("delegating", "receiving", "verifying")
|
||||
if all(keys[key_type] for key_type in key_types):
|
||||
self.delegating_pubkey = keys["delegating"]
|
||||
self.receiving_pubkey = keys["receiving"]
|
||||
self.verifying_pubkey = keys["verifying"]
|
||||
elif all((delegating_pubkey, receiving_pubkey, verifying_pubkey)):
|
||||
self.delegating_pubkey = delegating_pubkey
|
||||
self.receiving_pubkey = receiving_pubkey
|
||||
self.verifying_pubkey = verifying_pubkey
|
||||
else:
|
||||
raise ValueError("All correctness keys are required to compute evidence. "
|
||||
"Either pass them as arguments or in the capsule.")
|
||||
|
||||
|
||||
def get_proof_challenge_scalar(self) -> CurveBN:
|
||||
umbral_params = default_params()
|
||||
e, v, _ = self.capsule.components()
|
||||
|
||||
e1 = self.cfrag.point_e1
|
||||
v1 = self.cfrag.point_v1
|
||||
e2 = self.cfrag.proof.point_e2
|
||||
v2 = self.cfrag.proof.point_v2
|
||||
u = umbral_params.u
|
||||
u1 = self.cfrag.proof.point_kfrag_commitment
|
||||
u2 = self.cfrag.proof.point_kfrag_pok
|
||||
metadata = self.cfrag.proof.metadata
|
||||
|
||||
from umbral.random_oracles import hash_to_curvebn, ExtendedKeccak
|
||||
|
||||
hash_input = (e, e1, e2, v, v1, v2, u, u1, u2, metadata)
|
||||
|
||||
h = hash_to_curvebn(*hash_input,
|
||||
params=umbral_params,
|
||||
hash_class=ExtendedKeccak)
|
||||
return h
|
||||
|
||||
def precompute_values(self) -> bytes:
|
||||
|
||||
umbral_params = default_params()
|
||||
e, v, _ = self.capsule.components()
|
||||
|
||||
e1 = self.cfrag.point_e1
|
||||
v1 = self.cfrag.point_v1
|
||||
e2 = self.cfrag.proof.point_e2
|
||||
v2 = self.cfrag.proof.point_v2
|
||||
u = umbral_params.u
|
||||
u1 = self.cfrag.proof.point_kfrag_commitment
|
||||
u2 = self.cfrag.proof.point_kfrag_pok
|
||||
|
||||
h = self.get_proof_challenge_scalar()
|
||||
|
||||
e1h = h * e1
|
||||
v1h = h * v1
|
||||
u1h = h * u1
|
||||
|
||||
z = self.cfrag.proof.bn_sig
|
||||
ez = z * e
|
||||
vz = z * v
|
||||
uz = z * u
|
||||
|
||||
def raw_bytes_from_point(point: Point, only_y_coord=False) -> bytes:
|
||||
uncompressed_point_bytes = point.to_bytes(is_compressed=False)
|
||||
if only_y_coord:
|
||||
y_coord_start = (1 + Point.expected_bytes_length(is_compressed=False)) // 2
|
||||
return uncompressed_point_bytes[y_coord_start:]
|
||||
else:
|
||||
return uncompressed_point_bytes[1:]
|
||||
|
||||
# E points
|
||||
e_y = raw_bytes_from_point(e, only_y_coord=True)
|
||||
ez_xy = raw_bytes_from_point(ez)
|
||||
e1_y = raw_bytes_from_point(e1, only_y_coord=True)
|
||||
e1h_xy = raw_bytes_from_point(e1h)
|
||||
e2_y = raw_bytes_from_point(e2, only_y_coord=True)
|
||||
# V points
|
||||
v_y = raw_bytes_from_point(v, only_y_coord=True)
|
||||
vz_xy = raw_bytes_from_point(vz)
|
||||
v1_y = raw_bytes_from_point(v1, only_y_coord=True)
|
||||
v1h_xy = raw_bytes_from_point(v1h)
|
||||
v2_y = raw_bytes_from_point(v2, only_y_coord=True)
|
||||
# U points
|
||||
uz_xy = raw_bytes_from_point(uz)
|
||||
u1_y = raw_bytes_from_point(u1, only_y_coord=True)
|
||||
u1h_xy = raw_bytes_from_point(u1h)
|
||||
u2_y = raw_bytes_from_point(u2, only_y_coord=True)
|
||||
|
||||
# Get hashed KFrag validity message
|
||||
hash_function = hashes.Hash(hashes.SHA256(), backend=backend)
|
||||
|
||||
kfrag_id = self.cfrag.kfrag_id
|
||||
precursor = self.cfrag.point_precursor
|
||||
delegating_pubkey = self.delegating_pubkey
|
||||
receiving_pubkey = self.receiving_pubkey
|
||||
|
||||
validity_input = (kfrag_id, delegating_pubkey, receiving_pubkey, u1, precursor)
|
||||
kfrag_validity_message = bytes().join(bytes(item) for item in validity_input)
|
||||
hash_function.update(kfrag_validity_message)
|
||||
hashed_kfrag_validity_message = hash_function.finalize()
|
||||
|
||||
# Get Alice's verifying pubkey as ETH address
|
||||
alice_address = canonical_address_from_umbral_key(self.verifying_pubkey)
|
||||
|
||||
# Get KFrag signature's v value
|
||||
v_value = 27
|
||||
pubkey_bytes = recover_pubkey_from_signature(prehashed_message=hashed_kfrag_validity_message,
|
||||
signature=self.cfrag.proof.kfrag_signature,
|
||||
v_value_to_try=v_value)
|
||||
if not pubkey_bytes == self.verifying_pubkey.to_bytes():
|
||||
v_value = 28
|
||||
pubkey_bytes = recover_pubkey_from_signature(prehashed_message=hashed_kfrag_validity_message,
|
||||
signature=self.cfrag.proof.kfrag_signature,
|
||||
v_value_to_try=v_value)
|
||||
if not pubkey_bytes == self.verifying_pubkey.to_bytes():
|
||||
raise InvalidSignature("Bad signature: Not possible to recover public key from it.")
|
||||
|
||||
# Bundle everything together
|
||||
pieces = (
|
||||
e_y, ez_xy, e1_y, e1h_xy, e2_y,
|
||||
v_y, vz_xy, v1_y, v1h_xy, v2_y,
|
||||
uz_xy, u1_y, u1h_xy, u2_y,
|
||||
hashed_kfrag_validity_message,
|
||||
alice_address,
|
||||
v_value.to_bytes(1, 'big'),
|
||||
)
|
||||
return b''.join(pieces)
|
||||
|
|
|
@ -4,21 +4,22 @@ argh==0.26.2
|
|||
asn1crypto==0.24.0
|
||||
attrdict==2.0.1
|
||||
attrs==18.2.0
|
||||
autobahn==19.1.1
|
||||
autobahn==19.2.1
|
||||
automat==0.7.0
|
||||
boto3==1.9.91
|
||||
botocore==1.12.91
|
||||
boto3==1.9.96
|
||||
botocore==1.12.96
|
||||
bytestring-splitter==1.0.0a4
|
||||
certifi==2018.11.29
|
||||
cffi==1.11.5
|
||||
cffi==1.12.0
|
||||
chardet==3.0.4
|
||||
click==7.0
|
||||
coincurve==11.0.0
|
||||
colorama==0.4.1
|
||||
constant-sorrow==0.1.0a8
|
||||
constantly==15.1.0
|
||||
cryptography==2.5
|
||||
cytoolz==0.9.0.1 ; implementation_name == 'cpython'
|
||||
dateparser==0.7.0
|
||||
dateparser==0.7.1
|
||||
docutils==0.14
|
||||
eth-abi==2.0.0b5
|
||||
eth-account==0.3.0
|
||||
|
@ -30,6 +31,7 @@ eth-rlp==0.1.2
|
|||
eth-tester==0.1.0b37
|
||||
eth-typing==2.0.0
|
||||
eth-utils==1.4.1
|
||||
ethpm==0.1.4a12
|
||||
flask==1.0.2
|
||||
hendrix==3.2.2
|
||||
hexbytes==0.1.0
|
||||
|
|
|
@ -104,6 +104,7 @@ def test_inflation_rate(testerchain, token):
|
|||
testerchain.wait_for_receipt(tx)
|
||||
tx = issuer.functions.initialize().transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
reward = issuer.functions.getReservedReward().call()
|
||||
|
||||
# Mint some tokens and save result of minting
|
||||
period = issuer.functions.getCurrentPeriod().call()
|
||||
|
@ -115,11 +116,13 @@ def test_inflation_rate(testerchain, token):
|
|||
tx = issuer.functions.testMint(period + 1, 1, 1, 0).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 2 * one_period == token.functions.balanceOf(ursula).call()
|
||||
assert reward - token.functions.balanceOf(ursula).call() == issuer.functions.getReservedReward().call()
|
||||
|
||||
# Mint tokens in the next period, inflation rate must be lower than in previous minting
|
||||
tx = issuer.functions.testMint(period + 2, 1, 1, 0).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 3 * one_period > token.functions.balanceOf(ursula).call()
|
||||
assert reward - token.functions.balanceOf(ursula).call() == issuer.functions.getReservedReward().call()
|
||||
minted_amount = token.functions.balanceOf(ursula).call() - 2 * one_period
|
||||
|
||||
# Mint tokens in the first period again, inflation rate must be the same as in previous minting
|
||||
|
@ -127,11 +130,26 @@ def test_inflation_rate(testerchain, token):
|
|||
tx = issuer.functions.testMint(period + 1, 1, 1, 0).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 2 * one_period + 2 * minted_amount == token.functions.balanceOf(ursula).call()
|
||||
assert reward - token.functions.balanceOf(ursula).call() == issuer.functions.getReservedReward().call()
|
||||
|
||||
# Mint tokens in the next period, inflation rate must be lower than in previous minting
|
||||
tx = issuer.functions.testMint(period + 3, 1, 1, 0).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 2 * one_period + 3 * minted_amount > token.functions.balanceOf(ursula).call()
|
||||
assert reward - token.functions.balanceOf(ursula).call() == issuer.functions.getReservedReward().call()
|
||||
|
||||
# Return some tokens as a reward
|
||||
balance = token.functions.balanceOf(ursula).call()
|
||||
reward = issuer.functions.getReservedReward().call()
|
||||
tx = issuer.functions.testUnMint(2 * one_period + 2 * minted_amount).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert reward + 2 * one_period + 2 * minted_amount == issuer.functions.getReservedReward().call()
|
||||
|
||||
# Rate will be increased because some tokens were returned
|
||||
tx = issuer.functions.testMint(period + 3, 1, 1, 0).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert balance + one_period == token.functions.balanceOf(ursula).call()
|
||||
assert reward + one_period + 2 * minted_amount == issuer.functions.getReservedReward().call()
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
|
@ -161,7 +179,7 @@ def test_upgrading(testerchain, token):
|
|||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Upgrade to the second version, check new and old values of variables
|
||||
period = contract.functions.lastMintedPeriod().call()
|
||||
period = contract.functions.currentMintingPeriod().call()
|
||||
assert 1 == contract.functions.miningCoefficient().call()
|
||||
tx = dispatcher.functions.upgrade(contract_library_v2.address, secret, secret2_hash).transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
@ -170,7 +188,7 @@ def test_upgrading(testerchain, token):
|
|||
assert 2 * 3600 == contract.functions.secondsPerPeriod().call()
|
||||
assert 2 == contract.functions.lockedPeriodsCoefficient().call()
|
||||
assert 2 == contract.functions.rewardedPeriods().call()
|
||||
assert period == contract.functions.lastMintedPeriod().call()
|
||||
assert period == contract.functions.currentMintingPeriod().call()
|
||||
assert 2 * 10 ** 40 == contract.functions.totalSupply().call()
|
||||
# Check method from new ABI
|
||||
tx = contract.functions.setValueToCheck(3).transact({'from': creator})
|
||||
|
@ -197,7 +215,7 @@ def test_upgrading(testerchain, token):
|
|||
assert 3600 == contract.functions.secondsPerPeriod().call()
|
||||
assert 1 == contract.functions.lockedPeriodsCoefficient().call()
|
||||
assert 1 == contract.functions.rewardedPeriods().call()
|
||||
assert period == contract.functions.lastMintedPeriod().call()
|
||||
assert period == contract.functions.currentMintingPeriod().call()
|
||||
assert 2 * 10 ** 40 == contract.functions.totalSupply().call()
|
||||
# After rollback can't use new ABI
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
|
|
|
@ -30,7 +30,7 @@ contract IssuerMock is Issuer {
|
|||
}
|
||||
|
||||
function testMint(
|
||||
uint16 _period,
|
||||
uint16 _currentPeriod,
|
||||
uint256 _lockedValue,
|
||||
uint256 _totalLockedValue,
|
||||
uint16 _allLockedPeriods
|
||||
|
@ -38,13 +38,17 @@ contract IssuerMock is Issuer {
|
|||
public returns (uint256 amount)
|
||||
{
|
||||
amount = mint(
|
||||
_period,
|
||||
_currentPeriod,
|
||||
_lockedValue,
|
||||
_totalLockedValue,
|
||||
_allLockedPeriods);
|
||||
token.transfer(msg.sender, amount);
|
||||
}
|
||||
|
||||
function testUnMint(uint256 _amount) public {
|
||||
unMint(_amount);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -103,4 +107,3 @@ contract IssuerV2Mock is Issuer {
|
|||
require(delegateGet(_testTarget, "valueToCheck()") == valueToCheck);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
pragma solidity ^0.5.3;
|
||||
|
||||
|
||||
import "contracts/lib/SignatureVerifier.sol";
|
||||
import "contracts/lib/UmbralDeserializer.sol";
|
||||
|
||||
|
||||
/**
|
||||
* @notice Contract for using SignatureVerifier library
|
||||
**/
|
||||
contract SignatureVerifierMock {
|
||||
|
||||
function recover(bytes32 _hash, bytes memory _signature)
|
||||
public
|
||||
pure
|
||||
returns (address)
|
||||
{
|
||||
return SignatureVerifier.recover(_hash, _signature);
|
||||
}
|
||||
|
||||
function toAddress(bytes memory _publicKey) public pure returns (address) {
|
||||
return SignatureVerifier.toAddress(_publicKey);
|
||||
}
|
||||
|
||||
function hash(bytes memory _message, SignatureVerifier.HashAlgorithm _algorithm)
|
||||
public
|
||||
pure
|
||||
returns (bytes32 result)
|
||||
{
|
||||
return SignatureVerifier.hash(_message, _algorithm);
|
||||
}
|
||||
|
||||
function verify(
|
||||
bytes memory _message,
|
||||
bytes memory _signature,
|
||||
bytes memory _publicKey,
|
||||
SignatureVerifier.HashAlgorithm _algorithm
|
||||
)
|
||||
public
|
||||
pure
|
||||
returns (bool)
|
||||
{
|
||||
return SignatureVerifier.verify(_message, _signature, _publicKey, _algorithm);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @dev Contract for testing UmbralDeserializer library
|
||||
**/
|
||||
contract UmbralDeserializerMock {
|
||||
using UmbralDeserializer for bytes;
|
||||
|
||||
function toCapsule(bytes memory _capsuleBytes)
|
||||
public pure returns (
|
||||
byte pointESign,
|
||||
bytes32 pointEXCoord,
|
||||
byte pointVSign,
|
||||
bytes32 pointVXCoord,
|
||||
bytes32 bnSig
|
||||
)
|
||||
{
|
||||
UmbralDeserializer.Capsule memory capsule = _capsuleBytes.toCapsule();
|
||||
pointESign = byte(capsule.pointE.sign);
|
||||
pointEXCoord = bytes32(capsule.pointE.xCoord);
|
||||
pointVSign = byte(capsule.pointV.sign);
|
||||
pointVXCoord = bytes32(capsule.pointV.xCoord);
|
||||
bnSig = bytes32(capsule.bnSig);
|
||||
}
|
||||
|
||||
function toCorrectnessProof(bytes memory _proofBytes)
|
||||
public pure returns (
|
||||
byte pointE2Sign,
|
||||
bytes32 pointE2XCoord,
|
||||
byte pointV2Sign,
|
||||
bytes32 pointV2XCoord,
|
||||
byte pointKFragCommitmentSign,
|
||||
bytes32 pointKFragCommitmentXCoord,
|
||||
byte pointKFragPokSign,
|
||||
bytes32 pointKFragPokXCoord,
|
||||
bytes32 bnSig,
|
||||
bytes memory kFragSignature,
|
||||
bytes memory metadata
|
||||
)
|
||||
{
|
||||
UmbralDeserializer.CorrectnessProof memory proof = _proofBytes.toCorrectnessProof();
|
||||
pointE2Sign = byte(proof.pointE2.sign);
|
||||
pointE2XCoord = bytes32(proof.pointE2.xCoord);
|
||||
pointV2Sign = byte(proof.pointV2.sign);
|
||||
pointV2XCoord = bytes32(proof.pointV2.xCoord);
|
||||
pointKFragCommitmentSign = byte(proof.pointKFragCommitment.sign);
|
||||
pointKFragCommitmentXCoord = bytes32(proof.pointKFragCommitment.xCoord);
|
||||
pointKFragPokSign = byte(proof.pointKFragPok.sign);
|
||||
pointKFragPokXCoord = bytes32(proof.pointKFragPok.xCoord);
|
||||
bnSig = bytes32(proof.bnSig);
|
||||
kFragSignature = proof.kFragSignature;
|
||||
metadata = proof.metadata;
|
||||
}
|
||||
|
||||
// `toCapsuleFrag` is splitted into two methods because of EVM stack problems with many variables
|
||||
function toCorrectnessProofFromCapsuleFrag(bytes memory _cFragBytes)
|
||||
public pure returns (
|
||||
byte pointE2Sign,
|
||||
bytes32 pointE2XCoord,
|
||||
byte pointV2Sign,
|
||||
bytes32 pointV2XCoord,
|
||||
byte pointKFragCommitmentSign,
|
||||
bytes32 pointKFragCommitmentXCoord,
|
||||
byte pointKFragPokSign,
|
||||
bytes32 pointKFragPokXCoord,
|
||||
bytes32 bnSig,
|
||||
bytes memory kFragSignature,
|
||||
bytes memory metadata
|
||||
)
|
||||
{
|
||||
UmbralDeserializer.CapsuleFrag memory cFrag = _cFragBytes.toCapsuleFrag();
|
||||
UmbralDeserializer.CorrectnessProof memory proof = cFrag.proof;
|
||||
pointE2Sign = byte(proof.pointE2.sign);
|
||||
pointE2XCoord = bytes32(proof.pointE2.xCoord);
|
||||
pointV2Sign = byte(proof.pointV2.sign);
|
||||
pointV2XCoord = bytes32(proof.pointV2.xCoord);
|
||||
pointKFragCommitmentSign = byte(proof.pointKFragCommitment.sign);
|
||||
pointKFragCommitmentXCoord = bytes32(proof.pointKFragCommitment.xCoord);
|
||||
pointKFragPokSign = byte(proof.pointKFragPok.sign);
|
||||
pointKFragPokXCoord = bytes32(proof.pointKFragPok.xCoord);
|
||||
bnSig = bytes32(proof.bnSig);
|
||||
kFragSignature = proof.kFragSignature;
|
||||
metadata = proof.metadata;
|
||||
}
|
||||
|
||||
function toCapsuleFrag(bytes memory _cFragBytes)
|
||||
public pure returns (
|
||||
byte pointE1Sign,
|
||||
bytes32 pointE1XCoord,
|
||||
byte pointV1Sign,
|
||||
bytes32 pointV1XCoord,
|
||||
bytes32 kFragId,
|
||||
byte pointPrecursorSign,
|
||||
bytes32 pointPrecursorXCoord
|
||||
)
|
||||
{
|
||||
UmbralDeserializer.CapsuleFrag memory cFrag = _cFragBytes.toCapsuleFrag();
|
||||
pointE1Sign = byte(cFrag.pointE1.sign);
|
||||
pointE1XCoord = bytes32(cFrag.pointE1.xCoord);
|
||||
pointV1Sign = byte(cFrag.pointV1.sign);
|
||||
pointV1XCoord = bytes32(cFrag.pointV1.xCoord);
|
||||
kFragId = cFrag.kFragId;
|
||||
pointPrecursorSign = byte(cFrag.pointPrecursor.sign);
|
||||
pointPrecursorXCoord = bytes32(cFrag.pointPrecursor.xCoord);
|
||||
}
|
||||
}
|
|
@ -34,7 +34,7 @@ contract MinersEscrowBad is MinersEscrow {
|
|||
{
|
||||
}
|
||||
|
||||
function getStakeInfo(address, uint256) public view returns (uint16, uint16, uint16, uint256)
|
||||
function getSubStakeInfo(address, uint256) public view returns (uint16, uint16, uint16, uint256)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -127,4 +127,28 @@ contract PolicyManagerForMinersEscrowMock {
|
|||
return nodes[_node][_index];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notice Contract for testing miners escrow contract
|
||||
**/
|
||||
contract MiningAdjudicatorForMinersEscrowMock {
|
||||
|
||||
MinersEscrow public escrow;
|
||||
|
||||
constructor(MinersEscrow _escrow) public {
|
||||
escrow = _escrow;
|
||||
}
|
||||
|
||||
function slashMiner(
|
||||
address _miner,
|
||||
uint256 _penalty,
|
||||
address _investigator,
|
||||
uint256 _reward
|
||||
)
|
||||
public
|
||||
{
|
||||
escrow.slashMiner(_miner, _penalty, _investigator, _reward);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
pragma solidity ^0.5.3;
|
||||
|
||||
|
||||
import "contracts/MiningAdjudicator.sol";
|
||||
import "contracts/MinersEscrow.sol";
|
||||
import "contracts/lib/SignatureVerifier.sol";
|
||||
import "contracts/proxy/Upgradeable.sol";
|
||||
|
||||
|
||||
/**
|
||||
* @notice Contract for testing the MiningAdjudicator contract
|
||||
**/
|
||||
contract MinersEscrowForMiningAdjudicatorMock {
|
||||
|
||||
struct MinerInfo {
|
||||
uint256 value;
|
||||
uint16 stubValue1;
|
||||
uint16 stubValue2;
|
||||
uint16 stubValue3;
|
||||
}
|
||||
|
||||
mapping (address => MinerInfo) public minerInfo;
|
||||
mapping (address => uint256) public rewardInfo;
|
||||
|
||||
function setMinerInfo(address _miner, uint256 _amount) public {
|
||||
minerInfo[_miner].value = _amount;
|
||||
}
|
||||
|
||||
function slashMiner(
|
||||
address _miner,
|
||||
uint256 _penalty,
|
||||
address _investigator,
|
||||
uint256 _reward
|
||||
)
|
||||
public
|
||||
{
|
||||
minerInfo[_miner].value -= _penalty;
|
||||
rewardInfo[_investigator] += _reward;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notice Upgrade to this contract must lead to fail
|
||||
**/
|
||||
contract MiningAdjudicatorBad is Upgradeable {
|
||||
|
||||
MinersEscrow public escrow;
|
||||
SignatureVerifier.HashAlgorithm public hashAlgorithm;
|
||||
uint256 public basePenalty;
|
||||
uint256 public penaltyHistoryCoefficient;
|
||||
uint256 public percentagePenalty;
|
||||
uint256 public rewardCoefficient;
|
||||
mapping (bytes32 => bool) public evaluatedCFrags;
|
||||
mapping (address => uint256) public penaltyHistory;
|
||||
|
||||
function verifyState(address) public onlyOwner {}
|
||||
function finishUpgrade(address) public onlyOwner {}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notice Contract for testing upgrading the MiningAdjudicator contract
|
||||
**/
|
||||
contract MiningAdjudicatorV2Mock is MiningAdjudicator {
|
||||
|
||||
uint256 public valueToCheck;
|
||||
|
||||
constructor(
|
||||
MinersEscrow _escrow,
|
||||
SignatureVerifier.HashAlgorithm _hashAlgorithm,
|
||||
uint256 _basePenalty,
|
||||
uint256 _percentagePenalty,
|
||||
uint256 _penaltyHistoryCoefficient,
|
||||
uint256 _rewardCoefficient
|
||||
)
|
||||
public
|
||||
MiningAdjudicator(
|
||||
_escrow,
|
||||
_hashAlgorithm,
|
||||
_basePenalty,
|
||||
_percentagePenalty,
|
||||
_penaltyHistoryCoefficient,
|
||||
_rewardCoefficient
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
function setValueToCheck(uint256 _valueToCheck) public {
|
||||
valueToCheck = _valueToCheck;
|
||||
}
|
||||
|
||||
function verifyState(address _testTarget) public onlyOwner {
|
||||
super.verifyState(_testTarget);
|
||||
require(uint256(delegateGet(_testTarget, "valueToCheck()")) == valueToCheck);
|
||||
}
|
||||
}
|
|
@ -133,4 +133,4 @@ contract MinersEscrowForPolicyMock {
|
|||
function register(address _node) external {
|
||||
policyManager.register(_node, getCurrentPeriod() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -122,4 +122,4 @@ contract UserEscrowLibraryMockV2 {
|
|||
|
||||
function thirdMethod() public pure {}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,12 +14,26 @@ GNU General Public License for more details.
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
|
||||
import coincurve
|
||||
import pytest
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from eth_tester.exceptions import TransactionFailed
|
||||
from eth_utils import to_canonical_address
|
||||
from web3.contract import Contract
|
||||
|
||||
from nucypher.policy.models import IndisputableEvidence
|
||||
from umbral import pre
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.keys import UmbralPrivateKey
|
||||
from umbral.signing import Signer, Signature
|
||||
from cryptography.hazmat.backends.openssl import backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
|
||||
|
||||
NULL_ADDR = '0x' + '0' * 40
|
||||
|
||||
VALUE_FIELD = 0
|
||||
|
@ -47,6 +61,13 @@ SECRET_LENGTH = 32
|
|||
escrow_secret = os.urandom(SECRET_LENGTH)
|
||||
policy_manager_secret = os.urandom(SECRET_LENGTH)
|
||||
user_escrow_secret = os.urandom(SECRET_LENGTH)
|
||||
adjudicator_secret = os.urandom(SECRET_LENGTH)
|
||||
|
||||
ALGORITHM_SHA256 = 1
|
||||
BASE_PENALTY = 300
|
||||
PENALTY_HISTORY_COEFFICIENT = 10
|
||||
PERCENTAGE_PENALTY_COEFFICIENT = 2
|
||||
REWARD_COEFFICIENT = 2
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
|
@ -104,6 +125,108 @@ def policy_manager(testerchain, escrow):
|
|||
return contract, dispatcher
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def adjudicator(testerchain, escrow):
|
||||
escrow, _ = escrow
|
||||
creator = testerchain.interface.w3.eth.accounts[0]
|
||||
|
||||
secret_hash = testerchain.interface.w3.keccak(adjudicator_secret)
|
||||
|
||||
# Creator deploys the contract
|
||||
contract, _ = testerchain.interface.deploy_contract(
|
||||
'MiningAdjudicator',
|
||||
escrow.address,
|
||||
ALGORITHM_SHA256,
|
||||
BASE_PENALTY,
|
||||
PENALTY_HISTORY_COEFFICIENT,
|
||||
PERCENTAGE_PENALTY_COEFFICIENT,
|
||||
REWARD_COEFFICIENT)
|
||||
dispatcher, _ = testerchain.interface.deploy_contract('Dispatcher', contract.address, secret_hash)
|
||||
|
||||
# Wrap dispatcher contract
|
||||
contract = testerchain.interface.w3.eth.contract(
|
||||
abi=contract.abi,
|
||||
address=dispatcher.address,
|
||||
ContractFactoryClass=Contract)
|
||||
|
||||
tx = escrow.functions.setMiningAdjudicator(contract.address).transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
return contract, dispatcher
|
||||
|
||||
|
||||
# TODO: Obtain real re-encryption metadata. Maybe constructing a WorkOrder and obtaining a response.
|
||||
# TODO organize support functions
|
||||
def generate_args_for_slashing(testerchain, miner):
|
||||
def sign_data(data, umbral_privkey):
|
||||
umbral_pubkey_bytes = umbral_privkey.get_pubkey().to_bytes(is_compressed=False)
|
||||
|
||||
# Prepare hash of the data
|
||||
hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend)
|
||||
hash_ctx.update(data)
|
||||
data_hash = hash_ctx.finalize()
|
||||
|
||||
# Sign data and calculate recoverable signature
|
||||
cryptography_priv_key = umbral_privkey.to_cryptography_privkey()
|
||||
signature_der_bytes = cryptography_priv_key.sign(data, ec.ECDSA(hashes.SHA256()))
|
||||
signature = Signature.from_bytes(signature_der_bytes, der_encoded=True)
|
||||
recoverable_signature = bytes(signature) + bytes([0])
|
||||
pubkey_bytes = coincurve.PublicKey.from_signature_and_message(recoverable_signature, data_hash, hasher=None) \
|
||||
.format(compressed=False)
|
||||
if pubkey_bytes != umbral_pubkey_bytes:
|
||||
recoverable_signature = bytes(signature) + bytes([1])
|
||||
return recoverable_signature
|
||||
|
||||
delegating_privkey = UmbralPrivateKey.gen_key()
|
||||
_symmetric_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey())
|
||||
signing_privkey = UmbralPrivateKey.gen_key()
|
||||
signer = Signer(signing_privkey)
|
||||
priv_key_bob = UmbralPrivateKey.gen_key()
|
||||
pub_key_bob = priv_key_bob.get_pubkey()
|
||||
kfrags = pre.generate_kfrags(delegating_privkey=delegating_privkey,
|
||||
signer=signer,
|
||||
receiving_pubkey=pub_key_bob,
|
||||
threshold=2,
|
||||
N=4,
|
||||
sign_delegating_key=False,
|
||||
sign_receiving_key=False)
|
||||
capsule.set_correctness_keys(delegating_privkey.get_pubkey(), pub_key_bob, signing_privkey.get_pubkey())
|
||||
cfrag = pre.reencrypt(kfrags[0], capsule, metadata=os.urandom(34))
|
||||
capsule_bytes = capsule.to_bytes()
|
||||
# Corrupt proof
|
||||
cfrag.proof.bn_sig = CurveBN.gen_rand(capsule.params.curve)
|
||||
cfrag_bytes = cfrag.to_bytes()
|
||||
hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend)
|
||||
hash_ctx.update(capsule_bytes + cfrag_bytes)
|
||||
data_hash = hash_ctx.finalize()
|
||||
requester_umbral_private_key = UmbralPrivateKey.gen_key()
|
||||
requester_umbral_public_key_bytes = requester_umbral_private_key.get_pubkey().to_bytes(is_compressed=False)
|
||||
capsule_signature_by_requester = sign_data(capsule_bytes, requester_umbral_private_key)
|
||||
miner_umbral_private_key = UmbralPrivateKey.gen_key()
|
||||
miner_umbral_public_key_bytes = miner_umbral_private_key.get_pubkey().to_bytes(is_compressed=False)
|
||||
# Sign Umbral public key using eth-key
|
||||
hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend)
|
||||
hash_ctx.update(miner_umbral_public_key_bytes)
|
||||
miner_umbral_public_key_hash = hash_ctx.finalize()
|
||||
address = to_canonical_address(miner)
|
||||
sig_key = testerchain.interface.provider.ethereum_tester.backend._key_lookup[address]
|
||||
signed_miner_umbral_public_key = bytes(sig_key.sign_msg_hash(miner_umbral_public_key_hash))
|
||||
|
||||
capsule_signature_by_requester_and_miner = sign_data(capsule_signature_by_requester, miner_umbral_private_key)
|
||||
cfrag_signature_by_miner = sign_data(cfrag_bytes, miner_umbral_private_key)
|
||||
evidence = IndisputableEvidence(capsule, cfrag, ursula=None)
|
||||
evidence_data = evidence.precompute_values()
|
||||
return data_hash, (capsule_bytes,
|
||||
capsule_signature_by_requester,
|
||||
capsule_signature_by_requester_and_miner,
|
||||
cfrag_bytes,
|
||||
cfrag_signature_by_miner,
|
||||
requester_umbral_public_key_bytes,
|
||||
miner_umbral_public_key_bytes,
|
||||
signed_miner_umbral_public_key,
|
||||
evidence_data)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def user_escrow_proxy(testerchain, token, escrow, policy_manager):
|
||||
escrow, _ = escrow
|
||||
|
@ -118,12 +241,13 @@ def user_escrow_proxy(testerchain, token, escrow, policy_manager):
|
|||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_all(testerchain, token, escrow, policy_manager, user_escrow_proxy):
|
||||
def test_all(testerchain, token, escrow, policy_manager, adjudicator, user_escrow_proxy):
|
||||
# Travel to the start of the next period to prevent problems with unexpected overflow first period
|
||||
testerchain.time_travel(hours=1)
|
||||
|
||||
escrow, escrow_dispatcher = escrow
|
||||
policy_manager, policy_manager_dispatcher = policy_manager
|
||||
adjudicator, adjudicator_dispatcher = adjudicator
|
||||
user_escrow_proxy, user_escrow_linker = user_escrow_proxy
|
||||
creator, ursula1, ursula2, ursula3, ursula4, alice1, alice2, *everyone_else = testerchain.interface.w3.eth.accounts
|
||||
|
||||
|
@ -249,7 +373,7 @@ def test_all(testerchain, token, escrow, policy_manager, user_escrow_proxy):
|
|||
tx = escrow.functions.preDeposit([ursula3], [1], [10]).transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = escrow.functions.preDeposit([ursula3], [10**6], [10]).transact({'from': creator})
|
||||
tx = escrow.functions.preDeposit([ursula3], [10 ** 6], [10]).transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = escrow.functions.preDeposit([ursula3], [500], [1]).transact({'from': creator})
|
||||
|
@ -316,27 +440,27 @@ def test_all(testerchain, token, escrow, policy_manager, user_escrow_proxy):
|
|||
|
||||
# Create policies
|
||||
policy_id_1 = os.urandom(16)
|
||||
tx = policy_manager.functions.createPolicy(policy_id_1, 5, 44, [ursula1, ursula2])\
|
||||
tx = policy_manager.functions.createPolicy(policy_id_1, 5, 44, [ursula1, ursula2]) \
|
||||
.transact({'from': alice1, 'value': 2 * 1000 + 2 * 44, 'gas_price': 0})
|
||||
|
||||
testerchain.wait_for_receipt(tx)
|
||||
policy_id_2 = os.urandom(16)
|
||||
tx = policy_manager.functions.createPolicy(policy_id_2, 5, 44, [ursula2, user_escrow_1.address])\
|
||||
tx = policy_manager.functions.createPolicy(policy_id_2, 5, 44, [ursula2, user_escrow_1.address]) \
|
||||
.transact({'from': alice1, 'value': 2 * 1000 + 2 * 44, 'gas_price': 0})
|
||||
|
||||
testerchain.wait_for_receipt(tx)
|
||||
policy_id_3 = os.urandom(16)
|
||||
tx = policy_manager.functions.createPolicy(policy_id_3, 5, 44, [ursula1, user_escrow_1.address])\
|
||||
tx = policy_manager.functions.createPolicy(policy_id_3, 5, 44, [ursula1, user_escrow_1.address]) \
|
||||
.transact({'from': alice2, 'value': 2 * 1000 + 2 * 44, 'gas_price': 0})
|
||||
|
||||
testerchain.wait_for_receipt(tx)
|
||||
policy_id_4 = os.urandom(16)
|
||||
tx = policy_manager.functions.createPolicy(policy_id_4, 5, 44, [ursula2, user_escrow_1.address])\
|
||||
tx = policy_manager.functions.createPolicy(policy_id_4, 5, 44, [ursula2, user_escrow_1.address]) \
|
||||
.transact({'from': alice2, 'value': 2 * 1000 + 2 * 44, 'gas_price': 0})
|
||||
|
||||
testerchain.wait_for_receipt(tx)
|
||||
policy_id_5 = os.urandom(16)
|
||||
tx = policy_manager.functions.createPolicy(policy_id_5, 5, 44, [ursula1, ursula2])\
|
||||
tx = policy_manager.functions.createPolicy(policy_id_5, 5, 44, [ursula1, ursula2]) \
|
||||
.transact({'from': alice2, 'value': 2 * 1000 + 2 * 44, 'gas_price': 0})
|
||||
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
@ -383,7 +507,7 @@ def test_all(testerchain, token, escrow, policy_manager, user_escrow_proxy):
|
|||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
testerchain.time_travel(hours=1)
|
||||
tx = policy_manager.functions.revokeArrangement(policy_id_3, user_escrow_1.address)\
|
||||
tx = policy_manager.functions.revokeArrangement(policy_id_3, user_escrow_1.address) \
|
||||
.transact({'from': alice2, 'gas_price': 0})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
|
@ -454,7 +578,7 @@ def test_all(testerchain, token, escrow, policy_manager, user_escrow_proxy):
|
|||
policy_manager_v2, _ = testerchain.interface.deploy_contract('PolicyManager', escrow.address)
|
||||
# Ursula and Alice can't upgrade contracts, only owner can
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = escrow_dispatcher.functions.upgrade(escrow_v2.address, escrow_secret, escrow_secret2_hash)\
|
||||
tx = escrow_dispatcher.functions.upgrade(escrow_v2.address, escrow_secret, escrow_secret2_hash) \
|
||||
.transact({'from': alice1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
|
@ -462,12 +586,12 @@ def test_all(testerchain, token, escrow, policy_manager, user_escrow_proxy):
|
|||
.transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = policy_manager_dispatcher.functions\
|
||||
.upgrade(policy_manager_v2.address, policy_manager_secret, policy_manager_secret2_hash)\
|
||||
tx = policy_manager_dispatcher.functions \
|
||||
.upgrade(policy_manager_v2.address, policy_manager_secret, policy_manager_secret2_hash) \
|
||||
.transact({'from': alice1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = policy_manager_dispatcher.functions\
|
||||
tx = policy_manager_dispatcher.functions \
|
||||
.upgrade(policy_manager_v2.address, policy_manager_secret, policy_manager_secret2_hash) \
|
||||
.transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
@ -477,7 +601,7 @@ def test_all(testerchain, token, escrow, policy_manager, user_escrow_proxy):
|
|||
.transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert escrow_v2.address == escrow.functions.target().call()
|
||||
tx = policy_manager_dispatcher.functions\
|
||||
tx = policy_manager_dispatcher.functions \
|
||||
.upgrade(policy_manager_v2.address, policy_manager_secret, policy_manager_secret2_hash) \
|
||||
.transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
@ -495,7 +619,7 @@ def test_all(testerchain, token, escrow, policy_manager, user_escrow_proxy):
|
|||
tx = escrow_dispatcher.functions.rollback(escrow_secret2, escrow_secret3_hash).transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = policy_manager_dispatcher.functions.rollback(policy_manager_secret2, policy_manager_secret3_hash)\
|
||||
tx = policy_manager_dispatcher.functions.rollback(policy_manager_secret2, policy_manager_secret3_hash) \
|
||||
.transact({'from': alice1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
|
@ -521,25 +645,172 @@ def test_all(testerchain, token, escrow, policy_manager, user_escrow_proxy):
|
|||
user_escrow_secret2_hash = testerchain.interface.w3.keccak(user_escrow_secret2)
|
||||
# Ursula and Alice can't upgrade library, only owner can
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = user_escrow_linker.functions\
|
||||
tx = user_escrow_linker.functions \
|
||||
.upgrade(user_escrow_proxy_v2.address, user_escrow_secret, user_escrow_secret2_hash) \
|
||||
.transact({'from': alice1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = user_escrow_linker.functions\
|
||||
tx = user_escrow_linker.functions \
|
||||
.upgrade(user_escrow_proxy_v2.address, user_escrow_secret, user_escrow_secret2_hash) \
|
||||
.transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Upgrade library
|
||||
tx = user_escrow_linker.functions\
|
||||
tx = user_escrow_linker.functions \
|
||||
.upgrade(user_escrow_proxy_v2.address, user_escrow_secret, user_escrow_secret2_hash) \
|
||||
.transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert user_escrow_proxy_v2.address == user_escrow_linker.functions.target().call()
|
||||
|
||||
# Slash miners
|
||||
# Confirm activity for two periods
|
||||
tx = escrow.functions.confirmActivity().transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = escrow.functions.confirmActivity().transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = user_escrow_proxy_1.functions.confirmActivity().transact({'from': ursula3})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
testerchain.time_travel(hours=1)
|
||||
tx = escrow.functions.confirmActivity().transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = escrow.functions.confirmActivity().transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = user_escrow_proxy_1.functions.confirmActivity().transact({'from': ursula3})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
testerchain.time_travel(hours=1)
|
||||
|
||||
# Can't slash directly using the escrow contract
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = escrow.functions.slashMiner(ursula1, 100, alice1, 10).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Slash part of the free amount of tokens
|
||||
period = escrow.functions.getCurrentPeriod().call()
|
||||
tokens_amount = escrow.functions.minerInfo(ursula1).call()[VALUE_FIELD]
|
||||
previous_lock = escrow.functions.getLockedTokensInPast(ursula1, 1).call()
|
||||
lock = escrow.functions.getLockedTokens(ursula1).call()
|
||||
next_lock = escrow.functions.getLockedTokens(ursula1, 1).call()
|
||||
total_previous_lock = escrow.functions.lockedPerPeriod(period - 1).call()
|
||||
total_lock = escrow.functions.lockedPerPeriod(period).call()
|
||||
alice1_balance = token.functions.balanceOf(alice1).call()
|
||||
|
||||
data_hash, slashing_args = generate_args_for_slashing(testerchain, ursula1)
|
||||
assert not adjudicator.functions.evaluatedCFrags(data_hash).call()
|
||||
tx = adjudicator.functions.evaluateCFrag(*slashing_args).transact({'from': alice1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert adjudicator.functions.evaluatedCFrags(data_hash).call()
|
||||
assert tokens_amount - BASE_PENALTY == escrow.functions.minerInfo(ursula1).call()[VALUE_FIELD]
|
||||
assert previous_lock == escrow.functions.getLockedTokensInPast(ursula1, 1).call()
|
||||
assert lock == escrow.functions.getLockedTokens(ursula1).call()
|
||||
assert next_lock == escrow.functions.getLockedTokens(ursula1, 1).call()
|
||||
assert total_previous_lock == escrow.functions.lockedPerPeriod(period - 1).call()
|
||||
assert total_lock == escrow.functions.lockedPerPeriod(period).call()
|
||||
assert 0 == escrow.functions.lockedPerPeriod(period + 1).call()
|
||||
assert alice1_balance + BASE_PENALTY / REWARD_COEFFICIENT == token.functions.balanceOf(alice1).call()
|
||||
|
||||
# Slash part of the one sub stake
|
||||
tokens_amount = escrow.functions.minerInfo(ursula2).call()[VALUE_FIELD]
|
||||
unlocked_amount = tokens_amount - escrow.functions.getLockedTokens(ursula2).call()
|
||||
tx = escrow.functions.withdraw(unlocked_amount).transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
previous_lock = escrow.functions.getLockedTokensInPast(ursula2, 1).call()
|
||||
lock = escrow.functions.getLockedTokens(ursula2).call()
|
||||
next_lock = escrow.functions.getLockedTokens(ursula2, 1).call()
|
||||
data_hash, slashing_args = generate_args_for_slashing(testerchain, ursula2)
|
||||
assert not adjudicator.functions.evaluatedCFrags(data_hash).call()
|
||||
tx = adjudicator.functions.evaluateCFrag(*slashing_args).transact({'from': alice1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert adjudicator.functions.evaluatedCFrags(data_hash).call()
|
||||
assert lock - BASE_PENALTY == escrow.functions.minerInfo(ursula2).call()[VALUE_FIELD]
|
||||
assert previous_lock == escrow.functions.getLockedTokensInPast(ursula2, 1).call()
|
||||
assert lock - BASE_PENALTY == escrow.functions.getLockedTokens(ursula2).call()
|
||||
assert next_lock - BASE_PENALTY == escrow.functions.getLockedTokens(ursula2, 1).call()
|
||||
assert total_previous_lock == escrow.functions.lockedPerPeriod(period - 1).call()
|
||||
assert total_lock - BASE_PENALTY == escrow.functions.lockedPerPeriod(period).call()
|
||||
assert 0 == escrow.functions.lockedPerPeriod(period + 1).call()
|
||||
assert alice1_balance + BASE_PENALTY == token.functions.balanceOf(alice1).call()
|
||||
|
||||
# Upgrade the adjudicator
|
||||
# Deploy the same contract as the second version
|
||||
adjudicator_v1 = adjudicator.functions.target().call()
|
||||
adjudicator_v2, _ = testerchain.interface.deploy_contract(
|
||||
'MiningAdjudicator',
|
||||
escrow.address,
|
||||
ALGORITHM_SHA256,
|
||||
BASE_PENALTY,
|
||||
PENALTY_HISTORY_COEFFICIENT,
|
||||
PERCENTAGE_PENALTY_COEFFICIENT,
|
||||
REWARD_COEFFICIENT)
|
||||
adjudicator_secret2 = os.urandom(SECRET_LENGTH)
|
||||
adjudicator_secret2_hash = testerchain.interface.w3.keccak(adjudicator_secret2)
|
||||
# Ursula and Alice can't upgrade library, only owner can
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = adjudicator_dispatcher.functions \
|
||||
.upgrade(adjudicator_v2.address, adjudicator_secret, adjudicator_secret2_hash) \
|
||||
.transact({'from': alice1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = adjudicator_dispatcher.functions \
|
||||
.upgrade(adjudicator_v2.address, adjudicator_secret, adjudicator_secret2_hash) \
|
||||
.transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Upgrade contract
|
||||
tx = adjudicator_dispatcher.functions.upgrade(adjudicator_v2.address, adjudicator_secret, adjudicator_secret2_hash) \
|
||||
.transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert adjudicator_v2.address == adjudicator.functions.target().call()
|
||||
|
||||
# Ursula and Alice can't rollback contract, only owner can
|
||||
adjudicator_secret3 = os.urandom(SECRET_LENGTH)
|
||||
adjudicator_secret3_hash = testerchain.interface.w3.keccak(adjudicator_secret3)
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = adjudicator_dispatcher.functions.rollback(adjudicator_secret2, adjudicator_secret3_hash)\
|
||||
.transact({'from': alice1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = adjudicator_dispatcher.functions.rollback(adjudicator_secret2, adjudicator_secret3_hash)\
|
||||
.transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Rollback contracts
|
||||
tx = adjudicator_dispatcher.functions.rollback(adjudicator_secret2, adjudicator_secret3_hash) \
|
||||
.transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert adjudicator_v1 == adjudicator.functions.target().call()
|
||||
|
||||
# Slash two sub stakes
|
||||
tokens_amount = escrow.functions.minerInfo(ursula1).call()[VALUE_FIELD]
|
||||
unlocked_amount = tokens_amount - escrow.functions.getLockedTokens(ursula1).call()
|
||||
tx = escrow.functions.withdraw(unlocked_amount).transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
previous_lock = escrow.functions.getLockedTokensInPast(ursula1, 1).call()
|
||||
lock = escrow.functions.getLockedTokens(ursula1).call()
|
||||
next_lock = escrow.functions.getLockedTokens(ursula1, 1).call()
|
||||
total_lock = escrow.functions.lockedPerPeriod(period).call()
|
||||
alice2_balance = token.functions.balanceOf(alice2).call()
|
||||
data_hash, slashing_args = generate_args_for_slashing(testerchain, ursula1)
|
||||
assert not adjudicator.functions.evaluatedCFrags(data_hash).call()
|
||||
tx = adjudicator.functions.evaluateCFrag(*slashing_args).transact({'from': alice2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert adjudicator.functions.evaluatedCFrags(data_hash).call()
|
||||
data_hash, slashing_args = generate_args_for_slashing(testerchain, ursula1)
|
||||
assert not adjudicator.functions.evaluatedCFrags(data_hash).call()
|
||||
tx = adjudicator.functions.evaluateCFrag(*slashing_args).transact({'from': alice2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert adjudicator.functions.evaluatedCFrags(data_hash).call()
|
||||
penalty = (2 * BASE_PENALTY + 3 * PENALTY_HISTORY_COEFFICIENT)
|
||||
assert lock - penalty == escrow.functions.minerInfo(ursula1).call()[VALUE_FIELD]
|
||||
assert previous_lock == escrow.functions.getLockedTokensInPast(ursula1, 1).call()
|
||||
assert lock - penalty == escrow.functions.getLockedTokens(ursula1).call()
|
||||
assert next_lock - (penalty - (lock - next_lock)) == escrow.functions.getLockedTokens(ursula1, 1).call()
|
||||
assert total_previous_lock == escrow.functions.lockedPerPeriod(period - 1).call()
|
||||
assert total_lock - penalty == escrow.functions.lockedPerPeriod(period).call()
|
||||
assert 0 == escrow.functions.lockedPerPeriod(period + 1).call()
|
||||
assert alice2_balance + penalty / REWARD_COEFFICIENT == token.functions.balanceOf(alice2).call()
|
||||
|
||||
# Unlock and withdraw all tokens in MinersEscrow
|
||||
for index in range(11):
|
||||
for index in range(9):
|
||||
tx = escrow.functions.confirmActivity().transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = escrow.functions.confirmActivity().transact({'from': ursula2})
|
||||
|
@ -563,6 +834,9 @@ def test_all(testerchain, token, escrow, policy_manager, user_escrow_proxy):
|
|||
assert 0 == escrow.functions.getLockedTokens(user_escrow_1.address).call()
|
||||
assert 0 == escrow.functions.getLockedTokens(user_escrow_2.address).call()
|
||||
|
||||
ursula1_balance = token.functions.balanceOf(ursula1).call()
|
||||
ursula2_balance = token.functions.balanceOf(ursula2).call()
|
||||
user_escrow_1_balance = token.functions.balanceOf(user_escrow_1.address).call()
|
||||
tokens_amount = escrow.functions.minerInfo(ursula1).call()[VALUE_FIELD]
|
||||
tx = escrow.functions.withdraw(tokens_amount).transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
@ -572,19 +846,21 @@ def test_all(testerchain, token, escrow, policy_manager, user_escrow_proxy):
|
|||
tokens_amount = escrow.functions.minerInfo(user_escrow_1.address).call()[VALUE_FIELD]
|
||||
tx = user_escrow_proxy_1.functions.withdrawAsMiner(tokens_amount).transact({'from': ursula3})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 10000 < token.functions.balanceOf(ursula1).call()
|
||||
assert 1000 < token.functions.balanceOf(ursula2).call()
|
||||
assert 10000 < token.functions.balanceOf(user_escrow_1.address).call()
|
||||
assert ursula1_balance < token.functions.balanceOf(ursula1).call()
|
||||
assert ursula2_balance < token.functions.balanceOf(ursula2).call()
|
||||
assert user_escrow_1_balance < token.functions.balanceOf(user_escrow_1.address).call()
|
||||
|
||||
# Unlock and withdraw all tokens in UserEscrow
|
||||
testerchain.time_travel(hours=1)
|
||||
assert 0 == user_escrow_1.functions.getLockedTokens().call()
|
||||
assert 0 == user_escrow_2.functions.getLockedTokens().call()
|
||||
ursula3_balance = token.functions.balanceOf(ursula3).call()
|
||||
ursula4_balance = token.functions.balanceOf(ursula4).call()
|
||||
tokens_amount = token.functions.balanceOf(user_escrow_1.address).call()
|
||||
tx = user_escrow_1.functions.withdrawTokens(tokens_amount).transact({'from': ursula3})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tokens_amount = token.functions.balanceOf(user_escrow_2.address).call()
|
||||
tx = user_escrow_2.functions.withdrawTokens(tokens_amount).transact({'from': ursula4})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 10000 < token.functions.balanceOf(ursula3).call()
|
||||
assert 10000 == token.functions.balanceOf(ursula4).call()
|
||||
assert ursula3_balance < token.functions.balanceOf(ursula3).call()
|
||||
assert ursula4_balance < token.functions.balanceOf(ursula4).call()
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
"""
|
||||
This file is part of nucypher.
|
||||
|
||||
nucypher is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
nucypher is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
|
||||
import coincurve
|
||||
import pytest
|
||||
from cryptography.hazmat.backends.openssl import backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from eth_tester.exceptions import TransactionFailed
|
||||
from eth_utils import to_normalized_address
|
||||
|
||||
from umbral.keys import UmbralPrivateKey
|
||||
from umbral.signing import Signature
|
||||
|
||||
|
||||
ALGORITHM_KECCAK256 = 0
|
||||
ALGORITHM_SHA256 = 1
|
||||
ALGORITHM_RIPEMD160 = 2
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def signature_verifier(testerchain):
|
||||
contract, _ = testerchain.interface.deploy_contract('SignatureVerifierMock')
|
||||
return contract
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_recover(testerchain, signature_verifier):
|
||||
message = os.urandom(100)
|
||||
|
||||
# Prepare message hash
|
||||
hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend)
|
||||
hash_ctx.update(message)
|
||||
message_hash = hash_ctx.finalize()
|
||||
|
||||
# Generate Umbral key and extract "address" from the public key
|
||||
umbral_privkey = UmbralPrivateKey.gen_key()
|
||||
umbral_pubkey_bytes = umbral_privkey.get_pubkey().to_bytes(is_compressed=False)
|
||||
signer_address = bytearray(testerchain.interface.w3.solidityKeccak(['bytes32'], [umbral_pubkey_bytes[1:]]))
|
||||
signer_address = to_normalized_address(signer_address[12:])
|
||||
|
||||
# Sign message using SHA-256 hash (because only 32 bytes hash can be used in the `ecrecover` method)
|
||||
cryptography_priv_key = umbral_privkey.to_cryptography_privkey()
|
||||
signature_der_bytes = cryptography_priv_key.sign(message, ec.ECDSA(hashes.SHA256()))
|
||||
signature = Signature.from_bytes(signature_der_bytes, der_encoded=True)
|
||||
|
||||
# Get recovery id (v) before using contract
|
||||
# If we don't have recovery id while signing than we should try to recover public key with different v
|
||||
# Only the correct v will match the correct public key
|
||||
# First try v = 0
|
||||
recoverable_signature = bytes(signature) + bytes([0])
|
||||
pubkey_bytes = coincurve.PublicKey.from_signature_and_message(recoverable_signature, message_hash, hasher=None)\
|
||||
.format(compressed=False)
|
||||
if pubkey_bytes != umbral_pubkey_bytes:
|
||||
# Extracted public key is not ours, that means v = 1
|
||||
recoverable_signature = bytes(signature) + bytes([1])
|
||||
pubkey_bytes = coincurve.PublicKey.from_signature_and_message(recoverable_signature, message_hash, hasher=None)\
|
||||
.format(compressed=False)
|
||||
|
||||
# Check that recovery was ok
|
||||
assert umbral_pubkey_bytes == pubkey_bytes
|
||||
# Check recovery method in the contract
|
||||
assert signer_address == to_normalized_address(
|
||||
signature_verifier.functions.recover(message_hash, recoverable_signature).call())
|
||||
|
||||
# Also numbers 27 and 28 can be used for v
|
||||
recoverable_signature = recoverable_signature[0:-1] + bytes([recoverable_signature[-1] + 27])
|
||||
assert signer_address == to_normalized_address(
|
||||
signature_verifier.functions.recover(message_hash, recoverable_signature).call())
|
||||
|
||||
# Only number 0,1,27,28 are supported for v
|
||||
recoverable_signature = bytes(signature) + bytes([2])
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
signature_verifier.functions.recover(message_hash, recoverable_signature).call()
|
||||
|
||||
# Signature must include r, s and v
|
||||
recoverable_signature = bytes(signature)
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
signature_verifier.functions.recover(message_hash, recoverable_signature).call()
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_address(testerchain, signature_verifier):
|
||||
# Generate Umbral key and extract "address" from the public key
|
||||
umbral_privkey = UmbralPrivateKey.gen_key()
|
||||
umbral_pubkey_bytes = umbral_privkey.get_pubkey().to_bytes(is_compressed=False)
|
||||
signer_address = bytearray(testerchain.interface.w3.solidityKeccak(['bytes32'], [umbral_pubkey_bytes[1:]]))
|
||||
signer_address = to_normalized_address(signer_address[12:])
|
||||
|
||||
# Check extracting address in library
|
||||
result_address = signature_verifier.functions.toAddress(umbral_pubkey_bytes[1:]).call()
|
||||
assert signer_address == to_normalized_address(result_address)
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_hash(testerchain, signature_verifier):
|
||||
message = os.urandom(100)
|
||||
|
||||
# Prepare message hash
|
||||
hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend)
|
||||
hash_ctx.update(message)
|
||||
message_hash = hash_ctx.finalize()
|
||||
|
||||
# Verify hash function
|
||||
assert message_hash == signature_verifier.functions.hash(message, ALGORITHM_SHA256).call()
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_verify(testerchain, signature_verifier):
|
||||
message = os.urandom(100)
|
||||
|
||||
# Generate Umbral key
|
||||
umbral_privkey = UmbralPrivateKey.gen_key()
|
||||
umbral_pubkey_bytes = umbral_privkey.get_pubkey().to_bytes(is_compressed=False)
|
||||
|
||||
# Sign message using SHA-256 hash
|
||||
cryptography_priv_key = umbral_privkey.to_cryptography_privkey()
|
||||
signature_der_bytes = cryptography_priv_key.sign(message, ec.ECDSA(hashes.SHA256()))
|
||||
signature = Signature.from_bytes(signature_der_bytes, der_encoded=True)
|
||||
|
||||
# Prepare message hash
|
||||
hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend)
|
||||
hash_ctx.update(message)
|
||||
message_hash = hash_ctx.finalize()
|
||||
|
||||
# Get recovery id (v) before using contract
|
||||
# First try v = 0
|
||||
recoverable_signature = bytes(signature) + bytes([0])
|
||||
pubkey_bytes = coincurve.PublicKey.from_signature_and_message(recoverable_signature, message_hash, hasher=None) \
|
||||
.format(compressed=False)
|
||||
if pubkey_bytes != umbral_pubkey_bytes:
|
||||
# Extracted public key is not ours, that means v = 1
|
||||
recoverable_signature = bytes(signature) + bytes([1])
|
||||
|
||||
# Verify signature
|
||||
assert signature_verifier.functions.\
|
||||
verify(message, recoverable_signature, umbral_pubkey_bytes[1:], ALGORITHM_SHA256).call()
|
||||
|
||||
# Verify signature using wrong key
|
||||
umbral_privkey = UmbralPrivateKey.gen_key()
|
||||
umbral_pubkey_bytes = umbral_privkey.get_pubkey().to_bytes(is_compressed=False)
|
||||
assert not signature_verifier.functions.\
|
||||
verify(message, recoverable_signature, umbral_pubkey_bytes[1:], ALGORITHM_SHA256).call()
|
|
@ -0,0 +1,145 @@
|
|||
"""
|
||||
This file is part of nucypher.
|
||||
|
||||
nucypher is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
nucypher is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from eth_tester.exceptions import TransactionFailed
|
||||
|
||||
from umbral import pre, keys
|
||||
from umbral.signing import Signer
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def deserializer(testerchain):
|
||||
contract, _ = testerchain.interface.deploy_contract('UmbralDeserializerMock')
|
||||
return contract
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def fragments():
|
||||
delegating_privkey = keys.UmbralPrivateKey.gen_key()
|
||||
delegating_pubkey = delegating_privkey.get_pubkey()
|
||||
signing_privkey = keys.UmbralPrivateKey.gen_key()
|
||||
signer = Signer(signing_privkey)
|
||||
priv_key_bob = keys.UmbralPrivateKey.gen_key()
|
||||
pub_key_bob = priv_key_bob.get_pubkey()
|
||||
kfrags = pre.generate_kfrags(delegating_privkey=delegating_privkey,
|
||||
signer=signer,
|
||||
receiving_pubkey=pub_key_bob,
|
||||
threshold=2,
|
||||
N=4,
|
||||
sign_delegating_key=False,
|
||||
sign_receiving_key=False)
|
||||
# TODO: Use nucypher re-encryption metadata
|
||||
metadata = b"This is an example of metadata for re-encryption request"
|
||||
|
||||
_symmetric_key, capsule = pre._encapsulate(delegating_pubkey)
|
||||
capsule.set_correctness_keys(delegating=delegating_pubkey,
|
||||
receiving=pub_key_bob,
|
||||
verifying=signing_privkey.get_pubkey())
|
||||
cfrag = pre.reencrypt(kfrags[0], capsule, metadata=metadata)
|
||||
return capsule, cfrag
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_capsule(testerchain, deserializer, fragments):
|
||||
# Wrong number of bytes to deserialize capsule
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
deserializer.functions.toCapsule(os.urandom(97)).call()
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
deserializer.functions.toCapsule(os.urandom(99)).call()
|
||||
|
||||
# Check random capsule bytes
|
||||
capsule_bytes = os.urandom(98)
|
||||
result = deserializer.functions.toCapsule(capsule_bytes).call()
|
||||
assert capsule_bytes == bytes().join(bytes(item) for item in result)
|
||||
|
||||
# Check real capsule
|
||||
capsule, _cfrag = fragments
|
||||
capsule_bytes = capsule.to_bytes()
|
||||
result = deserializer.functions.toCapsule(capsule_bytes).call()
|
||||
assert bytes(capsule.point_e) == result[0] + result[1]
|
||||
assert bytes(capsule.point_v) == result[2] + result[3]
|
||||
assert capsule.bn_sig.to_bytes() == bytes(result[4])
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_proof(testerchain, deserializer, fragments):
|
||||
# Wrong number of bytes to deserialize proof
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
deserializer.functions.toCorrectnessProof(os.urandom(227)).call()
|
||||
|
||||
# Check random proof bytes without metadata
|
||||
proof_bytes = os.urandom(228)
|
||||
result = deserializer.functions.toCorrectnessProof(proof_bytes).call()
|
||||
assert proof_bytes == bytes().join(result)
|
||||
|
||||
# Check random proof bytes with metadata
|
||||
proof_bytes = os.urandom(270)
|
||||
result = deserializer.functions.toCorrectnessProof(proof_bytes).call()
|
||||
assert proof_bytes == bytes().join(result)
|
||||
|
||||
# Get real cfrag and proof
|
||||
_capsule, cfrag = fragments
|
||||
proof = cfrag.proof
|
||||
proof_bytes = proof.to_bytes()
|
||||
|
||||
# Check real proof
|
||||
result = deserializer.functions.toCorrectnessProof(proof_bytes).call()
|
||||
assert bytes(proof.point_e2) == result[0] + result[1]
|
||||
assert bytes(proof.point_v2) == result[2] + result[3]
|
||||
assert bytes(proof.point_kfrag_commitment) == result[4] + result[5]
|
||||
assert bytes(proof.point_kfrag_pok) == result[6] + result[7]
|
||||
assert proof.bn_sig.to_bytes() == result[8]
|
||||
assert bytes(proof.kfrag_signature) == result[9]
|
||||
assert bytes(proof.metadata) == result[10]
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_cfrag(testerchain, deserializer, fragments):
|
||||
# Wrong number of bytes to deserialize cfrag
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
deserializer.functions.toCapsuleFrag(os.urandom(358)).call()
|
||||
|
||||
# Check random cfrag bytes
|
||||
cfrag_bytes = os.urandom(131)
|
||||
proof_bytes = os.urandom(228)
|
||||
full_cfrag_bytes = cfrag_bytes + proof_bytes
|
||||
result = deserializer.functions.toCapsuleFrag(full_cfrag_bytes).call()
|
||||
assert cfrag_bytes == bytes().join(result)
|
||||
result = deserializer.functions.toCorrectnessProofFromCapsuleFrag(full_cfrag_bytes).call()
|
||||
assert proof_bytes == bytes().join(result)
|
||||
|
||||
# Check real cfrag
|
||||
_capsule, cfrag = fragments
|
||||
proof = cfrag.proof
|
||||
cfrag_bytes = cfrag.to_bytes()
|
||||
result = deserializer.functions.toCapsuleFrag(cfrag_bytes).call()
|
||||
assert bytes(cfrag.point_e1) == result[0] + result[1]
|
||||
assert bytes(cfrag.point_v1) == result[2] + result[3]
|
||||
assert bytes(cfrag.kfrag_id) == result[4]
|
||||
assert bytes(cfrag.point_precursor) == result[5] + result[6]
|
||||
result = deserializer.functions.toCorrectnessProofFromCapsuleFrag(cfrag_bytes).call()
|
||||
assert bytes(proof.point_e2) == result[0] + result[1]
|
||||
assert bytes(proof.point_v2) == result[2] + result[3]
|
||||
assert bytes(proof.point_kfrag_commitment) == result[4] + result[5]
|
||||
assert bytes(proof.point_kfrag_pok) == result[6] + result[7]
|
||||
assert proof.bn_sig.to_bytes() == result[8]
|
||||
assert bytes(proof.kfrag_signature) == result[9]
|
||||
assert bytes(proof.metadata) == result[10]
|
|
@ -14,6 +14,8 @@ GNU General Public License for more details.
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import pytest
|
||||
from web3.contract import Contract
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ GNU General Public License for more details.
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import pytest
|
||||
from eth_tester.exceptions import TransactionFailed
|
||||
|
||||
|
@ -23,6 +25,8 @@ CONFIRMED_PERIOD_1_FIELD = 2
|
|||
CONFIRMED_PERIOD_2_FIELD = 3
|
||||
LAST_ACTIVE_PERIOD_FIELD = 4
|
||||
|
||||
MAX_SUB_STAKES = 30
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_staking(testerchain, token, escrow_contract):
|
||||
|
@ -310,8 +314,8 @@ def test_staking(testerchain, token, escrow_contract):
|
|||
|
||||
# And increases locked time by dividing stake into two parts
|
||||
period = escrow.functions.getCurrentPeriod().call()
|
||||
assert 2 == escrow.functions.getStakesLength(ursula2).call()
|
||||
assert period + 1 == escrow.functions.getLastPeriodOfStake(ursula2, 1).call()
|
||||
assert 2 == escrow.functions.getSubStakesLength(ursula2).call()
|
||||
assert period + 1 == escrow.functions.getLastPeriodOfSubStake(ursula2, 1).call()
|
||||
tx = escrow.functions.divideStake(1, 200, 1).transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 1000 == escrow.functions.getLockedTokens(ursula2).call()
|
||||
|
@ -340,8 +344,8 @@ def test_staking(testerchain, token, escrow_contract):
|
|||
testerchain.time_travel(hours=1)
|
||||
# Check number of stakes and last stake parameters
|
||||
period = escrow.functions.getCurrentPeriod().call()
|
||||
assert 3 == escrow.functions.getStakesLength(ursula2).call()
|
||||
assert period == escrow.functions.getLastPeriodOfStake(ursula2, 1).call()
|
||||
assert 3 == escrow.functions.getSubStakesLength(ursula2).call()
|
||||
assert period == escrow.functions.getLastPeriodOfSubStake(ursula2, 1).call()
|
||||
|
||||
# Divide stake again
|
||||
tx = escrow.functions.divideStake(1, 200, 2).transact({'from': ursula2})
|
||||
|
@ -361,8 +365,8 @@ def test_staking(testerchain, token, escrow_contract):
|
|||
assert 2 == event_args['periods']
|
||||
|
||||
# Check number of stakes and last stake parameters
|
||||
assert 4 == escrow.functions.getStakesLength(ursula2).call()
|
||||
assert period + 1 == escrow.functions.getLastPeriodOfStake(ursula2, 2).call()
|
||||
assert 4 == escrow.functions.getSubStakesLength(ursula2).call()
|
||||
assert period + 1 == escrow.functions.getLastPeriodOfSubStake(ursula2, 2).call()
|
||||
|
||||
# Divide stake again
|
||||
tx = escrow.functions.divideStake(2, 100, 2).transact({'from': ursula2})
|
||||
|
@ -398,8 +402,8 @@ def test_staking(testerchain, token, escrow_contract):
|
|||
tx = escrow.functions.divideStake(0, 200, 10).transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
assert 5 == escrow.functions.getStakesLength(ursula2).call()
|
||||
assert period == escrow.functions.getLastPeriodOfStake(ursula2, 3).call()
|
||||
assert 5 == escrow.functions.getSubStakesLength(ursula2).call()
|
||||
assert period == escrow.functions.getLastPeriodOfSubStake(ursula2, 3).call()
|
||||
tx = escrow.functions.divideStake(3, 100, 1).transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
|
@ -430,3 +434,60 @@ def test_staking(testerchain, token, escrow_contract):
|
|||
assert 5 == len(deposit_log.get_all_entries())
|
||||
assert 11 == len(lock_log.get_all_entries())
|
||||
assert 1 == len(withdraw_log.get_all_entries())
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_max_sub_stakes(testerchain, token, escrow_contract):
|
||||
escrow = escrow_contract(10000)
|
||||
creator = testerchain.interface.w3.eth.accounts[0]
|
||||
ursula = testerchain.interface.w3.eth.accounts[1]
|
||||
|
||||
# Initialize Escrow contract
|
||||
tx = escrow.functions.initialize().transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Prepare before deposit
|
||||
tx = token.functions.transfer(ursula, 4000).transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = token.functions.approve(escrow.address, 4000).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Lock one sub stake from current period and others from next one
|
||||
tx = escrow.functions.deposit(100, 2).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 1 == escrow.functions.getSubStakesLength(ursula).call()
|
||||
|
||||
testerchain.time_travel(hours=1)
|
||||
for index in range(MAX_SUB_STAKES - 1):
|
||||
tx = escrow.functions.deposit(100, 2).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert MAX_SUB_STAKES == escrow.functions.getSubStakesLength(ursula).call()
|
||||
assert 3000 == escrow.functions.getLockedTokens(ursula, 1).call()
|
||||
|
||||
# Can't lock more because of reaching the maximum number of active sub stakes
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = escrow.functions.deposit(100, 2).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# After two periods first sub stake will be unlocked and we can lock again
|
||||
testerchain.time_travel(hours=1)
|
||||
tx = escrow.functions.confirmActivity().transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
# Before sub stake will be inactive it must be mined
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = escrow.functions.deposit(100, 2).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
testerchain.time_travel(hours=1)
|
||||
assert 2900 == escrow.functions.getLockedTokens(ursula).call()
|
||||
assert 0 == escrow.functions.getLockedTokens(ursula, 1).call()
|
||||
assert MAX_SUB_STAKES == escrow.functions.getSubStakesLength(ursula).call()
|
||||
tx = escrow.functions.deposit(100, 2).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 2900 == escrow.functions.getLockedTokens(ursula).call()
|
||||
assert 100 == escrow.functions.getLockedTokens(ursula, 1).call()
|
||||
assert MAX_SUB_STAKES == escrow.functions.getSubStakesLength(ursula).call()
|
||||
|
||||
# Can't lock more because of reaching the maximum number of active sub stakes and they are not mined yet
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = escrow.functions.deposit(100, 2).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
|
|
@ -14,6 +14,8 @@ GNU General Public License for more details.
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import pytest
|
||||
from eth_tester.exceptions import TransactionFailed
|
||||
from web3.contract import Contract
|
||||
|
|
|
@ -14,6 +14,8 @@ GNU General Public License for more details.
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import pytest
|
||||
from eth_tester.exceptions import TransactionFailed
|
||||
from web3.contract import Contract
|
||||
|
@ -97,10 +99,13 @@ def test_mining(testerchain, token, escrow_contract):
|
|||
tx = escrow.functions.divideStake(0, 500, 1).transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Ursula can't use method from Issuer contract directly, only from mint() method
|
||||
# Can't use methods from Issuer contract directly
|
||||
with pytest.raises(Exception):
|
||||
tx = escrow.functions.mint(1, 1, 1, 1).transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
with pytest.raises(Exception):
|
||||
tx = escrow.functions.unMint(1).transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Only Ursula confirms next period
|
||||
testerchain.time_travel(hours=1)
|
||||
|
@ -294,3 +299,391 @@ def test_mining(testerchain, token, escrow_contract):
|
|||
assert 6 == len(lock_log.get_all_entries())
|
||||
assert 1 == len(divides_log.get_all_entries())
|
||||
assert 10 == len(activity_log.get_all_entries())
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_slashing(testerchain, token, escrow_contract):
|
||||
escrow = escrow_contract(1500)
|
||||
adjudicator, _ = testerchain.interface.deploy_contract(
|
||||
'MiningAdjudicatorForMinersEscrowMock', escrow.address
|
||||
)
|
||||
tx = escrow.functions.setMiningAdjudicator(adjudicator.address).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
creator = testerchain.interface.w3.eth.accounts[0]
|
||||
ursula = testerchain.interface.w3.eth.accounts[1]
|
||||
investigator = testerchain.interface.w3.eth.accounts[2]
|
||||
|
||||
slashing_log = escrow.events.Slashed.createFilter(fromBlock='latest')
|
||||
|
||||
# Give Escrow tokens for reward and initialize contract
|
||||
tx = token.functions.transfer(escrow.address, 10 ** 9).transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = escrow.functions.initialize().transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Give Ursula deposit some tokens
|
||||
tx = token.functions.transfer(ursula, 10000).transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = token.functions.approve(escrow.address, 10000).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = escrow.functions.deposit(100, 2).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
testerchain.time_travel(hours=1)
|
||||
period = escrow.functions.getCurrentPeriod().call()
|
||||
assert 100 == escrow.functions.minerInfo(ursula).call()[VALUE_FIELD]
|
||||
assert 100 == escrow.functions.getLockedTokens(ursula).call()
|
||||
assert 100 == escrow.functions.lockedPerPeriod(period).call()
|
||||
assert 0 == escrow.functions.lockedPerPeriod(period + 1).call()
|
||||
|
||||
# Can't slash directly using the escrow contract
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = escrow.functions.slashMiner(ursula, 100, investigator, 10).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
# Penalty must be greater than zero
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = escrow.functions.slashMiner(ursula, 0, investigator, 0).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Slash the whole stake
|
||||
reward = escrow.functions.getReservedReward().call()
|
||||
tx = adjudicator.functions.slashMiner(ursula, 100, investigator, 10).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
# Miner has no more sub stakes
|
||||
assert 0 == escrow.functions.minerInfo(ursula).call()[VALUE_FIELD]
|
||||
assert 0 == escrow.functions.getLockedTokens(ursula).call()
|
||||
assert 10 == token.functions.balanceOf(investigator).call()
|
||||
assert 0 == escrow.functions.lockedPerPeriod(period).call()
|
||||
assert reward + 90 == escrow.functions.getReservedReward().call()
|
||||
|
||||
events = slashing_log.get_all_entries()
|
||||
assert 1 == len(events)
|
||||
event_args = events[0]['args']
|
||||
assert ursula == event_args['miner']
|
||||
assert 100 == event_args['penalty']
|
||||
assert investigator == event_args['investigator']
|
||||
assert 10 == event_args['reward']
|
||||
|
||||
# New deposit and confirmation of activity
|
||||
tx = escrow.functions.deposit(100, 5).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
testerchain.time_travel(hours=1)
|
||||
period += 1
|
||||
tx = escrow.functions.confirmActivity().transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 100 == escrow.functions.minerInfo(ursula).call()[VALUE_FIELD]
|
||||
assert 100 == escrow.functions.getLockedTokens(ursula).call()
|
||||
assert 100 == escrow.functions.getLockedTokens(ursula, 1).call()
|
||||
assert 100 == escrow.functions.lockedPerPeriod(period).call()
|
||||
assert 100 == escrow.functions.lockedPerPeriod(period + 1).call()
|
||||
|
||||
# Slash part of one sub stake (there is only one sub stake)
|
||||
reward = escrow.functions.getReservedReward().call()
|
||||
tx = adjudicator.functions.slashMiner(ursula, 10, investigator, 11).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 90 == escrow.functions.minerInfo(ursula).call()[VALUE_FIELD]
|
||||
assert 90 == escrow.functions.getLockedTokens(ursula).call()
|
||||
assert 90 == escrow.functions.getLockedTokens(ursula, 1).call()
|
||||
# The reward will be equal to the penalty (can't be more then penalty)
|
||||
assert 20 == token.functions.balanceOf(investigator).call()
|
||||
assert 90 == escrow.functions.lockedPerPeriod(period).call()
|
||||
assert 90 == escrow.functions.lockedPerPeriod(period + 1).call()
|
||||
assert reward == escrow.functions.getReservedReward().call()
|
||||
|
||||
events = slashing_log.get_all_entries()
|
||||
assert 2 == len(events)
|
||||
event_args = events[1]['args']
|
||||
assert ursula == event_args['miner']
|
||||
assert 10 == event_args['penalty']
|
||||
assert investigator == event_args['investigator']
|
||||
assert 10 == event_args['reward']
|
||||
|
||||
# New deposit of a longer sub stake
|
||||
tx = escrow.functions.deposit(100, 6).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
testerchain.time_travel(hours=1)
|
||||
period += 1
|
||||
assert 90 == escrow.functions.getLockedTokensInPast(ursula, 1).call()
|
||||
assert 190 == escrow.functions.getLockedTokens(ursula).call()
|
||||
assert 100 == escrow.functions.getLockedTokens(ursula, 4).call()
|
||||
assert 190 == escrow.functions.minerInfo(ursula).call()[VALUE_FIELD]
|
||||
assert 90 == escrow.functions.lockedPerPeriod(period - 1).call()
|
||||
assert 190 == escrow.functions.lockedPerPeriod(period).call()
|
||||
assert 0 == escrow.functions.lockedPerPeriod(period + 1).call()
|
||||
|
||||
# Slash again part of the first sub stake because new sub stake is longer (there are two main sub stakes)
|
||||
reward = escrow.functions.getReservedReward().call()
|
||||
tx = adjudicator.functions.slashMiner(ursula, 10, investigator, 0).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 180 == escrow.functions.minerInfo(ursula).call()[VALUE_FIELD]
|
||||
assert 90 == escrow.functions.getLockedTokensInPast(ursula, 1).call()
|
||||
assert 180 == escrow.functions.getLockedTokens(ursula).call()
|
||||
assert 100 == escrow.functions.getLockedTokens(ursula, 4).call()
|
||||
assert 20 == token.functions.balanceOf(investigator).call()
|
||||
assert 90 == escrow.functions.lockedPerPeriod(period - 1).call()
|
||||
assert 180 == escrow.functions.lockedPerPeriod(period).call()
|
||||
assert reward + 10 == escrow.functions.getReservedReward().call()
|
||||
|
||||
events = slashing_log.get_all_entries()
|
||||
assert 3 == len(events)
|
||||
event_args = events[2]['args']
|
||||
assert ursula == event_args['miner']
|
||||
assert 10 == event_args['penalty']
|
||||
assert investigator == event_args['investigator']
|
||||
assert 0 == event_args['reward']
|
||||
|
||||
# New deposit of a shorter sub stake
|
||||
tx = escrow.functions.deposit(110, 2).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
testerchain.time_travel(hours=2)
|
||||
period += 2
|
||||
assert 290 == escrow.functions.getLockedTokensInPast(ursula, 1).call()
|
||||
assert 290 == escrow.functions.getLockedTokens(ursula).call()
|
||||
assert 180 == escrow.functions.getLockedTokens(ursula, 2).call()
|
||||
deposit = escrow.functions.minerInfo(ursula).call()[VALUE_FIELD] # Some reward is already mined
|
||||
assert 290 == escrow.functions.lockedPerPeriod(period - 1).call()
|
||||
assert 0 == escrow.functions.lockedPerPeriod(period).call()
|
||||
|
||||
# Slash only free amount of tokens
|
||||
reward = escrow.functions.getReservedReward().call()
|
||||
tx = adjudicator.functions.slashMiner(ursula, deposit - 290, investigator, 0).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 290 == escrow.functions.minerInfo(ursula).call()[VALUE_FIELD]
|
||||
assert 290 == escrow.functions.getLockedTokensInPast(ursula, 1).call()
|
||||
assert 290 == escrow.functions.getLockedTokens(ursula).call()
|
||||
assert 180 == escrow.functions.getLockedTokens(ursula, 2).call()
|
||||
assert 20 == token.functions.balanceOf(investigator).call()
|
||||
assert 290 == escrow.functions.lockedPerPeriod(period - 1).call()
|
||||
assert 0 == escrow.functions.lockedPerPeriod(period).call()
|
||||
assert reward + deposit - 290 == escrow.functions.getReservedReward().call()
|
||||
|
||||
events = slashing_log.get_all_entries()
|
||||
assert 4 == len(events)
|
||||
event_args = events[3]['args']
|
||||
assert ursula == event_args['miner']
|
||||
assert deposit - 290 == event_args['penalty']
|
||||
assert investigator == event_args['investigator']
|
||||
assert 0 == event_args['reward']
|
||||
|
||||
# Slash only the new sub stake because it's the shortest one (there are three main sub stakes)
|
||||
tx = adjudicator.functions.slashMiner(ursula, 20, investigator, 0).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 270 == escrow.functions.minerInfo(ursula).call()[VALUE_FIELD]
|
||||
assert 290 == escrow.functions.getLockedTokensInPast(ursula, 1).call()
|
||||
assert 270 == escrow.functions.getLockedTokens(ursula).call()
|
||||
assert 180 == escrow.functions.getLockedTokens(ursula, 2).call()
|
||||
assert 20 == token.functions.balanceOf(investigator).call()
|
||||
assert 290 == escrow.functions.lockedPerPeriod(period - 1).call()
|
||||
assert 0 == escrow.functions.lockedPerPeriod(period).call()
|
||||
assert reward + deposit - 270 == escrow.functions.getReservedReward().call()
|
||||
|
||||
events = slashing_log.get_all_entries()
|
||||
assert 5 == len(events)
|
||||
event_args = events[4]['args']
|
||||
assert ursula == event_args['miner']
|
||||
assert 20 == event_args['penalty']
|
||||
assert investigator == event_args['investigator']
|
||||
assert 0 == event_args['reward']
|
||||
|
||||
# Slash the whole new sub stake and part of the next shortest (there are three main sub stakes)
|
||||
reward = escrow.functions.getReservedReward().call()
|
||||
tx = adjudicator.functions.slashMiner(ursula, 100, investigator, 0).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 170 == escrow.functions.minerInfo(ursula).call()[VALUE_FIELD]
|
||||
assert 290 == escrow.functions.getLockedTokensInPast(ursula, 1).call()
|
||||
assert 170 == escrow.functions.getLockedTokens(ursula).call()
|
||||
assert 170 == escrow.functions.getLockedTokens(ursula, 2).call()
|
||||
assert 20 == token.functions.balanceOf(investigator).call()
|
||||
assert 290 == escrow.functions.lockedPerPeriod(period - 1).call()
|
||||
assert 0 == escrow.functions.lockedPerPeriod(period).call()
|
||||
assert reward + 100 == escrow.functions.getReservedReward().call()
|
||||
|
||||
events = slashing_log.get_all_entries()
|
||||
assert 6 == len(events)
|
||||
event_args = events[5]['args']
|
||||
assert ursula == event_args['miner']
|
||||
assert 100 == event_args['penalty']
|
||||
assert investigator == event_args['investigator']
|
||||
assert 0 == event_args['reward']
|
||||
|
||||
# Confirmation of activity must handle correctly inactive sub stakes after slashing
|
||||
tx = escrow.functions.confirmActivity().transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# New deposit
|
||||
tx = escrow.functions.deposit(100, 2).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 170 == escrow.functions.getLockedTokens(ursula).call()
|
||||
assert 270 == escrow.functions.getLockedTokens(ursula, 1).call()
|
||||
assert 270 == escrow.functions.lockedPerPeriod(period + 1).call()
|
||||
deposit = escrow.functions.minerInfo(ursula).call()[VALUE_FIELD] # Some reward is already mined
|
||||
unlocked_deposit = deposit - 270
|
||||
reward = escrow.functions.getReservedReward().call()
|
||||
|
||||
# Slash the new sub stake which starts in the next period
|
||||
# Because locked value is more in the next period than in the current period
|
||||
tx = adjudicator.functions.slashMiner(ursula, unlocked_deposit + 10, investigator, 0).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 170 == escrow.functions.getLockedTokens(ursula).call()
|
||||
assert 260 == escrow.functions.getLockedTokens(ursula, 1).call()
|
||||
assert 260 == escrow.functions.minerInfo(ursula).call()[VALUE_FIELD]
|
||||
assert 260 == escrow.functions.lockedPerPeriod(period + 1).call()
|
||||
assert reward + unlocked_deposit + 10 == escrow.functions.getReservedReward().call()
|
||||
|
||||
events = slashing_log.get_all_entries()
|
||||
assert 7 == len(events)
|
||||
event_args = events[6]['args']
|
||||
assert ursula == event_args['miner']
|
||||
assert unlocked_deposit + 10 == event_args['penalty']
|
||||
assert investigator == event_args['investigator']
|
||||
assert 0 == event_args['reward']
|
||||
|
||||
# After two periods two shortest sub stakes will be unlocked, lock again and slash after this
|
||||
testerchain.time_travel(hours=1)
|
||||
tx = escrow.functions.confirmActivity().transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
testerchain.time_travel(hours=1)
|
||||
|
||||
period += 2
|
||||
assert 260 == escrow.functions.getLockedTokens(ursula).call()
|
||||
assert 100 == escrow.functions.getLockedTokens(ursula, 1).call()
|
||||
assert 0 == escrow.functions.getLockedTokens(ursula, 3).call()
|
||||
assert 260 == escrow.functions.lockedPerPeriod(period - 1).call()
|
||||
assert 260 == escrow.functions.lockedPerPeriod(period).call()
|
||||
assert 0 == escrow.functions.lockedPerPeriod(period + 1).call()
|
||||
tx = escrow.functions.lock(160, 2).transact({'from': ursula})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 260 == escrow.functions.getLockedTokens(ursula).call()
|
||||
assert 260 == escrow.functions.getLockedTokens(ursula, 1).call()
|
||||
assert 0 == escrow.functions.getLockedTokens(ursula, 3).call()
|
||||
assert 260 == escrow.functions.lockedPerPeriod(period - 1).call()
|
||||
assert 260 == escrow.functions.lockedPerPeriod(period).call()
|
||||
assert 260 == escrow.functions.lockedPerPeriod(period + 1).call()
|
||||
deposit = escrow.functions.minerInfo(ursula).call()[VALUE_FIELD] # Some reward is already mined
|
||||
unlocked_deposit = deposit - 260
|
||||
|
||||
# Slash two sub stakes:
|
||||
# one which will be unlocked after current period and new sub stake
|
||||
reward = escrow.functions.getReservedReward().call()
|
||||
tx = adjudicator.functions.slashMiner(ursula, unlocked_deposit + 10, investigator, 0).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 250 == escrow.functions.getLockedTokens(ursula).call()
|
||||
assert 250 == escrow.functions.getLockedTokens(ursula, 1).call()
|
||||
assert 0 == escrow.functions.getLockedTokens(ursula, 3).call()
|
||||
assert 250 == escrow.functions.minerInfo(ursula).call()[VALUE_FIELD]
|
||||
assert 260 == escrow.functions.lockedPerPeriod(period - 1).call()
|
||||
assert 250 == escrow.functions.lockedPerPeriod(period).call()
|
||||
assert 250 == escrow.functions.lockedPerPeriod(period + 1).call()
|
||||
assert reward + unlocked_deposit + 10 == escrow.functions.getReservedReward().call()
|
||||
|
||||
events = slashing_log.get_all_entries()
|
||||
assert 8 == len(events)
|
||||
event_args = events[7]['args']
|
||||
assert ursula == event_args['miner']
|
||||
assert unlocked_deposit + 10 == event_args['penalty']
|
||||
assert investigator == event_args['investigator']
|
||||
assert 0 == event_args['reward']
|
||||
|
||||
# Slash four sub stakes:
|
||||
# two that will be unlocked after current period, new sub stake and another short sub stake
|
||||
tx = adjudicator.functions.slashMiner(ursula, 90, investigator, 0).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 160 == escrow.functions.getLockedTokens(ursula).call()
|
||||
assert 160 == escrow.functions.getLockedTokens(ursula, 1).call()
|
||||
assert 0 == escrow.functions.getLockedTokens(ursula, 3).call()
|
||||
assert 160 == escrow.functions.minerInfo(ursula).call()[VALUE_FIELD]
|
||||
assert 260 == escrow.functions.lockedPerPeriod(period - 1).call()
|
||||
assert 160 == escrow.functions.lockedPerPeriod(period).call()
|
||||
assert 160 == escrow.functions.lockedPerPeriod(period + 1).call()
|
||||
assert reward + unlocked_deposit + 100 == escrow.functions.getReservedReward().call()
|
||||
|
||||
events = slashing_log.get_all_entries()
|
||||
assert 9 == len(events)
|
||||
event_args = events[8]['args']
|
||||
assert ursula == event_args['miner']
|
||||
assert 90 == event_args['penalty']
|
||||
assert investigator == event_args['investigator']
|
||||
assert 0 == event_args['reward']
|
||||
|
||||
# Prepare second Ursula for tests
|
||||
ursula2 = testerchain.interface.w3.eth.accounts[3]
|
||||
tx = token.functions.transfer(ursula2, 10000).transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = token.functions.approve(escrow.address, 10000).transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Two deposits in consecutive periods
|
||||
tx = escrow.functions.deposit(100, 4).transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
testerchain.time_travel(hours=1)
|
||||
tx = escrow.functions.deposit(100, 2).transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
testerchain.time_travel(hours=2)
|
||||
assert 100 == escrow.functions.getLockedTokensInPast(ursula2, 2).call()
|
||||
assert 200 == escrow.functions.getLockedTokensInPast(ursula2, 1).call()
|
||||
assert 200 == escrow.functions.getLockedTokens(ursula2).call()
|
||||
assert 200 == escrow.functions.getLockedTokens(ursula2, 1).call()
|
||||
|
||||
# Slash one sub stake to set the last period of this sub stake to the previous period
|
||||
tx = adjudicator.functions.slashMiner(ursula2, 100, investigator, 0).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 100 == escrow.functions.getLockedTokensInPast(ursula2, 2).call()
|
||||
assert 200 == escrow.functions.getLockedTokensInPast(ursula2, 1).call()
|
||||
assert 100 == escrow.functions.getLockedTokens(ursula2).call()
|
||||
assert 100 == escrow.functions.getLockedTokens(ursula2, 1).call()
|
||||
|
||||
events = slashing_log.get_all_entries()
|
||||
assert 10 == len(events)
|
||||
event_args = events[9]['args']
|
||||
assert ursula2 == event_args['miner']
|
||||
assert 100 == event_args['penalty']
|
||||
assert investigator == event_args['investigator']
|
||||
assert 0 == event_args['reward']
|
||||
|
||||
# Slash the first sub stake
|
||||
# and check that the second sub stake will not combine with the slashed amount of the first one
|
||||
tx = adjudicator.functions.slashMiner(ursula2, 50, investigator, 0).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 100 == escrow.functions.getLockedTokensInPast(ursula2, 2).call()
|
||||
assert 200 == escrow.functions.getLockedTokensInPast(ursula2, 1).call()
|
||||
assert 50 == escrow.functions.getLockedTokens(ursula2).call()
|
||||
assert 50 == escrow.functions.getLockedTokens(ursula2, 1).call()
|
||||
|
||||
events = slashing_log.get_all_entries()
|
||||
assert 11 == len(events)
|
||||
event_args = events[10]['args']
|
||||
assert ursula2 == event_args['miner']
|
||||
assert 50 == event_args['penalty']
|
||||
assert investigator == event_args['investigator']
|
||||
assert 0 == event_args['reward']
|
||||
|
||||
# Prepare next case: new deposit is longer than previous sub stake
|
||||
tx = escrow.functions.confirmActivity().transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
testerchain.time_travel(hours=1)
|
||||
tx = escrow.functions.deposit(100, 3).transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 50 == escrow.functions.getLockedTokens(ursula2).call()
|
||||
assert 150 == escrow.functions.getLockedTokens(ursula2, 1).call()
|
||||
assert 100 == escrow.functions.getLockedTokens(ursula2, 2).call()
|
||||
|
||||
# Withdraw all not locked tokens
|
||||
deposit = escrow.functions.minerInfo(ursula2).call()[VALUE_FIELD] # Some reward is already mined
|
||||
unlocked_deposit = deposit - 150
|
||||
tx = escrow.functions.withdraw(unlocked_deposit).transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Slash the previous sub stake
|
||||
# Stake in the current period should not change, because overflow starts from the next period
|
||||
tx = adjudicator.functions.slashMiner(ursula2, 10, investigator, 0).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 50 == escrow.functions.getLockedTokens(ursula2).call()
|
||||
assert 140 == escrow.functions.getLockedTokens(ursula2, 1).call()
|
||||
assert 100 == escrow.functions.getLockedTokens(ursula2, 2).call()
|
||||
|
||||
events = slashing_log.get_all_entries()
|
||||
assert 12 == len(events)
|
||||
event_args = events[11]['args']
|
||||
assert ursula2 == event_args['miner']
|
||||
assert 10 == event_args['penalty']
|
||||
assert investigator == event_args['investigator']
|
||||
assert 0 == event_args['reward']
|
||||
|
|
|
@ -14,6 +14,8 @@ GNU General Public License for more details.
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import pytest
|
||||
from eth_tester.exceptions import TransactionFailed
|
||||
from web3.contract import Contract
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
"""
|
||||
This file is part of nucypher.
|
||||
|
||||
nucypher is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
nucypher is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import pytest
|
||||
from web3.contract import Contract
|
||||
|
||||
|
||||
ALGORITHM_SHA256 = 1
|
||||
BASE_PENALTY = 100
|
||||
PENALTY_HISTORY_COEFFICIENT = 10
|
||||
PERCENTAGE_PENALTY_COEFFICIENT = 8
|
||||
REWARD_COEFFICIENT = 2
|
||||
|
||||
secret = (123456).to_bytes(32, byteorder='big')
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def escrow(testerchain):
|
||||
escrow, _ = testerchain.interface.deploy_contract('MinersEscrowForMiningAdjudicatorMock')
|
||||
return escrow
|
||||
|
||||
|
||||
@pytest.fixture(params=[False, True])
|
||||
def adjudicator_contract(testerchain, escrow, request):
|
||||
contract, _ = testerchain.interface.deploy_contract(
|
||||
'MiningAdjudicator',
|
||||
escrow.address,
|
||||
ALGORITHM_SHA256,
|
||||
BASE_PENALTY,
|
||||
PENALTY_HISTORY_COEFFICIENT,
|
||||
PERCENTAGE_PENALTY_COEFFICIENT,
|
||||
REWARD_COEFFICIENT)
|
||||
|
||||
if request.param:
|
||||
secret_hash = testerchain.interface.w3.keccak(secret)
|
||||
dispatcher, _ = testerchain.interface.deploy_contract('Dispatcher', contract.address, secret_hash)
|
||||
|
||||
# Deploy second version of the government contract
|
||||
contract = testerchain.interface.w3.eth.contract(
|
||||
abi=contract.abi,
|
||||
address=dispatcher.address,
|
||||
ContractFactoryClass=Contract)
|
||||
|
||||
return contract
|
|
@ -0,0 +1,467 @@
|
|||
"""
|
||||
This file is part of nucypher.
|
||||
|
||||
nucypher is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
nucypher is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
|
||||
import coincurve
|
||||
import pytest
|
||||
from cryptography.hazmat.backends.openssl import backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from eth_tester.exceptions import TransactionFailed
|
||||
from eth_utils import to_canonical_address, to_checksum_address
|
||||
from web3.contract import Contract
|
||||
|
||||
from umbral import pre
|
||||
from umbral.config import default_params
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.keys import UmbralPrivateKey
|
||||
from umbral.random_oracles import hash_to_curvebn, ExtendedKeccak
|
||||
from umbral.signing import Signature, Signer
|
||||
|
||||
from nucypher.policy.models import IndisputableEvidence
|
||||
|
||||
|
||||
ALGORITHM_KECCAK256 = 0
|
||||
ALGORITHM_SHA256 = 1
|
||||
BASE_PENALTY = 100
|
||||
PENALTY_HISTORY_COEFFICIENT = 10
|
||||
PERCENTAGE_PENALTY_COEFFICIENT = 8
|
||||
REWARD_COEFFICIENT = 2
|
||||
secret = (123456).to_bytes(32, byteorder='big')
|
||||
secret2 = (654321).to_bytes(32, byteorder='big')
|
||||
|
||||
|
||||
def sign_data(data, umbral_privkey):
|
||||
umbral_pubkey_bytes = umbral_privkey.get_pubkey().to_bytes(is_compressed=False)
|
||||
|
||||
# Prepare hash of the data
|
||||
hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend)
|
||||
hash_ctx.update(data)
|
||||
data_hash = hash_ctx.finalize()
|
||||
|
||||
# Sign data and calculate recoverable signature
|
||||
cryptography_priv_key = umbral_privkey.to_cryptography_privkey()
|
||||
signature_der_bytes = cryptography_priv_key.sign(data, ec.ECDSA(hashes.SHA256()))
|
||||
signature = Signature.from_bytes(signature_der_bytes, der_encoded=True)
|
||||
recoverable_signature = make_recoverable_signature(data_hash, signature, umbral_pubkey_bytes)
|
||||
return recoverable_signature
|
||||
|
||||
|
||||
def make_recoverable_signature(data_hash, signature, umbral_pubkey_bytes):
|
||||
recoverable_signature = bytes(signature) + bytes([0])
|
||||
pubkey_bytes = coincurve.PublicKey.from_signature_and_message(recoverable_signature, data_hash, hasher=None) \
|
||||
.format(compressed=False)
|
||||
if pubkey_bytes != umbral_pubkey_bytes:
|
||||
recoverable_signature = bytes(signature) + bytes([1])
|
||||
return recoverable_signature
|
||||
|
||||
|
||||
# TODO: Obtain real re-encryption metadata. Maybe constructing a WorkOrder and obtaining a response.
|
||||
def fragments(metadata):
|
||||
delegating_privkey = UmbralPrivateKey.gen_key()
|
||||
_symmetric_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey())
|
||||
signing_privkey = UmbralPrivateKey.gen_key()
|
||||
signer = Signer(signing_privkey)
|
||||
priv_key_bob = UmbralPrivateKey.gen_key()
|
||||
pub_key_bob = priv_key_bob.get_pubkey()
|
||||
kfrags = pre.generate_kfrags(delegating_privkey=delegating_privkey,
|
||||
signer=signer,
|
||||
receiving_pubkey=pub_key_bob,
|
||||
threshold=2,
|
||||
N=4,
|
||||
sign_delegating_key=False,
|
||||
sign_receiving_key=False)
|
||||
capsule.set_correctness_keys(delegating_privkey.get_pubkey(), pub_key_bob, signing_privkey.get_pubkey())
|
||||
cfrag = pre.reencrypt(kfrags[0], capsule, metadata=metadata)
|
||||
return capsule, cfrag
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_evaluate_cfrag(testerchain, escrow, adjudicator_contract):
|
||||
creator, miner, wrong_miner, investigator, *everyone_else = testerchain.interface.w3.eth.accounts
|
||||
evaluation_log = adjudicator_contract.events.CFragEvaluated.createFilter(fromBlock='latest')
|
||||
|
||||
# TODO: Move this to an integration test?
|
||||
umbral_params = default_params()
|
||||
u_xcoord, u_ycoord = umbral_params.u.to_affine()
|
||||
u_sign = 2 + (u_ycoord % 2)
|
||||
assert u_sign == adjudicator_contract.functions.UMBRAL_PARAMETER_U_SIGN().call()
|
||||
assert u_xcoord == adjudicator_contract.functions.UMBRAL_PARAMETER_U_XCOORD().call()
|
||||
assert u_ycoord == adjudicator_contract.functions.UMBRAL_PARAMETER_U_YCOORD().call()
|
||||
|
||||
# TODO: Move this to an integration test?
|
||||
test_data = os.urandom(40)
|
||||
h = hash_to_curvebn(test_data,
|
||||
params=umbral_params,
|
||||
hash_class=ExtendedKeccak)
|
||||
assert int(h) == adjudicator_contract.functions.extendedKeccakToBN(test_data).call()
|
||||
|
||||
# Prepare one miner
|
||||
tx = escrow.functions.setMinerInfo(miner, 1000).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Generate miner's Umbral key
|
||||
miner_umbral_private_key = UmbralPrivateKey.gen_key()
|
||||
miner_umbral_public_key_bytes = miner_umbral_private_key.get_pubkey().to_bytes(is_compressed=False)
|
||||
|
||||
# Sign Umbral public key using eth-key
|
||||
hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend)
|
||||
hash_ctx.update(miner_umbral_public_key_bytes)
|
||||
miner_umbral_public_key_hash = hash_ctx.finalize()
|
||||
provider = testerchain.interface.provider
|
||||
address = to_canonical_address(miner)
|
||||
sig_key = provider.ethereum_tester.backend._key_lookup[address]
|
||||
signed_miner_umbral_public_key = bytes(sig_key.sign_msg_hash(miner_umbral_public_key_hash))
|
||||
|
||||
# Prepare hash of the data
|
||||
metadata = os.urandom(33)
|
||||
capsule, cfrag = fragments(metadata)
|
||||
|
||||
assert cfrag.verify_correctness(capsule)
|
||||
|
||||
capsule_bytes = capsule.to_bytes()
|
||||
cfrag_bytes = cfrag.to_bytes()
|
||||
|
||||
# Bob prepares supporting Evidence
|
||||
evidence = IndisputableEvidence(capsule, cfrag, ursula=None)
|
||||
|
||||
evidence_data = evidence.precompute_values()
|
||||
assert len(evidence_data) == 20 * 32 + 32 + 20 + 1
|
||||
|
||||
# This check is a workaround in order to test the signature validation is correct.
|
||||
# In reality, this check should be part of the isCapsuleFragCorrect logic,
|
||||
# but we're facing with "Stack too deep" errors at the moment.
|
||||
address = adjudicator_contract.functions.aliceAddress(cfrag_bytes, evidence_data).call()
|
||||
assert address == to_checksum_address(evidence_data[-21:-1].hex())
|
||||
|
||||
proof_challenge_scalar = int(evidence.get_proof_challenge_scalar())
|
||||
computeProofChallengeScalar = adjudicator_contract.functions.computeProofChallengeScalar
|
||||
assert proof_challenge_scalar == computeProofChallengeScalar(capsule_bytes, cfrag_bytes).call()
|
||||
|
||||
hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend)
|
||||
hash_ctx.update(capsule_bytes + cfrag_bytes)
|
||||
data_hash = hash_ctx.finalize()
|
||||
# This capsule and cFrag are not yet evaluated
|
||||
assert not adjudicator_contract.functions.evaluatedCFrags(data_hash).call()
|
||||
|
||||
# Generate requester's Umbral key
|
||||
requester_umbral_private_key = UmbralPrivateKey.gen_key()
|
||||
requester_umbral_public_key_bytes = requester_umbral_private_key.get_pubkey().to_bytes(is_compressed=False)
|
||||
|
||||
# Sign capsule and cFrag
|
||||
capsule_signature_by_requester = sign_data(capsule_bytes, requester_umbral_private_key)
|
||||
capsule_signature_by_requester_and_miner = sign_data(capsule_signature_by_requester, miner_umbral_private_key)
|
||||
cfrag_signature_by_miner = sign_data(cfrag_bytes, miner_umbral_private_key)
|
||||
|
||||
# Challenge using good data
|
||||
args = (capsule_bytes,
|
||||
capsule_signature_by_requester,
|
||||
capsule_signature_by_requester_and_miner,
|
||||
cfrag_bytes,
|
||||
cfrag_signature_by_miner,
|
||||
requester_umbral_public_key_bytes,
|
||||
miner_umbral_public_key_bytes,
|
||||
signed_miner_umbral_public_key,
|
||||
evidence_data)
|
||||
|
||||
value = escrow.functions.minerInfo(miner).call()[0]
|
||||
tx = adjudicator_contract.functions.evaluateCFrag(*args).transact({'from': investigator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
# Hash of the data is saved and miner was not slashed
|
||||
assert adjudicator_contract.functions.evaluatedCFrags(data_hash).call()
|
||||
assert value == escrow.functions.minerInfo(miner).call()[0]
|
||||
assert 0 == escrow.functions.rewardInfo(investigator).call()
|
||||
|
||||
events = evaluation_log.get_all_entries()
|
||||
assert 1 == len(events)
|
||||
event_args = events[0]['args']
|
||||
assert data_hash == event_args['evaluationHash']
|
||||
assert miner == event_args['miner']
|
||||
assert investigator == event_args['investigator']
|
||||
assert event_args['correctness']
|
||||
|
||||
# Can't evaluate miner with data that already was checked
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = adjudicator_contract.functions.evaluateCFrag(*args).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Challenge using bad data
|
||||
metadata = os.urandom(34)
|
||||
capsule, cfrag = fragments(metadata)
|
||||
capsule_bytes = capsule.to_bytes()
|
||||
# Corrupt proof
|
||||
cfrag.proof.bn_sig = CurveBN.gen_rand(capsule.params.curve)
|
||||
cfrag_bytes = cfrag.to_bytes()
|
||||
hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend)
|
||||
hash_ctx.update(capsule_bytes + cfrag_bytes)
|
||||
data_hash = hash_ctx.finalize()
|
||||
capsule_signature_by_requester = sign_data(capsule_bytes, requester_umbral_private_key)
|
||||
capsule_signature_by_requester_and_miner = sign_data(capsule_signature_by_requester, miner_umbral_private_key)
|
||||
cfrag_signature_by_miner = sign_data(cfrag_bytes, miner_umbral_private_key)
|
||||
evidence = IndisputableEvidence(capsule, cfrag, ursula=None)
|
||||
evidence_data = evidence.precompute_values()
|
||||
args = (capsule_bytes,
|
||||
capsule_signature_by_requester,
|
||||
capsule_signature_by_requester_and_miner,
|
||||
cfrag_bytes,
|
||||
cfrag_signature_by_miner,
|
||||
requester_umbral_public_key_bytes,
|
||||
miner_umbral_public_key_bytes,
|
||||
signed_miner_umbral_public_key,
|
||||
evidence_data)
|
||||
|
||||
assert not adjudicator_contract.functions.evaluatedCFrags(data_hash).call()
|
||||
tx = adjudicator_contract.functions.evaluateCFrag(*args).transact({'from': investigator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
# Hash of the data is saved and miner was slashed
|
||||
assert adjudicator_contract.functions.evaluatedCFrags(data_hash).call()
|
||||
assert value - BASE_PENALTY == escrow.functions.minerInfo(miner).call()[0]
|
||||
assert BASE_PENALTY / REWARD_COEFFICIENT == escrow.functions.rewardInfo(investigator).call()
|
||||
|
||||
events = evaluation_log.get_all_entries()
|
||||
assert 2 == len(events)
|
||||
event_args = events[1]['args']
|
||||
assert data_hash == event_args['evaluationHash']
|
||||
assert miner == event_args['miner']
|
||||
assert investigator == event_args['investigator']
|
||||
assert not event_args['correctness']
|
||||
|
||||
# Prepare hash of the data
|
||||
metadata = os.urandom(34)
|
||||
capsule, cfrag = fragments(metadata)
|
||||
capsule_bytes = capsule.to_bytes()
|
||||
# Corrupt proof
|
||||
cfrag.proof.bn_sig = CurveBN.gen_rand(capsule.params.curve)
|
||||
cfrag_bytes = cfrag.to_bytes()
|
||||
hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend)
|
||||
hash_ctx.update(capsule_bytes + cfrag_bytes)
|
||||
data_hash = hash_ctx.finalize()
|
||||
capsule_signature_by_requester = sign_data(capsule_bytes, requester_umbral_private_key)
|
||||
capsule_signature_by_requester_and_miner = sign_data(capsule_signature_by_requester, miner_umbral_private_key)
|
||||
cfrag_signature_by_miner = sign_data(cfrag_bytes, miner_umbral_private_key)
|
||||
evidence = IndisputableEvidence(capsule, cfrag, ursula=None)
|
||||
evidence_data = evidence.precompute_values()
|
||||
args = [capsule_bytes,
|
||||
capsule_signature_by_requester,
|
||||
capsule_signature_by_requester_and_miner,
|
||||
cfrag_bytes,
|
||||
cfrag_signature_by_miner,
|
||||
requester_umbral_public_key_bytes,
|
||||
miner_umbral_public_key_bytes,
|
||||
signed_miner_umbral_public_key,
|
||||
evidence_data]
|
||||
assert not adjudicator_contract.functions.evaluatedCFrags(data_hash).call()
|
||||
|
||||
# Can't evaluate miner using broken signatures
|
||||
wrong_args = args[:]
|
||||
wrong_args[1] = capsule_signature_by_requester[1:]
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
wrong_args = args[:]
|
||||
wrong_args[2] = capsule_signature_by_requester_and_miner[1:]
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
wrong_args = args[:]
|
||||
wrong_args[4] = cfrag_signature_by_miner[1:]
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
wrong_args = args[:]
|
||||
wrong_args[7] = signed_miner_umbral_public_key[1:]
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Can't evaluate miner using wrong keys
|
||||
wrong_args = args[:]
|
||||
wrong_args[5] = UmbralPrivateKey.gen_key().get_pubkey().to_bytes(is_compressed=False)
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
wrong_args = args[:]
|
||||
wrong_args[6] = UmbralPrivateKey.gen_key().get_pubkey().to_bytes(is_compressed=False)
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Can't use signature for another data
|
||||
wrong_args = args[:]
|
||||
wrong_args[0] = bytes(args[0][0] + 1) + args[0][1:]
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
wrong_args = args[:]
|
||||
wrong_args[3] = bytes(args[3][0] + 1) + args[3][1:]
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Can't evaluate nonexistent miner
|
||||
address = to_canonical_address(wrong_miner)
|
||||
sig_key = provider.ethereum_tester.backend._key_lookup[address]
|
||||
signed_wrong_miner_umbral_public_key = bytes(sig_key.sign_msg_hash(miner_umbral_public_key_hash))
|
||||
wrong_args = args[:]
|
||||
wrong_args[7] = signed_wrong_miner_umbral_public_key
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = adjudicator_contract.functions.evaluateCFrag(*wrong_args).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Initial arguments were correct
|
||||
value = escrow.functions.minerInfo(miner).call()[0]
|
||||
reward = escrow.functions.rewardInfo(investigator).call()
|
||||
assert not adjudicator_contract.functions.evaluatedCFrags(data_hash).call()
|
||||
tx = adjudicator_contract.functions.evaluateCFrag(*args).transact({'from': investigator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert adjudicator_contract.functions.evaluatedCFrags(data_hash).call()
|
||||
# Penalty was increased because it's the second violation
|
||||
assert value - (BASE_PENALTY + PENALTY_HISTORY_COEFFICIENT) == escrow.functions.minerInfo(miner).call()[0]
|
||||
assert reward + (BASE_PENALTY + PENALTY_HISTORY_COEFFICIENT) / REWARD_COEFFICIENT == \
|
||||
escrow.functions.rewardInfo(investigator).call()
|
||||
|
||||
events = evaluation_log.get_all_entries()
|
||||
assert 3 == len(events)
|
||||
event_args = events[2]['args']
|
||||
assert data_hash == event_args['evaluationHash']
|
||||
assert miner == event_args['miner']
|
||||
assert investigator == event_args['investigator']
|
||||
assert not event_args['correctness']
|
||||
|
||||
# Prepare corrupted data again
|
||||
capsule, cfrag = fragments(metadata)
|
||||
capsule_bytes = capsule.to_bytes()
|
||||
# Corrupt proof
|
||||
cfrag.proof.bn_sig = CurveBN.gen_rand(capsule.params.curve)
|
||||
cfrag_bytes = cfrag.to_bytes()
|
||||
hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend)
|
||||
hash_ctx.update(capsule_bytes + cfrag_bytes)
|
||||
data_hash = hash_ctx.finalize()
|
||||
capsule_signature_by_requester = sign_data(capsule_bytes, requester_umbral_private_key)
|
||||
capsule_signature_by_requester_and_miner = sign_data(capsule_signature_by_requester, miner_umbral_private_key)
|
||||
cfrag_signature_by_miner = sign_data(cfrag_bytes, miner_umbral_private_key)
|
||||
evidence = IndisputableEvidence(capsule, cfrag, ursula=None)
|
||||
evidence_data = evidence.precompute_values()
|
||||
args = (capsule_bytes,
|
||||
capsule_signature_by_requester,
|
||||
capsule_signature_by_requester_and_miner,
|
||||
cfrag_bytes,
|
||||
cfrag_signature_by_miner,
|
||||
requester_umbral_public_key_bytes,
|
||||
miner_umbral_public_key_bytes,
|
||||
signed_miner_umbral_public_key,
|
||||
evidence_data)
|
||||
|
||||
value = escrow.functions.minerInfo(miner).call()[0]
|
||||
reward = escrow.functions.rewardInfo(investigator).call()
|
||||
assert not adjudicator_contract.functions.evaluatedCFrags(data_hash).call()
|
||||
tx = adjudicator_contract.functions.evaluateCFrag(*args).transact({'from': investigator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert adjudicator_contract.functions.evaluatedCFrags(data_hash).call()
|
||||
# Penalty was decreased because it's more than maximum available percentage of value
|
||||
assert value - value // PERCENTAGE_PENALTY_COEFFICIENT == escrow.functions.minerInfo(miner).call()[0]
|
||||
assert reward + value // PERCENTAGE_PENALTY_COEFFICIENT / REWARD_COEFFICIENT == \
|
||||
escrow.functions.rewardInfo(investigator).call()
|
||||
|
||||
events = evaluation_log.get_all_entries()
|
||||
assert 4 == len(events)
|
||||
event_args = events[3]['args']
|
||||
assert data_hash == event_args['evaluationHash']
|
||||
assert miner == event_args['miner']
|
||||
assert investigator == event_args['investigator']
|
||||
assert not event_args['correctness']
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_upgrading(testerchain):
|
||||
creator = testerchain.interface.w3.eth.accounts[0]
|
||||
|
||||
secret_hash = testerchain.interface.w3.keccak(secret)
|
||||
secret2_hash = testerchain.interface.w3.keccak(secret2)
|
||||
|
||||
# Deploy contracts
|
||||
escrow1, _ = testerchain.interface.deploy_contract('MinersEscrowForMiningAdjudicatorMock')
|
||||
escrow2, _ = testerchain.interface.deploy_contract('MinersEscrowForMiningAdjudicatorMock')
|
||||
address1 = escrow1.address
|
||||
address2 = escrow2.address
|
||||
contract_library_v1, _ = testerchain.interface.deploy_contract(
|
||||
'MiningAdjudicator', address1, ALGORITHM_KECCAK256, 1, 2, 3, 4)
|
||||
dispatcher, _ = testerchain.interface.deploy_contract('Dispatcher', contract_library_v1.address, secret_hash)
|
||||
|
||||
# Deploy second version of the contract
|
||||
contract_library_v2, _ = testerchain.interface.deploy_contract(
|
||||
'MiningAdjudicatorV2Mock', address2, ALGORITHM_SHA256, 5, 6, 7, 8)
|
||||
contract = testerchain.interface.w3.eth.contract(
|
||||
abi=contract_library_v2.abi,
|
||||
address=dispatcher.address,
|
||||
ContractFactoryClass=Contract)
|
||||
|
||||
# Upgrade to the second version
|
||||
assert address1 == contract.functions.escrow().call()
|
||||
assert ALGORITHM_KECCAK256 == contract.functions.hashAlgorithm().call()
|
||||
assert 1 == contract.functions.basePenalty().call()
|
||||
assert 2 == contract.functions.penaltyHistoryCoefficient().call()
|
||||
assert 3 == contract.functions.percentagePenaltyCoefficient().call()
|
||||
assert 4 == contract.functions.rewardCoefficient().call()
|
||||
tx = dispatcher.functions.upgrade(contract_library_v2.address, secret, secret2_hash).transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
# Check constructor and storage values
|
||||
assert contract_library_v2.address == dispatcher.functions.target().call()
|
||||
assert address2 == contract.functions.escrow().call()
|
||||
assert ALGORITHM_SHA256 == contract.functions.hashAlgorithm().call()
|
||||
assert 5 == contract.functions.basePenalty().call()
|
||||
assert 6 == contract.functions.penaltyHistoryCoefficient().call()
|
||||
assert 7 == contract.functions.percentagePenaltyCoefficient().call()
|
||||
assert 8 == contract.functions.rewardCoefficient().call()
|
||||
# Check new ABI
|
||||
tx = contract.functions.setValueToCheck(3).transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert 3 == contract.functions.valueToCheck().call()
|
||||
|
||||
# Can't upgrade to the previous version or to the bad version
|
||||
contract_library_bad, _ = testerchain.interface.deploy_contract('MiningAdjudicatorBad')
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = dispatcher.functions.upgrade(contract_library_v1.address, secret2, secret_hash) \
|
||||
.transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = dispatcher.functions.upgrade(contract_library_bad.address, secret2, secret_hash) \
|
||||
.transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# But can rollback
|
||||
tx = dispatcher.functions.rollback(secret2, secret_hash).transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
assert contract_library_v1.address == dispatcher.functions.target().call()
|
||||
assert address1 == contract.functions.escrow().call()
|
||||
assert ALGORITHM_KECCAK256 == contract.functions.hashAlgorithm().call()
|
||||
assert 1 == contract.functions.basePenalty().call()
|
||||
assert 2 == contract.functions.penaltyHistoryCoefficient().call()
|
||||
assert 3 == contract.functions.percentagePenaltyCoefficient().call()
|
||||
assert 4 == contract.functions.rewardCoefficient().call()
|
||||
# After rollback new ABI is unavailable
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = contract.functions.setValueToCheck(2).transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
# Try to upgrade to the bad version
|
||||
with pytest.raises((TransactionFailed, ValueError)):
|
||||
tx = dispatcher.functions.upgrade(contract_library_bad.address, secret, secret2_hash) \
|
||||
.transact({'from': creator})
|
||||
testerchain.wait_for_receipt(tx)
|
|
@ -14,6 +14,8 @@ GNU General Public License for more details.
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import pytest
|
||||
from web3.contract import Contract
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ GNU General Public License for more details.
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -14,6 +14,8 @@ GNU General Public License for more details.
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
@ -257,8 +259,8 @@ def test_refund(testerchain, escrow, policy_manager):
|
|||
|
||||
# Create new policy
|
||||
testerchain.time_travel(hours=1)
|
||||
period = escrow.call().getCurrentPeriod()
|
||||
tx = escrow.transact().setLastActivePeriod(period)
|
||||
period = escrow.functions.getCurrentPeriod().call()
|
||||
tx = escrow.functions.setLastActivePeriod(period).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = policy_manager.functions.createPolicy(policy_id_2, number_of_periods, int(0.5 * rate), [node1, node2, node3]) \
|
||||
.transact({'from': client, 'value': int(3 * value + 1.5 * rate), 'gas_price': 0})
|
||||
|
|
|
@ -14,6 +14,8 @@ GNU General Public License for more details.
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import pytest
|
||||
from eth_tester.exceptions import TransactionFailed
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ GNU General Public License for more details.
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import pytest
|
||||
from web3.contract import Contract
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ GNU General Public License for more details.
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -147,7 +147,11 @@ def test_bob_can_issue_a_work_order_to_a_specific_ursula(enacted_federated_polic
|
|||
|
||||
# We'll test against just a single Ursula - here, we make a WorkOrder for just one.
|
||||
# We can pass any number of capsules as args; here we pass just one.
|
||||
work_orders = federated_bob.generate_work_orders(map_id, capsule_side_channel[0].capsule, num_ursulas=1)
|
||||
capsule = capsule_side_channel[0].capsule
|
||||
capsule.set_correctness_keys(delegating=enacted_federated_policy.public_key,
|
||||
receiving=federated_bob.public_keys(DecryptingPower),
|
||||
verifying=federated_alice.stamp.as_umbral_pubkey())
|
||||
work_orders = federated_bob.generate_work_orders(map_id, capsule, num_ursulas=1)
|
||||
|
||||
# Again: one Ursula, one work_order.
|
||||
assert len(work_orders) == 1
|
||||
|
@ -173,10 +177,6 @@ def test_bob_can_issue_a_work_order_to_a_specific_ursula(enacted_federated_polic
|
|||
assert work_order.completed
|
||||
|
||||
# Attach the CFrag to the Capsule.
|
||||
capsule = capsule_side_channel[0].capsule
|
||||
capsule.set_correctness_keys(delegating=enacted_federated_policy.public_key,
|
||||
receiving=federated_bob.public_keys(DecryptingPower),
|
||||
verifying=federated_alice.stamp.as_umbral_pubkey())
|
||||
capsule.attach_cfrag(the_cfrag)
|
||||
|
||||
# Having received the cFrag, Bob also saved the WorkOrder as complete.
|
||||
|
|
|
@ -14,15 +14,19 @@ GNU General Public License for more details.
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
import pytest
|
||||
from cryptography.hazmat.backends.openssl import backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from umbral.keys import UmbralPrivateKey
|
||||
|
||||
from nucypher.crypto.api import ecdsa_sign
|
||||
from nucypher.crypto.signing import Signature
|
||||
from nucypher.crypto.signing import Signature, Signer
|
||||
from nucypher.crypto.utils import recover_pubkey_from_signature
|
||||
|
||||
|
||||
def test_signature_can_verify():
|
||||
privkey = UmbralPrivateKey.gen_key()
|
||||
message = b"attack at dawn"
|
||||
message = b"peace at dawn"
|
||||
der_sig_bytes = ecdsa_sign(message, privkey)
|
||||
signature = Signature.from_bytes(der_sig_bytes, der_encoded=True)
|
||||
assert signature.verify(message, privkey.get_pubkey())
|
||||
|
@ -30,7 +34,7 @@ def test_signature_can_verify():
|
|||
|
||||
def test_signature_rs_serialization():
|
||||
privkey = UmbralPrivateKey.gen_key()
|
||||
message = b"attack at dawn"
|
||||
message = b"peace at dawn"
|
||||
der_sig_bytes = ecdsa_sign(message, privkey)
|
||||
|
||||
signature_from_der = Signature.from_bytes(der_sig_bytes, der_encoded=True)
|
||||
|
@ -42,3 +46,29 @@ def test_signature_rs_serialization():
|
|||
assert signature_from_rs == signature_from_der
|
||||
assert signature_from_rs == der_sig_bytes
|
||||
assert signature_from_rs.verify(message, privkey.get_pubkey())
|
||||
|
||||
|
||||
@pytest.mark.parametrize('execution_number', range(100)) # Run this test 100 times.
|
||||
def test_recover_pubkey_from_signature(execution_number):
|
||||
privkey = UmbralPrivateKey.gen_key()
|
||||
pubkey = privkey.get_pubkey()
|
||||
signer = Signer(private_key=privkey)
|
||||
message = b"peace at dawn"
|
||||
signature = signer(message=message)
|
||||
|
||||
assert signature.verify(message, pubkey)
|
||||
|
||||
hash_function = hashes.Hash(hashes.SHA256(), backend=backend)
|
||||
hash_function.update(message)
|
||||
prehashed_message = hash_function.finalize()
|
||||
|
||||
v_value = 27
|
||||
pubkey_bytes = recover_pubkey_from_signature(prehashed_message=prehashed_message,
|
||||
signature=signature,
|
||||
v_value_to_try=v_value)
|
||||
if not pubkey_bytes == pubkey.to_bytes():
|
||||
v_value = 28
|
||||
pubkey_bytes = recover_pubkey_from_signature(prehashed_message=prehashed_message,
|
||||
signature=signature,
|
||||
v_value_to_try=v_value)
|
||||
assert pubkey_bytes == pubkey.to_bytes()
|
||||
|
|
|
@ -18,34 +18,42 @@ You should have received a copy of the GNU General Public License
|
|||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
import coincurve
|
||||
import json
|
||||
import os
|
||||
from typing import List, Tuple
|
||||
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from eth_utils import to_canonical_address
|
||||
|
||||
from nucypher.policy.models import IndisputableEvidence
|
||||
from umbral import pre
|
||||
from umbral.curvebn import CurveBN
|
||||
from umbral.keys import UmbralPrivateKey
|
||||
from umbral.signing import Signer, Signature
|
||||
|
||||
import time
|
||||
from os.path import abspath, dirname
|
||||
|
||||
import io
|
||||
import re
|
||||
|
||||
from cryptography.hazmat.backends.openssl import backend
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from twisted.logger import globalLogPublisher, Logger, jsonFileLogObserver, ILogObserver
|
||||
from zope.interface import provider
|
||||
|
||||
from nucypher.blockchain.eth.actors import Deployer
|
||||
from nucypher.blockchain.eth.agents import NucypherTokenAgent, MinerAgent, PolicyAgent
|
||||
from nucypher.blockchain.eth.constants import (
|
||||
DISPATCHER_SECRET_LENGTH,
|
||||
MIN_ALLOWED_LOCKED,
|
||||
MIN_LOCKED_PERIODS,
|
||||
POLICY_ID_LENGTH
|
||||
)
|
||||
from nucypher.blockchain.eth.interfaces import BlockchainDeployerInterface
|
||||
from nucypher.blockchain.eth.registry import InMemoryEthereumContractRegistry
|
||||
from nucypher.blockchain.eth.sol.compile import SolidityCompiler
|
||||
from nucypher.config.constants import CONTRACT_ROOT
|
||||
from nucypher.utilities.sandbox.blockchain import TesterBlockchain
|
||||
|
||||
|
||||
ALGORITHM_SHA256 = 1
|
||||
|
||||
|
||||
class AnalyzeGas:
|
||||
"""
|
||||
Callable twisted log observer with built-in record-keeping for gas estimation runs.
|
||||
|
@ -57,11 +65,6 @@ class AnalyzeGas:
|
|||
OUTPUT_DIR = os.path.join(abspath(dirname(__file__)), 'results')
|
||||
JSON_OUTPUT_FILENAME = '{}.json'.format(LOG_NAME)
|
||||
|
||||
# Tweaks
|
||||
CONTRACT_DIR = CONTRACT_ROOT
|
||||
PROVIDER_URI = "tester://pyevm"
|
||||
TEST_ACCOUNTS = 10
|
||||
|
||||
_PATTERN = re.compile(r'''
|
||||
^ # Anchor at the start of a string
|
||||
(.+) # Any character sequence longer than 1; Captured
|
||||
|
@ -95,7 +98,7 @@ class AnalyzeGas:
|
|||
|
||||
@staticmethod
|
||||
def paint_line(label: str, gas: str) -> None:
|
||||
print('{label} {gas:,}'.format(label=label.ljust(65, '.'), gas=int(gas)))
|
||||
print('{label} {gas:,}'.format(label=label.ljust(70, '.'), gas=int(gas)))
|
||||
|
||||
def to_json_file(self) -> None:
|
||||
print('Saving JSON Output...')
|
||||
|
@ -116,6 +119,76 @@ class AnalyzeGas:
|
|||
globalLogPublisher.addObserver(self)
|
||||
|
||||
|
||||
# TODO organize support functions
|
||||
def generate_args_for_slashing(testerchain, miner, corrupt: bool = True):
|
||||
def sign_data(data, umbral_privkey):
|
||||
umbral_pubkey_bytes = umbral_privkey.get_pubkey().to_bytes(is_compressed=False)
|
||||
|
||||
# Prepare hash of the data
|
||||
hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend)
|
||||
hash_ctx.update(data)
|
||||
data_hash = hash_ctx.finalize()
|
||||
|
||||
# Sign data and calculate recoverable signature
|
||||
cryptography_priv_key = umbral_privkey.to_cryptography_privkey()
|
||||
signature_der_bytes = cryptography_priv_key.sign(data, ec.ECDSA(hashes.SHA256()))
|
||||
signature = Signature.from_bytes(signature_der_bytes, der_encoded=True)
|
||||
recoverable_signature = bytes(signature) + bytes([0])
|
||||
pubkey_bytes = coincurve.PublicKey.from_signature_and_message(recoverable_signature, data_hash, hasher=None) \
|
||||
.format(compressed=False)
|
||||
if pubkey_bytes != umbral_pubkey_bytes:
|
||||
recoverable_signature = bytes(signature) + bytes([1])
|
||||
return recoverable_signature
|
||||
|
||||
delegating_privkey = UmbralPrivateKey.gen_key()
|
||||
_symmetric_key, capsule = pre._encapsulate(delegating_privkey.get_pubkey())
|
||||
signing_privkey = UmbralPrivateKey.gen_key()
|
||||
signer = Signer(signing_privkey)
|
||||
priv_key_bob = UmbralPrivateKey.gen_key()
|
||||
pub_key_bob = priv_key_bob.get_pubkey()
|
||||
kfrags = pre.generate_kfrags(delegating_privkey=delegating_privkey,
|
||||
signer=signer,
|
||||
receiving_pubkey=pub_key_bob,
|
||||
threshold=2,
|
||||
N=4,
|
||||
sign_delegating_key=False,
|
||||
sign_receiving_key=False)
|
||||
capsule.set_correctness_keys(delegating_privkey.get_pubkey(), pub_key_bob, signing_privkey.get_pubkey())
|
||||
cfrag = pre.reencrypt(kfrags[0], capsule, metadata=os.urandom(34))
|
||||
capsule_bytes = capsule.to_bytes()
|
||||
if corrupt:
|
||||
cfrag.proof.bn_sig = CurveBN.gen_rand(capsule.params.curve)
|
||||
cfrag_bytes = cfrag.to_bytes()
|
||||
hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend)
|
||||
hash_ctx.update(capsule_bytes + cfrag_bytes)
|
||||
requester_umbral_private_key = UmbralPrivateKey.gen_key()
|
||||
requester_umbral_public_key_bytes = requester_umbral_private_key.get_pubkey().to_bytes(is_compressed=False)
|
||||
capsule_signature_by_requester = sign_data(capsule_bytes, requester_umbral_private_key)
|
||||
miner_umbral_private_key = UmbralPrivateKey.gen_key()
|
||||
miner_umbral_public_key_bytes = miner_umbral_private_key.get_pubkey().to_bytes(is_compressed=False)
|
||||
# Sign Umbral public key using eth-key
|
||||
hash_ctx = hashes.Hash(hashes.SHA256(), backend=backend)
|
||||
hash_ctx.update(miner_umbral_public_key_bytes)
|
||||
miner_umbral_public_key_hash = hash_ctx.finalize()
|
||||
address = to_canonical_address(miner)
|
||||
sig_key = testerchain.interface.provider.ethereum_tester.backend._key_lookup[address]
|
||||
signed_miner_umbral_public_key = bytes(sig_key.sign_msg_hash(miner_umbral_public_key_hash))
|
||||
|
||||
capsule_signature_by_requester_and_miner = sign_data(capsule_signature_by_requester, miner_umbral_private_key)
|
||||
cfrag_signature_by_miner = sign_data(cfrag_bytes, miner_umbral_private_key)
|
||||
evidence = IndisputableEvidence(capsule, cfrag, ursula=None)
|
||||
evidence_data = evidence.precompute_values()
|
||||
return (capsule_bytes,
|
||||
capsule_signature_by_requester,
|
||||
capsule_signature_by_requester_and_miner,
|
||||
cfrag_bytes,
|
||||
cfrag_signature_by_miner,
|
||||
requester_umbral_public_key_bytes,
|
||||
miner_umbral_public_key_bytes,
|
||||
signed_miner_umbral_public_key,
|
||||
evidence_data)
|
||||
|
||||
|
||||
def estimate_gas(analyzer: AnalyzeGas = None) -> None:
|
||||
"""
|
||||
Execute a linear sequence of NyCypher transactions mimicking
|
||||
|
@ -162,12 +235,13 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None:
|
|||
log.info("Pre-deposit tokens for 5 owners = " + str(miner_functions.preDeposit(everyone_else[0:5],
|
||||
[MIN_ALLOWED_LOCKED] * 5,
|
||||
[MIN_LOCKED_PERIODS] * 5)
|
||||
.estimateGas({'from': origin})))
|
||||
.estimateGas({'from': origin})))
|
||||
|
||||
#
|
||||
# Give Ursula and Alice some coins
|
||||
#
|
||||
log.info("Transfer tokens = " + str(token_functions.transfer(ursula1, MIN_ALLOWED_LOCKED * 10).estimateGas({'from': origin})))
|
||||
log.info("Transfer tokens = " + str(
|
||||
token_functions.transfer(ursula1, MIN_ALLOWED_LOCKED * 10).estimateGas({'from': origin})))
|
||||
tx = token_functions.transfer(ursula1, MIN_ALLOWED_LOCKED * 10).transact({'from': origin})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = token_functions.transfer(ursula2, MIN_ALLOWED_LOCKED * 10).transact({'from': origin})
|
||||
|
@ -179,7 +253,8 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None:
|
|||
# Ursula and Alice give Escrow rights to transfer
|
||||
#
|
||||
log.info("Approving transfer = "
|
||||
+ str(token_functions.approve(miner_agent.contract_address, MIN_ALLOWED_LOCKED * 6).estimateGas({'from': ursula1})))
|
||||
+ str(
|
||||
token_functions.approve(miner_agent.contract_address, MIN_ALLOWED_LOCKED * 6).estimateGas({'from': ursula1})))
|
||||
tx = token_functions.approve(miner_agent.contract_address, MIN_ALLOWED_LOCKED * 6).transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
tx = token_functions.approve(miner_agent.contract_address, MIN_ALLOWED_LOCKED * 6).transact({'from': ursula2})
|
||||
|
@ -191,15 +266,15 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None:
|
|||
# Ursula and Alice transfer some tokens to the escrow and lock them
|
||||
#
|
||||
log.info("First initial deposit tokens = " +
|
||||
str(miner_functions.deposit(MIN_ALLOWED_LOCKED * 3, MIN_LOCKED_PERIODS).estimateGas({'from': ursula1})))
|
||||
str(miner_functions.deposit(MIN_ALLOWED_LOCKED * 3, MIN_LOCKED_PERIODS).estimateGas({'from': ursula1})))
|
||||
tx = miner_functions.deposit(MIN_ALLOWED_LOCKED * 3, MIN_LOCKED_PERIODS).transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Second initial deposit tokens = " +
|
||||
str(miner_functions.deposit(MIN_ALLOWED_LOCKED * 3, MIN_LOCKED_PERIODS).estimateGas({'from': ursula2})))
|
||||
str(miner_functions.deposit(MIN_ALLOWED_LOCKED * 3, MIN_LOCKED_PERIODS).estimateGas({'from': ursula2})))
|
||||
tx = miner_functions.deposit(MIN_ALLOWED_LOCKED * 3, MIN_LOCKED_PERIODS).transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Third initial deposit tokens = " +
|
||||
str(miner_functions.deposit(MIN_ALLOWED_LOCKED * 3, MIN_LOCKED_PERIODS).estimateGas({'from': ursula3})))
|
||||
str(miner_functions.deposit(MIN_ALLOWED_LOCKED * 3, MIN_LOCKED_PERIODS).estimateGas({'from': ursula3})))
|
||||
tx = miner_functions.deposit(MIN_ALLOWED_LOCKED * 3, MIN_LOCKED_PERIODS).transact({'from': ursula3})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
|
@ -208,15 +283,15 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None:
|
|||
#
|
||||
testerchain.time_travel(periods=1)
|
||||
log.info("First confirm activity = " +
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula1})))
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula1})))
|
||||
tx = miner_functions.confirmActivity().transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Second confirm activity = " +
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula2})))
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula2})))
|
||||
tx = miner_functions.confirmActivity().transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Third confirm activity = " +
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula3})))
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula3})))
|
||||
tx = miner_functions.confirmActivity().transact({'from': ursula3})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
|
@ -235,15 +310,15 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None:
|
|||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
log.info("First confirm activity again = " +
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula1})))
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula1})))
|
||||
tx = miner_functions.confirmActivity().transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Second confirm activity again = " +
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula2})))
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula2})))
|
||||
tx = miner_functions.confirmActivity().transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Third confirm activity again = " +
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula3})))
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula3})))
|
||||
tx = miner_functions.confirmActivity().transact({'from': ursula3})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
|
@ -252,15 +327,15 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None:
|
|||
#
|
||||
testerchain.time_travel(periods=1)
|
||||
log.info("First confirm activity + mint = " +
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula1})))
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula1})))
|
||||
tx = miner_functions.confirmActivity().transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Second confirm activity + mint = " +
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula2})))
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula2})))
|
||||
tx = miner_functions.confirmActivity().transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Third confirm activity + mint = " +
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula3})))
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula3})))
|
||||
tx = miner_functions.confirmActivity().transact({'from': ursula3})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
|
@ -288,15 +363,15 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None:
|
|||
#
|
||||
testerchain.time_travel(periods=1)
|
||||
log.info("First confirm activity after downtime = " +
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula1})))
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula1})))
|
||||
tx = miner_functions.confirmActivity().transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Second confirm activity after downtime = " +
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula2})))
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula2})))
|
||||
tx = miner_functions.confirmActivity().transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Third confirm activity after downtime = " +
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula3})))
|
||||
str(miner_functions.confirmActivity().estimateGas({'from': ursula3})))
|
||||
tx = miner_functions.confirmActivity().transact({'from': ursula3})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
|
@ -304,15 +379,15 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None:
|
|||
# Ursula and Alice deposit some tokens to the escrow again
|
||||
#
|
||||
log.info("First deposit tokens again = " +
|
||||
str(miner_functions.deposit(MIN_ALLOWED_LOCKED * 2, MIN_LOCKED_PERIODS).estimateGas({'from': ursula1})))
|
||||
str(miner_functions.deposit(MIN_ALLOWED_LOCKED * 2, MIN_LOCKED_PERIODS).estimateGas({'from': ursula1})))
|
||||
tx = miner_functions.deposit(MIN_ALLOWED_LOCKED * 2, MIN_LOCKED_PERIODS).transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Second deposit tokens again = " +
|
||||
str(miner_functions.deposit(MIN_ALLOWED_LOCKED * 2, MIN_LOCKED_PERIODS).estimateGas({'from': ursula2})))
|
||||
str(miner_functions.deposit(MIN_ALLOWED_LOCKED * 2, MIN_LOCKED_PERIODS).estimateGas({'from': ursula2})))
|
||||
tx = miner_functions.deposit(MIN_ALLOWED_LOCKED * 2, MIN_LOCKED_PERIODS).transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Third deposit tokens again = " +
|
||||
str(miner_functions.deposit(MIN_ALLOWED_LOCKED * 2, MIN_LOCKED_PERIODS).estimateGas({'from': ursula3})))
|
||||
str(miner_functions.deposit(MIN_ALLOWED_LOCKED * 2, MIN_LOCKED_PERIODS).estimateGas({'from': ursula3})))
|
||||
tx = miner_functions.deposit(MIN_ALLOWED_LOCKED * 2, MIN_LOCKED_PERIODS).transact({'from': ursula3})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
|
@ -337,12 +412,16 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None:
|
|||
policy_id_2 = os.urandom(int(POLICY_ID_LENGTH))
|
||||
number_of_periods = 10
|
||||
log.info("First creating policy (1 node, 10 periods) = " +
|
||||
str(policy_functions.createPolicy(policy_id_1, number_of_periods, 0, [ursula1]).estimateGas({'from': alice1, 'value': 10000})))
|
||||
tx = policy_functions.createPolicy(policy_id_1, number_of_periods, 0, [ursula1]).transact({'from': alice1, 'value': 10000})
|
||||
str(policy_functions.createPolicy(policy_id_1, number_of_periods, 0, [ursula1]).estimateGas(
|
||||
{'from': alice1, 'value': 10000})))
|
||||
tx = policy_functions.createPolicy(policy_id_1, number_of_periods, 0, [ursula1]).transact(
|
||||
{'from': alice1, 'value': 10000})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Second creating policy (1 node, 10 periods) = " +
|
||||
str(policy_functions.createPolicy(policy_id_2, number_of_periods, 0, [ursula1]).estimateGas({'from': alice1, 'value': 10000})))
|
||||
tx = policy_functions.createPolicy(policy_id_2, number_of_periods, 0, [ursula1]).transact({'from': alice1, 'value': 10000})
|
||||
str(policy_functions.createPolicy(policy_id_2, number_of_periods, 0, [ursula1]).estimateGas(
|
||||
{'from': alice1, 'value': 10000})))
|
||||
tx = policy_functions.createPolicy(policy_id_2, number_of_periods, 0, [ursula1]).transact(
|
||||
{'from': alice1, 'value': 10000})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
#
|
||||
|
@ -362,17 +441,23 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None:
|
|||
policy_id_3 = os.urandom(int(POLICY_ID_LENGTH))
|
||||
number_of_periods = 100
|
||||
log.info("First creating policy (1 node, " + str(number_of_periods) + " periods, first reward) = " +
|
||||
str(policy_functions.createPolicy(policy_id_1, number_of_periods, 50, [ursula2]).estimateGas({'from': alice1, 'value': 10050})))
|
||||
tx = policy_functions.createPolicy(policy_id_1, number_of_periods, 50, [ursula2]).transact({'from': alice1, 'value': 10050})
|
||||
str(policy_functions.createPolicy(policy_id_1, number_of_periods, 50, [ursula2]).estimateGas(
|
||||
{'from': alice1, 'value': 10050})))
|
||||
tx = policy_functions.createPolicy(policy_id_1, number_of_periods, 50, [ursula2]).transact(
|
||||
{'from': alice1, 'value': 10050})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
testerchain.time_travel(periods=1)
|
||||
log.info("Second creating policy (1 node, " + str(number_of_periods) + " periods, first reward) = " +
|
||||
str(policy_functions.createPolicy(policy_id_2, number_of_periods, 50, [ursula2]).estimateGas({'from': alice1, 'value': 10050})))
|
||||
tx = policy_functions.createPolicy(policy_id_2, number_of_periods, 50, [ursula2]).transact({'from': alice1, 'value': 10050})
|
||||
str(policy_functions.createPolicy(policy_id_2, number_of_periods, 50, [ursula2]).estimateGas(
|
||||
{'from': alice1, 'value': 10050})))
|
||||
tx = policy_functions.createPolicy(policy_id_2, number_of_periods, 50, [ursula2]).transact(
|
||||
{'from': alice1, 'value': 10050})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Third creating policy (1 node, " + str(number_of_periods) + " periods, first reward) = " +
|
||||
str(policy_functions.createPolicy(policy_id_3, number_of_periods, 50, [ursula1]).estimateGas({'from': alice1, 'value': 10050})))
|
||||
tx = policy_functions.createPolicy(policy_id_3, number_of_periods, 50, [ursula1]).transact({'from': alice1, 'value': 10050})
|
||||
str(policy_functions.createPolicy(policy_id_3, number_of_periods, 50, [ursula1]).estimateGas(
|
||||
{'from': alice1, 'value': 10050})))
|
||||
tx = policy_functions.createPolicy(policy_id_3, number_of_periods, 50, [ursula1]).transact(
|
||||
{'from': alice1, 'value': 10050})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
#
|
||||
|
@ -394,15 +479,15 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None:
|
|||
|
||||
testerchain.time_travel(periods=10)
|
||||
log.info("First revoking policy after downtime = " +
|
||||
str(policy_functions.revokePolicy(policy_id_1).estimateGas({'from': alice1})))
|
||||
str(policy_functions.revokePolicy(policy_id_1).estimateGas({'from': alice1})))
|
||||
tx = policy_functions.revokePolicy(policy_id_1).transact({'from': alice1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Second revoking policy after downtime = " +
|
||||
str(policy_functions.revokePolicy(policy_id_2).estimateGas({'from': alice1})))
|
||||
str(policy_functions.revokePolicy(policy_id_2).estimateGas({'from': alice1})))
|
||||
tx = policy_functions.revokePolicy(policy_id_2).transact({'from': alice1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Second revoking policy after downtime = " +
|
||||
str(policy_functions.revokePolicy(policy_id_3).estimateGas({'from': alice1})))
|
||||
str(policy_functions.revokePolicy(policy_id_3).estimateGas({'from': alice1})))
|
||||
tx = policy_functions.revokePolicy(policy_id_3).transact({'from': alice1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
|
@ -417,17 +502,21 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None:
|
|||
str(policy_functions
|
||||
.createPolicy(policy_id_1, number_of_periods, 50, [ursula1, ursula2, ursula3])
|
||||
.estimateGas({'from': alice1, 'value': 30150})))
|
||||
tx = policy_functions.createPolicy(policy_id_1, number_of_periods, 50, [ursula1, ursula2, ursula3]).transact({'from': alice1, 'value': 30150})
|
||||
tx = policy_functions.createPolicy(policy_id_1, number_of_periods, 50, [ursula1, ursula2, ursula3]).transact(
|
||||
{'from': alice1, 'value': 30150})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Second creating policy (3 nodes, 100 periods, first reward) = " +
|
||||
str(policy_functions
|
||||
.createPolicy(policy_id_2, number_of_periods, 50, [ursula1, ursula2, ursula3])
|
||||
.estimateGas({'from': alice1, 'value': 30150})))
|
||||
tx = policy_functions.createPolicy(policy_id_2, number_of_periods, 50, [ursula1, ursula2, ursula3]).transact({'from': alice1, 'value': 30150})
|
||||
tx = policy_functions.createPolicy(policy_id_2, number_of_periods, 50, [ursula1, ursula2, ursula3]).transact(
|
||||
{'from': alice1, 'value': 30150})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Third creating policy (2 nodes, 100 periods, first reward) = " +
|
||||
str(policy_functions.createPolicy(policy_id_3, number_of_periods, 50, [ursula1, ursula2]).estimateGas({'from': alice1, 'value': 20100})))
|
||||
tx = policy_functions.createPolicy(policy_id_3, number_of_periods, 50, [ursula1, ursula2]).transact({'from': alice1, 'value': 20100})
|
||||
str(policy_functions.createPolicy(policy_id_3, number_of_periods, 50, [ursula1, ursula2]).estimateGas(
|
||||
{'from': alice1, 'value': 20100})))
|
||||
tx = policy_functions.createPolicy(policy_id_3, number_of_periods, 50, [ursula1, ursula2]).transact(
|
||||
{'from': alice1, 'value': 20100})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
for index in range(5):
|
||||
|
@ -449,13 +538,16 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None:
|
|||
#
|
||||
# Check regular deposit
|
||||
#
|
||||
log.info("First deposit tokens = " + str(miner_functions.deposit(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).estimateGas({'from': ursula1})))
|
||||
log.info("First deposit tokens = " + str(
|
||||
miner_functions.deposit(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).estimateGas({'from': ursula1})))
|
||||
tx = miner_functions.deposit(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Second deposit tokens = " + str(miner_functions.deposit(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).estimateGas({'from': ursula2})))
|
||||
log.info("Second deposit tokens = " + str(
|
||||
miner_functions.deposit(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).estimateGas({'from': ursula2})))
|
||||
tx = miner_functions.deposit(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Third deposit tokens = " + str(miner_functions.deposit(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).estimateGas({'from': ursula3})))
|
||||
log.info("Third deposit tokens = " + str(
|
||||
miner_functions.deposit(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).estimateGas({'from': ursula3})))
|
||||
tx = miner_functions.deposit(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).transact({'from': ursula3})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
|
@ -508,25 +600,27 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None:
|
|||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
log.info("First locking tokens = " +
|
||||
str(miner_functions.lock(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).estimateGas({'from': ursula1})))
|
||||
str(miner_functions.lock(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).estimateGas({'from': ursula1})))
|
||||
tx = miner_functions.lock(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Second locking tokens = " +
|
||||
str(miner_functions.lock(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).estimateGas({'from': ursula2})))
|
||||
tx = miner_functions.lock(MIN_ALLOWED_LOCKED,MIN_LOCKED_PERIODS).transact({'from': ursula2})
|
||||
str(miner_functions.lock(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).estimateGas({'from': ursula2})))
|
||||
tx = miner_functions.lock(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).transact({'from': ursula2})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Third locking tokens = " +
|
||||
str(miner_functions.lock(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).estimateGas({'from': ursula3})))
|
||||
str(miner_functions.lock(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).estimateGas({'from': ursula3})))
|
||||
tx = miner_functions.lock(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).transact({'from': ursula3})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
#
|
||||
# Divide stake
|
||||
#
|
||||
log.info("First divide stake = " + str(miner_functions.divideStake(1, MIN_ALLOWED_LOCKED, 2).estimateGas({'from': ursula1})))
|
||||
log.info("First divide stake = " + str(
|
||||
miner_functions.divideStake(1, MIN_ALLOWED_LOCKED, 2).estimateGas({'from': ursula1})))
|
||||
tx = miner_functions.divideStake(1, MIN_ALLOWED_LOCKED, 2).transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Second divide stake = " + str(miner_functions.divideStake(3, MIN_ALLOWED_LOCKED, 2).estimateGas({'from': ursula1})))
|
||||
log.info("Second divide stake = " + str(
|
||||
miner_functions.divideStake(3, MIN_ALLOWED_LOCKED, 2).estimateGas({'from': ursula1})))
|
||||
tx = miner_functions.divideStake(3, MIN_ALLOWED_LOCKED, 2).transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
|
@ -537,10 +631,96 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None:
|
|||
tx = miner_functions.confirmActivity().transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
testerchain.time_travel(periods=1)
|
||||
log.info("Divide stake (next period is not confirmed) = " + str(miner_functions.divideStake(0, MIN_ALLOWED_LOCKED, 2).estimateGas({'from': ursula1})))
|
||||
log.info("Divide stake (next period is not confirmed) = " + str(
|
||||
miner_functions.divideStake(0, MIN_ALLOWED_LOCKED, 2).estimateGas({'from': ursula1})))
|
||||
tx = miner_functions.confirmActivity().transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
log.info("Divide stake (next period is confirmed) = " + str(miner_functions.divideStake(0, MIN_ALLOWED_LOCKED, 2).estimateGas({'from': ursula1})))
|
||||
log.info("Divide stake (next period is confirmed) = " + str(
|
||||
miner_functions.divideStake(0, MIN_ALLOWED_LOCKED, 2).estimateGas({'from': ursula1})))
|
||||
|
||||
# Slashing tests
|
||||
tx = miner_functions.confirmActivity().transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
testerchain.time_travel(periods=1)
|
||||
# Deploy adjudicator mock to estimate slashing method in MinersEscrow contract
|
||||
adjudicator, _ = testerchain.interface.deploy_contract(
|
||||
'MiningAdjudicator', miner_agent.contract.address, ALGORITHM_SHA256, MIN_ALLOWED_LOCKED - 1, 0, 2, 2
|
||||
)
|
||||
tx = miner_functions.setMiningAdjudicator(adjudicator.address).transact()
|
||||
testerchain.wait_for_receipt(tx)
|
||||
adjudicator_functions = adjudicator.functions
|
||||
|
||||
# Slashing
|
||||
slashing_args = generate_args_for_slashing(testerchain, ursula1)
|
||||
log.info("Slash just value = " + str(
|
||||
adjudicator_functions.evaluateCFrag(*slashing_args).estimateGas({'from': alice1})))
|
||||
tx = adjudicator_functions.evaluateCFrag(*slashing_args).transact({'from': alice1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
deposit = miner_functions.minerInfo(ursula1).call()[0]
|
||||
unlocked = deposit - miner_functions.getLockedTokens(ursula1).call()
|
||||
tx = miner_functions.withdraw(unlocked).transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
sub_stakes_length = str(miner_functions.getSubStakesLength(ursula1).call())
|
||||
slashing_args = generate_args_for_slashing(testerchain, ursula1)
|
||||
log.info("First slashing one sub stake and saving old one (" + sub_stakes_length + " sub stakes) = " +
|
||||
str(adjudicator_functions.evaluateCFrag(*slashing_args).estimateGas({'from': alice1})))
|
||||
tx = adjudicator_functions.evaluateCFrag(*slashing_args).transact({'from': alice1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
sub_stakes_length = str(miner_functions.getSubStakesLength(ursula1).call())
|
||||
slashing_args = generate_args_for_slashing(testerchain, ursula1)
|
||||
log.info("Second slashing one sub stake and saving old one (" + sub_stakes_length + " sub stakes) = " +
|
||||
str(adjudicator_functions.evaluateCFrag(*slashing_args).estimateGas({'from': alice1})))
|
||||
tx = adjudicator_functions.evaluateCFrag(*slashing_args).transact({'from': alice1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
sub_stakes_length = str(miner_functions.getSubStakesLength(ursula1).call())
|
||||
slashing_args = generate_args_for_slashing(testerchain, ursula1)
|
||||
log.info("Third slashing one sub stake and saving old one (" + sub_stakes_length + " sub stakes) = " +
|
||||
str(adjudicator_functions.evaluateCFrag(*slashing_args).estimateGas({'from': alice1})))
|
||||
tx = adjudicator_functions.evaluateCFrag(*slashing_args).transact({'from': alice1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
sub_stakes_length = str(miner_functions.getSubStakesLength(ursula1).call())
|
||||
slashing_args = generate_args_for_slashing(testerchain, ursula1)
|
||||
log.info("Slashing two sub stakes and saving old one (" + sub_stakes_length + " sub stakes) = " +
|
||||
str(adjudicator_functions.evaluateCFrag(*slashing_args).estimateGas({'from': alice1})))
|
||||
tx = adjudicator_functions.evaluateCFrag(*slashing_args).transact({'from': alice1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
for index in range(18):
|
||||
tx = miner_functions.confirmActivity().transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
testerchain.time_travel(periods=1)
|
||||
|
||||
tx = miner_functions.lock(MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS).transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
deposit = miner_functions.minerInfo(ursula1).call()[0]
|
||||
unlocked = deposit - miner_functions.getLockedTokens(ursula1, 1).call()
|
||||
tx = miner_functions.withdraw(unlocked).transact({'from': ursula1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
sub_stakes_length = str(miner_functions.getSubStakesLength(ursula1).call())
|
||||
slashing_args = generate_args_for_slashing(testerchain, ursula1)
|
||||
log.info("Slashing two sub stakes, shortest and new one (" + sub_stakes_length + " sub stakes) = " +
|
||||
str(adjudicator_functions.evaluateCFrag(*slashing_args).estimateGas({'from': alice1})))
|
||||
tx = adjudicator_functions.evaluateCFrag(*slashing_args).transact({'from': alice1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
sub_stakes_length = str(miner_functions.getSubStakesLength(ursula1).call())
|
||||
slashing_args = generate_args_for_slashing(testerchain, ursula1)
|
||||
log.info("Slashing three sub stakes, two shortest and new one (" + sub_stakes_length + " sub stakes) = " +
|
||||
str(adjudicator_functions.evaluateCFrag(*slashing_args).estimateGas({'from': alice1})))
|
||||
tx = adjudicator_functions.evaluateCFrag(*slashing_args).transact({'from': alice1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
slashing_args = generate_args_for_slashing(testerchain, ursula1, corrupt=False)
|
||||
log.info("Evaluating correct CFrag = " +
|
||||
str(adjudicator_functions.evaluateCFrag(*slashing_args).estimateGas({'from': alice1})))
|
||||
tx = adjudicator_functions.evaluateCFrag(*slashing_args).transact({'from': alice1})
|
||||
testerchain.wait_for_receipt(tx)
|
||||
|
||||
print("********* All Done! *********")
|
||||
|
||||
|
|
Loading…
Reference in New Issue