Merge pull request #507 from nucypher/vodka

VODKA: Slashing for incorrect re-encryption
pull/811/head
David Núñez 2019-02-26 06:55:26 +01:00 committed by GitHub
commit ec6b754ad8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 5297 additions and 492 deletions

View File

@ -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"

814
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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;
}
}
}

View File

@ -129,4 +129,4 @@ contract UserEscrow is Ownable {
}
}
}
}

View File

@ -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));
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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 {

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)):

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -133,4 +133,4 @@ contract MinersEscrowForPolicyMock {
function register(address _node) external {
policyManager.register(_node, getCurrentPeriod() - 1);
}
}
}

View File

@ -122,4 +122,4 @@ contract UserEscrowLibraryMockV2 {
function thirdMethod() public pure {}
}
}

View File

@ -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()

View File

@ -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()

View File

@ -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]

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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']

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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})

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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()

View File

@ -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! *********")