Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Contract][UST-196] Check if reputation is negative and the epoch last claimed proof < 1 day #601

Merged
merged 12 commits into from
Jan 21, 2025
3 changes: 2 additions & 1 deletion packages/circuits/circuits/dailyClaimProof.circom
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pragma circom 2.1.0;

include "../../../node_modules/@unirep/circuits/circuits/circomlib/circuits/comparators.circom";
include "../../../node_modules/@unirep/circuits/circuits/circomlib/circuits/gates.circom";
include "../../../node_modules/@unirep/circuits/circuits/reputation.circom";

template DailyClaimProof(STATE_TREE_DEPTH, EPOCH_KEY_NONCE_PER_EPOCH, SUM_FIELD_COUNT, FIELD_COUNT, REPL_NONCE_BITS) {
Expand Down Expand Up @@ -59,7 +60,7 @@ template DailyClaimProof(STATE_TREE_DEPTH, EPOCH_KEY_NONCE_PER_EPOCH, SUM_FIELD_
sig_data
);

signal max_rep_check <== GreaterThan(REP_BITS)([max_rep, min_rep]);
signal max_rep_check <== GreaterThan(REP_BITS)([data[1], data[0]]);
max_rep_check === 1;

// Step 2: check daily nullifier
Expand Down
12 changes: 6 additions & 6 deletions packages/circuits/test/proveDailyClaim.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ describe('Prove report identity in Unirep Social-TW', function () {
const identitySecret = user.id.secret
const dailyEpoch = 0
const dailyNullifier = genNullifier(user.id, dailyEpoch)
data[1] = 1

const reputationCircuitInput = genReputationCircuitInput({
identitySecret,
Expand All @@ -59,7 +60,6 @@ describe('Prove report identity in Unirep Social-TW', function () {
stateTreeIndices: leafProof.pathIndices,
stateTreeElements: leafProof.siblings,
data: data,
maxRep: 1,
chainId: chainId,
})

Expand Down Expand Up @@ -89,6 +89,7 @@ describe('Prove report identity in Unirep Social-TW', function () {
const identitySecret = user.id.secret
const dailyEpoch = 0
const dailyNullifier = genNullifier(user.id, dailyEpoch)
data[0] = 2

const reputationCircuitInput = genReputationCircuitInput({
identitySecret,
Expand All @@ -98,7 +99,6 @@ describe('Prove report identity in Unirep Social-TW', function () {
stateTreeIndices: leafProof.pathIndices,
stateTreeElements: leafProof.siblings,
data: data,
minRep: 1,
chainId: chainId,
})

Expand All @@ -115,7 +115,7 @@ describe('Prove report identity in Unirep Social-TW', function () {
expect?.(error).to.be.an.instanceof(ProofGenerationError)
expect?.(error).to.have.property(
'message',
'Error: Assert Failed. Error in template DailyClaimProof_97 line: 63\n'
'Error: Assert Failed. Error in template DailyClaimProof_97 line: 64\n'
)
}
})
Expand All @@ -126,6 +126,7 @@ describe('Prove report identity in Unirep Social-TW', function () {
const dailyNullifier = genNullifier(user.id, dailyEpoch)
const wrongAttesterId =
BigInt(2) ** CircuitConfig.default.ATTESTER_ID_BITS
data[0] = 0

const reputationCircuitInput = genReputationCircuitInput({
identitySecret,
Expand All @@ -135,7 +136,6 @@ describe('Prove report identity in Unirep Social-TW', function () {
stateTreeIndices: leafProof.pathIndices,
stateTreeElements: leafProof.siblings,
data: data,
maxRep: 1,
chainId: chainId,
})

Expand All @@ -154,7 +154,7 @@ describe('Prove report identity in Unirep Social-TW', function () {
expect?.(error).to.be.an.instanceof(ProofGenerationError)
expect?.(error).to.have.property(
'message',
'Error: Assert Failed. Error in template Num2Bits_3 line: 38\nError in template Reputation_93 line: 111\nError in template DailyClaimProof_97 line: 42\n'
'Error: Assert Failed. Error in template Num2Bits_3 line: 38\nError in template Reputation_93 line: 111\nError in template DailyClaimProof_97 line: 43\n'
)
}
})
Expand Down Expand Up @@ -190,7 +190,7 @@ describe('Prove report identity in Unirep Social-TW', function () {
expect?.(error).to.be.an.instanceof(ProofGenerationError)
expect?.(error).to.have.property(
'message',
'Error: Assert Failed. Error in template DailyClaimProof_97 line: 67\n'
'Error: Assert Failed. Error in template DailyClaimProof_97 line: 68\n'
)
}
})
Expand Down
Binary file modified packages/circuits/zksnarkBuild/dailyClaimProof.wasm
Binary file not shown.
Binary file modified packages/circuits/zksnarkBuild/dailyClaimProof.zkey
Binary file not shown.
66 changes: 66 additions & 0 deletions packages/contracts/abi/UnirepApp.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@
"name": "InvalidCommentId",
"type": "error"
},
{ "inputs": [], "name": "InvalidDailyEpoch", "type": "error" },
{ "inputs": [], "name": "InvalidEpoch", "type": "error" },
{ "inputs": [], "name": "NonNegativeReputation", "type": "error" },
{ "inputs": [], "name": "ProofHasUsed", "type": "error" },
{
"inputs": [
Expand Down Expand Up @@ -143,6 +145,19 @@
"name": "Comment",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint48",
"name": "epoch",
"type": "uint48"
}
],
"name": "DailyEpochEnded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
Expand Down Expand Up @@ -249,6 +264,15 @@
"name": "UserSignUp",
"type": "event"
},
{
"inputs": [],
"name": "_updateDailyEpochIfNeeded",
"outputs": [
{ "internalType": "uint48", "name": "epoch", "type": "uint48" }
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
Expand All @@ -260,6 +284,11 @@
"internalType": "uint256[8]",
"name": "proof",
"type": "uint256[8]"
},
{
"internalType": "bytes32",
"name": "identifier",
"type": "bytes32"
}
],
"name": "claimDailyLoginRep",
Expand Down Expand Up @@ -315,6 +344,43 @@
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "dailyCurrentEpoch",
"outputs": [{ "internalType": "uint48", "name": "", "type": "uint48" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "dailyEpochData",
"outputs": [
{
"internalType": "uint48",
"name": "startTimestamp",
"type": "uint48"
},
{
"internalType": "uint48",
"name": "currentEpoch",
"type": "uint48"
},
{
"internalType": "uint48",
"name": "epochLength",
"type": "uint48"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "dailyEpochRemainingTime",
"outputs": [{ "internalType": "uint48", "name": "", "type": "uint48" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
Expand Down
77 changes: 67 additions & 10 deletions packages/contracts/contracts/UnirepApp.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import { Unirep } from "@unirep/contracts/Unirep.sol";
import { EpochKeyVerifierHelper } from "@unirep/contracts/verifierHelpers/EpochKeyVerifierHelper.sol";
import { EpochKeyLiteVerifierHelper } from "@unirep/contracts/verifierHelpers/EpochKeyLiteVerifierHelper.sol";
import { BaseVerifierHelper } from "@unirep/contracts/verifierHelpers/BaseVerifierHelper.sol";
import { VerifierHelperManager } from "./verifierHelpers/VerifierHelperManager.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { DailyClaimVHelper } from "./verifierHelpers/DailyClaimVHelper.sol";

// Uncomment this line to use console.log
// import "hardhat/console.sol";
Expand All @@ -24,6 +24,12 @@ contract UnirepApp is Ownable {
uint256 downVote;
}

struct DailyEpochData {
uint48 startTimestamp;
uint48 currentEpoch;
uint48 epochLength;
}

Unirep public unirep;
IVerifier internal dataVerifier;
EpochKeyVerifierHelper internal epkHelper;
Expand All @@ -38,6 +44,7 @@ contract UnirepApp is Ownable {
mapping(bytes32 => bool) public proofNullifier;

mapping(uint256 => bool) public userRegistry;
DailyEpochData public dailyEpochData;

// Positive Reputation field index in Unirep protocol
uint256 public immutable posRepFieldIndex = 0;
Expand Down Expand Up @@ -78,6 +85,10 @@ contract UnirepApp is Ownable {
uint256 epoch
);

event DailyEpochEnded(
uint48 indexed epoch
);

uint160 immutable attesterId;

event UserSignUp(uint256 indexed hashUserId, bool indexed fromServer);
Expand All @@ -89,6 +100,8 @@ contract UnirepApp is Ownable {
error ArrMismatch();
error InvalidCommentEpochKey(uint256 epochKey);
error InvalidCommentId(uint256 commentId);
error NonNegativeReputation();
error InvalidDailyEpoch();

constructor(
Unirep _unirep,
Expand All @@ -113,6 +126,12 @@ contract UnirepApp is Ownable {
// set verifierHelper manager
verifierHelperManager = _verifierHelperManager;

dailyEpochData = DailyEpochData({
startTimestamp: uint48(block.timestamp),
currentEpoch: 0,
epochLength: 24*60*60
});

// sign up as an attester
attesterId = uint160(msg.sender);
unirep.attesterSignUp(_epochLength);
Expand Down Expand Up @@ -370,12 +389,18 @@ contract UnirepApp is Ownable {
/**
* Claim the daily login reputation
* @param publicSignals: public signals
* @param proof: epoch key proof
* @param proof: daily claim proof
*/
function claimDailyLoginRep(
uint256[] calldata publicSignals,
uint256[8] calldata proof
uint256[8] calldata proof,
bytes32 identifier
) public onlyOwner() {
_updateDailyEpochIfNeeded();

DailyClaimVHelper dailyClaimVHelpers = DailyClaimVHelper(verifierHelperManager.registeredVHelpers(identifier));
DailyClaimVHelper.DailyClaimSignals memory signals = dailyClaimVHelpers.decodeDailyClaimSignals(publicSignals);

// check if proof is used before
bytes32 nullifier = keccak256(abi.encodePacked(publicSignals, proof));
if (proofNullifier[nullifier]) {
Expand All @@ -384,18 +409,26 @@ contract UnirepApp is Ownable {

proofNullifier[nullifier] = true;

EpochKeyVerifierHelper.EpochKeySignals
memory signals = epkHelper.decodeEpochKeySignals(
publicSignals
);

// check the epoch != current epoch (ppl can only post in current aepoch)
// check the epoch != current epoch (ppl can only claim in current epoch)
uint48 epoch = unirep.attesterCurrentEpoch(signals.attesterId);
if (signals.epoch > epoch) {
revert InvalidEpoch();
}

epkHelper.verifyAndCheckCaller(publicSignals, proof);
uint48 dailyEpoch = dailyEpochData.currentEpoch;
if (signals.dailyEpoch != dailyEpoch) {
revert InvalidDailyEpoch();
}

if (signals.minRep > 0 && signals.proveMinRep) {
revert NonNegativeReputation();
}

verifierHelperManager.verifyProof(
publicSignals,
proof,
identifier
);

// attesting on Unirep contract:
unirep.attest(
Expand Down Expand Up @@ -455,4 +488,28 @@ contract UnirepApp is Ownable {

emit ClaimNegRep(signals.epochKey, epoch);
}

function _updateDailyEpochIfNeeded() public returns (uint48 epoch) {
epoch = dailyCurrentEpoch();
uint48 fromEpoch = dailyEpochData.currentEpoch;
if (epoch == fromEpoch) return epoch;

emit DailyEpochEnded(epoch - 1);

dailyEpochData.currentEpoch = epoch;
}

function dailyCurrentEpoch() public view returns (uint48) {
uint48 timestamp = dailyEpochData.startTimestamp;
uint48 epochLength = dailyEpochData.epochLength;
return (uint48(block.timestamp) - timestamp) / epochLength;
}

function dailyEpochRemainingTime() public view returns (uint48) {
uint48 timestamp = dailyEpochData.startTimestamp;
uint48 epochLength = dailyEpochData.epochLength;
uint48 blockTimestamp = uint48(block.timestamp);
uint48 _currentEpoch = (blockTimestamp - timestamp) / epochLength;
return timestamp + (_currentEpoch + 1) * epochLength - blockTimestamp;
}
}
Loading