From ec12d2b1ec31b0607f65be49a5679fcae5e3939d Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Fri, 16 Feb 2024 10:43:51 +0000 Subject: [PATCH] fix(topup): check valid new state leaf balance --- circuits/circom/processMessages.circom | 18 ++++++-- core/ts/Poll.ts | 8 ++++ website/versioned_docs/version-v1.x/topup.md | 43 ++++++++++++++++++++ 3 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 website/versioned_docs/version-v1.x/topup.md diff --git a/circuits/circom/processMessages.circom b/circuits/circom/processMessages.circom index 3121b46f47..8858a42f4a 100644 --- a/circuits/circom/processMessages.circom +++ b/circuits/circom/processMessages.circom @@ -401,8 +401,8 @@ template ProcessTopup(stateTreeDepth) { // msgType of topup command is 2 amt <== amount * (msgType - 1); index <== stateTreeIndex * (msgType - 1); - component validCreditBalance = LessEqThan(N_BITS); - // check stateIndex, if invalid index, set index and amount to zero + + // check stateIndex, if invalid index, set index to zero component validStateLeafIndex = LessEqThan(N_BITS); validStateLeafIndex.in[0] <== index; validStateLeafIndex.in[1] <== numSignUps; @@ -419,17 +419,27 @@ template ProcessTopup(stateTreeDepth) { amtMux.c[0] <== 0; amtMux.c[1] <== amt; - // check less than field size + // check new balance is valid signal newCreditBalance; + // add either 0 or the actual amount (if the state index is valid) newCreditBalance <== stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] + amtMux.out; + + // we need to ensure it did not overflow (so previous must be <= new) + component validCreditBalance = SafeLessEqThan(N_BITS); validCreditBalance.in[0] <== stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX]; validCreditBalance.in[1] <== newCreditBalance; + // if the new one is <= the old one, then we have a valid topup + component creditBalanceMux = Mux1(); + creditBalanceMux.s <== validCreditBalance.out; + creditBalanceMux.c[0] <== stateLeaf[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX]; + creditBalanceMux.c[1] <== newCreditBalance; + // update credit voice balance component newStateLeafHasher = Hasher4(); newStateLeafHasher.in[STATE_LEAF_PUB_X_IDX] <== stateLeaf[STATE_LEAF_PUB_X_IDX]; newStateLeafHasher.in[STATE_LEAF_PUB_Y_IDX] <== stateLeaf[STATE_LEAF_PUB_Y_IDX]; - newStateLeafHasher.in[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] <== newCreditBalance; + newStateLeafHasher.in[STATE_LEAF_VOICE_CREDIT_BALANCE_IDX] <== creditBalanceMux.out; newStateLeafHasher.in[STATE_LEAF_TIMESTAMP_IDX] <== stateLeaf[STATE_LEAF_TIMESTAMP_IDX]; component stateLeafPathIndices = QuinGeneratePathIndices(stateTreeDepth); diff --git a/core/ts/Poll.ts b/core/ts/Poll.ts index a63aee8998..25217d9e5f 100644 --- a/core/ts/Poll.ts +++ b/core/ts/Poll.ts @@ -657,6 +657,14 @@ export class Poll implements IPoll { const newStateLeaf = this.stateLeaves[stateIndex].copy(); // update the voice credit balance newStateLeaf.voiceCreditBalance += amount; + + // we should not be in this state as it means we are dealing with very large numbers which will cause problems in the circuits + if (newStateLeaf.voiceCreditBalance > SNARK_FIELD_SIZE) { + throw new Error( + "State leaf voice credit balance exceeds SNARK_FIELD_SIZE. This should not be a state MACI should find itself in, as it will cause complications in the circuits. Rounds should not accept topups with large values.", + ); + } + // save it this.stateLeaves[stateIndex] = newStateLeaf; // update the state tree diff --git a/website/versioned_docs/version-v1.x/topup.md b/website/versioned_docs/version-v1.x/topup.md new file mode 100644 index 0000000000..f5e27f1678 --- /dev/null +++ b/website/versioned_docs/version-v1.x/topup.md @@ -0,0 +1,43 @@ +--- +title: MACI Topup +description: How the Topup feature works +sidebar_label: Topup +sidebar_position: 19 +--- + +# Topup + +MACI v1.1.1 introduced the topup feature. This was developed by [chaosma](https://github.com/chaosma): you can find more information on their [post](https://hackmd.io/@chaosma/rkyPfI7Iq) and the original idea post [here](https://hackmd.io/@ef-zkp/rk6uaQBrI). + +In a nutshell, this feature was added to support some application like quadratic funding, where once a user uses all of their voice credits when voting, they would need to signup again to be able to vote again. This is not ideal, and thus with this feature, a user can topup their voice credits, and continue to vote for a poll/quadratic funding round. + +## How topup messages are processed + +Topup messages are submitted on chain using the same smart contract used for voting, [`Poll`](https://github.com/privacy-scaling-explorations/maci/blob/dev/contracts/contracts/Poll.sol). With the current design, a TopUpCredit token smart contract is required. In this implementation of MACI, there is an example token, which is an ERC20 contract with 1 decimal, of which the coordinator (or MACI's operator) has ownership of. This privileged figure can mint tokens to users (at their discretion) and users will need to deposit such tokens to the Poll contract in order to topup their voice credits. + +:::info +MACI is not concerned with the way of distributing such tokens. The operators of the MACI instance should implement their own way of distributing such tokens, as well as withdrawing them from the Poll contract. For instance, if the operator chooses to use a token with a monetary value, the Poll balance could be withdrawn and sent to fund public goods. +::: + +To submit a topup message, a user will then need to call the TopUpCredit token approve function and approve the Poll contract to spend their tokens. Then, they will need to call the `topup` function in the Poll contract, passing the amount of tokens they want to topup and their state index. + +## Considerations + +### Voice credits + +:::danger +As a MACI operator, you should be aware that voice credits should ideally be small values (< 2 \*\* 32) to avoid overflows or issues in the circuits operations. It is recommended to use a factor to scale down voice credits in the smart contract. +::: + +### Reverse processing + +As with normal vote messages (or key changes), topup messages are processed in [reverse order](https://maci.pse.dev/docs/key-change/#why-are-messages-processed-in-reverse-order), you should be aware that as a user, you will first need to spend your voice credits, then topup. + +Here is an [extract from Chao's post](https://hackmd.io/@chaosma/rkyPfI7Iq#Remarks): + +> With above design, the order of vote type and topup type message matters. MACI process the message queue in reverse order. Suppose the initial > credit balance for any user is 100. Consider the following two scenarios in message queue: +> +> **Case 1** > [topup(balance=350), vote(weight=20,nonce=2), vote(weight=9,nonce=1)] +> +> **case 2** > [vote(weight=20,nonce=2), topup(balance=350), vote(weight=9,nonce=1)] +> The first case, the topup message is processed at last, so the vote(weight=20,nonce=2) will fail because 20 \* 20 > 100. vote(weight=9,nonce=1) is the final result. In the second case, the topup message is processed before the second vote, so vote(weight=20,nonce=2) will invalidate the first vote and become the final result.