From 7ee628b67e5cc9f7bb9d6ea2b4d4ee7d681a73dc Mon Sep 17 00:00:00 2001 From: James Campbell Date: Wed, 27 Mar 2024 17:32:05 +0100 Subject: [PATCH 1/3] Create initial mvp contract for peer snitching --- contracts/contracts/TACoEvidence.sol | 93 ++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 contracts/contracts/TACoEvidence.sol diff --git a/contracts/contracts/TACoEvidence.sol b/contracts/contracts/TACoEvidence.sol new file mode 100644 index 00000000..621c9cf2 --- /dev/null +++ b/contracts/contracts/TACoEvidence.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Assuming the use of OpenZeppelin contracts for secure cryptographic operations +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "../threshold/ITACoChildApplication.sol"; + +contract TACoEvidence { + using ECDSA for bytes32; + + // Event declarations for logging + event CollectionStarted( + uint32 indexed id, + uint32 indexed startTime, + uint32 endTime, + uint256 nonce + ); + + struct Submission { + address operator; + bytes32 evidence; + } + struct Collection { + uint32 initTimestamp; + uint32 endTimestamp; + uint256 nonce; + mapping(address => Submission[]) submissions; + } + + ITACoChildApplication public immutable application; + + uint256 public submissionWindow = 1 hours; + Collection[] public collections; + + constructor() { + admin = msg.sender; + } + + // Modifier to restrict certain functions to the contract admin + modifier onlyAdmin() { + require(msg.sender == admin, "Not authorized"); + _; + } + + // Function to initiate a new submission period by the admin + function initiateCollection() public onlyAdmin { + uint32 id = uint32(collections.length); + Collection storage collection = collections.push(); + collection.initTimestamp = uint32(block.timestamp); + collection.endTimestamp = collection.initTimestamp + submissionWindow; + collection.nonce = + uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty))) % + 10 ** 18; + emit CollectionStarted( + id, + collection.initTimestamp, + collection.endTimestamp, + collection.nonce + ); + } + + // Function for nodes to submit their online status with evidence + function submitEvidence(uint32 id, bytes[] memory signatures, address[] memory peers) public { + address provider = application.operatorToStakingProvider(msg.sender); + require(application.authorizedStake(provider) > 0, "Not enough authorization"); + + Collection storage collection = collections[id]; + require( + block.timestamp >= collection.initTimestamp && + block.timestamp <= collection.endTimestamp, + "Submission period closed" + ); + require(signatures.length == peers.length, "Mismatched inputs"); + + // Verify each signature + for (uint256 i = 0; i < signatures.length; i++) { + address peerProvider = application.operatorToStakingProvider(peers[i]); + // is this a require?? maybe just a condition check + require( + application.authorizedStake(peerProvider) > 0, + "Not enough authorization for peer evidence" + ); + bytes32 message = keccak256(abi.encodePacked(msg.sender, collection.nonce)) + .toEthSignedMessageHash(); + address signer = message.recover(signatures[i]); + if (signer == peers[i]) { + collection.submissions[msg.sender].push( + Submission({operator: peers[i], evidence: signatures[i]}) + ); + } + } + } +} From cc8df57b3415749c849fac561db0d434ea3066e4 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Fri, 29 Mar 2024 14:12:39 +0100 Subject: [PATCH 2/3] Emit submissions in events rather than storing them --- contracts/contracts/TACoEvidence.sol | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/contracts/contracts/TACoEvidence.sol b/contracts/contracts/TACoEvidence.sol index 621c9cf2..d5f34b4b 100644 --- a/contracts/contracts/TACoEvidence.sol +++ b/contracts/contracts/TACoEvidence.sol @@ -16,15 +16,18 @@ contract TACoEvidence { uint256 nonce ); - struct Submission { - address operator; - bytes32 evidence; - } + event SubmissionSubmitted( + uint32 indexed id, + address indexed operator, + bytes[] evidence, + address[] peers + ); + struct Collection { uint32 initTimestamp; uint32 endTimestamp; uint256 nonce; - mapping(address => Submission[]) submissions; + mapping(address => mapping(address => bool)) submissions; } ITACoChildApplication public immutable application; @@ -71,6 +74,7 @@ contract TACoEvidence { "Submission period closed" ); require(signatures.length == peers.length, "Mismatched inputs"); + emit SubmissionSubmitted(id, msg.sender, signatures, peers); // Verify each signature for (uint256 i = 0; i < signatures.length; i++) { @@ -84,9 +88,7 @@ contract TACoEvidence { .toEthSignedMessageHash(); address signer = message.recover(signatures[i]); if (signer == peers[i]) { - collection.submissions[msg.sender].push( - Submission({operator: peers[i], evidence: signatures[i]}) - ); + collection.submissions[msg.sender][peers[i]] = true; } } } From a89fd9c50b436f1d24310d307676bb724f0b4f3e Mon Sep 17 00:00:00 2001 From: James Campbell Date: Tue, 23 Apr 2024 14:49:30 +0200 Subject: [PATCH 3/3] Address PR suggestions for Ownable, peers and nonce --- contracts/contracts/TACoEvidence.sol | 34 +++++++++------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/contracts/contracts/TACoEvidence.sol b/contracts/contracts/TACoEvidence.sol index d5f34b4b..e10ead63 100644 --- a/contracts/contracts/TACoEvidence.sol +++ b/contracts/contracts/TACoEvidence.sol @@ -4,8 +4,9 @@ pragma solidity ^0.8.0; // Assuming the use of OpenZeppelin contracts for secure cryptographic operations import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "../threshold/ITACoChildApplication.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; -contract TACoEvidence { +contract TACoEvidence is Ownable { using ECDSA for bytes32; // Event declarations for logging @@ -32,28 +33,19 @@ contract TACoEvidence { ITACoChildApplication public immutable application; - uint256 public submissionWindow = 1 hours; + uint256 public immutable submissionWindow = 1 hours; Collection[] public collections; - constructor() { - admin = msg.sender; - } - - // Modifier to restrict certain functions to the contract admin - modifier onlyAdmin() { - require(msg.sender == admin, "Not authorized"); - _; - } + constructor() Ownable(msg.sender) {}; // Function to initiate a new submission period by the admin - function initiateCollection() public onlyAdmin { + function initiateCollection() public onlyOwner { uint32 id = uint32(collections.length); Collection storage collection = collections.push(); collection.initTimestamp = uint32(block.timestamp); collection.endTimestamp = collection.initTimestamp + submissionWindow; collection.nonce = - uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty))) % - 10 ** 18; + uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty))); emit CollectionStarted( id, collection.initTimestamp, @@ -63,7 +55,7 @@ contract TACoEvidence { } // Function for nodes to submit their online status with evidence - function submitEvidence(uint32 id, bytes[] memory signatures, address[] memory peers) public { + function submitEvidence(uint32 id, bytes[] memory signatures) public { address provider = application.operatorToStakingProvider(msg.sender); require(application.authorizedStake(provider) > 0, "Not enough authorization"); @@ -78,17 +70,13 @@ contract TACoEvidence { // Verify each signature for (uint256 i = 0; i < signatures.length; i++) { - address peerProvider = application.operatorToStakingProvider(peers[i]); - // is this a require?? maybe just a condition check - require( - application.authorizedStake(peerProvider) > 0, - "Not enough authorization for peer evidence" - ); + bytes32 message = keccak256(abi.encodePacked(msg.sender, collection.nonce)) .toEthSignedMessageHash(); address signer = message.recover(signatures[i]); - if (signer == peers[i]) { - collection.submissions[msg.sender][peers[i]] = true; + address peerProvider = application.operatorToStakingProvider(signer); + if (application.authorizedStake(peerProvider) > 0) && (peerProvider != provider) { + collection.submissions[msg.sender][peerProvider] = true; } } }