-
Notifications
You must be signed in to change notification settings - Fork 0
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
Inconsistency in range checks #14
Comments
We talked about this issue with @Y5Yash and it could prevent users from verifying their proofs. #[test]
fn test_range_dos() {
let max_per_level =
BigUint::from(2u32).pow(8 * N_BYTES as u32) - BigUint::one() - BigUint::one();
println!(
"max: {max_per_level:?} 0x{}",
max_per_level.to_str_radix(16)
);
let entries = vec![
Entry::<1>::new("user1".to_string(), [max_per_level]).unwrap(),
Entry::<1>::new("user2".to_string(), [1u32.into()]).unwrap(),
Entry::<1>::new("user3".to_string(), [5u32.into()]).unwrap(),
Entry::<1>::new("user4".to_string(), [9u32.into()]).unwrap(),
];
let mst = MerkleSumTree::<1, N_BYTES>::from_entries(
entries.clone(),
vec![Cryptocurrency {
name: String::from("eth"),
chain: String::from("Ethereum"),
}],
false,
)
.unwrap();
let proof1 = mst.generate_proof(0).unwrap();
let circuit1 = MstInclusionCircuit::<2, 1, N_BYTES>::init(proof1);
let prover1 = MockProver::run(K, &circuit1, circuit1.instances()).unwrap();
prover1.assert_satisfied();
let proof2 = mst.generate_proof(2).unwrap();
let circuit2 = MstInclusionCircuit::<2, 1, N_BYTES>::init(proof2);
let prover2 = MockProver::run(K, &circuit2, circuit2.instances()).unwrap();
assert!(prover2.verify().is_err());
} Node1's balance would be: In the circuit generation script example, there is a function to check for overflow: is_there_risk_of_overflow. But it only checks for overflow relative to the prime field. |
Isn't that sufficient of a check? |
I believe so, also check my comments on #10 |
The focus of the inconsistency is on the fact that calculate_max_root_balance assumes the maximum value that root can take is Steps involved when we are setting the parameters:
Given the inconsistency between the intention while setting the parameters (
Overflow is not an issue here, unexpected constraints leading to unexpected failure of proofs is a possibility. |
I see your point.
Do you think that modifying the direct check inside the circuit such that the range is different from any level would fix this issue? For example in your first illustration, the Node 2 would be constrained in the
Also, can you please better define what is the unintended consequence of this issue? I think that is not a soundness bug, namely a prover cannot generate a valid proof of inclusion of a leaf that is not in the tree. |
I'll give an example. Let's say that the number of levels
Thus, a leaf can have values upto
(Notice how the root balance exceeds Now, for the first user (
The reason for these failures is that the constraint
I believe (under the assumptions I mention below) the correct constraints should be:
Under these rules, our parameters in the above example would be:
It can be seen that with these parameters, a valid tree will be generated. I assume that we want to constrain each user to an upper limit such that each user can independently reach that upper limit simultaneously while the root doesn't exceed the modulus. An alternative to this would be not limit any user but just ensure that the root (i.e. the sum total of all users) doesn't exceed the modulus. Even if that's the case, there remain inconsistencies that I can expand upon (with an example similar to the first comment in the thread and new rules). |
Expanding upon the alternate assumption where only root is to be limited: Let
For the first user (L1[1]), the checks (as per the current protocol) will be as follows:
For the third user (L1[3]), the checks will be:
The proof will fail for all users >= 3. It can be shown that if second user with balance Under these assumption, our rules should be updated to check at each level if the sum of balances of the current and sibling node exceeds |
I follow your point of view, but let me challenge the assumption that underlies the issue that you pointed out. You interpret Instead, when we set In particular, this is how you can calculate the maximum value that the root balance should be equal to. And that script is useful to check whether that value is less than the modulus of the circuit and therefore there's no risk of overflow.
In the example you provided here, the proof generation for the first user will fail and this is the expected behaviour, because this tree state would lead to a root balance that is greater than the limit we expect (see the script). Your suggestion is valid and it is probably a more logic way of handling the range checks, but it's an alternative approach. I don't see any issue with the current implementation. |
Can you also critique my comments on the alternate assumption? If you feel that's not a problem either, we should be good to close this issue. |
I agree with this. However, I would like to add more. In the early stages, we assumed that the custodian, who generates the commitment proof, would also generate inclusion proofs for all users. However, we've realized that this requires substantial resources, which means that the prover will likely start generating inclusion proofs only upon users' requests. Considering this, I think this issue is a valid concern in the current implementation. During the commitment phase, the prover completes the root hash without necessarily detecting potential failures that could occur in the subsequent phase of generating inclusion proofs. Thus, we can conclude that Summa relies on the provers' diligence when generating the commitment, which is the root hash of the MST, to avoid these failures if not generating all inclusion proofs. To assist provers, how about adding a warning when the total balance exceeds 2^{N_BYTES * 8}? What do you think of this approach to mitigate potential faults in the commitment phase? |
@Y5Yash I understand your concern and I think that both this issue and this other issue denote that our implementation of the range checks creates some confusion. I propose here a way to fix both issues trying to limit the sacrifice in terms of performance of the circuit. The simple rule is to let the exchange set The rule must be clear to the prover, which, when generating the commitment, should be warned (as suggested by @sifnoc) that if the sum of the balances is greater than Furthermore, similar to what happens right now, the user should still perform some checks on the verifier logic as there might be combinations of One example of that is the following one, by considering a prime |
I have a similar suggestion with a well defined rule: "No subtree should exceed
|
My initial thought was adding a range check for the user leaf balance in the MST inclusion circuit, but now I think that an assertion on the prover side would be enough. The commitment is unbound because it's not generated in a ZK circuit, so we cannot prevent a malicious prover from overflowing the summation. Therefore, the problem will manifest at the inclusion verification stage. Having a failing proof from a CEX would be enough to expose a dishonest prover. On the other hand, having an assertion in the prover code would be enough to prevent an honest prover from making an accidental mistake. |
Background
The circuit checks for all levels in the tree if the sibling node's balance (and two leaf balances) is less than
m = 2 ** (NBYTES * 8)
. In the first look, this suggests that the max balance for the immediate parent of the leaf nodes at level 1 would be2 * m
and the parent at level 2 would be3 * m
, ..., and the max balance at the root will be(NLEVEL - 1) * m
.The inconsistency
Let's consider two scenarios for a tree with just four leaves. Scenario 1: The user corresponding to Leaf 1 generates a proof. Scenario 2: The user corresponding to Leaf 3 generates a proof. The following diagram represents node balance range checks. A green node represents a direct check in the circuit, and a red node represents forced ranges.
Notice that depending on which leaf is generating the proof, the inner nodes have different ranges of values allowed. For all the cases to be consistent together, the root node can't be more than
2 * m
(because you can find cases in which either of the root's children are checked to have max valuem
).Expected behavior
Root's max balance is
(NLEVEL - 1) * m
as can be inferred from circuits/contracts.The text was updated successfully, but these errors were encountered: