Skip to content

Commit

Permalink
Merge pull request #99 from nucypher/threshold-app
Browse files Browse the repository at this point in the history
TACo Threshold Application
  • Loading branch information
cygnusv authored Aug 1, 2023
2 parents c1e264b + 3759907 commit 5251e07
Show file tree
Hide file tree
Showing 22 changed files with 2,458 additions and 1,025 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ jobs:
- name: Deploy NuCypher Token
run: ape run scripts/deploy_nucypher_token.py --network ethereum:local

- name: Deploy Simple PRE
run: ape run scripts/deploy_simple_pre.py --network ethereum:local
- name: Deploy TACo Application
run: ape run scripts/deploy_taco_application.py --network ethereum:local

- name: Deploy Staking Escrow
run: ape run scripts/deploy_staking_escrow.py --network ethereum:local
Expand Down
12 changes: 12 additions & 0 deletions ape-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ dependencies:
- name: fx-portal
github: 0xPolygon/fx-portal
version: 1.0.5
- name: threshold
github: threshold-network/solidity-contracts
version: 1.2.1

solidity:
version: 0.8.20
Expand All @@ -26,6 +29,7 @@ solidity:
- "@openzeppelin/contracts=openzeppelin/v4.9.1"
- "@openzeppelin-upgradeable/contracts=openzeppelin-upgradeable/v4.9.1"
- "@fx-portal/contracts=fx-portal/v1.0.5"
- "@threshold/contracts=threshold/v1.2.1"

deployments:
polygon:
Expand All @@ -44,6 +48,14 @@ deployments:
- nu_token_supply: 1_000_000_000
pre_min_authorization: 40000000000000000000000
pre_min_operator_seconds: 86400 # one day in seconds
pre_hash_algorithm: 1
pre_base_penalty: 2
pre_penalty_history_coefficient: 0
pre_percentage_penalty_coefficient: 100000
pre_min_authorization: 40000000000000000000000
pre_min_operator_seconds: 86400 # one day in seconds
reward_duration: 604800 # one week in seconds
deauthorization_duration: 5184000 # 60 days in seconds
verify: False
rinkeby:
- nu_token: '0x78D591D90a4a768B9D2790deA465D472b6Fe0f18'
Expand Down
126 changes: 51 additions & 75 deletions contracts/contracts/Adjudicator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@ pragma solidity ^0.8.0;

import "./lib/ReEncryptionValidator.sol";
import "./lib/SignatureVerifier.sol";
import "./IStakingEscrow.sol";
import "./proxy/Upgradeable.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
import "@openzeppelin/contracts/utils/math/SafeCast.sol";
import "./TACoApplication.sol";


