From cf8749be38a46b16d482ee1b4e4fa42e8e4fad66 Mon Sep 17 00:00:00 2001 From: Kieran Prasch Date: Fri, 10 Feb 2023 12:47:06 -0800 Subject: [PATCH] temporary inclusion of Coordinatorv3 from baacbcd529cace1451dbaab84c0acb4beebea384 --- .../sol/source/contracts/CoordinatorV3.sol | 225 +++++++++--------- 1 file changed, 117 insertions(+), 108 deletions(-) diff --git a/nucypher/blockchain/eth/sol/source/contracts/CoordinatorV3.sol b/nucypher/blockchain/eth/sol/source/contracts/CoordinatorV3.sol index 53a03b32e..8b593b804 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/CoordinatorV3.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/CoordinatorV3.sol @@ -2,174 +2,183 @@ pragma solidity ^0.8.0; +import "./proxy/Upgradeable.sol"; + /** -* @title CoordinatorV1 +* @title CoordinatorV3 * @notice Coordination layer for DKG-TDec */ -contract CoordinatorV1 { +contract CoordinatorV3 is Upgradeable { - uint32 public constant DKG_SIZE = 8; - uint32 public TIMEOUT = 9600; - - event StartRitual(uint32 indexed ritualId, address[] nodes); + // Ritual + event StartRitual(uint32 indexed ritualId, address[] nodes, address initiator); event StartTranscriptRound(uint32 indexed ritualId); - event StartConfirmationRound(uint32 indexed ritualId); - event RitualEnded(uint32 indexed ritualId); + event StartAggregationRound(uint32 indexed ritualId); + event EndRitual(uint32 indexed ritualId, RitualStatus status, address initiator); + // Node event TranscriptPosted(uint32 indexed ritualId, address indexed node, bytes32 transcriptDigest); - event ConfirmationPosted(uint32 indexed ritualId, address indexed node, address[] confirmedNodes); + event AggregationPosted(uint32 indexed ritualId, address indexed node, bytes32 aggregatedTranscriptDigest); - event TimeoutChanged(uint32 timeout); - event DkgSizeChanged(uint8 dkgSize); + // Admin + event TimeoutChanged(uint32 oldTimeout, uint32 newTimeout); + event MaxDkgSizeChanged(uint32 oldSize, uint32 newSize); enum RitualStatus { - WAITING_FOR_CHECKINS, WAITING_FOR_TRANSCRIPTS, - WAITING_FOR_CONFIRMATIONS, - COMPLETED, - FAILED + WAITING_FOR_AGGREGATIONS, + WAITING_FOR_FINALIZATION, + FAILED_TIMEOUT, + FAILED_INVALID_TRANSCRIPTS, + FINALIZED } - // TODO: Find better name - struct Performance { + struct Rite { address node; - uint32 checkinTimestamp; - uint96 confirmedBy; - bytes32 transcript; + bool aggregated; + bytes transcript; } struct Ritual { uint32 id; + address initiator; + uint32 dkgSize; uint32 initTimestamp; - uint32 totalCheckins; uint32 totalTranscripts; - uint32 totalConfirmations; + uint32 totalAggregations; + uint32 threshold; + bytes32 publicMaterial; RitualStatus status; - Performance[DKG_SIZE] performance; + Rite[] rite; } Ritual[] public rituals; - function numberOfRituals() external view returns(uint256){ + uint32 public timeout; + uint32 public maxDkgSize; + + constructor(uint32 _timeout) { + timeout = _timeout; + maxDkgSize = 64; // TODO Who knows? https://www.youtube.com/watch?v=hzqFmXZ8tOE&ab_channel=Protoje + } + + function _checkActiveRitual(Ritual storage _ritual) internal { + uint32 delta = uint32(block.timestamp) - _ritual.initTimestamp; + if (delta > timeout) { + _ritual.status = RitualStatus.FAILED_TIMEOUT; + emit EndRitual(_ritual.id, _ritual.status); // penalty hook, missing nodes can be known at this stage + revert("Ritual timed out"); + } + } + + function setTimeout(uint32 newTimeout) external onlyOwner { + uint32 oldTimeout = timeout; + timeout = newTimeout; + emit TimeoutChanged(oldTimeout, newTimeout); + } + + function setMaxDkgSize(uint32 newSize) external onlyOwner { + uint32 oldSize = maxDkgSize; + maxDkgSize = newSize; + emit MaxDkgSizeChanged(oldSize, newSize); + } + + function numberOfRituals() external view returns(uint256) { return rituals.length; } - function getPerformances(uint32 ritualId) external view returns(Performance[] memory){ - Performance[] memory performances = new Performance[](rituals[ritualId].performance.length); - for(uint32 i=0; i < rituals[ritualId].performance.length; i++){ - performances[i] = rituals[ritualId].performance[i]; + function getRites(uint32 ritualId) external view returns(Rite[] memory) { + Rite[] memory rites = new Rite[](rituals[ritualId].rite.length); + for(uint32 i=0; i < rituals[ritualId].rite.length; i++){ + rites[i] = rituals[ritualId].rite[i]; } - return performances; + return rites; } - function setTimeout(uint32 timeout) external { - TIMEOUT = timeout; - emit TimeoutChanged(timeout); - } - - function initiateRitual(address[] calldata nodes) external { - // TODO: Check for payment - // TODO: Check for expiration time - // TODO: Improve DKG size choices - require(nodes.length == DKG_SIZE, "Invalid number of nodes"); + function initiateRitual(address[] calldata nodes) external returns (uint32) { + require(nodes.length <= maxDkgSize, "Invalid number of nodes"); uint32 id = uint32(rituals.length); Ritual storage ritual = rituals.push(); ritual.id = id; + ritual.initiator = msg.sender; + ritual.threshold = threshold; + ritual.dkgSize = uint32(nodes.length); ritual.initTimestamp = uint32(block.timestamp); - ritual.status = RitualStatus.WAITING_FOR_CHECKINS; - ritual.totalTranscripts = 0; - ritual.totalConfirmations = 0; - ritual.totalCheckins = 0; + ritual.status = RitualStatus.WAITING_FOR_TRANSCRIPTS; address previousNode = nodes[0]; - ritual.performance[0].node = previousNode; + ritual.rite[0].node = previousNode; address currentNode; for(uint256 i=1; i < nodes.length; i++){ currentNode = nodes[i]; - require(currentNode > previousNode, "Nodes must be sorted"); - ritual.performance[i].node = currentNode; + ritual.rite[i].node = currentNode; previousNode = currentNode; // TODO: Check nodes are eligible (staking, etc) } - emit StartRitual(id, nodes); - } - - function checkIn(uint32 ritualId, uint256 nodeIndex) external { - Ritual storage ritual = rituals[ritualId]; - require(ritual.status == RitualStatus.WAITING_FOR_CHECKINS, "Not waiting for check-ins"); - require(ritual.performance[nodeIndex].node == msg.sender, "Node not part of ritual"); - if ((uint32(block.timestamp) - ritual.initTimestamp) > TIMEOUT) { - ritual.status = RitualStatus.FAILED; - emit RitualEnded(ritualId); - revert("Ritual timed out"); - } - require(ritual.performance[nodeIndex].checkinTimestamp == 0, "Node already checked in"); - ritual.performance[nodeIndex].checkinTimestamp = uint32(block.timestamp); - ritual.totalCheckins++; - if (ritual.totalCheckins == DKG_SIZE){ - ritual.status = RitualStatus.WAITING_FOR_TRANSCRIPTS; - emit StartTranscriptRound(ritualId); - } + emit StartRitual(id, nodes, msg.sender); + return ritual.id; } function postTranscript(uint32 ritualId, uint256 nodeIndex, bytes calldata transcript) external { Ritual storage ritual = rituals[ritualId]; + require(ritual.rite[nodeIndex].node == msg.sender, "Node not part of ritual"); require(ritual.status == RitualStatus.WAITING_FOR_TRANSCRIPTS, "Not waiting for transcripts"); - require(ritual.performance[nodeIndex].node == msg.sender, "Node not part of ritual"); - if ((uint32(block.timestamp) - ritual.initTimestamp) > TIMEOUT) { - ritual.status = RitualStatus.FAILED; - emit RitualEnded(ritualId); - revert("Ritual timed out"); - } - require(ritual.performance[nodeIndex].transcript == bytes32(0), "Node already posted transcript"); + require(ritual.rite[nodeIndex].transcript.length == 0, "Node already posted transcript"); + require(ritual.rite[nodeIndex].aggregated == false, "Node already posted aggregation"); + _checkActiveRitual(ritual); // Nodes commit to their transcript bytes32 transcriptDigest = keccak256(transcript); - ritual.performance[nodeIndex].transcript = transcriptDigest; - ritual.totalTranscripts++; - if (ritual.totalTranscripts == DKG_SIZE){ - ritual.status = RitualStatus.WAITING_FOR_CONFIRMATIONS; - emit StartConfirmationRound(ritualId); - } + ritual.rite[nodeIndex].transcript = transcript; emit TranscriptPosted(ritualId, msg.sender, transcriptDigest); + ritual.totalTranscripts++; + + // end round + if (ritual.totalTranscripts == ritual.dkgSize){ + ritual.status = RitualStatus.WAITING_FOR_AGGREGATIONS; + emit StartAggregationRound(ritualId); + } } - function postConfirmation(uint32 ritualId, uint256 nodeIndex, uint256[] calldata confirmedNodesIndexes) external { + function postAggregation(uint32 ritualId, uint256 nodeIndex, bytes calldata aggregatedTranscripts) external { Ritual storage ritual = rituals[ritualId]; - require(ritual.status == RitualStatus.WAITING_FOR_CONFIRMATIONS, "Not waiting for confirmations"); - require( - ritual.performance[nodeIndex].node == msg.sender && - ritual.performance[nodeIndex].transcript != bytes32(0), - "Node not part of ritual" - ); + require(ritual.status == RitualStatus.WAITING_FOR_AGGREGATIONS, "Not waiting for confirmations"); + require(ritual.rite[nodeIndex].node == msg.sender, "Node not part of ritual"); + _checkActiveRitual(ritual); - require(confirmedNodesIndexes.length <= DKG_SIZE, "Invalid number of confirmations"); - if ((uint32(block.timestamp) - ritual.initTimestamp) > TIMEOUT) { - ritual.status = RitualStatus.FAILED; - emit RitualEnded(ritualId); - revert("Ritual timed out"); + // nodes commit to their aggregation result + bytes32 aggregatedTranscriptDigest = keccak256(aggregatedTranscripts); + ritual.rite[nodeIndex].transcript = aggregatedTranscriptDigest; + ritual.rite[nodeIndex].aggregated = true; + emit AggregationPosted(ritualId, msg.sender, aggregatedTranscripts); + ritual.totalAggregations++; + + // end round + if (ritual.totalAggregations == ritual.dkgSize){ + ritual.status = RitualStatus.WAITING_FOR_FINALIZATION; + emit EndRitual(ritualId, ritual.status, ritual.initiator); + } + } + + function finalizeRitual(uint32 ritualId) public { + Ritual storage ritual = rituals[ritualId]; + require(ritual.status == RitualStatus.WAITING_FOR_FINALIZATION, 'ritual cannot be finalized'); + + bytes32 firstRiteDigest = keccak256(ritual.rite[0].transcript); + for(uint32 i=1; i < ritual.rite.length; i++){ + bytes32 currentRiteDigest = keccak256(ritual.rite[i].transcript); + if (firstRiteDigest != currentRiteDigest) { + ritual.status = RitualStatus.FAILED_INVALID_TRANSCRIPTS; + emit EndRitual(ritualId, ritual.status, ritual.initiator); + revert('aggregated transcripts do not match'); + } } - address[] memory confirmedNodes = new address[](confirmedNodesIndexes.length); - - // First, node adds itself to its list of confirmers - uint96 caller = uint96(2 ** nodeIndex); - ritual.performance[nodeIndex].confirmedBy |= caller; - for(uint256 i=0; i < confirmedNodesIndexes.length; i++){ - uint256 confirmedNodeIndex = confirmedNodesIndexes[i]; - require(confirmedNodeIndex < DKG_SIZE, "Invalid node index"); - // We add caller to the list of confirmations of each confirmed node - ritual.performance[confirmedNodeIndex].confirmedBy |= caller; - confirmedNodes[i] = ritual.performance[confirmedNodeIndex].node; - } - ritual.totalConfirmations++; - if (ritual.totalConfirmations == DKG_SIZE){ - ritual.status = RitualStatus.COMPLETED; - emit RitualEnded(ritualId); - } - emit ConfirmationPosted(ritualId, msg.sender, confirmedNodes); + ritual.publicMaterial = firstRiteDigest; + ritual.status = RitualStatus.FINALIZED; + emit EndRitual(ritualId, ritual.status, ritual.initiator); } }