/**
* @title Adjudicator
* @notice Supervises stakers' behavior and punishes when something's wrong.
* @dev |v2.1.2|
* @notice Supervises operators' behavior and punishes when something's wrong.
* @dev |v3.1.1|
*/
contract Adjudicator is Upgradeable {
contract Adjudicator {

using UmbralDeserializer for bytes;
using SafeCast for uint256;

event CFragEvaluated(
bytes32 indexed evaluationHash,
Expand All @@ -25,62 +26,57 @@ contract Adjudicator is Upgradeable {
);
event IncorrectCFragVerdict(
bytes32 indexed evaluationHash,
address indexed worker,
address indexed staker
address indexed operator,
address indexed stakingProvider
);

// used only for upgrading
bytes32 constant RESERVED_CAPSULE_AND_CFRAG_BYTES = bytes32(0);
address constant RESERVED_ADDRESS = address(0);

IStakingEscrow public immutable escrow;
SignatureVerifier.HashAlgorithm public immutable hashAlgorithm;
uint256 public immutable basePenalty;
uint256 public immutable penaltyHistoryCoefficient;
uint256 public immutable percentagePenaltyCoefficient;
uint256 public immutable rewardCoefficient;
TACoApplication public immutable application;

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(
IStakingEscrow _escrow,
TACoApplication _application,
SignatureVerifier.HashAlgorithm _hashAlgorithm,
uint256 _basePenalty,
uint256 _penaltyHistoryCoefficient,
uint256 _percentagePenaltyCoefficient,
uint256 _rewardCoefficient
uint256 _percentagePenaltyCoefficient
) {
// Sanity checks.
require(_escrow.secondsPerPeriod() > 0 && // This contract has an escrow, and it's not the null address.
// The reward and penalty coefficients are set.
_percentagePenaltyCoefficient != 0 &&
_rewardCoefficient != 0);
escrow = _escrow;
require(
_percentagePenaltyCoefficient != 0 &&
address(_application.token()) != address(0),
"Wrong input parameters"
);
hashAlgorithm = _hashAlgorithm;
basePenalty = _basePenalty;
percentagePenaltyCoefficient = _percentagePenaltyCoefficient;
penaltyHistoryCoefficient = _penaltyHistoryCoefficient;
rewardCoefficient = _rewardCoefficient;
application = _application;
}

/**
* @notice Submit proof that a worker created wrong CFrag
* @notice Submit proof that a operator created wrong CFrag
* @param _capsuleBytes Serialized capsule
* @param _cFragBytes Serialized CFrag
* @param _cFragSignature Signature of CFrag by worker
* @param _cFragSignature Signature of CFrag by operator
* @param _taskSignature Signature of task specification by Bob
* @param _requesterPublicKey Bob's signing public key, also known as "stamp"
* @param _workerPublicKey Worker's signing public key, also known as "stamp"
* @param _workerIdentityEvidence Signature of worker's public key by worker's eth-key
* @param _operatorPublicKey Operator's signing public key, also known as "stamp"
* @param _operatorIdentityEvidence Signature of operator's public key by operator's eth-key
* @param _preComputedData Additional pre-computed data for CFrag correctness verification
*/
function evaluateCFrag(
Expand All @@ -89,8 +85,8 @@ contract Adjudicator is Upgradeable {
bytes memory _cFragSignature,
bytes memory _taskSignature,
bytes memory _requesterPublicKey,
bytes memory _workerPublicKey,
bytes memory _workerIdentityEvidence,
bytes memory _operatorPublicKey,
bytes memory _operatorIdentityEvidence,
bytes memory _preComputedData
)
public
Expand All @@ -106,28 +102,28 @@ contract Adjudicator is Upgradeable {
emit CFragEvaluated(evaluationHash, msg.sender, cFragIsCorrect);

// 3. Verify associated public keys and signatures
require(ReEncryptionValidator.checkSerializedCoordinates(_workerPublicKey),
require(ReEncryptionValidator.checkSerializedCoordinates(_operatorPublicKey),
"Staker's public key is invalid");
require(ReEncryptionValidator.checkSerializedCoordinates(_requesterPublicKey),
"Requester's public key is invalid");

UmbralDeserializer.PreComputedData memory precomp = _preComputedData.toPreComputedData();

// Verify worker's signature of CFrag
// Verify operator's signature of CFrag
require(SignatureVerifier.verify(
_cFragBytes,
abi.encodePacked(_cFragSignature, precomp.lostBytes[1]),
_workerPublicKey,
_operatorPublicKey,
hashAlgorithm),
"CFrag signature is invalid"
);

// Verify worker's signature of taskSignature and that it corresponds to cfrag.proof.metadata
// Verify operator's signature of taskSignature and that it corresponds to cfrag.proof.metadata
UmbralDeserializer.CapsuleFrag memory cFrag = _cFragBytes.toCapsuleFrag();
require(SignatureVerifier.verify(
_taskSignature,
abi.encodePacked(cFrag.proof.metadata, precomp.lostBytes[2]),
_workerPublicKey,
_operatorPublicKey,
hashAlgorithm),
"Task signature is invalid"
);
Expand All @@ -136,14 +132,14 @@ contract Adjudicator is Upgradeable {
// A task specification is: capsule + ursula pubkey + alice address + blockhash
bytes32 stampXCoord;
assembly {
stampXCoord := mload(add(_workerPublicKey, 32))
stampXCoord := mload(add(_operatorPublicKey, 32))
}
bytes memory stamp = abi.encodePacked(precomp.lostBytes[4], stampXCoord);

require(SignatureVerifier.verify(
abi.encodePacked(_capsuleBytes,
stamp,
_workerIdentityEvidence,
_operatorIdentityEvidence,
precomp.alicesKeyAsAddress,
bytes32(0)),
abi.encodePacked(_taskSignature, precomp.lostBytes[3]),
Expand All @@ -152,58 +148,38 @@ contract Adjudicator is Upgradeable {
"Specification signature is invalid"
);

// 4. Extract worker address from stamp signature.
address worker = SignatureVerifier.recover(
// 4. Extract operator address from stamp signature.
address operator = SignatureVerifier.recover(
SignatureVerifier.hashEIP191(stamp, bytes1(0x45)), // Currently, we use version E (0x45) of EIP191 signatures
_workerIdentityEvidence);
address staker = escrow.stakerFromWorker(worker);
require(staker != address(0), "Worker must be related to a staker");
_operatorIdentityEvidence);
address stakingProvider = application.stakingProviderFromOperator(operator);
require(stakingProvider != address(0), "Operator must be associated with a provider");

// 5. Check that staker can be slashed
uint256 stakerValue = escrow.getAllTokens(staker);
require(stakerValue > 0, "Staker has no tokens");
// 5. Check that staking provider can be slashed
uint96 stakingProviderValue = application.authorizedStake(stakingProvider);
require(stakingProviderValue > 0, "Provider has no tokens");

// 6. If CFrag was incorrect, slash staker
// 6. If CFrag was incorrect, slash staking provider
if (!cFragIsCorrect) {
(uint256 penalty, uint256 reward) = calculatePenaltyAndReward(staker, stakerValue);
escrow.slashStaker(staker, penalty, msg.sender, reward);
emit IncorrectCFragVerdict(evaluationHash, worker, staker);
uint96 penalty = calculatePenalty(stakingProvider, stakingProviderValue);
application.slash(stakingProvider, penalty, msg.sender);
emit IncorrectCFragVerdict(evaluationHash, operator, stakingProvider);
}
}

/**
* @notice Calculate penalty to the staker and reward to the investigator
* @param _staker Staker's address
* @param _stakerValue Amount of tokens that belong to the staker
* @notice Calculate penalty to the staking provider
* @param _stakingProvider Staking provider address
* @param _stakingProviderValue Amount of tokens that belong to the staking provider
*/
function calculatePenaltyAndReward(address _staker, uint256 _stakerValue)
internal returns (uint256 penalty, uint256 reward)
function calculatePenalty(address _stakingProvider, uint96 _stakingProviderValue)
internal returns (uint96)
{
penalty = basePenalty + penaltyHistoryCoefficient * penaltyHistory[_staker];
penalty = Math.min(penalty, _stakerValue / percentagePenaltyCoefficient);
reward = penalty / rewardCoefficient;
uint256 penalty = basePenalty + penaltyHistoryCoefficient * penaltyHistory[_stakingProvider];
penalty = Math.min(penalty, _stakingProviderValue / percentagePenaltyCoefficient);
// TODO add maximum condition or other overflow protection or other penalty condition (#305?)
penaltyHistory[_staker] = penaltyHistory[_staker] + 1;
}

/// @dev the `onlyWhileUpgrading` modifier works through a call to the parent `verifyState`
function verifyState(address _testTarget) public override virtual {
super.verifyState(_testTarget);
bytes32 evaluationCFragHash = SignatureVerifier.hash(
abi.encodePacked(RESERVED_CAPSULE_AND_CFRAG_BYTES), SignatureVerifier.HashAlgorithm.SHA256);
require(delegateGet(_testTarget, this.evaluatedCFrags.selector, evaluationCFragHash) ==
(evaluatedCFrags[evaluationCFragHash] ? 1 : 0));
require(delegateGet(_testTarget, this.penaltyHistory.selector, bytes32(bytes20(RESERVED_ADDRESS))) ==
penaltyHistory[RESERVED_ADDRESS]);
penaltyHistory[_stakingProvider] = penaltyHistory[_stakingProvider] + 1;
return penalty.toUint96();
}

/// @dev the `onlyWhileUpgrading` modifier works through a call to the parent `finishUpgrade`
function finishUpgrade(address _target) public override virtual {
super.finishUpgrade(_target);
// preparation for the verifyState method
bytes32 evaluationCFragHash = SignatureVerifier.hash(
abi.encodePacked(RESERVED_CAPSULE_AND_CFRAG_BYTES), SignatureVerifier.HashAlgorithm.SHA256);
evaluatedCFrags[evaluationCFragHash] = true;
penaltyHistory[RESERVED_ADDRESS] = 123;
}
}
Loading

0 comments on commit 5251e07

Please sign in to comment.