diff --git a/.gitignore b/.gitignore index 9d9ed9d..2ed7973 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,10 @@ tests/Output .DS_Store tags/ *.swp + +# Excluding zkOrCa artifacts .artifacts + +# Excluding the circom artifacts +*.r1cs +*.sym \ No newline at end of file diff --git a/configs/zkOrCa/BigLessThan/test_config.json b/configs/zkOrCa/BigLessThan/test_config.json new file mode 100644 index 0000000..febd5f6 --- /dev/null +++ b/configs/zkOrCa/BigLessThan/test_config.json @@ -0,0 +1,25 @@ +{ + "src_path": "../../../tests/vulnerabilities/uo/BigLessThan/src", + "include_path": "../../../tests/libs", + "language": "circom", + "timeout": 600, + "circom_template": + { + "bigComparators.circom": { + "BigLessThan": { + "args": [], + "values" : {}, + "signals": [ + { + "name": "in", + "signal_type": "Input" + }, + { + "name": "out", + "signal_type": "Output" + } + ] + } + } + } +} diff --git a/configs/zkOrCa/BigModOld/test_config.json b/configs/zkOrCa/BigModOld/test_config.json index 7484dd2..b1a150a 100644 --- a/configs/zkOrCa/BigModOld/test_config.json +++ b/configs/zkOrCa/BigModOld/test_config.json @@ -5,7 +5,7 @@ "timeout": 600, "circom_template": { - "BigModOld@circom-pairing@n=8@k=2.circom": { + "BigModOld.circom": { "BigModOld": { "args": [ "n", diff --git a/configs/zkOrCa/EpochKeyLite/test_config.json b/configs/zkOrCa/EpochKeyLite/test_config.json new file mode 100644 index 0000000..4a0b13d --- /dev/null +++ b/configs/zkOrCa/EpochKeyLite/test_config.json @@ -0,0 +1,49 @@ +{ + "src_path": "../../../tests/vulnerabilities/uo/EpochKeyLite/src", + "include_path": "../../../tests/libs", + "language": "circom", + "timeout": 600, + "circom_template": + { + "epochKeyLite.circom": { + "EpochKeyLite": { + "args": ["EPOCH_KEY_NONCE_PER_EPOCH"], + "values" : {"EPOCH_KEY_NONCE_PER_EPOCH": 2}, + "signals": [ + { + "name": "identity_secret", + "signal_type": "Input" + }, + { + "name": "reveal_nonce", + "signal_type": "Input" + }, + { + "name": "attester_id", + "signal_type": "Input" + }, + { + "name": "epoch", + "signal_type": "Input" + }, + { + "name": "nonce", + "signal_type": "Input" + }, + { + "name": "sig_data", + "signal_type": "Input" + }, + { + "name": "control", + "signal_type": "Output" + }, + { + "name": "epoch_key", + "signal_type": "Output" + } + ] + } + } + } +} diff --git a/configs/zkOrCa/I2OSP/test_config.json b/configs/zkOrCa/I2OSP/test_config.json new file mode 100644 index 0000000..d37d0df --- /dev/null +++ b/configs/zkOrCa/I2OSP/test_config.json @@ -0,0 +1,28 @@ +{ + "src_path": "../../../tests/vulnerabilities/uo/I2OSP/src", + "language": "circom", + "timeout": 3600, + "circom_template": + { + "I2OSP.circom": { + "I2OSP": { + "args": [ + "l" + ], + "values" : { + "l": 32 + }, + "signals": [ + { + "name": "in", + "signal_type": "Input" + }, + { + "name": "out", + "signal_type": "Output" + } + ] + } + } + } +} diff --git a/configs/zkOrCa/Num2BitsCheck/test_config.json b/configs/zkOrCa/Num2BitsCheck/test_config.json index da36288..24941e7 100644 --- a/configs/zkOrCa/Num2BitsCheck/test_config.json +++ b/configs/zkOrCa/Num2BitsCheck/test_config.json @@ -12,7 +12,7 @@ "msgBits" ], "values" : { - "msgBits": 32 + "msgBits": 256 }, "signals": [ { diff --git a/configs/zkOrCa/ProveReputation/test_config.json b/configs/zkOrCa/ProveReputation/test_config.json new file mode 100644 index 0000000..cab0123 --- /dev/null +++ b/configs/zkOrCa/ProveReputation/test_config.json @@ -0,0 +1,106 @@ +{ + "src_path": "../../../tests/vulnerabilities/uo/ProveReputation/src", + "include_path": "../../../tests/libs", + "language": "circom", + "timeout": 600, + "circom_template": + { + "proveReputation.circom": { + "ProveReputation": { + "args": [ + "STATE_TREE_DEPTH", + "EPOCH_KEY_NONCE_PER_EPOCH", + "SUM_FIELD_COUNT", + "FIELD_COUNT" + ], + "values" : { + "STATE_TREE_DEPTH": 17, + "EPOCH_KEY_NONCE_PER_EPOCH": 2, + "SUM_FIELD_COUNT": 4, + "FIELD_COUNT": 6 + }, + "signals": [ + { + "name": "identity_secret", + "signal_type": "Input" + }, + { + "name": "state_tree_indexes", + "signal_type": "Input", + "array_constraints": ["STATE_TREE_DEPTH"] + }, + { + "name": "state_tree_elements", + "signal_type": "Input", + "array_constraints": ["STATE_TREE_DEPTH"] + }, + { + "name": "data", + "signal_type": "Input", + "array_constraints": ["FIELD_COUNT"] + }, + { + "name": "prove_graffiti", + "signal_type": "Input" + }, + { + "name": "graffiti_pre_image", + "signal_type": "Input" + }, + { + "name": "reveal_nonce", + "signal_type": "Input" + }, + { + "name": "attester_id", + "signal_type": "Input" + }, + { + "name": "epoch", + "signal_type": "Input" + }, + { + "name": "nonce", + "signal_type": "Input" + }, + { + "name": "min_rep", + "signal_type": "Input" + }, + { + "name": "max_rep", + "signal_type": "Input" + }, + { + "name": "prove_min_rep", + "signal_type": "Input" + }, + { + "name": "prove_max_rep", + "signal_type": "Input" + }, + { + "name": "prove_zero_rep", + "signal_type": "Input" + }, + { + "name": "sig_data", + "signal_type": "Input" + }, + { + "name": "control", + "signal_type": "Output" + }, + { + "name": "epoch_key", + "signal_type": "Output" + }, + { + "name": "state_tree_root", + "signal_type": "Output" + } + ] + } + } + } +} diff --git a/configs/zkOrCa/SignedFpCarryModP_SmallerP/test_config.json b/configs/zkOrCa/SignedFpCarryModP_k1/test_config.json similarity index 93% rename from configs/zkOrCa/SignedFpCarryModP_SmallerP/test_config.json rename to configs/zkOrCa/SignedFpCarryModP_k1/test_config.json index 5cb2666..a49b60e 100644 --- a/configs/zkOrCa/SignedFpCarryModP_SmallerP/test_config.json +++ b/configs/zkOrCa/SignedFpCarryModP_k1/test_config.json @@ -2,7 +2,7 @@ "src_path": "../../../tests/vulnerabilities/uo/SignedFpCarryModP/src", "include_path": "../../../tests/libs", "language": "circom", - "timeout": 3600, + "timeout": 14400, "save_artifacts": false, "debug": true, "circom_template": @@ -16,9 +16,9 @@ "p" ], "values" : { - "n": 5, + "n": 10, "k": 1, - "overflow": 6, + "overflow": 11, "p": [13] }, "signals": [ diff --git a/configs/zkOrCa/SignedFpCarryModP_k2/test_config.json b/configs/zkOrCa/SignedFpCarryModP_k2/test_config.json new file mode 100644 index 0000000..aabe9b3 --- /dev/null +++ b/configs/zkOrCa/SignedFpCarryModP_k2/test_config.json @@ -0,0 +1,44 @@ +{ + "src_path": "../../../tests/vulnerabilities/uo/SignedFpCarryModP/src", + "include_path": "../../../tests/libs", + "language": "circom", + "timeout": 14400, + "save_artifacts": false, + "debug": true, + "circom_template": + { + "fp.circom": { + "SignedFpCarryModP": { + "args": [ + "n", + "k", + "overflow", + "p" + ], + "values" : { + "n": 10, + "k": 2, + "overflow": 11, + "p": [63, 1] + }, + "signals": [ + { + "name": "in", + "signal_type": "Input", + "array_constraints": ["k"] + }, + { + "name": "X", + "signal_type": "Output", + "array_constraints": ["overflow", "n"] + }, + { + "name": "out", + "signal_type": "Output", + "array_constraints": ["k"] + } + ] + } + } + } +} diff --git a/configs/zkOrCa/SignedFpCarryModP_k3/test_config.json b/configs/zkOrCa/SignedFpCarryModP_k3/test_config.json new file mode 100644 index 0000000..d5115fa --- /dev/null +++ b/configs/zkOrCa/SignedFpCarryModP_k3/test_config.json @@ -0,0 +1,44 @@ +{ + "src_path": "../../../tests/vulnerabilities/uo/SignedFpCarryModP/src", + "include_path": "../../../tests/libs", + "language": "circom", + "timeout": 14400, + "save_artifacts": false, + "debug": true, + "circom_template": + { + "fp.circom": { + "SignedFpCarryModP": { + "args": [ + "n", + "k", + "overflow", + "p" + ], + "values" : { + "n": 10, + "k": 3, + "overflow": 11, + "p": [63, 1, 0] + }, + "signals": [ + { + "name": "in", + "signal_type": "Input", + "array_constraints": ["k"] + }, + { + "name": "X", + "signal_type": "Output", + "array_constraints": ["overflow", "n"] + }, + { + "name": "out", + "signal_type": "Output", + "array_constraints": ["k"] + } + ] + } + } + } +} diff --git a/configs/zkOrCa/SignedFpCarryModP_k4/test_config.json b/configs/zkOrCa/SignedFpCarryModP_k4/test_config.json new file mode 100644 index 0000000..0de1212 --- /dev/null +++ b/configs/zkOrCa/SignedFpCarryModP_k4/test_config.json @@ -0,0 +1,44 @@ +{ + "src_path": "../../../tests/vulnerabilities/uo/SignedFpCarryModP/src", + "include_path": "../../../tests/libs", + "language": "circom", + "timeout": 14400, + "save_artifacts": false, + "debug": true, + "circom_template": + { + "fp.circom": { + "SignedFpCarryModP": { + "args": [ + "n", + "k", + "overflow", + "p" + ], + "values" : { + "n": 20, + "k": 4, + "overflow": 21, + "p": [63, 1, 1, 1] + }, + "signals": [ + { + "name": "in", + "signal_type": "Input", + "array_constraints": ["k"] + }, + { + "name": "X", + "signal_type": "Output", + "array_constraints": ["overflow", "n"] + }, + { + "name": "out", + "signal_type": "Output", + "array_constraints": ["k"] + } + ] + } + } + } +} diff --git a/configs/zkOrCa/UpperLessThan/test_config.json b/configs/zkOrCa/UpperLessThan/test_config.json new file mode 100644 index 0000000..f27153a --- /dev/null +++ b/configs/zkOrCa/UpperLessThan/test_config.json @@ -0,0 +1,25 @@ +{ + "src_path": "../../../tests/vulnerabilities/uo/UpperLessThan/src", + "include_path": "../../../tests/libs", + "language": "circom", + "timeout": 600, + "circom_template": + { + "bigComparators.circom": { + "UpperLessThan": { + "args": ["n"], + "values" : {"n":128}, + "signals": [ + { + "name": "in", + "signal_type": "Input" + }, + { + "name": "out", + "signal_type": "Output" + } + ] + } + } + } +} diff --git a/configs/zkOrCa/UserStateTransition/test_config.json b/configs/zkOrCa/UserStateTransition/test_config.json new file mode 100644 index 0000000..76a701f --- /dev/null +++ b/configs/zkOrCa/UserStateTransition/test_config.json @@ -0,0 +1,106 @@ +{ + "src_path": "../../../tests/vulnerabilities/uo/UserStateTransition/src", + "include_path": "../../../tests/libs", + "language": "circom", + "timeout": 600, + "circom_template": + { + "userStateTransition.circom": { + "UserStateTransition": { + "args": [ + "STATE_TREE_DEPTH", + "EPOCH_TREE_DEPTH", + "HISTORY_TREE_DEPTH", + "EPOCH_KEY_NONCE_PER_EPOCH", + "FIELD_COUNT", + "SUM_FIELD_COUNT", + "REPL_NONCE_BITS" + ], + "values" : { + "STATE_TREE_DEPTH": 17, + "EPOCH_TREE_DEPTH": 17, + "HISTORY_TREE_DEPTH": 17, + "EPOCH_KEY_NONCE_PER_EPOCH": 2, + "FIELD_COUNT": 6, + "SUM_FIELD_COUNT": 4, + "REPL_NONCE_BITS": 48 + }, + "signals": [ + { + "name": "from_epoch", + "signal_type": "Input" + }, + { + "name": "to_epoch", + "signal_type": "Input" + }, + { + "name": "identity_secret", + "signal_type": "Input" + }, + { + "name": "state_tree_indexes", + "signal_type": "Input", + "array_constraints": ["STATE_TREE_DEPTH"] + }, + { + "name": "state_tree_elements", + "signal_type": "Input", + "array_constraints": ["STATE_TREE_DEPTH"] + }, + { + "name": "history_tree_root", + "signal_type": "Output" + }, + { + "name": "state_tree_leaf", + "signal_type": "Output" + }, + { + "name": "history_tree_indices", + "signal_type": "Input", + "array_constraints": ["HISTORY_TREE_DEPTH"] + }, + { + "name": "history_tree_elements", + "signal_type": "Input", + "array_constraints": ["HISTORY_TREE_DEPTH"] + }, + { + "name": "attester_id", + "signal_type": "Input" + }, + { + "name": "data", + "signal_type": "Input", + "array_constraints": ["FIELD_COUNT"] + }, + { + "name": "new_data", + "signal_type": "Input", + "array_constraints": ["EPOCH_KEY_NONCE_PER_EPOCH", "FIELD_COUNT"] + }, + { + "name": "epoch_tree_root", + "signal_type": "Input" + }, + { + "name": "epoch_tree_elements", + "signal_type": "Input", + "array_constraints": ["EPOCH_KEY_NONCE_PER_EPOCH", "EPOCH_TREE_DEPTH"] + }, + { + "name": "epoch_tree_indices", + "signal_type": "Input", + "array_constraints": ["EPOCH_KEY_NONCE_PER_EPOCH", "EPOCH_TREE_DEPTH"] + }, + { + "name": "epks", + "signal_type": "Output", + "array_constraints": ["EPOCH_KEY_NONCE_PER_EPOCH"] + } + ] + } + } + } +} diff --git a/tests/vulnerabilities/uo/ArrayXOR/src/main.circom b/tests/vulnerabilities/uo/ArrayXOR/src/main.circom index 6c6e266..9d2555e 100644 --- a/tests/vulnerabilities/uo/ArrayXOR/src/main.circom +++ b/tests/vulnerabilities/uo/ArrayXOR/src/main.circom @@ -1,5 +1,5 @@ pragma circom 2.0.0; -include "ArrayXOR_dummy.circom"; +include "ArrayXOR.circom"; component main{public [a, b]} = ArrayXOR(4); diff --git a/tests/vulnerabilities/uo/BigLessThan/src/bigComparators.circom b/tests/vulnerabilities/uo/BigLessThan/src/bigComparators.circom new file mode 100644 index 0000000..6122b73 --- /dev/null +++ b/tests/vulnerabilities/uo/BigLessThan/src/bigComparators.circom @@ -0,0 +1,93 @@ +pragma circom 2.0.3; + +include "../libs/circomlib/circuits/comparators.circom"; +include "../libs/circomlib/circuits/bitify.circom"; +include "../libs/circomlib/circuits/mux1.circom"; +include "./modulo.circom"; + +//~~ support comparisons of numbers up to the field size + +template UpperLessThan(n) { + signal input in[2]; + signal output out; + + component bits[2]; + for (var x = 0; x < 2; x++) { + bits[x] = Num2Bits(254); + bits[x].in <== in[x]; + } + + component upper_bits[2]; + upper_bits[0] = Bits2Num(n); + upper_bits[1] = Bits2Num(n); + + for (var x = 0; x < n; x++) { + upper_bits[0].in[x] <== bits[0].out[x+(254-n)]; + upper_bits[1].in[x] <== bits[1].out[x+(254-n)]; + } + + component lt = LessThan(n); + lt.in[0] <== upper_bits[0].out; + lt.in[1] <== upper_bits[1].out; + + out <== lt.out; +} + +template BigLessThan() { + signal input in[2]; + signal output out; + + component high_lt; + component low_lt; + + component bits[2]; + for (var x = 0; x < 2; x++) { + bits[x] = Num2Bits(254); + bits[x].in <== in[x]; + } + + component high[2]; + component low[2]; + for (var x = 0; x < 2; x++) { + high[x] = Bits2Num(127); + low[x] = Bits2Num(127); + for (var y = 0; y < 127; y++) { + high[x].in[y] <== bits[x].out[y+127]; + low[x].in[y] <== bits[x].out[y]; + } + } + + high_lt = LessThan(127); + high_lt.in[0] <== high[0].out; + high_lt.in[1] <== high[1].out; + + low_lt = LessThan(127); + low_lt.in[0] <== low[0].out; + low_lt.in[1] <== low[1].out; + + // if high[0] == high[1] then out = low_lt + // else out = high_lt + + component is_high_eq = IsEqual(); + is_high_eq.in[0] <== high[0].out; + is_high_eq.in[1] <== high[1].out; + + component mux = Mux1(); + mux.s <== is_high_eq.out; + + mux.c[0] <== high_lt.out; + mux.c[1] <== low_lt.out; + + out <== mux.out; +} + +template BigGreaterThan() { + signal input in[2]; + signal output out; + + component lt = BigLessThan(); + + lt.in[0] <== in[1]; + lt.in[1] <== in[0]; + lt.out ==> out; +} diff --git a/tests/vulnerabilities/uo/BigLessThan/src/main.circom b/tests/vulnerabilities/uo/BigLessThan/src/main.circom new file mode 100644 index 0000000..8088057 --- /dev/null +++ b/tests/vulnerabilities/uo/BigLessThan/src/main.circom @@ -0,0 +1,5 @@ +pragma circom 2.0.0; + +include "bigComparators.circom"; + +component main{public [in]} = BigLessThan(); diff --git a/tests/vulnerabilities/uo/BigLessThan/src/modulo.circom b/tests/vulnerabilities/uo/BigLessThan/src/modulo.circom new file mode 100644 index 0000000..421b160 --- /dev/null +++ b/tests/vulnerabilities/uo/BigLessThan/src/modulo.circom @@ -0,0 +1,36 @@ +pragma circom 2.0.3; + +include "../libs/circomlib/circuits/bitify.circom"; +include "../libs/circomlib/circuits/comparators.circom"; + +template Modulo() { + signal input divisor; + signal input dividend; + signal output remainder; + + signal output quotient; + + // circom's best practices state that we should avoid using <-- unless + // we know what we are doing. But this is the only way to perform the + // modulo operation. + quotient <-- dividend \ divisor; + remainder <-- dividend % divisor; + + // check that remainder and divisor are both < 2**252 + component remainder_bits = Num2Bits(252); + remainder_bits.in <== remainder; + + component divisor_bits = Num2Bits(252); + divisor_bits.in <== divisor; + + // now we can safely do a range check on remainder + component remainder_lt; + // Range check on remainder + remainder_lt = LessThan(252); + remainder_lt.in[0] <== remainder; + remainder_lt.in[1] <== divisor; + remainder_lt.out === 1; + + // Check equality + dividend === divisor * quotient + remainder; +} diff --git a/tests/vulnerabilities/uo/BigLessThan/test.json b/tests/vulnerabilities/uo/BigLessThan/test.json new file mode 100644 index 0000000..a57b36d --- /dev/null +++ b/tests/vulnerabilities/uo/BigLessThan/test.json @@ -0,0 +1,17 @@ +{ + "id": "BigLessThan", + "source": "UniRep", + "project_url": "https://github.com/Unirep/Unirep/", + "commit": "0985a28c38c8b2e7b7a9e80f43e63179fdd08b89", + "internal": false, + "synthetic": false, + "tests": [ + { + "id": "BigLessThan", + "file": "src/bigComparators.circom", + "template": "BigLessThan", + "main": "src/main.circom", + "description": "BigLessThan template uses Num2Bits(254) which is underconstrained." + } + ] +} diff --git a/tests/vulnerabilities/misc/BigModOld/README.md b/tests/vulnerabilities/uo/BigModOld/README.md similarity index 100% rename from tests/vulnerabilities/misc/BigModOld/README.md rename to tests/vulnerabilities/uo/BigModOld/README.md diff --git a/tests/vulnerabilities/misc/BigModOld/src/BigModOld.circom b/tests/vulnerabilities/uo/BigModOld/src/BigModOld.circom similarity index 100% rename from tests/vulnerabilities/misc/BigModOld/src/BigModOld.circom rename to tests/vulnerabilities/uo/BigModOld/src/BigModOld.circom diff --git a/tests/vulnerabilities/misc/BigModOld/src/main.circom b/tests/vulnerabilities/uo/BigModOld/src/main.circom similarity index 100% rename from tests/vulnerabilities/misc/BigModOld/src/main.circom rename to tests/vulnerabilities/uo/BigModOld/src/main.circom diff --git a/tests/vulnerabilities/misc/BigModOld/test.json b/tests/vulnerabilities/uo/BigModOld/test.json similarity index 100% rename from tests/vulnerabilities/misc/BigModOld/test.json rename to tests/vulnerabilities/uo/BigModOld/test.json diff --git a/tests/vulnerabilities/uo/DivModN/src/main.circom b/tests/vulnerabilities/uo/DivModN/src/main.circom index 7b72bc5..4ad13b0 100644 --- a/tests/vulnerabilities/uo/DivModN/src/main.circom +++ b/tests/vulnerabilities/uo/DivModN/src/main.circom @@ -1,5 +1,5 @@ pragma circom 2.0.0; -include "DivModN_dummy.circom"; +include "DivModN.circom"; component main{public [inp]} = DivModN(13); diff --git a/tests/vulnerabilities/uo/DivModNArray/src/main.circom b/tests/vulnerabilities/uo/DivModNArray/src/main.circom index dd702fd..25cc11c 100644 --- a/tests/vulnerabilities/uo/DivModNArray/src/main.circom +++ b/tests/vulnerabilities/uo/DivModNArray/src/main.circom @@ -1,5 +1,5 @@ pragma circom 2.0.0; -include "DivModNArray_dummy.circom"; +include "DivModNArray.circom"; component main{public [inp]} = DivModNArray(13, 2); diff --git a/tests/vulnerabilities/uo/Edwards2Montgomery/src/main.circom b/tests/vulnerabilities/uo/Edwards2Montgomery/src/main.circom index 88a1833..da2f51a 100644 --- a/tests/vulnerabilities/uo/Edwards2Montgomery/src/main.circom +++ b/tests/vulnerabilities/uo/Edwards2Montgomery/src/main.circom @@ -1,5 +1,5 @@ pragma circom 2.0.0; -include "montgomery_dummy.circom"; +include "montgomery.circom"; component main{public [in]} = Edwards2Montgomery(); diff --git a/tests/vulnerabilities/uo/EpochKeyLite/src/bigComparators.circom b/tests/vulnerabilities/uo/EpochKeyLite/src/bigComparators.circom new file mode 100644 index 0000000..982aadc --- /dev/null +++ b/tests/vulnerabilities/uo/EpochKeyLite/src/bigComparators.circom @@ -0,0 +1,91 @@ +include "../libs/circomlib/circuits/comparators.circom"; +include "../libs/circomlib/circuits/bitify.circom"; +include "../libs/circomlib/circuits/mux1.circom"; +include "./modulo.circom"; + +//~~ support comparisons of numbers up to the field size + +template UpperLessThan(n) { + signal input in[2]; + signal output out; + + component bits[2]; + for (var x = 0; x < 2; x++) { + bits[x] = Num2Bits(254); + bits[x].in <== in[x]; + } + + component upper_bits[2]; + upper_bits[0] = Bits2Num(n); + upper_bits[1] = Bits2Num(n); + + for (var x = 0; x < n; x++) { + upper_bits[0].in[x] <== bits[0].out[x+(254-n)]; + upper_bits[1].in[x] <== bits[1].out[x+(254-n)]; + } + + component lt = LessThan(n); + lt.in[0] <== upper_bits[0].out; + lt.in[1] <== upper_bits[1].out; + + out <== lt.out; +} + +template BigLessThan() { + signal input in[2]; + signal output out; + + component high_lt; + component low_lt; + + component bits[2]; + for (var x = 0; x < 2; x++) { + bits[x] = Num2Bits(254); + bits[x].in <== in[x]; + } + + component high[2]; + component low[2]; + for (var x = 0; x < 2; x++) { + high[x] = Bits2Num(127); + low[x] = Bits2Num(127); + for (var y = 0; y < 127; y++) { + high[x].in[y] <== bits[x].out[y+127]; + low[x].in[y] <== bits[x].out[y]; + } + } + + high_lt = LessThan(127); + high_lt.in[0] <== high[0].out; + high_lt.in[1] <== high[1].out; + + low_lt = LessThan(127); + low_lt.in[0] <== low[0].out; + low_lt.in[1] <== low[1].out; + + // if high[0] == high[1] then out = low_lt + // else out = high_lt + + component is_high_eq = IsEqual(); + is_high_eq.in[0] <== high[0].out; + is_high_eq.in[1] <== high[1].out; + + component mux = Mux1(); + mux.s <== is_high_eq.out; + + mux.c[0] <== high_lt.out; + mux.c[1] <== low_lt.out; + + out <== mux.out; +} + +template BigGreaterThan() { + signal input in[2]; + signal output out; + + component lt = BigLessThan(); + + lt.in[0] <== in[1]; + lt.in[1] <== in[0]; + lt.out ==> out; +} diff --git a/tests/vulnerabilities/uo/EpochKeyLite/src/epochKey.circom b/tests/vulnerabilities/uo/EpochKeyLite/src/epochKey.circom new file mode 100644 index 0000000..d08179d --- /dev/null +++ b/tests/vulnerabilities/uo/EpochKeyLite/src/epochKey.circom @@ -0,0 +1,71 @@ +pragma circom 2.0.0; + +/* + Verify that an epoch key exists in a state tree +*/ + +include "../libs/circomlib/circuits/poseidon.circom"; +include "../libs/circomlib/circuits/bitify.circom"; +include "./incrementalMerkleTree.circom"; +include "./epochKeyLite.circom"; +include "./leafHasher.circom"; + +template EpochKey(STATE_TREE_DEPTH, EPOCH_KEY_NONCE_PER_EPOCH, FIELD_COUNT) { + // Global state tree + signal input state_tree_indexes[STATE_TREE_DEPTH]; + signal input state_tree_elements[STATE_TREE_DEPTH]; + // Global state tree leaf: Identity & user state root + signal input identity_secret; + + signal output epoch_key; + signal output state_tree_root; + + signal input reveal_nonce; + signal input attester_id; + signal input epoch; + signal input nonce; + + signal input data[FIELD_COUNT]; + + // Some arbitrary data to endorse + signal input sig_data; + + /** + * Optionally reveal nonce, epoch, attester_id + **/ + signal output control; + + /* 1. Check if user exists in the Global State Tree */ + + // Compute user state tree root + component leaf_hasher = StateTreeLeaf(FIELD_COUNT); + leaf_hasher.identity_secret <== identity_secret; + leaf_hasher.attester_id <== attester_id; + leaf_hasher.epoch <== epoch; + for (var x = 0; x < FIELD_COUNT; x++) { + leaf_hasher.data[x] <== data[x]; + } + + component merkletree = MerkleTreeInclusionProof(STATE_TREE_DEPTH); + merkletree.leaf <== leaf_hasher.out; + for (var i = 0; i < STATE_TREE_DEPTH; i++) { + merkletree.path_index[i] <== state_tree_indexes[i]; + merkletree.path_elements[i] <== state_tree_elements[i]; + } + state_tree_root <== merkletree.root; + + /* End of check 1 */ + + /* 2. Check epoch key validity */ + + component epoch_key_lite = EpochKeyLite(EPOCH_KEY_NONCE_PER_EPOCH); + epoch_key_lite.identity_secret <== identity_secret; + epoch_key_lite.reveal_nonce <== reveal_nonce; + epoch_key_lite.attester_id <== attester_id; + epoch_key_lite.epoch <== epoch; + epoch_key_lite.nonce <== nonce; + epoch_key_lite.sig_data <== sig_data; + control <== epoch_key_lite.control; + epoch_key <== epoch_key_lite.epoch_key; + /* End of check 2*/ +} diff --git a/tests/vulnerabilities/uo/EpochKeyLite/src/epochKeyLite.circom b/tests/vulnerabilities/uo/EpochKeyLite/src/epochKeyLite.circom new file mode 100644 index 0000000..4a2a814 --- /dev/null +++ b/tests/vulnerabilities/uo/EpochKeyLite/src/epochKeyLite.circom @@ -0,0 +1,59 @@ +pragma circom 2.0.0; + +include "../libs/circomlib/circuits/bitify.circom"; +include "../libs/circomlib/circuits/comparators.circom"; +include "../libs/circomlib/circuits/poseidon.circom"; + +template EpochKeyLite(EPOCH_KEY_NONCE_PER_EPOCH) { + signal input identity_secret; + + signal input reveal_nonce; + signal input attester_id; + signal input epoch; + signal input nonce; + + signal input sig_data; + + signal output control; + signal output epoch_key; + + /** + * Control structure + * 8 bits nonce + * 64 bits epoch + * 160 bits attester_id + * 1 bit reveal nonce + **/ + + // check that reveal_nonce is 0 or 1 + reveal_nonce * (reveal_nonce - 1) === 0; + + // then range check the others + + component attester_id_bits = Num2Bits(254); + attester_id_bits.in <== attester_id; + for (var x = 160; x < 254; x++) { + attester_id_bits.out[x] === 0; + } + + component epoch_bits = Num2Bits(254); + epoch_bits.in <== epoch; + for (var x = 64; x < 254; x++) { + epoch_bits.out[x] === 0; + } + + component nonce_lt = LessThan(8); + nonce_lt.in[0] <== nonce; + nonce_lt.in[1] <== EPOCH_KEY_NONCE_PER_EPOCH; + nonce_lt.out === 1; + + control <== reveal_nonce * 2**232 + attester_id * 2**72 + epoch * 2**8 + reveal_nonce * nonce; + + component epoch_key_hasher = Poseidon(4); + epoch_key_hasher.inputs[0] <== identity_secret; + epoch_key_hasher.inputs[1] <== attester_id; + epoch_key_hasher.inputs[2] <== epoch; + epoch_key_hasher.inputs[3] <== nonce; + + epoch_key <== epoch_key_hasher.out; +} diff --git a/tests/vulnerabilities/uo/EpochKeyLite/src/identity.circom b/tests/vulnerabilities/uo/EpochKeyLite/src/identity.circom new file mode 100644 index 0000000..5b349ce --- /dev/null +++ b/tests/vulnerabilities/uo/EpochKeyLite/src/identity.circom @@ -0,0 +1,33 @@ +include "../libs/circomlib/circuits/poseidon.circom"; + +template IdentitySecret() { + signal input nullifier; + signal input trapdoor; + + signal output out; + + component hasher = Poseidon(2); + hasher.inputs[0] <== nullifier; + hasher.inputs[1] <== trapdoor; + + out <== hasher.out; +} + +template IdentityCommitment() { + signal input nullifier; + signal input trapdoor; + + signal output secret; + signal output out; + + component _secret = IdentitySecret(); + _secret.nullifier <== nullifier; + _secret.trapdoor <== trapdoor; + + secret <== _secret.out; + + component hasher = Poseidon(1); + hasher.inputs[0] <== _secret.out; + + out <== hasher.out; +} diff --git a/tests/vulnerabilities/uo/EpochKeyLite/src/incrementalMerkleTree.circom b/tests/vulnerabilities/uo/EpochKeyLite/src/incrementalMerkleTree.circom new file mode 100644 index 0000000..290f611 --- /dev/null +++ b/tests/vulnerabilities/uo/EpochKeyLite/src/incrementalMerkleTree.circom @@ -0,0 +1,49 @@ +pragma circom 2.0.0; + +// Refer to: +// https://github.com/peppersec/tornado-mixer/blob/master/circuits/merkleTree.circom +// https://github.com/appliedzkp/semaphore/blob/master/circuits/circom/semaphore-base.circom + +include "../libs/circomlib/circuits/mux1.circom"; +include "../libs/circomlib/circuits/poseidon.circom"; +include "../libs/circomlib/circuits/comparators.circom"; + +template MerkleTreeInclusionProof(n_levels) { + signal input leaf; + signal input path_index[n_levels]; + signal input path_elements[n_levels]; + signal output root; + + component hashers[n_levels]; + component mux[n_levels]; + + signal levelHashes[n_levels + 1]; + levelHashes[0] <== leaf; + + // don't allow inclusion proof for 0 leaf + component leaf_zero = IsZero(); + leaf_zero.in <== leaf; + leaf_zero.out === 0; + + for (var i = 0; i < n_levels; i++) { + // Should be 0 or 1 + path_index[i] * (1 - path_index[i]) === 0; + + hashers[i] = Poseidon(2); + mux[i] = MultiMux1(2); + + mux[i].c[0][0] <== levelHashes[i]; + mux[i].c[0][1] <== path_elements[i]; + + mux[i].c[1][0] <== path_elements[i]; + mux[i].c[1][1] <== levelHashes[i]; + + mux[i].s <== path_index[i]; + hashers[i].inputs[0] <== mux[i].out[0]; + hashers[i].inputs[1] <== mux[i].out[1]; + + levelHashes[i + 1] <== hashers[i].out; + } + + root <== levelHashes[n_levels]; +} diff --git a/tests/vulnerabilities/uo/EpochKeyLite/src/leafHasher.circom b/tests/vulnerabilities/uo/EpochKeyLite/src/leafHasher.circom new file mode 100644 index 0000000..392fbd4 --- /dev/null +++ b/tests/vulnerabilities/uo/EpochKeyLite/src/leafHasher.circom @@ -0,0 +1,51 @@ +include "../libs/circomlib/circuits/poseidon.circom"; + +template EpochTreeLeaf(FIELD_COUNT) { + signal input epoch_key; + signal input data[FIELD_COUNT]; + + signal output out; + + component hasher[FIELD_COUNT]; + + for (var x = 0; x < FIELD_COUNT; x++) { + hasher[x] = Poseidon(2); + if (x == 0) { + hasher[x].inputs[0] <== epoch_key; + } else { + hasher[x].inputs[0] <== hasher[x-1].out; + } + hasher[x].inputs[1] <== data[x]; + } + + out <== hasher[FIELD_COUNT-1].out; +} + +template StateTreeLeaf(FIELD_COUNT) { + signal input data[FIELD_COUNT]; + signal input identity_secret; + signal input attester_id; + signal input epoch; + + signal output out; + + component hasher[FIELD_COUNT]; + + for (var x = 0; x < FIELD_COUNT; x++) { + hasher[x] = Poseidon(2); + if (x == 0) { + hasher[x].inputs[0] <== 0; + } else { + hasher[x].inputs[0] <== hasher[x-1].out; + } + hasher[x].inputs[1] <== data[x]; + } + + component final_hasher = Poseidon(4); + final_hasher.inputs[0] <== identity_secret; + final_hasher.inputs[1] <== attester_id; + final_hasher.inputs[2] <== epoch; + final_hasher.inputs[3] <== hasher[FIELD_COUNT-1].out; + + out <== final_hasher.out; +} diff --git a/tests/vulnerabilities/uo/EpochKeyLite/src/main.circom b/tests/vulnerabilities/uo/EpochKeyLite/src/main.circom new file mode 100644 index 0000000..714cff1 --- /dev/null +++ b/tests/vulnerabilities/uo/EpochKeyLite/src/main.circom @@ -0,0 +1,5 @@ +pragma circom 2.0.0; + +include "epochKeyLite.circom"; + +component main{public [identity_secret, reveal_nonce, attester_id, epoch, nonce, sig_data]} = EpochKeyLite(2); diff --git a/tests/vulnerabilities/uo/EpochKeyLite/src/modulo.circom b/tests/vulnerabilities/uo/EpochKeyLite/src/modulo.circom new file mode 100644 index 0000000..595525f --- /dev/null +++ b/tests/vulnerabilities/uo/EpochKeyLite/src/modulo.circom @@ -0,0 +1,42 @@ +pragma circom 2.0.0; + +include "../libs/circomlib/circuits/bitify.circom"; +include "../libs/circomlib/circuits/comparators.circom"; + +template Modulo() { + signal input divisor; + signal input dividend; + signal output remainder; + + signal output quotient; + + // circom's best practices state that we should avoid using <-- unless + // we know what we are doing. But this is the only way to perform the + // modulo operation. + quotient <-- dividend \ divisor; + remainder <-- dividend % divisor; + + // check that remainder and divisor are both < 2**252 + component remainder_bits = Num2Bits(254); + remainder_bits.in <== remainder; + for (var x = 252; x < 254; x++) { + remainder_bits.out[x] === 0; + } + + component divisor_bits = Num2Bits(254); + divisor_bits.in <== divisor; + for (var x = 252; x < 254; x++) { + divisor_bits.out[x] === 0; + } + + // now we can safely do a range check on remainder + component remainder_lt; + // Range check on remainder + remainder_lt = LessThan(252); + remainder_lt.in[0] <== remainder; + remainder_lt.in[1] <== divisor; + remainder_lt.out === 1; + + // Check equality + dividend === divisor * quotient + remainder; +} diff --git a/tests/vulnerabilities/uo/EpochKeyLite/src/proveReputation.circom b/tests/vulnerabilities/uo/EpochKeyLite/src/proveReputation.circom new file mode 100644 index 0000000..a8c8286 --- /dev/null +++ b/tests/vulnerabilities/uo/EpochKeyLite/src/proveReputation.circom @@ -0,0 +1,181 @@ +pragma circom 2.0.0; + +/* + Prove: + 1. if user has a leaf in current state tree + 2. leaf has claimed reputation + 4. output a chosen epoch key +*/ + +include "../libs/circomlib/circuits/comparators.circom"; +include "../libs/circomlib/circuits/gates.circom"; +include "../libs/circomlib/circuits/poseidon.circom"; +include "./incrementalMerkleTree.circom"; +include "./epochKey.circom"; + +template ProveReputation(STATE_TREE_DEPTH, EPOCH_KEY_NONCE_PER_EPOCH, SUM_FIELD_COUNT, FIELD_COUNT) { + signal output epoch_key; + + // Global state tree leaf: Identity & user state root + signal input identity_secret; + // Global state tree + signal input state_tree_indexes[STATE_TREE_DEPTH]; + signal input state_tree_elements[STATE_TREE_DEPTH]; + signal output state_tree_root; + // Attestation by the attester + signal input data[FIELD_COUNT]; + // Graffiti + signal input prove_graffiti; + signal input graffiti_pre_image; + // Epoch key + signal input reveal_nonce; + signal input attester_id; + signal input epoch; + signal input nonce; + // Reputation + signal input min_rep; + signal input max_rep; + signal input prove_min_rep; + signal input prove_max_rep; + signal input prove_zero_rep; + + signal output control[2]; + + signal input sig_data; + + /** + * control[0]: + * 8 bits nonce + * 64 bits epoch + * 160 bits attester_id + * 1 bit reveal_nonce + * control[1]: + * 64 bits min_rep + * 64 bits max_rep + * 1 bit prove min_rep + * 1 bit prove max_rep + * 1 bit prove zero rep + * 1 bit prove_graffiti + **/ + + // check that one bit signal is 0 or 1 + prove_graffiti * (prove_graffiti - 1) === 0; + prove_min_rep * (prove_min_rep - 1) === 0; + prove_max_rep * (prove_max_rep - 1) === 0; + prove_zero_rep * (prove_zero_rep - 1) === 0; + + // then range check the others + component min_rep_bits = Num2Bits(254); + min_rep_bits.in <== min_rep; + for (var x = 64; x < 254; x++) { + min_rep_bits.out[x] === 0; + } + + component max_rep_bits = Num2Bits(254); + max_rep_bits.in <== max_rep; + for (var x = 64; x < 254; x++) { + max_rep_bits.out[x] === 0; + } + + control[1] <== prove_graffiti * 2 ** 131 + prove_zero_rep * 2 ** 130 + prove_max_rep * 2**129 + prove_min_rep * 2**128 + max_rep * 2**64 + min_rep; + + /* 1a. Do the epoch key proof, state tree membership */ + + component epoch_key_prover = EpochKey( + STATE_TREE_DEPTH, + EPOCH_KEY_NONCE_PER_EPOCH, + FIELD_COUNT + ); + epoch_key_prover.identity_secret <== identity_secret; + epoch_key_prover.reveal_nonce <== reveal_nonce; + epoch_key_prover.attester_id <== attester_id; + epoch_key_prover.epoch <== epoch; + epoch_key_prover.nonce <== nonce; + epoch_key_prover.sig_data <== sig_data; + for (var i = 0; i < STATE_TREE_DEPTH; i++) { + epoch_key_prover.state_tree_indexes[i] <== state_tree_indexes[i]; + epoch_key_prover.state_tree_elements[i] <== state_tree_elements[i]; + } + for (var i = 0; i < FIELD_COUNT; i++) { + epoch_key_prover.data[i] <== data[i]; + } + + control[0] <== epoch_key_prover.control; + epoch_key <== epoch_key_prover.epoch_key; + state_tree_root <== epoch_key_prover.state_tree_root; + + /* End of check 1a */ + + /* 2. Check if user has reputation greater than min_rep */ + // if proving min_rep > 0, check if data[0] >= data[1] + min_rep + + component min_rep_check = GreaterEqThan(252); + min_rep_check.in[0] <== data[0]; + min_rep_check.in[1] <== data[1] + min_rep; + + component if_not_prove_min_rep = IsZero(); + if_not_prove_min_rep.in <== prove_min_rep; + + component output_rep_check = OR(); + output_rep_check.a <== if_not_prove_min_rep.out; + output_rep_check.b <== min_rep_check.out; + + output_rep_check.out === 1; + + /* End of check 2 */ + + /* 3. Check if user has reputation less than max_rep */ + // if proving max_rep > 0, check if data[1] >= data[0] + max_rep + + component max_rep_check = GreaterEqThan(252); + max_rep_check.in[0] <== data[1]; + max_rep_check.in[1] <== data[0] + max_rep; + + component if_not_prove_max_rep = IsZero(); + if_not_prove_max_rep.in <== prove_max_rep; + + component max_rep_check_out = OR(); + max_rep_check_out.a <== if_not_prove_max_rep.out; + max_rep_check_out.b <== max_rep_check.out; + + max_rep_check_out.out === 1; + + /* End of check 3 */ + + /* 4. Check if user has net 0 reputation */ + + component zero_rep_check = IsEqual(); + zero_rep_check.in[0] <== data[0]; + zero_rep_check.in[1] <== data[1]; + + component if_not_prove_zero_rep = IsZero(); + if_not_prove_zero_rep.in <== prove_zero_rep; + + component zero_rep_check_out = OR(); + zero_rep_check_out.a <== if_not_prove_zero_rep.out; + zero_rep_check_out.b <== zero_rep_check.out; + + zero_rep_check_out.out === 1; + + /* End of check 4 */ + + /* 3. Prove the graffiti pre-image if needed */ + + component if_not_check_graffiti = IsZero(); + if_not_check_graffiti.in <== prove_graffiti; + + component graffiti_hasher = Poseidon(1); + graffiti_hasher.inputs[0] <== graffiti_pre_image; + + component graffiti_eq = IsEqual(); + graffiti_eq.in[0] <== graffiti_hasher.out; + graffiti_eq.in[1] <== data[SUM_FIELD_COUNT]; + + component check_graffiti = OR(); + check_graffiti.a <== if_not_check_graffiti.out; + check_graffiti.b <== graffiti_eq.out; + + check_graffiti.out === 1; + + /* End of check 3 */ +} diff --git a/tests/vulnerabilities/uo/EpochKeyLite/src/signup.circom b/tests/vulnerabilities/uo/EpochKeyLite/src/signup.circom new file mode 100644 index 0000000..e3a47ec --- /dev/null +++ b/tests/vulnerabilities/uo/EpochKeyLite/src/signup.circom @@ -0,0 +1,34 @@ +pragma circom 2.0.0; + +// Output identity commitment and starting state tree leaf + +include "../libs/circomlib/circuits/poseidon.circom"; +include "./identity.circom"; +include "./leafHasher.circom"; + +template Signup(FIELD_COUNT) { + + signal output identity_commitment; + signal output state_tree_leaf; + + signal input attester_id; + signal input epoch; + + signal input identity_nullifier; + signal input identity_trapdoor; + + component commitment_calc = IdentityCommitment(); + commitment_calc.nullifier <== identity_nullifier; + commitment_calc.trapdoor <== identity_trapdoor; + identity_commitment <== commitment_calc.out; + + component leaf_hasher = StateTreeLeaf(FIELD_COUNT); + leaf_hasher.identity_secret <== commitment_calc.secret; + leaf_hasher.attester_id <== attester_id; + leaf_hasher.epoch <== epoch; + for (var x = 0; x < FIELD_COUNT; x++) { + leaf_hasher.data[x] <== 0; + } + + state_tree_leaf <== leaf_hasher.out; +} diff --git a/tests/vulnerabilities/uo/EpochKeyLite/src/userStateTransition.circom b/tests/vulnerabilities/uo/EpochKeyLite/src/userStateTransition.circom new file mode 100644 index 0000000..2c27281 --- /dev/null +++ b/tests/vulnerabilities/uo/EpochKeyLite/src/userStateTransition.circom @@ -0,0 +1,205 @@ +pragma circom 2.0.0; + +include "../libs/circomlib/circuits/poseidon.circom"; +include "../libs/circomlib/circuits/comparators.circom"; +include "../libs/circomlib/circuits/mux1.circom"; +include "../libs/circomlib/circuits/gates.circom"; +include "./incrementalMerkleTree.circom"; +include "./leafHasher.circom"; +include "./bigComparators.circom"; + +template UserStateTransition( + STATE_TREE_DEPTH, + EPOCH_TREE_DEPTH, + HISTORY_TREE_DEPTH, + EPOCH_KEY_NONCE_PER_EPOCH, + FIELD_COUNT, + SUM_FIELD_COUNT, + REPL_NONCE_BITS +) { + signal input from_epoch; + signal input to_epoch; + + // State tree leaf: Identity & user state root + signal input identity_secret; + // State tree + signal input state_tree_indexes[STATE_TREE_DEPTH]; + signal input state_tree_elements[STATE_TREE_DEPTH]; + + signal output history_tree_root; + signal output state_tree_leaf; + + // History tree + signal input history_tree_indices[HISTORY_TREE_DEPTH]; + signal input history_tree_elements[HISTORY_TREE_DEPTH]; + + // Attester to prove reputation from + signal input attester_id; + + // The starting data in fromEpoch + signal input data[FIELD_COUNT]; + + // prove what we've received in fromEpoch + signal input new_data[EPOCH_KEY_NONCE_PER_EPOCH][FIELD_COUNT]; + + // A root to prove against + signal input epoch_tree_root; + + // the inclusion proofs + signal input epoch_tree_elements[EPOCH_KEY_NONCE_PER_EPOCH][EPOCH_TREE_DEPTH]; + signal input epoch_tree_indices[EPOCH_KEY_NONCE_PER_EPOCH][EPOCH_TREE_DEPTH]; + + signal output epks[EPOCH_KEY_NONCE_PER_EPOCH]; + + // to_epoch will be checked on chain + // from_epoch is implicitly checked by the + // state tree leaf membership proof + component epoch_check = GreaterThan(64); + epoch_check.in[0] <== to_epoch; + epoch_check.in[1] <== from_epoch; + epoch_check.out === 1; + + /* 1. Check if user exists in the Global State Tree */ + + component leaf_hasher = StateTreeLeaf(FIELD_COUNT); + leaf_hasher.identity_secret <== identity_secret; + leaf_hasher.attester_id <== attester_id; + leaf_hasher.epoch <== from_epoch; + for (var x = 0; x < FIELD_COUNT; x++) { + leaf_hasher.data[x] <== data[x]; + } + + component state_merkletree = MerkleTreeInclusionProof(STATE_TREE_DEPTH); + state_merkletree.leaf <== leaf_hasher.out; + for (var i = 0; i < STATE_TREE_DEPTH; i++) { + state_merkletree.path_index[i] <== state_tree_indexes[i]; + state_merkletree.path_elements[i] <== state_tree_elements[i]; + } + + component history_leaf_hasher = Poseidon(2); + history_leaf_hasher.inputs[0] <== state_merkletree.root; + history_leaf_hasher.inputs[1] <== epoch_tree_root; + + component history_merkletree = MerkleTreeInclusionProof(HISTORY_TREE_DEPTH); + history_merkletree.leaf <== history_leaf_hasher.out; + for (var i = 0; i < HISTORY_TREE_DEPTH; i++) { + history_merkletree.path_index[i] <== history_tree_indices[i]; + history_merkletree.path_elements[i] <== history_tree_elements[i]; + } + history_tree_root <== history_merkletree.root; + + /* End of check 1 */ + + /* 2. Verify new reputation for the from epoch */ + + component epoch_key_hashers[EPOCH_KEY_NONCE_PER_EPOCH]; + component leaf_hashers[EPOCH_KEY_NONCE_PER_EPOCH]; + for (var i = 0; i < EPOCH_KEY_NONCE_PER_EPOCH; i++) { + epoch_key_hashers[i] = Poseidon(4); + epoch_key_hashers[i].inputs[0] <== identity_secret; + epoch_key_hashers[i].inputs[1] <== attester_id; + epoch_key_hashers[i].inputs[2] <== from_epoch; + epoch_key_hashers[i].inputs[3] <== i; // nonce + + leaf_hashers[i] = EpochTreeLeaf(FIELD_COUNT); + leaf_hashers[i].epoch_key <== epoch_key_hashers[i].out; + for (var x = 0; x < FIELD_COUNT; x++) { + leaf_hashers[i].data[x] <== new_data[i][x]; + } + } + + // do an inclusion proof for each epoch key + + component epoch_tree_proof[EPOCH_KEY_NONCE_PER_EPOCH]; + component epoch_tree_proof_valid[EPOCH_KEY_NONCE_PER_EPOCH]; + for (var x = 0; x < EPOCH_KEY_NONCE_PER_EPOCH; x++) { + epoch_tree_proof[x] = MerkleTreeInclusionProof(EPOCH_TREE_DEPTH); + epoch_tree_proof[x].leaf <== leaf_hashers[x].out; + for (var y = 0; y < EPOCH_TREE_DEPTH; y++) { + epoch_tree_proof[x].path_index[y] <== epoch_tree_indices[x][y]; + epoch_tree_proof[x].path_elements[y] <== epoch_tree_elements[x][y]; + } + epoch_tree_proof_valid[x] = IsEqual(); + epoch_tree_proof_valid[x].in[0] <== epoch_tree_root; + epoch_tree_proof_valid[x].in[1] <== epoch_tree_proof[x].root; + } + + component epk_out_hashers[EPOCH_KEY_NONCE_PER_EPOCH]; + for (var x = 0; x < EPOCH_KEY_NONCE_PER_EPOCH; x++) { + epk_out_hashers[x] = Poseidon(4); + epk_out_hashers[x].inputs[0] <== identity_secret; + epk_out_hashers[x].inputs[1] <== attester_id; + epk_out_hashers[x].inputs[2] <== from_epoch; + epk_out_hashers[x].inputs[3] <== epoch_tree_proof_valid[x].out * EPOCH_KEY_NONCE_PER_EPOCH + x; // nonce + epks[x] <== epk_out_hashers[x].out; + } + + // if an inclusion proof is not valid the newData must be 0 + component data_check[EPOCH_KEY_NONCE_PER_EPOCH][FIELD_COUNT]; + component proof_invalid[EPOCH_KEY_NONCE_PER_EPOCH]; + for (var x = 0; x < EPOCH_KEY_NONCE_PER_EPOCH; x++) { + proof_invalid[x] = IsZero(); + proof_invalid[x].in <== epoch_tree_proof_valid[x].out; + for (var y = 0; y < FIELD_COUNT; y++) { + data_check[x][y] = ForceEqualIfEnabled(); + data_check[x][y].enabled <== proof_invalid[x].out; + data_check[x][y].in[0] <== 0; + data_check[x][y].in[1] <== new_data[x][y]; + } + } + + /* End of check 2 */ + + /* 3. Calculate the new state tree leaf */ + + signal final_data[EPOCH_KEY_NONCE_PER_EPOCH][FIELD_COUNT]; + + var REPL_FIELD_COUNT = FIELD_COUNT - SUM_FIELD_COUNT; + + component index_check[EPOCH_KEY_NONCE_PER_EPOCH][REPL_FIELD_COUNT]; + component field_select[EPOCH_KEY_NONCE_PER_EPOCH][REPL_FIELD_COUNT]; + + for (var i = 0; i < EPOCH_KEY_NONCE_PER_EPOCH; i++) { + // first combine the sum data + for (var j = 0; j < SUM_FIELD_COUNT; j++) { + if (i == 0) { + final_data[i][j] <== data[j] + new_data[i][j]; + } else { + final_data[i][j] <== final_data[i-1][j] + new_data[i][j]; + } + } + // then combine the replacement data + for (var j = 0; j < REPL_FIELD_COUNT; j++) { + var field_i = SUM_FIELD_COUNT + j; + index_check[i][j] = UpperLessThan(REPL_NONCE_BITS); + index_check[i][j].in[0] <== new_data[i][field_i]; + if (i == 0) { + index_check[i][j].in[1] <== data[field_i]; + } else { + index_check[i][j].in[1] <== final_data[i-1][field_i]; + } + + field_select[i][j] = Mux1(); + field_select[i][j].s <== index_check[i][j].out; + if (i == 0) { + field_select[i][j].c[1] <== data[field_i]; + } else { + field_select[i][j].c[1] <== final_data[i-1][field_i]; + } + field_select[i][j].c[0] <== new_data[i][field_i]; + + final_data[i][field_i] <== field_select[i][j].out; + } + } + + component out_leaf_hasher = StateTreeLeaf(FIELD_COUNT); + out_leaf_hasher.identity_secret <== identity_secret; + out_leaf_hasher.attester_id <== attester_id; + out_leaf_hasher.epoch <== to_epoch; + for (var x = 0; x < FIELD_COUNT; x++) { + out_leaf_hasher.data[x] <== final_data[EPOCH_KEY_NONCE_PER_EPOCH - 1][x]; + } + state_tree_leaf <== out_leaf_hasher.out; + + /* End of check 3 */ +} diff --git a/tests/vulnerabilities/uo/EpochKeyLite/test.json b/tests/vulnerabilities/uo/EpochKeyLite/test.json new file mode 100644 index 0000000..0e73cb4 --- /dev/null +++ b/tests/vulnerabilities/uo/EpochKeyLite/test.json @@ -0,0 +1,17 @@ +{ + "id": "EpochKeyLite", + "source": "UniRep", + "project_url": "https://github.com/Unirep/Unirep/", + "commit": "0985a28c38c8b2e7b7a9e80f43e63179fdd08b89", + "internal": false, + "synthetic": false, + "tests": [ + { + "id": "EpochKeyLite", + "file": "src/epochKeyLite.circom", + "template": "EpochKeyLite", + "main": "src/main.circom", + "description": "EpochKeyLite template is missing range check, making output underconstrained." + } + ] +} diff --git a/tests/vulnerabilities/uo/I2OSP/src/I2OSP.circom b/tests/vulnerabilities/uo/I2OSP/src/I2OSP.circom new file mode 100644 index 0000000..f50b989 --- /dev/null +++ b/tests/vulnerabilities/uo/I2OSP/src/I2OSP.circom @@ -0,0 +1,23 @@ +pragma circom 2.0.3; + +template I2OSP(l) { + signal input in; + signal output out[l]; + + var value = in; + for (var i = l - 1; i >= 0; i--) { + out[i] <-- value & 255; + value = value \ 256; + } + + signal acc[l]; + for (var i = 0; i < l; i++) { + if (i == 0) { + acc[i] <== out[i]; + } else { + acc[i] <== 256 * acc[i-1] + out[i]; + } + } + + acc[l-1] === in; +} \ No newline at end of file diff --git a/tests/vulnerabilities/uo/I2OSP/src/main.circom b/tests/vulnerabilities/uo/I2OSP/src/main.circom new file mode 100644 index 0000000..e18d96b --- /dev/null +++ b/tests/vulnerabilities/uo/I2OSP/src/main.circom @@ -0,0 +1,5 @@ +pragma circom 2.0.0; + +include "I2OSP.circom"; + +component main{public [in]} = I2OSP(32); diff --git a/tests/vulnerabilities/uo/I2OSP/test.json b/tests/vulnerabilities/uo/I2OSP/test.json new file mode 100644 index 0000000..99d73b2 --- /dev/null +++ b/tests/vulnerabilities/uo/I2OSP/test.json @@ -0,0 +1,17 @@ +{ + "id": "IO2SP", + "source": "Telepathy", + "project_url": "https://www.notion.so/veridise/Audit-Issue-Tracker-Internal-1edee4daa6904548ad0e1d36d262d4c6?p=3e02b7914102488fb63b247619e0dc09&pm=s", + "commit": "", + "internal": true, + "synthetic": false, + "tests": [ + { + "id": "IO2SP", + "file": "src/IO2SP.circom", + "template": "IO2SP", + "main": "src/main.circom", + "description": "Output signal `out` is underconstrained for large values of l." + } + ] +} diff --git a/tests/vulnerabilities/uo/LessThanBounded/src/lt.circom b/tests/vulnerabilities/uo/LessThanBounded/src/lt.circom index ad2c2eb..765c0b3 100644 --- a/tests/vulnerabilities/uo/LessThanBounded/src/lt.circom +++ b/tests/vulnerabilities/uo/LessThanBounded/src/lt.circom @@ -1,7 +1,5 @@ pragma circom 2.0.0; -include "../libs/circomlib/circuits/bitify.circom"; - template LessThanPower(base) { signal input in; signal output out; diff --git a/tests/vulnerabilities/uo/LessThanBounded/src/lt_dummy.circom b/tests/vulnerabilities/uo/LessThanBounded/src/lt_dummy.circom index 845294e..f90dc81 100644 --- a/tests/vulnerabilities/uo/LessThanBounded/src/lt_dummy.circom +++ b/tests/vulnerabilities/uo/LessThanBounded/src/lt_dummy.circom @@ -1,7 +1,5 @@ pragma circom 2.0.0; -include "../libs/circomlib/circuits/bitify.circom"; - template LessThanPowerDummy(base) { signal input in; signal output out; diff --git a/tests/vulnerabilities/uo/LessThanBounded/src/main.circom b/tests/vulnerabilities/uo/LessThanBounded/src/main.circom index fc83d9e..d2b9077 100644 --- a/tests/vulnerabilities/uo/LessThanBounded/src/main.circom +++ b/tests/vulnerabilities/uo/LessThanBounded/src/main.circom @@ -1,5 +1,5 @@ pragma circom 2.0.0; -include "lt_dummy.circom"; +include "lt.circom"; component main{public [in]} = LessThanBounded(8); diff --git a/tests/vulnerabilities/uo/LessThanPower/src/lt.circom b/tests/vulnerabilities/uo/LessThanPower/src/lt.circom index ad2c2eb..765c0b3 100644 --- a/tests/vulnerabilities/uo/LessThanPower/src/lt.circom +++ b/tests/vulnerabilities/uo/LessThanPower/src/lt.circom @@ -1,7 +1,5 @@ pragma circom 2.0.0; -include "../libs/circomlib/circuits/bitify.circom"; - template LessThanPower(base) { signal input in; signal output out; diff --git a/tests/vulnerabilities/uo/LessThanPower/src/lt_dummy.circom b/tests/vulnerabilities/uo/LessThanPower/src/lt_dummy.circom index 845294e..f90dc81 100644 --- a/tests/vulnerabilities/uo/LessThanPower/src/lt_dummy.circom +++ b/tests/vulnerabilities/uo/LessThanPower/src/lt_dummy.circom @@ -1,7 +1,5 @@ pragma circom 2.0.0; -include "../libs/circomlib/circuits/bitify.circom"; - template LessThanPowerDummy(base) { signal input in; signal output out; diff --git a/tests/vulnerabilities/uo/LessThanPower/src/main.circom b/tests/vulnerabilities/uo/LessThanPower/src/main.circom index d949852..36c0c52 100644 --- a/tests/vulnerabilities/uo/LessThanPower/src/main.circom +++ b/tests/vulnerabilities/uo/LessThanPower/src/main.circom @@ -1,5 +1,5 @@ pragma circom 2.0.0; -include "lt_dummy.circom"; +include "lt.circom"; -component main{public [in]} = LessThanPowerDummy(2); +component main{public [in]} = LessThanPower(2); diff --git a/tests/vulnerabilities/uo/Montgomery2Edwards/src/main.circom b/tests/vulnerabilities/uo/Montgomery2Edwards/src/main.circom index 5be3c15..796d39d 100644 --- a/tests/vulnerabilities/uo/Montgomery2Edwards/src/main.circom +++ b/tests/vulnerabilities/uo/Montgomery2Edwards/src/main.circom @@ -1,5 +1,5 @@ pragma circom 2.0.0; -include "montgomery_dummy.circom"; +include "montgomery.circom"; component main{public [in]} = Montgomery2Edwards(); diff --git a/tests/vulnerabilities/uo/MontgomeryDouble/src/main.circom b/tests/vulnerabilities/uo/MontgomeryDouble/src/main.circom index 695f7fd..cc2c5b3 100644 --- a/tests/vulnerabilities/uo/MontgomeryDouble/src/main.circom +++ b/tests/vulnerabilities/uo/MontgomeryDouble/src/main.circom @@ -1,5 +1,5 @@ pragma circom 2.0.0; -include "montgomery_dummy.circom"; +include "montgomery.circom"; component main{public [in]} = MontgomeryDouble(); diff --git a/tests/vulnerabilities/uo/Num2Bits/src/main.circom b/tests/vulnerabilities/uo/Num2Bits/src/main.circom index cc30035..efcf736 100644 --- a/tests/vulnerabilities/uo/Num2Bits/src/main.circom +++ b/tests/vulnerabilities/uo/Num2Bits/src/main.circom @@ -1,5 +1,5 @@ pragma circom 2.0.0; -include "Num2Bits_dummy.circom"; +include "Num2Bits.circom"; component main{public [in]} = Num2Bits(255); diff --git a/tests/vulnerabilities/uo/ProveReputation/src/bigComparators.circom b/tests/vulnerabilities/uo/ProveReputation/src/bigComparators.circom new file mode 100644 index 0000000..982aadc --- /dev/null +++ b/tests/vulnerabilities/uo/ProveReputation/src/bigComparators.circom @@ -0,0 +1,91 @@ +include "../libs/circomlib/circuits/comparators.circom"; +include "../libs/circomlib/circuits/bitify.circom"; +include "../libs/circomlib/circuits/mux1.circom"; +include "./modulo.circom"; + +//~~ support comparisons of numbers up to the field size + +template UpperLessThan(n) { + signal input in[2]; + signal output out; + + component bits[2]; + for (var x = 0; x < 2; x++) { + bits[x] = Num2Bits(254); + bits[x].in <== in[x]; + } + + component upper_bits[2]; + upper_bits[0] = Bits2Num(n); + upper_bits[1] = Bits2Num(n); + + for (var x = 0; x < n; x++) { + upper_bits[0].in[x] <== bits[0].out[x+(254-n)]; + upper_bits[1].in[x] <== bits[1].out[x+(254-n)]; + } + + component lt = LessThan(n); + lt.in[0] <== upper_bits[0].out; + lt.in[1] <== upper_bits[1].out; + + out <== lt.out; +} + +template BigLessThan() { + signal input in[2]; + signal output out; + + component high_lt; + component low_lt; + + component bits[2]; + for (var x = 0; x < 2; x++) { + bits[x] = Num2Bits(254); + bits[x].in <== in[x]; + } + + component high[2]; + component low[2]; + for (var x = 0; x < 2; x++) { + high[x] = Bits2Num(127); + low[x] = Bits2Num(127); + for (var y = 0; y < 127; y++) { + high[x].in[y] <== bits[x].out[y+127]; + low[x].in[y] <== bits[x].out[y]; + } + } + + high_lt = LessThan(127); + high_lt.in[0] <== high[0].out; + high_lt.in[1] <== high[1].out; + + low_lt = LessThan(127); + low_lt.in[0] <== low[0].out; + low_lt.in[1] <== low[1].out; + + // if high[0] == high[1] then out = low_lt + // else out = high_lt + + component is_high_eq = IsEqual(); + is_high_eq.in[0] <== high[0].out; + is_high_eq.in[1] <== high[1].out; + + component mux = Mux1(); + mux.s <== is_high_eq.out; + + mux.c[0] <== high_lt.out; + mux.c[1] <== low_lt.out; + + out <== mux.out; +} + +template BigGreaterThan() { + signal input in[2]; + signal output out; + + component lt = BigLessThan(); + + lt.in[0] <== in[1]; + lt.in[1] <== in[0]; + lt.out ==> out; +} diff --git a/tests/vulnerabilities/uo/ProveReputation/src/epochKey.circom b/tests/vulnerabilities/uo/ProveReputation/src/epochKey.circom new file mode 100644 index 0000000..d08179d --- /dev/null +++ b/tests/vulnerabilities/uo/ProveReputation/src/epochKey.circom @@ -0,0 +1,71 @@ +pragma circom 2.0.0; + +/* + Verify that an epoch key exists in a state tree +*/ + +include "../libs/circomlib/circuits/poseidon.circom"; +include "../libs/circomlib/circuits/bitify.circom"; +include "./incrementalMerkleTree.circom"; +include "./epochKeyLite.circom"; +include "./leafHasher.circom"; + +template EpochKey(STATE_TREE_DEPTH, EPOCH_KEY_NONCE_PER_EPOCH, FIELD_COUNT) { + // Global state tree + signal input state_tree_indexes[STATE_TREE_DEPTH]; + signal input state_tree_elements[STATE_TREE_DEPTH]; + // Global state tree leaf: Identity & user state root + signal input identity_secret; + + signal output epoch_key; + signal output state_tree_root; + + signal input reveal_nonce; + signal input attester_id; + signal input epoch; + signal input nonce; + + signal input data[FIELD_COUNT]; + + // Some arbitrary data to endorse + signal input sig_data; + + /** + * Optionally reveal nonce, epoch, attester_id + **/ + signal output control; + + /* 1. Check if user exists in the Global State Tree */ + + // Compute user state tree root + component leaf_hasher = StateTreeLeaf(FIELD_COUNT); + leaf_hasher.identity_secret <== identity_secret; + leaf_hasher.attester_id <== attester_id; + leaf_hasher.epoch <== epoch; + for (var x = 0; x < FIELD_COUNT; x++) { + leaf_hasher.data[x] <== data[x]; + } + + component merkletree = MerkleTreeInclusionProof(STATE_TREE_DEPTH); + merkletree.leaf <== leaf_hasher.out; + for (var i = 0; i < STATE_TREE_DEPTH; i++) { + merkletree.path_index[i] <== state_tree_indexes[i]; + merkletree.path_elements[i] <== state_tree_elements[i]; + } + state_tree_root <== merkletree.root; + + /* End of check 1 */ + + /* 2. Check epoch key validity */ + + component epoch_key_lite = EpochKeyLite(EPOCH_KEY_NONCE_PER_EPOCH); + epoch_key_lite.identity_secret <== identity_secret; + epoch_key_lite.reveal_nonce <== reveal_nonce; + epoch_key_lite.attester_id <== attester_id; + epoch_key_lite.epoch <== epoch; + epoch_key_lite.nonce <== nonce; + epoch_key_lite.sig_data <== sig_data; + control <== epoch_key_lite.control; + epoch_key <== epoch_key_lite.epoch_key; + /* End of check 2*/ +} diff --git a/tests/vulnerabilities/uo/ProveReputation/src/epochKeyLite.circom b/tests/vulnerabilities/uo/ProveReputation/src/epochKeyLite.circom new file mode 100644 index 0000000..4a2a814 --- /dev/null +++ b/tests/vulnerabilities/uo/ProveReputation/src/epochKeyLite.circom @@ -0,0 +1,59 @@ +pragma circom 2.0.0; + +include "../libs/circomlib/circuits/bitify.circom"; +include "../libs/circomlib/circuits/comparators.circom"; +include "../libs/circomlib/circuits/poseidon.circom"; + +template EpochKeyLite(EPOCH_KEY_NONCE_PER_EPOCH) { + signal input identity_secret; + + signal input reveal_nonce; + signal input attester_id; + signal input epoch; + signal input nonce; + + signal input sig_data; + + signal output control; + signal output epoch_key; + + /** + * Control structure + * 8 bits nonce + * 64 bits epoch + * 160 bits attester_id + * 1 bit reveal nonce + **/ + + // check that reveal_nonce is 0 or 1 + reveal_nonce * (reveal_nonce - 1) === 0; + + // then range check the others + + component attester_id_bits = Num2Bits(254); + attester_id_bits.in <== attester_id; + for (var x = 160; x < 254; x++) { + attester_id_bits.out[x] === 0; + } + + component epoch_bits = Num2Bits(254); + epoch_bits.in <== epoch; + for (var x = 64; x < 254; x++) { + epoch_bits.out[x] === 0; + } + + component nonce_lt = LessThan(8); + nonce_lt.in[0] <== nonce; + nonce_lt.in[1] <== EPOCH_KEY_NONCE_PER_EPOCH; + nonce_lt.out === 1; + + control <== reveal_nonce * 2**232 + attester_id * 2**72 + epoch * 2**8 + reveal_nonce * nonce; + + component epoch_key_hasher = Poseidon(4); + epoch_key_hasher.inputs[0] <== identity_secret; + epoch_key_hasher.inputs[1] <== attester_id; + epoch_key_hasher.inputs[2] <== epoch; + epoch_key_hasher.inputs[3] <== nonce; + + epoch_key <== epoch_key_hasher.out; +} diff --git a/tests/vulnerabilities/uo/ProveReputation/src/identity.circom b/tests/vulnerabilities/uo/ProveReputation/src/identity.circom new file mode 100644 index 0000000..5b349ce --- /dev/null +++ b/tests/vulnerabilities/uo/ProveReputation/src/identity.circom @@ -0,0 +1,33 @@ +include "../libs/circomlib/circuits/poseidon.circom"; + +template IdentitySecret() { + signal input nullifier; + signal input trapdoor; + + signal output out; + + component hasher = Poseidon(2); + hasher.inputs[0] <== nullifier; + hasher.inputs[1] <== trapdoor; + + out <== hasher.out; +} + +template IdentityCommitment() { + signal input nullifier; + signal input trapdoor; + + signal output secret; + signal output out; + + component _secret = IdentitySecret(); + _secret.nullifier <== nullifier; + _secret.trapdoor <== trapdoor; + + secret <== _secret.out; + + component hasher = Poseidon(1); + hasher.inputs[0] <== _secret.out; + + out <== hasher.out; +} diff --git a/tests/vulnerabilities/uo/ProveReputation/src/incrementalMerkleTree.circom b/tests/vulnerabilities/uo/ProveReputation/src/incrementalMerkleTree.circom new file mode 100644 index 0000000..290f611 --- /dev/null +++ b/tests/vulnerabilities/uo/ProveReputation/src/incrementalMerkleTree.circom @@ -0,0 +1,49 @@ +pragma circom 2.0.0; + +// Refer to: +// https://github.com/peppersec/tornado-mixer/blob/master/circuits/merkleTree.circom +// https://github.com/appliedzkp/semaphore/blob/master/circuits/circom/semaphore-base.circom + +include "../libs/circomlib/circuits/mux1.circom"; +include "../libs/circomlib/circuits/poseidon.circom"; +include "../libs/circomlib/circuits/comparators.circom"; + +template MerkleTreeInclusionProof(n_levels) { + signal input leaf; + signal input path_index[n_levels]; + signal input path_elements[n_levels]; + signal output root; + + component hashers[n_levels]; + component mux[n_levels]; + + signal levelHashes[n_levels + 1]; + levelHashes[0] <== leaf; + + // don't allow inclusion proof for 0 leaf + component leaf_zero = IsZero(); + leaf_zero.in <== leaf; + leaf_zero.out === 0; + + for (var i = 0; i < n_levels; i++) { + // Should be 0 or 1 + path_index[i] * (1 - path_index[i]) === 0; + + hashers[i] = Poseidon(2); + mux[i] = MultiMux1(2); + + mux[i].c[0][0] <== levelHashes[i]; + mux[i].c[0][1] <== path_elements[i]; + + mux[i].c[1][0] <== path_elements[i]; + mux[i].c[1][1] <== levelHashes[i]; + + mux[i].s <== path_index[i]; + hashers[i].inputs[0] <== mux[i].out[0]; + hashers[i].inputs[1] <== mux[i].out[1]; + + levelHashes[i + 1] <== hashers[i].out; + } + + root <== levelHashes[n_levels]; +} diff --git a/tests/vulnerabilities/uo/ProveReputation/src/leafHasher.circom b/tests/vulnerabilities/uo/ProveReputation/src/leafHasher.circom new file mode 100644 index 0000000..392fbd4 --- /dev/null +++ b/tests/vulnerabilities/uo/ProveReputation/src/leafHasher.circom @@ -0,0 +1,51 @@ +include "../libs/circomlib/circuits/poseidon.circom"; + +template EpochTreeLeaf(FIELD_COUNT) { + signal input epoch_key; + signal input data[FIELD_COUNT]; + + signal output out; + + component hasher[FIELD_COUNT]; + + for (var x = 0; x < FIELD_COUNT; x++) { + hasher[x] = Poseidon(2); + if (x == 0) { + hasher[x].inputs[0] <== epoch_key; + } else { + hasher[x].inputs[0] <== hasher[x-1].out; + } + hasher[x].inputs[1] <== data[x]; + } + + out <== hasher[FIELD_COUNT-1].out; +} + +template StateTreeLeaf(FIELD_COUNT) { + signal input data[FIELD_COUNT]; + signal input identity_secret; + signal input attester_id; + signal input epoch; + + signal output out; + + component hasher[FIELD_COUNT]; + + for (var x = 0; x < FIELD_COUNT; x++) { + hasher[x] = Poseidon(2); + if (x == 0) { + hasher[x].inputs[0] <== 0; + } else { + hasher[x].inputs[0] <== hasher[x-1].out; + } + hasher[x].inputs[1] <== data[x]; + } + + component final_hasher = Poseidon(4); + final_hasher.inputs[0] <== identity_secret; + final_hasher.inputs[1] <== attester_id; + final_hasher.inputs[2] <== epoch; + final_hasher.inputs[3] <== hasher[FIELD_COUNT-1].out; + + out <== final_hasher.out; +} diff --git a/tests/vulnerabilities/uo/ProveReputation/src/main.circom b/tests/vulnerabilities/uo/ProveReputation/src/main.circom new file mode 100644 index 0000000..77d9b28 --- /dev/null +++ b/tests/vulnerabilities/uo/ProveReputation/src/main.circom @@ -0,0 +1,5 @@ +pragma circom 2.0.0; + +include "proveReputation.circom"; + +component main = ProveReputation(17, 2, 4, 6); diff --git a/tests/vulnerabilities/uo/ProveReputation/src/modulo.circom b/tests/vulnerabilities/uo/ProveReputation/src/modulo.circom new file mode 100644 index 0000000..595525f --- /dev/null +++ b/tests/vulnerabilities/uo/ProveReputation/src/modulo.circom @@ -0,0 +1,42 @@ +pragma circom 2.0.0; + +include "../libs/circomlib/circuits/bitify.circom"; +include "../libs/circomlib/circuits/comparators.circom"; + +template Modulo() { + signal input divisor; + signal input dividend; + signal output remainder; + + signal output quotient; + + // circom's best practices state that we should avoid using <-- unless + // we know what we are doing. But this is the only way to perform the + // modulo operation. + quotient <-- dividend \ divisor; + remainder <-- dividend % divisor; + + // check that remainder and divisor are both < 2**252 + component remainder_bits = Num2Bits(254); + remainder_bits.in <== remainder; + for (var x = 252; x < 254; x++) { + remainder_bits.out[x] === 0; + } + + component divisor_bits = Num2Bits(254); + divisor_bits.in <== divisor; + for (var x = 252; x < 254; x++) { + divisor_bits.out[x] === 0; + } + + // now we can safely do a range check on remainder + component remainder_lt; + // Range check on remainder + remainder_lt = LessThan(252); + remainder_lt.in[0] <== remainder; + remainder_lt.in[1] <== divisor; + remainder_lt.out === 1; + + // Check equality + dividend === divisor * quotient + remainder; +} diff --git a/tests/vulnerabilities/uo/ProveReputation/src/proveReputation.circom b/tests/vulnerabilities/uo/ProveReputation/src/proveReputation.circom new file mode 100644 index 0000000..a8c8286 --- /dev/null +++ b/tests/vulnerabilities/uo/ProveReputation/src/proveReputation.circom @@ -0,0 +1,181 @@ +pragma circom 2.0.0; + +/* + Prove: + 1. if user has a leaf in current state tree + 2. leaf has claimed reputation + 4. output a chosen epoch key +*/ + +include "../libs/circomlib/circuits/comparators.circom"; +include "../libs/circomlib/circuits/gates.circom"; +include "../libs/circomlib/circuits/poseidon.circom"; +include "./incrementalMerkleTree.circom"; +include "./epochKey.circom"; + +template ProveReputation(STATE_TREE_DEPTH, EPOCH_KEY_NONCE_PER_EPOCH, SUM_FIELD_COUNT, FIELD_COUNT) { + signal output epoch_key; + + // Global state tree leaf: Identity & user state root + signal input identity_secret; + // Global state tree + signal input state_tree_indexes[STATE_TREE_DEPTH]; + signal input state_tree_elements[STATE_TREE_DEPTH]; + signal output state_tree_root; + // Attestation by the attester + signal input data[FIELD_COUNT]; + // Graffiti + signal input prove_graffiti; + signal input graffiti_pre_image; + // Epoch key + signal input reveal_nonce; + signal input attester_id; + signal input epoch; + signal input nonce; + // Reputation + signal input min_rep; + signal input max_rep; + signal input prove_min_rep; + signal input prove_max_rep; + signal input prove_zero_rep; + + signal output control[2]; + + signal input sig_data; + + /** + * control[0]: + * 8 bits nonce + * 64 bits epoch + * 160 bits attester_id + * 1 bit reveal_nonce + * control[1]: + * 64 bits min_rep + * 64 bits max_rep + * 1 bit prove min_rep + * 1 bit prove max_rep + * 1 bit prove zero rep + * 1 bit prove_graffiti + **/ + + // check that one bit signal is 0 or 1 + prove_graffiti * (prove_graffiti - 1) === 0; + prove_min_rep * (prove_min_rep - 1) === 0; + prove_max_rep * (prove_max_rep - 1) === 0; + prove_zero_rep * (prove_zero_rep - 1) === 0; + + // then range check the others + component min_rep_bits = Num2Bits(254); + min_rep_bits.in <== min_rep; + for (var x = 64; x < 254; x++) { + min_rep_bits.out[x] === 0; + } + + component max_rep_bits = Num2Bits(254); + max_rep_bits.in <== max_rep; + for (var x = 64; x < 254; x++) { + max_rep_bits.out[x] === 0; + } + + control[1] <== prove_graffiti * 2 ** 131 + prove_zero_rep * 2 ** 130 + prove_max_rep * 2**129 + prove_min_rep * 2**128 + max_rep * 2**64 + min_rep; + + /* 1a. Do the epoch key proof, state tree membership */ + + component epoch_key_prover = EpochKey( + STATE_TREE_DEPTH, + EPOCH_KEY_NONCE_PER_EPOCH, + FIELD_COUNT + ); + epoch_key_prover.identity_secret <== identity_secret; + epoch_key_prover.reveal_nonce <== reveal_nonce; + epoch_key_prover.attester_id <== attester_id; + epoch_key_prover.epoch <== epoch; + epoch_key_prover.nonce <== nonce; + epoch_key_prover.sig_data <== sig_data; + for (var i = 0; i < STATE_TREE_DEPTH; i++) { + epoch_key_prover.state_tree_indexes[i] <== state_tree_indexes[i]; + epoch_key_prover.state_tree_elements[i] <== state_tree_elements[i]; + } + for (var i = 0; i < FIELD_COUNT; i++) { + epoch_key_prover.data[i] <== data[i]; + } + + control[0] <== epoch_key_prover.control; + epoch_key <== epoch_key_prover.epoch_key; + state_tree_root <== epoch_key_prover.state_tree_root; + + /* End of check 1a */ + + /* 2. Check if user has reputation greater than min_rep */ + // if proving min_rep > 0, check if data[0] >= data[1] + min_rep + + component min_rep_check = GreaterEqThan(252); + min_rep_check.in[0] <== data[0]; + min_rep_check.in[1] <== data[1] + min_rep; + + component if_not_prove_min_rep = IsZero(); + if_not_prove_min_rep.in <== prove_min_rep; + + component output_rep_check = OR(); + output_rep_check.a <== if_not_prove_min_rep.out; + output_rep_check.b <== min_rep_check.out; + + output_rep_check.out === 1; + + /* End of check 2 */ + + /* 3. Check if user has reputation less than max_rep */ + // if proving max_rep > 0, check if data[1] >= data[0] + max_rep + + component max_rep_check = GreaterEqThan(252); + max_rep_check.in[0] <== data[1]; + max_rep_check.in[1] <== data[0] + max_rep; + + component if_not_prove_max_rep = IsZero(); + if_not_prove_max_rep.in <== prove_max_rep; + + component max_rep_check_out = OR(); + max_rep_check_out.a <== if_not_prove_max_rep.out; + max_rep_check_out.b <== max_rep_check.out; + + max_rep_check_out.out === 1; + + /* End of check 3 */ + + /* 4. Check if user has net 0 reputation */ + + component zero_rep_check = IsEqual(); + zero_rep_check.in[0] <== data[0]; + zero_rep_check.in[1] <== data[1]; + + component if_not_prove_zero_rep = IsZero(); + if_not_prove_zero_rep.in <== prove_zero_rep; + + component zero_rep_check_out = OR(); + zero_rep_check_out.a <== if_not_prove_zero_rep.out; + zero_rep_check_out.b <== zero_rep_check.out; + + zero_rep_check_out.out === 1; + + /* End of check 4 */ + + /* 3. Prove the graffiti pre-image if needed */ + + component if_not_check_graffiti = IsZero(); + if_not_check_graffiti.in <== prove_graffiti; + + component graffiti_hasher = Poseidon(1); + graffiti_hasher.inputs[0] <== graffiti_pre_image; + + component graffiti_eq = IsEqual(); + graffiti_eq.in[0] <== graffiti_hasher.out; + graffiti_eq.in[1] <== data[SUM_FIELD_COUNT]; + + component check_graffiti = OR(); + check_graffiti.a <== if_not_check_graffiti.out; + check_graffiti.b <== graffiti_eq.out; + + check_graffiti.out === 1; + + /* End of check 3 */ +} diff --git a/tests/vulnerabilities/uo/ProveReputation/src/signup.circom b/tests/vulnerabilities/uo/ProveReputation/src/signup.circom new file mode 100644 index 0000000..e3a47ec --- /dev/null +++ b/tests/vulnerabilities/uo/ProveReputation/src/signup.circom @@ -0,0 +1,34 @@ +pragma circom 2.0.0; + +// Output identity commitment and starting state tree leaf + +include "../libs/circomlib/circuits/poseidon.circom"; +include "./identity.circom"; +include "./leafHasher.circom"; + +template Signup(FIELD_COUNT) { + + signal output identity_commitment; + signal output state_tree_leaf; + + signal input attester_id; + signal input epoch; + + signal input identity_nullifier; + signal input identity_trapdoor; + + component commitment_calc = IdentityCommitment(); + commitment_calc.nullifier <== identity_nullifier; + commitment_calc.trapdoor <== identity_trapdoor; + identity_commitment <== commitment_calc.out; + + component leaf_hasher = StateTreeLeaf(FIELD_COUNT); + leaf_hasher.identity_secret <== commitment_calc.secret; + leaf_hasher.attester_id <== attester_id; + leaf_hasher.epoch <== epoch; + for (var x = 0; x < FIELD_COUNT; x++) { + leaf_hasher.data[x] <== 0; + } + + state_tree_leaf <== leaf_hasher.out; +} diff --git a/tests/vulnerabilities/uo/ProveReputation/src/userStateTransition.circom b/tests/vulnerabilities/uo/ProveReputation/src/userStateTransition.circom new file mode 100644 index 0000000..2c27281 --- /dev/null +++ b/tests/vulnerabilities/uo/ProveReputation/src/userStateTransition.circom @@ -0,0 +1,205 @@ +pragma circom 2.0.0; + +include "../libs/circomlib/circuits/poseidon.circom"; +include "../libs/circomlib/circuits/comparators.circom"; +include "../libs/circomlib/circuits/mux1.circom"; +include "../libs/circomlib/circuits/gates.circom"; +include "./incrementalMerkleTree.circom"; +include "./leafHasher.circom"; +include "./bigComparators.circom"; + +template UserStateTransition( + STATE_TREE_DEPTH, + EPOCH_TREE_DEPTH, + HISTORY_TREE_DEPTH, + EPOCH_KEY_NONCE_PER_EPOCH, + FIELD_COUNT, + SUM_FIELD_COUNT, + REPL_NONCE_BITS +) { + signal input from_epoch; + signal input to_epoch; + + // State tree leaf: Identity & user state root + signal input identity_secret; + // State tree + signal input state_tree_indexes[STATE_TREE_DEPTH]; + signal input state_tree_elements[STATE_TREE_DEPTH]; + + signal output history_tree_root; + signal output state_tree_leaf; + + // History tree + signal input history_tree_indices[HISTORY_TREE_DEPTH]; + signal input history_tree_elements[HISTORY_TREE_DEPTH]; + + // Attester to prove reputation from + signal input attester_id; + + // The starting data in fromEpoch + signal input data[FIELD_COUNT]; + + // prove what we've received in fromEpoch + signal input new_data[EPOCH_KEY_NONCE_PER_EPOCH][FIELD_COUNT]; + + // A root to prove against + signal input epoch_tree_root; + + // the inclusion proofs + signal input epoch_tree_elements[EPOCH_KEY_NONCE_PER_EPOCH][EPOCH_TREE_DEPTH]; + signal input epoch_tree_indices[EPOCH_KEY_NONCE_PER_EPOCH][EPOCH_TREE_DEPTH]; + + signal output epks[EPOCH_KEY_NONCE_PER_EPOCH]; + + // to_epoch will be checked on chain + // from_epoch is implicitly checked by the + // state tree leaf membership proof + component epoch_check = GreaterThan(64); + epoch_check.in[0] <== to_epoch; + epoch_check.in[1] <== from_epoch; + epoch_check.out === 1; + + /* 1. Check if user exists in the Global State Tree */ + + component leaf_hasher = StateTreeLeaf(FIELD_COUNT); + leaf_hasher.identity_secret <== identity_secret; + leaf_hasher.attester_id <== attester_id; + leaf_hasher.epoch <== from_epoch; + for (var x = 0; x < FIELD_COUNT; x++) { + leaf_hasher.data[x] <== data[x]; + } + + component state_merkletree = MerkleTreeInclusionProof(STATE_TREE_DEPTH); + state_merkletree.leaf <== leaf_hasher.out; + for (var i = 0; i < STATE_TREE_DEPTH; i++) { + state_merkletree.path_index[i] <== state_tree_indexes[i]; + state_merkletree.path_elements[i] <== state_tree_elements[i]; + } + + component history_leaf_hasher = Poseidon(2); + history_leaf_hasher.inputs[0] <== state_merkletree.root; + history_leaf_hasher.inputs[1] <== epoch_tree_root; + + component history_merkletree = MerkleTreeInclusionProof(HISTORY_TREE_DEPTH); + history_merkletree.leaf <== history_leaf_hasher.out; + for (var i = 0; i < HISTORY_TREE_DEPTH; i++) { + history_merkletree.path_index[i] <== history_tree_indices[i]; + history_merkletree.path_elements[i] <== history_tree_elements[i]; + } + history_tree_root <== history_merkletree.root; + + /* End of check 1 */ + + /* 2. Verify new reputation for the from epoch */ + + component epoch_key_hashers[EPOCH_KEY_NONCE_PER_EPOCH]; + component leaf_hashers[EPOCH_KEY_NONCE_PER_EPOCH]; + for (var i = 0; i < EPOCH_KEY_NONCE_PER_EPOCH; i++) { + epoch_key_hashers[i] = Poseidon(4); + epoch_key_hashers[i].inputs[0] <== identity_secret; + epoch_key_hashers[i].inputs[1] <== attester_id; + epoch_key_hashers[i].inputs[2] <== from_epoch; + epoch_key_hashers[i].inputs[3] <== i; // nonce + + leaf_hashers[i] = EpochTreeLeaf(FIELD_COUNT); + leaf_hashers[i].epoch_key <== epoch_key_hashers[i].out; + for (var x = 0; x < FIELD_COUNT; x++) { + leaf_hashers[i].data[x] <== new_data[i][x]; + } + } + + // do an inclusion proof for each epoch key + + component epoch_tree_proof[EPOCH_KEY_NONCE_PER_EPOCH]; + component epoch_tree_proof_valid[EPOCH_KEY_NONCE_PER_EPOCH]; + for (var x = 0; x < EPOCH_KEY_NONCE_PER_EPOCH; x++) { + epoch_tree_proof[x] = MerkleTreeInclusionProof(EPOCH_TREE_DEPTH); + epoch_tree_proof[x].leaf <== leaf_hashers[x].out; + for (var y = 0; y < EPOCH_TREE_DEPTH; y++) { + epoch_tree_proof[x].path_index[y] <== epoch_tree_indices[x][y]; + epoch_tree_proof[x].path_elements[y] <== epoch_tree_elements[x][y]; + } + epoch_tree_proof_valid[x] = IsEqual(); + epoch_tree_proof_valid[x].in[0] <== epoch_tree_root; + epoch_tree_proof_valid[x].in[1] <== epoch_tree_proof[x].root; + } + + component epk_out_hashers[EPOCH_KEY_NONCE_PER_EPOCH]; + for (var x = 0; x < EPOCH_KEY_NONCE_PER_EPOCH; x++) { + epk_out_hashers[x] = Poseidon(4); + epk_out_hashers[x].inputs[0] <== identity_secret; + epk_out_hashers[x].inputs[1] <== attester_id; + epk_out_hashers[x].inputs[2] <== from_epoch; + epk_out_hashers[x].inputs[3] <== epoch_tree_proof_valid[x].out * EPOCH_KEY_NONCE_PER_EPOCH + x; // nonce + epks[x] <== epk_out_hashers[x].out; + } + + // if an inclusion proof is not valid the newData must be 0 + component data_check[EPOCH_KEY_NONCE_PER_EPOCH][FIELD_COUNT]; + component proof_invalid[EPOCH_KEY_NONCE_PER_EPOCH]; + for (var x = 0; x < EPOCH_KEY_NONCE_PER_EPOCH; x++) { + proof_invalid[x] = IsZero(); + proof_invalid[x].in <== epoch_tree_proof_valid[x].out; + for (var y = 0; y < FIELD_COUNT; y++) { + data_check[x][y] = ForceEqualIfEnabled(); + data_check[x][y].enabled <== proof_invalid[x].out; + data_check[x][y].in[0] <== 0; + data_check[x][y].in[1] <== new_data[x][y]; + } + } + + /* End of check 2 */ + + /* 3. Calculate the new state tree leaf */ + + signal final_data[EPOCH_KEY_NONCE_PER_EPOCH][FIELD_COUNT]; + + var REPL_FIELD_COUNT = FIELD_COUNT - SUM_FIELD_COUNT; + + component index_check[EPOCH_KEY_NONCE_PER_EPOCH][REPL_FIELD_COUNT]; + component field_select[EPOCH_KEY_NONCE_PER_EPOCH][REPL_FIELD_COUNT]; + + for (var i = 0; i < EPOCH_KEY_NONCE_PER_EPOCH; i++) { + // first combine the sum data + for (var j = 0; j < SUM_FIELD_COUNT; j++) { + if (i == 0) { + final_data[i][j] <== data[j] + new_data[i][j]; + } else { + final_data[i][j] <== final_data[i-1][j] + new_data[i][j]; + } + } + // then combine the replacement data + for (var j = 0; j < REPL_FIELD_COUNT; j++) { + var field_i = SUM_FIELD_COUNT + j; + index_check[i][j] = UpperLessThan(REPL_NONCE_BITS); + index_check[i][j].in[0] <== new_data[i][field_i]; + if (i == 0) { + index_check[i][j].in[1] <== data[field_i]; + } else { + index_check[i][j].in[1] <== final_data[i-1][field_i]; + } + + field_select[i][j] = Mux1(); + field_select[i][j].s <== index_check[i][j].out; + if (i == 0) { + field_select[i][j].c[1] <== data[field_i]; + } else { + field_select[i][j].c[1] <== final_data[i-1][field_i]; + } + field_select[i][j].c[0] <== new_data[i][field_i]; + + final_data[i][field_i] <== field_select[i][j].out; + } + } + + component out_leaf_hasher = StateTreeLeaf(FIELD_COUNT); + out_leaf_hasher.identity_secret <== identity_secret; + out_leaf_hasher.attester_id <== attester_id; + out_leaf_hasher.epoch <== to_epoch; + for (var x = 0; x < FIELD_COUNT; x++) { + out_leaf_hasher.data[x] <== final_data[EPOCH_KEY_NONCE_PER_EPOCH - 1][x]; + } + state_tree_leaf <== out_leaf_hasher.out; + + /* End of check 3 */ +} diff --git a/tests/vulnerabilities/uo/ProveReputation/test.json b/tests/vulnerabilities/uo/ProveReputation/test.json new file mode 100644 index 0000000..ee846ad --- /dev/null +++ b/tests/vulnerabilities/uo/ProveReputation/test.json @@ -0,0 +1,17 @@ +{ + "id": "ProveReputation", + "source": "UniRep", + "project_url": "https://github.com/Unirep/Unirep/", + "commit": "0985a28c38c8b2e7b7a9e80f43e63179fdd08b89", + "internal": false, + "synthetic": false, + "tests": [ + { + "id": "ProveReputation", + "file": "src/proveReputation.circom", + "template": "ProveReputation", + "main": "src/main.circom", + "description": "ProveReputation template is missing range check, making output underconstrained." + } + ] +} diff --git a/tests/vulnerabilities/uo/SignedFpCarryModP_k1/README.md b/tests/vulnerabilities/uo/SignedFpCarryModP_k1/README.md new file mode 100644 index 0000000..1eedae8 --- /dev/null +++ b/tests/vulnerabilities/uo/SignedFpCarryModP_k1/README.md @@ -0,0 +1,23 @@ +# SignedFpCarryModP + +Source: ZKSolid + +Project URL: https://github.com/whbjzzwjxq/ZKSolid-bmk-circom-pairing/tree/master/circuits + +Commit: + +Internal: False + +Synthetic: False + +## SignedFpCarryModP + +**File**: src/fp.circom + +**Template**: SignedFpCarryModP + +**Main**: src/main.circom + +**Description**: Output signals `out` and `X` are underconstrained. + + diff --git a/tests/vulnerabilities/uo/SignedFpCarryModP_k1/src/fp.circom b/tests/vulnerabilities/uo/SignedFpCarryModP_k1/src/fp.circom new file mode 100644 index 0000000..8f874e4 --- /dev/null +++ b/tests/vulnerabilities/uo/SignedFpCarryModP_k1/src/fp.circom @@ -0,0 +1,292 @@ +pragma circom 2.0.3; + +include "../libs/circom-pairing/circuits/bigint.circom"; +include "../libs/circom-pairing/circuits/bigint_func.circom"; +include "../libs/circom-pairing/circuits/field_elements_func.circom"; + +// a[i], b[i] in 0... 2**n-1 +// represent a = a[0] + a[1] * 2**n + .. + a[k - 1] * 2**(n * k) +// calculates (a+b)%p, where 0<= a,b < p +template FpAdd(n, k, p){ + assert(n <= 252); + signal input a[k]; + signal input b[k]; + signal output out[k]; + + component add = BigAdd(n,k); + for (var i = 0; i < k; i++) { + add.a[i] <== a[i]; + add.b[i] <== b[i]; + } + component lt = BigLessThan(n, k+1); + for (var i = 0; i < k; i++) { + lt.a[i] <== add.out[i]; + lt.b[i] <== p[i]; + } + lt.a[k] <== add.out[k]; + lt.b[k] <== 0; + + component sub = BigSub(n,k+1); + for (var i = 0; i < k; i++) { + sub.a[i] <== add.out[i]; + sub.b[i] <== p[i] - lt.out * p[i]; + } + sub.a[k] <== add.out[k]; + sub.b[k] <== 0; + + sub.out[k] === 0; + for (var i = 0; i < k; i++) { + out[i] <== sub.out[i]; + } +} + +// calculates (a - b) % p, where a, b < p +// note: does not assume a >= b +template FpSubtract(n, k, p){ + assert(n <= 252); + signal input a[k]; + signal input b[k]; + signal output out[k]; + component sub = BigSub(n, k); + for (var i = 0; i < k; i++){ + sub.a[i] <== a[i]; + sub.b[i] <== b[i]; + } + signal flag; + flag <== sub.underflow; + component add = BigAdd(n, k); + for (var i = 0; i < k; i++){ + add.a[i] <== sub.out[i]; + add.b[i] <== p[i]; + } + signal tmp[k]; + for (var i = 0; i < k; i++){ + tmp[i] <== (1 - flag) * sub.out[i]; + out[i] <== tmp[i] + flag * add.out[i]; + } +} + +// Input: in <= p +// Output: -in (mod p) = p - in if in != 0, else 0 +// Constrains in <= p +template FpNegate(n, k, p){ + signal input in[k]; + signal output out[k]; + + component neg = BigSub(n, k); + component is_zero = BigIsZero(k); + for(var idx=0; idx= n +template CheckCarryModP(n, k, m, overflow, p){ + signal input in[k]; + signal input X[m]; + signal input Y[k]; + + assert( overflow < 251 ); + assert( n <= overflow - 1); + component pX; + component carry_check; + + pX = BigMultShortLongUnequal(n, k, m, overflow); // p has k registers, X has m registers, so output really has k+m-1 registers + // overflow register in (-2^{overflow-1} , 2^{overflow-1}) + for(var i=0; i= b +template FpSubtract(n, k, p){ + assert(n <= 252); + signal input a[k]; + signal input b[k]; + signal output out[k]; + component sub = BigSub(n, k); + for (var i = 0; i < k; i++){ + sub.a[i] <== a[i]; + sub.b[i] <== b[i]; + } + signal flag; + flag <== sub.underflow; + component add = BigAdd(n, k); + for (var i = 0; i < k; i++){ + add.a[i] <== sub.out[i]; + add.b[i] <== p[i]; + } + signal tmp[k]; + for (var i = 0; i < k; i++){ + tmp[i] <== (1 - flag) * sub.out[i]; + out[i] <== tmp[i] + flag * add.out[i]; + } +} + +// Input: in <= p +// Output: -in (mod p) = p - in if in != 0, else 0 +// Constrains in <= p +template FpNegate(n, k, p){ + signal input in[k]; + signal output out[k]; + + component neg = BigSub(n, k); + component is_zero = BigIsZero(k); + for(var idx=0; idx= n +template CheckCarryModP(n, k, m, overflow, p){ + signal input in[k]; + signal input X[m]; + signal input Y[k]; + + assert( overflow < 251 ); + assert( n <= overflow - 1); + component pX; + component carry_check; + + pX = BigMultShortLongUnequal(n, k, m, overflow); // p has k registers, X has m registers, so output really has k+m-1 registers + // overflow register in (-2^{overflow-1} , 2^{overflow-1}) + for(var i=0; i= b +template FpSubtract(n, k, p){ + assert(n <= 252); + signal input a[k]; + signal input b[k]; + signal output out[k]; + component sub = BigSub(n, k); + for (var i = 0; i < k; i++){ + sub.a[i] <== a[i]; + sub.b[i] <== b[i]; + } + signal flag; + flag <== sub.underflow; + component add = BigAdd(n, k); + for (var i = 0; i < k; i++){ + add.a[i] <== sub.out[i]; + add.b[i] <== p[i]; + } + signal tmp[k]; + for (var i = 0; i < k; i++){ + tmp[i] <== (1 - flag) * sub.out[i]; + out[i] <== tmp[i] + flag * add.out[i]; + } +} + +// Input: in <= p +// Output: -in (mod p) = p - in if in != 0, else 0 +// Constrains in <= p +template FpNegate(n, k, p){ + signal input in[k]; + signal output out[k]; + + component neg = BigSub(n, k); + component is_zero = BigIsZero(k); + for(var idx=0; idx= n +template CheckCarryModP(n, k, m, overflow, p){ + signal input in[k]; + signal input X[m]; + signal input Y[k]; + + assert( overflow < 251 ); + assert( n <= overflow - 1); + component pX; + component carry_check; + + pX = BigMultShortLongUnequal(n, k, m, overflow); // p has k registers, X has m registers, so output really has k+m-1 registers + // overflow register in (-2^{overflow-1} , 2^{overflow-1}) + for(var i=0; i= b +template FpSubtract(n, k, p){ + assert(n <= 252); + signal input a[k]; + signal input b[k]; + signal output out[k]; + component sub = BigSub(n, k); + for (var i = 0; i < k; i++){ + sub.a[i] <== a[i]; + sub.b[i] <== b[i]; + } + signal flag; + flag <== sub.underflow; + component add = BigAdd(n, k); + for (var i = 0; i < k; i++){ + add.a[i] <== sub.out[i]; + add.b[i] <== p[i]; + } + signal tmp[k]; + for (var i = 0; i < k; i++){ + tmp[i] <== (1 - flag) * sub.out[i]; + out[i] <== tmp[i] + flag * add.out[i]; + } +} + +// Input: in <= p +// Output: -in (mod p) = p - in if in != 0, else 0 +// Constrains in <= p +template FpNegate(n, k, p){ + signal input in[k]; + signal output out[k]; + + component neg = BigSub(n, k); + component is_zero = BigIsZero(k); + for(var idx=0; idx= n +template CheckCarryModP(n, k, m, overflow, p){ + signal input in[k]; + signal input X[m]; + signal input Y[k]; + + assert( overflow < 251 ); + assert( n <= overflow - 1); + component pX; + component carry_check; + + pX = BigMultShortLongUnequal(n, k, m, overflow); // p has k registers, X has m registers, so output really has k+m-1 registers + // overflow register in (-2^{overflow-1} , 2^{overflow-1}) + for(var i=0; i out; +} diff --git a/tests/vulnerabilities/uo/UpperLessThan/src/main.circom b/tests/vulnerabilities/uo/UpperLessThan/src/main.circom new file mode 100644 index 0000000..cd08ce1 --- /dev/null +++ b/tests/vulnerabilities/uo/UpperLessThan/src/main.circom @@ -0,0 +1,5 @@ +pragma circom 2.0.0; + +include "bigComparators.circom"; + +component main{public [in]} = UpperLessThan(252); diff --git a/tests/vulnerabilities/uo/UpperLessThan/src/modulo.circom b/tests/vulnerabilities/uo/UpperLessThan/src/modulo.circom new file mode 100644 index 0000000..421b160 --- /dev/null +++ b/tests/vulnerabilities/uo/UpperLessThan/src/modulo.circom @@ -0,0 +1,36 @@ +pragma circom 2.0.3; + +include "../libs/circomlib/circuits/bitify.circom"; +include "../libs/circomlib/circuits/comparators.circom"; + +template Modulo() { + signal input divisor; + signal input dividend; + signal output remainder; + + signal output quotient; + + // circom's best practices state that we should avoid using <-- unless + // we know what we are doing. But this is the only way to perform the + // modulo operation. + quotient <-- dividend \ divisor; + remainder <-- dividend % divisor; + + // check that remainder and divisor are both < 2**252 + component remainder_bits = Num2Bits(252); + remainder_bits.in <== remainder; + + component divisor_bits = Num2Bits(252); + divisor_bits.in <== divisor; + + // now we can safely do a range check on remainder + component remainder_lt; + // Range check on remainder + remainder_lt = LessThan(252); + remainder_lt.in[0] <== remainder; + remainder_lt.in[1] <== divisor; + remainder_lt.out === 1; + + // Check equality + dividend === divisor * quotient + remainder; +} diff --git a/tests/vulnerabilities/uo/UpperLessThan/test.json b/tests/vulnerabilities/uo/UpperLessThan/test.json new file mode 100644 index 0000000..eb2bd07 --- /dev/null +++ b/tests/vulnerabilities/uo/UpperLessThan/test.json @@ -0,0 +1,17 @@ +{ + "id": "UpperLessThan", + "source": "UniRep", + "project_url": "https://github.com/Unirep/Unirep/", + "commit": "0985a28c38c8b2e7b7a9e80f43e63179fdd08b89", + "internal": false, + "synthetic": false, + "tests": [ + { + "id": "UpperLessThan", + "file": "src/bigComparators.circom", + "template": "UpperLessThan", + "main": "src/main.circom", + "description": "UpperLessThan template uses Num2Bits(254) which is underconstrained." + } + ] +} diff --git a/tests/vulnerabilities/uo/UserStateTransition/src/bigComparators.circom b/tests/vulnerabilities/uo/UserStateTransition/src/bigComparators.circom new file mode 100644 index 0000000..982aadc --- /dev/null +++ b/tests/vulnerabilities/uo/UserStateTransition/src/bigComparators.circom @@ -0,0 +1,91 @@ +include "../libs/circomlib/circuits/comparators.circom"; +include "../libs/circomlib/circuits/bitify.circom"; +include "../libs/circomlib/circuits/mux1.circom"; +include "./modulo.circom"; + +//~~ support comparisons of numbers up to the field size + +template UpperLessThan(n) { + signal input in[2]; + signal output out; + + component bits[2]; + for (var x = 0; x < 2; x++) { + bits[x] = Num2Bits(254); + bits[x].in <== in[x]; + } + + component upper_bits[2]; + upper_bits[0] = Bits2Num(n); + upper_bits[1] = Bits2Num(n); + + for (var x = 0; x < n; x++) { + upper_bits[0].in[x] <== bits[0].out[x+(254-n)]; + upper_bits[1].in[x] <== bits[1].out[x+(254-n)]; + } + + component lt = LessThan(n); + lt.in[0] <== upper_bits[0].out; + lt.in[1] <== upper_bits[1].out; + + out <== lt.out; +} + +template BigLessThan() { + signal input in[2]; + signal output out; + + component high_lt; + component low_lt; + + component bits[2]; + for (var x = 0; x < 2; x++) { + bits[x] = Num2Bits(254); + bits[x].in <== in[x]; + } + + component high[2]; + component low[2]; + for (var x = 0; x < 2; x++) { + high[x] = Bits2Num(127); + low[x] = Bits2Num(127); + for (var y = 0; y < 127; y++) { + high[x].in[y] <== bits[x].out[y+127]; + low[x].in[y] <== bits[x].out[y]; + } + } + + high_lt = LessThan(127); + high_lt.in[0] <== high[0].out; + high_lt.in[1] <== high[1].out; + + low_lt = LessThan(127); + low_lt.in[0] <== low[0].out; + low_lt.in[1] <== low[1].out; + + // if high[0] == high[1] then out = low_lt + // else out = high_lt + + component is_high_eq = IsEqual(); + is_high_eq.in[0] <== high[0].out; + is_high_eq.in[1] <== high[1].out; + + component mux = Mux1(); + mux.s <== is_high_eq.out; + + mux.c[0] <== high_lt.out; + mux.c[1] <== low_lt.out; + + out <== mux.out; +} + +template BigGreaterThan() { + signal input in[2]; + signal output out; + + component lt = BigLessThan(); + + lt.in[0] <== in[1]; + lt.in[1] <== in[0]; + lt.out ==> out; +} diff --git a/tests/vulnerabilities/uo/UserStateTransition/src/epochKey.circom b/tests/vulnerabilities/uo/UserStateTransition/src/epochKey.circom new file mode 100644 index 0000000..d08179d --- /dev/null +++ b/tests/vulnerabilities/uo/UserStateTransition/src/epochKey.circom @@ -0,0 +1,71 @@ +pragma circom 2.0.0; + +/* + Verify that an epoch key exists in a state tree +*/ + +include "../libs/circomlib/circuits/poseidon.circom"; +include "../libs/circomlib/circuits/bitify.circom"; +include "./incrementalMerkleTree.circom"; +include "./epochKeyLite.circom"; +include "./leafHasher.circom"; + +template EpochKey(STATE_TREE_DEPTH, EPOCH_KEY_NONCE_PER_EPOCH, FIELD_COUNT) { + // Global state tree + signal input state_tree_indexes[STATE_TREE_DEPTH]; + signal input state_tree_elements[STATE_TREE_DEPTH]; + // Global state tree leaf: Identity & user state root + signal input identity_secret; + + signal output epoch_key; + signal output state_tree_root; + + signal input reveal_nonce; + signal input attester_id; + signal input epoch; + signal input nonce; + + signal input data[FIELD_COUNT]; + + // Some arbitrary data to endorse + signal input sig_data; + + /** + * Optionally reveal nonce, epoch, attester_id + **/ + signal output control; + + /* 1. Check if user exists in the Global State Tree */ + + // Compute user state tree root + component leaf_hasher = StateTreeLeaf(FIELD_COUNT); + leaf_hasher.identity_secret <== identity_secret; + leaf_hasher.attester_id <== attester_id; + leaf_hasher.epoch <== epoch; + for (var x = 0; x < FIELD_COUNT; x++) { + leaf_hasher.data[x] <== data[x]; + } + + component merkletree = MerkleTreeInclusionProof(STATE_TREE_DEPTH); + merkletree.leaf <== leaf_hasher.out; + for (var i = 0; i < STATE_TREE_DEPTH; i++) { + merkletree.path_index[i] <== state_tree_indexes[i]; + merkletree.path_elements[i] <== state_tree_elements[i]; + } + state_tree_root <== merkletree.root; + + /* End of check 1 */ + + /* 2. Check epoch key validity */ + + component epoch_key_lite = EpochKeyLite(EPOCH_KEY_NONCE_PER_EPOCH); + epoch_key_lite.identity_secret <== identity_secret; + epoch_key_lite.reveal_nonce <== reveal_nonce; + epoch_key_lite.attester_id <== attester_id; + epoch_key_lite.epoch <== epoch; + epoch_key_lite.nonce <== nonce; + epoch_key_lite.sig_data <== sig_data; + control <== epoch_key_lite.control; + epoch_key <== epoch_key_lite.epoch_key; + /* End of check 2*/ +} diff --git a/tests/vulnerabilities/uo/UserStateTransition/src/epochKeyLite.circom b/tests/vulnerabilities/uo/UserStateTransition/src/epochKeyLite.circom new file mode 100644 index 0000000..4a2a814 --- /dev/null +++ b/tests/vulnerabilities/uo/UserStateTransition/src/epochKeyLite.circom @@ -0,0 +1,59 @@ +pragma circom 2.0.0; + +include "../libs/circomlib/circuits/bitify.circom"; +include "../libs/circomlib/circuits/comparators.circom"; +include "../libs/circomlib/circuits/poseidon.circom"; + +template EpochKeyLite(EPOCH_KEY_NONCE_PER_EPOCH) { + signal input identity_secret; + + signal input reveal_nonce; + signal input attester_id; + signal input epoch; + signal input nonce; + + signal input sig_data; + + signal output control; + signal output epoch_key; + + /** + * Control structure + * 8 bits nonce + * 64 bits epoch + * 160 bits attester_id + * 1 bit reveal nonce + **/ + + // check that reveal_nonce is 0 or 1 + reveal_nonce * (reveal_nonce - 1) === 0; + + // then range check the others + + component attester_id_bits = Num2Bits(254); + attester_id_bits.in <== attester_id; + for (var x = 160; x < 254; x++) { + attester_id_bits.out[x] === 0; + } + + component epoch_bits = Num2Bits(254); + epoch_bits.in <== epoch; + for (var x = 64; x < 254; x++) { + epoch_bits.out[x] === 0; + } + + component nonce_lt = LessThan(8); + nonce_lt.in[0] <== nonce; + nonce_lt.in[1] <== EPOCH_KEY_NONCE_PER_EPOCH; + nonce_lt.out === 1; + + control <== reveal_nonce * 2**232 + attester_id * 2**72 + epoch * 2**8 + reveal_nonce * nonce; + + component epoch_key_hasher = Poseidon(4); + epoch_key_hasher.inputs[0] <== identity_secret; + epoch_key_hasher.inputs[1] <== attester_id; + epoch_key_hasher.inputs[2] <== epoch; + epoch_key_hasher.inputs[3] <== nonce; + + epoch_key <== epoch_key_hasher.out; +} diff --git a/tests/vulnerabilities/uo/UserStateTransition/src/identity.circom b/tests/vulnerabilities/uo/UserStateTransition/src/identity.circom new file mode 100644 index 0000000..5b349ce --- /dev/null +++ b/tests/vulnerabilities/uo/UserStateTransition/src/identity.circom @@ -0,0 +1,33 @@ +include "../libs/circomlib/circuits/poseidon.circom"; + +template IdentitySecret() { + signal input nullifier; + signal input trapdoor; + + signal output out; + + component hasher = Poseidon(2); + hasher.inputs[0] <== nullifier; + hasher.inputs[1] <== trapdoor; + + out <== hasher.out; +} + +template IdentityCommitment() { + signal input nullifier; + signal input trapdoor; + + signal output secret; + signal output out; + + component _secret = IdentitySecret(); + _secret.nullifier <== nullifier; + _secret.trapdoor <== trapdoor; + + secret <== _secret.out; + + component hasher = Poseidon(1); + hasher.inputs[0] <== _secret.out; + + out <== hasher.out; +} diff --git a/tests/vulnerabilities/uo/UserStateTransition/src/incrementalMerkleTree.circom b/tests/vulnerabilities/uo/UserStateTransition/src/incrementalMerkleTree.circom new file mode 100644 index 0000000..290f611 --- /dev/null +++ b/tests/vulnerabilities/uo/UserStateTransition/src/incrementalMerkleTree.circom @@ -0,0 +1,49 @@ +pragma circom 2.0.0; + +// Refer to: +// https://github.com/peppersec/tornado-mixer/blob/master/circuits/merkleTree.circom +// https://github.com/appliedzkp/semaphore/blob/master/circuits/circom/semaphore-base.circom + +include "../libs/circomlib/circuits/mux1.circom"; +include "../libs/circomlib/circuits/poseidon.circom"; +include "../libs/circomlib/circuits/comparators.circom"; + +template MerkleTreeInclusionProof(n_levels) { + signal input leaf; + signal input path_index[n_levels]; + signal input path_elements[n_levels]; + signal output root; + + component hashers[n_levels]; + component mux[n_levels]; + + signal levelHashes[n_levels + 1]; + levelHashes[0] <== leaf; + + // don't allow inclusion proof for 0 leaf + component leaf_zero = IsZero(); + leaf_zero.in <== leaf; + leaf_zero.out === 0; + + for (var i = 0; i < n_levels; i++) { + // Should be 0 or 1 + path_index[i] * (1 - path_index[i]) === 0; + + hashers[i] = Poseidon(2); + mux[i] = MultiMux1(2); + + mux[i].c[0][0] <== levelHashes[i]; + mux[i].c[0][1] <== path_elements[i]; + + mux[i].c[1][0] <== path_elements[i]; + mux[i].c[1][1] <== levelHashes[i]; + + mux[i].s <== path_index[i]; + hashers[i].inputs[0] <== mux[i].out[0]; + hashers[i].inputs[1] <== mux[i].out[1]; + + levelHashes[i + 1] <== hashers[i].out; + } + + root <== levelHashes[n_levels]; +} diff --git a/tests/vulnerabilities/uo/UserStateTransition/src/leafHasher.circom b/tests/vulnerabilities/uo/UserStateTransition/src/leafHasher.circom new file mode 100644 index 0000000..392fbd4 --- /dev/null +++ b/tests/vulnerabilities/uo/UserStateTransition/src/leafHasher.circom @@ -0,0 +1,51 @@ +include "../libs/circomlib/circuits/poseidon.circom"; + +template EpochTreeLeaf(FIELD_COUNT) { + signal input epoch_key; + signal input data[FIELD_COUNT]; + + signal output out; + + component hasher[FIELD_COUNT]; + + for (var x = 0; x < FIELD_COUNT; x++) { + hasher[x] = Poseidon(2); + if (x == 0) { + hasher[x].inputs[0] <== epoch_key; + } else { + hasher[x].inputs[0] <== hasher[x-1].out; + } + hasher[x].inputs[1] <== data[x]; + } + + out <== hasher[FIELD_COUNT-1].out; +} + +template StateTreeLeaf(FIELD_COUNT) { + signal input data[FIELD_COUNT]; + signal input identity_secret; + signal input attester_id; + signal input epoch; + + signal output out; + + component hasher[FIELD_COUNT]; + + for (var x = 0; x < FIELD_COUNT; x++) { + hasher[x] = Poseidon(2); + if (x == 0) { + hasher[x].inputs[0] <== 0; + } else { + hasher[x].inputs[0] <== hasher[x-1].out; + } + hasher[x].inputs[1] <== data[x]; + } + + component final_hasher = Poseidon(4); + final_hasher.inputs[0] <== identity_secret; + final_hasher.inputs[1] <== attester_id; + final_hasher.inputs[2] <== epoch; + final_hasher.inputs[3] <== hasher[FIELD_COUNT-1].out; + + out <== final_hasher.out; +} diff --git a/tests/vulnerabilities/uo/UserStateTransition/src/main.circom b/tests/vulnerabilities/uo/UserStateTransition/src/main.circom new file mode 100644 index 0000000..6abb29b --- /dev/null +++ b/tests/vulnerabilities/uo/UserStateTransition/src/main.circom @@ -0,0 +1,5 @@ +pragma circom 2.0.0; + +include "userStateTransition.circom"; + +component main = UserStateTransition(17, 17, 17, 2, 6, 4, 48); diff --git a/tests/vulnerabilities/uo/UserStateTransition/src/modulo.circom b/tests/vulnerabilities/uo/UserStateTransition/src/modulo.circom new file mode 100644 index 0000000..595525f --- /dev/null +++ b/tests/vulnerabilities/uo/UserStateTransition/src/modulo.circom @@ -0,0 +1,42 @@ +pragma circom 2.0.0; + +include "../libs/circomlib/circuits/bitify.circom"; +include "../libs/circomlib/circuits/comparators.circom"; + +template Modulo() { + signal input divisor; + signal input dividend; + signal output remainder; + + signal output quotient; + + // circom's best practices state that we should avoid using <-- unless + // we know what we are doing. But this is the only way to perform the + // modulo operation. + quotient <-- dividend \ divisor; + remainder <-- dividend % divisor; + + // check that remainder and divisor are both < 2**252 + component remainder_bits = Num2Bits(254); + remainder_bits.in <== remainder; + for (var x = 252; x < 254; x++) { + remainder_bits.out[x] === 0; + } + + component divisor_bits = Num2Bits(254); + divisor_bits.in <== divisor; + for (var x = 252; x < 254; x++) { + divisor_bits.out[x] === 0; + } + + // now we can safely do a range check on remainder + component remainder_lt; + // Range check on remainder + remainder_lt = LessThan(252); + remainder_lt.in[0] <== remainder; + remainder_lt.in[1] <== divisor; + remainder_lt.out === 1; + + // Check equality + dividend === divisor * quotient + remainder; +} diff --git a/tests/vulnerabilities/uo/UserStateTransition/src/proveReputation.circom b/tests/vulnerabilities/uo/UserStateTransition/src/proveReputation.circom new file mode 100644 index 0000000..a8c8286 --- /dev/null +++ b/tests/vulnerabilities/uo/UserStateTransition/src/proveReputation.circom @@ -0,0 +1,181 @@ +pragma circom 2.0.0; + +/* + Prove: + 1. if user has a leaf in current state tree + 2. leaf has claimed reputation + 4. output a chosen epoch key +*/ + +include "../libs/circomlib/circuits/comparators.circom"; +include "../libs/circomlib/circuits/gates.circom"; +include "../libs/circomlib/circuits/poseidon.circom"; +include "./incrementalMerkleTree.circom"; +include "./epochKey.circom"; + +template ProveReputation(STATE_TREE_DEPTH, EPOCH_KEY_NONCE_PER_EPOCH, SUM_FIELD_COUNT, FIELD_COUNT) { + signal output epoch_key; + + // Global state tree leaf: Identity & user state root + signal input identity_secret; + // Global state tree + signal input state_tree_indexes[STATE_TREE_DEPTH]; + signal input state_tree_elements[STATE_TREE_DEPTH]; + signal output state_tree_root; + // Attestation by the attester + signal input data[FIELD_COUNT]; + // Graffiti + signal input prove_graffiti; + signal input graffiti_pre_image; + // Epoch key + signal input reveal_nonce; + signal input attester_id; + signal input epoch; + signal input nonce; + // Reputation + signal input min_rep; + signal input max_rep; + signal input prove_min_rep; + signal input prove_max_rep; + signal input prove_zero_rep; + + signal output control[2]; + + signal input sig_data; + + /** + * control[0]: + * 8 bits nonce + * 64 bits epoch + * 160 bits attester_id + * 1 bit reveal_nonce + * control[1]: + * 64 bits min_rep + * 64 bits max_rep + * 1 bit prove min_rep + * 1 bit prove max_rep + * 1 bit prove zero rep + * 1 bit prove_graffiti + **/ + + // check that one bit signal is 0 or 1 + prove_graffiti * (prove_graffiti - 1) === 0; + prove_min_rep * (prove_min_rep - 1) === 0; + prove_max_rep * (prove_max_rep - 1) === 0; + prove_zero_rep * (prove_zero_rep - 1) === 0; + + // then range check the others + component min_rep_bits = Num2Bits(254); + min_rep_bits.in <== min_rep; + for (var x = 64; x < 254; x++) { + min_rep_bits.out[x] === 0; + } + + component max_rep_bits = Num2Bits(254); + max_rep_bits.in <== max_rep; + for (var x = 64; x < 254; x++) { + max_rep_bits.out[x] === 0; + } + + control[1] <== prove_graffiti * 2 ** 131 + prove_zero_rep * 2 ** 130 + prove_max_rep * 2**129 + prove_min_rep * 2**128 + max_rep * 2**64 + min_rep; + + /* 1a. Do the epoch key proof, state tree membership */ + + component epoch_key_prover = EpochKey( + STATE_TREE_DEPTH, + EPOCH_KEY_NONCE_PER_EPOCH, + FIELD_COUNT + ); + epoch_key_prover.identity_secret <== identity_secret; + epoch_key_prover.reveal_nonce <== reveal_nonce; + epoch_key_prover.attester_id <== attester_id; + epoch_key_prover.epoch <== epoch; + epoch_key_prover.nonce <== nonce; + epoch_key_prover.sig_data <== sig_data; + for (var i = 0; i < STATE_TREE_DEPTH; i++) { + epoch_key_prover.state_tree_indexes[i] <== state_tree_indexes[i]; + epoch_key_prover.state_tree_elements[i] <== state_tree_elements[i]; + } + for (var i = 0; i < FIELD_COUNT; i++) { + epoch_key_prover.data[i] <== data[i]; + } + + control[0] <== epoch_key_prover.control; + epoch_key <== epoch_key_prover.epoch_key; + state_tree_root <== epoch_key_prover.state_tree_root; + + /* End of check 1a */ + + /* 2. Check if user has reputation greater than min_rep */ + // if proving min_rep > 0, check if data[0] >= data[1] + min_rep + + component min_rep_check = GreaterEqThan(252); + min_rep_check.in[0] <== data[0]; + min_rep_check.in[1] <== data[1] + min_rep; + + component if_not_prove_min_rep = IsZero(); + if_not_prove_min_rep.in <== prove_min_rep; + + component output_rep_check = OR(); + output_rep_check.a <== if_not_prove_min_rep.out; + output_rep_check.b <== min_rep_check.out; + + output_rep_check.out === 1; + + /* End of check 2 */ + + /* 3. Check if user has reputation less than max_rep */ + // if proving max_rep > 0, check if data[1] >= data[0] + max_rep + + component max_rep_check = GreaterEqThan(252); + max_rep_check.in[0] <== data[1]; + max_rep_check.in[1] <== data[0] + max_rep; + + component if_not_prove_max_rep = IsZero(); + if_not_prove_max_rep.in <== prove_max_rep; + + component max_rep_check_out = OR(); + max_rep_check_out.a <== if_not_prove_max_rep.out; + max_rep_check_out.b <== max_rep_check.out; + + max_rep_check_out.out === 1; + + /* End of check 3 */ + + /* 4. Check if user has net 0 reputation */ + + component zero_rep_check = IsEqual(); + zero_rep_check.in[0] <== data[0]; + zero_rep_check.in[1] <== data[1]; + + component if_not_prove_zero_rep = IsZero(); + if_not_prove_zero_rep.in <== prove_zero_rep; + + component zero_rep_check_out = OR(); + zero_rep_check_out.a <== if_not_prove_zero_rep.out; + zero_rep_check_out.b <== zero_rep_check.out; + + zero_rep_check_out.out === 1; + + /* End of check 4 */ + + /* 3. Prove the graffiti pre-image if needed */ + + component if_not_check_graffiti = IsZero(); + if_not_check_graffiti.in <== prove_graffiti; + + component graffiti_hasher = Poseidon(1); + graffiti_hasher.inputs[0] <== graffiti_pre_image; + + component graffiti_eq = IsEqual(); + graffiti_eq.in[0] <== graffiti_hasher.out; + graffiti_eq.in[1] <== data[SUM_FIELD_COUNT]; + + component check_graffiti = OR(); + check_graffiti.a <== if_not_check_graffiti.out; + check_graffiti.b <== graffiti_eq.out; + + check_graffiti.out === 1; + + /* End of check 3 */ +} diff --git a/tests/vulnerabilities/uo/UserStateTransition/src/signup.circom b/tests/vulnerabilities/uo/UserStateTransition/src/signup.circom new file mode 100644 index 0000000..e3a47ec --- /dev/null +++ b/tests/vulnerabilities/uo/UserStateTransition/src/signup.circom @@ -0,0 +1,34 @@ +pragma circom 2.0.0; + +// Output identity commitment and starting state tree leaf + +include "../libs/circomlib/circuits/poseidon.circom"; +include "./identity.circom"; +include "./leafHasher.circom"; + +template Signup(FIELD_COUNT) { + + signal output identity_commitment; + signal output state_tree_leaf; + + signal input attester_id; + signal input epoch; + + signal input identity_nullifier; + signal input identity_trapdoor; + + component commitment_calc = IdentityCommitment(); + commitment_calc.nullifier <== identity_nullifier; + commitment_calc.trapdoor <== identity_trapdoor; + identity_commitment <== commitment_calc.out; + + component leaf_hasher = StateTreeLeaf(FIELD_COUNT); + leaf_hasher.identity_secret <== commitment_calc.secret; + leaf_hasher.attester_id <== attester_id; + leaf_hasher.epoch <== epoch; + for (var x = 0; x < FIELD_COUNT; x++) { + leaf_hasher.data[x] <== 0; + } + + state_tree_leaf <== leaf_hasher.out; +} diff --git a/tests/vulnerabilities/uo/UserStateTransition/src/userStateTransition.circom b/tests/vulnerabilities/uo/UserStateTransition/src/userStateTransition.circom new file mode 100644 index 0000000..2c27281 --- /dev/null +++ b/tests/vulnerabilities/uo/UserStateTransition/src/userStateTransition.circom @@ -0,0 +1,205 @@ +pragma circom 2.0.0; + +include "../libs/circomlib/circuits/poseidon.circom"; +include "../libs/circomlib/circuits/comparators.circom"; +include "../libs/circomlib/circuits/mux1.circom"; +include "../libs/circomlib/circuits/gates.circom"; +include "./incrementalMerkleTree.circom"; +include "./leafHasher.circom"; +include "./bigComparators.circom"; + +template UserStateTransition( + STATE_TREE_DEPTH, + EPOCH_TREE_DEPTH, + HISTORY_TREE_DEPTH, + EPOCH_KEY_NONCE_PER_EPOCH, + FIELD_COUNT, + SUM_FIELD_COUNT, + REPL_NONCE_BITS +) { + signal input from_epoch; + signal input to_epoch; + + // State tree leaf: Identity & user state root + signal input identity_secret; + // State tree + signal input state_tree_indexes[STATE_TREE_DEPTH]; + signal input state_tree_elements[STATE_TREE_DEPTH]; + + signal output history_tree_root; + signal output state_tree_leaf; + + // History tree + signal input history_tree_indices[HISTORY_TREE_DEPTH]; + signal input history_tree_elements[HISTORY_TREE_DEPTH]; + + // Attester to prove reputation from + signal input attester_id; + + // The starting data in fromEpoch + signal input data[FIELD_COUNT]; + + // prove what we've received in fromEpoch + signal input new_data[EPOCH_KEY_NONCE_PER_EPOCH][FIELD_COUNT]; + + // A root to prove against + signal input epoch_tree_root; + + // the inclusion proofs + signal input epoch_tree_elements[EPOCH_KEY_NONCE_PER_EPOCH][EPOCH_TREE_DEPTH]; + signal input epoch_tree_indices[EPOCH_KEY_NONCE_PER_EPOCH][EPOCH_TREE_DEPTH]; + + signal output epks[EPOCH_KEY_NONCE_PER_EPOCH]; + + // to_epoch will be checked on chain + // from_epoch is implicitly checked by the + // state tree leaf membership proof + component epoch_check = GreaterThan(64); + epoch_check.in[0] <== to_epoch; + epoch_check.in[1] <== from_epoch; + epoch_check.out === 1; + + /* 1. Check if user exists in the Global State Tree */ + + component leaf_hasher = StateTreeLeaf(FIELD_COUNT); + leaf_hasher.identity_secret <== identity_secret; + leaf_hasher.attester_id <== attester_id; + leaf_hasher.epoch <== from_epoch; + for (var x = 0; x < FIELD_COUNT; x++) { + leaf_hasher.data[x] <== data[x]; + } + + component state_merkletree = MerkleTreeInclusionProof(STATE_TREE_DEPTH); + state_merkletree.leaf <== leaf_hasher.out; + for (var i = 0; i < STATE_TREE_DEPTH; i++) { + state_merkletree.path_index[i] <== state_tree_indexes[i]; + state_merkletree.path_elements[i] <== state_tree_elements[i]; + } + + component history_leaf_hasher = Poseidon(2); + history_leaf_hasher.inputs[0] <== state_merkletree.root; + history_leaf_hasher.inputs[1] <== epoch_tree_root; + + component history_merkletree = MerkleTreeInclusionProof(HISTORY_TREE_DEPTH); + history_merkletree.leaf <== history_leaf_hasher.out; + for (var i = 0; i < HISTORY_TREE_DEPTH; i++) { + history_merkletree.path_index[i] <== history_tree_indices[i]; + history_merkletree.path_elements[i] <== history_tree_elements[i]; + } + history_tree_root <== history_merkletree.root; + + /* End of check 1 */ + + /* 2. Verify new reputation for the from epoch */ + + component epoch_key_hashers[EPOCH_KEY_NONCE_PER_EPOCH]; + component leaf_hashers[EPOCH_KEY_NONCE_PER_EPOCH]; + for (var i = 0; i < EPOCH_KEY_NONCE_PER_EPOCH; i++) { + epoch_key_hashers[i] = Poseidon(4); + epoch_key_hashers[i].inputs[0] <== identity_secret; + epoch_key_hashers[i].inputs[1] <== attester_id; + epoch_key_hashers[i].inputs[2] <== from_epoch; + epoch_key_hashers[i].inputs[3] <== i; // nonce + + leaf_hashers[i] = EpochTreeLeaf(FIELD_COUNT); + leaf_hashers[i].epoch_key <== epoch_key_hashers[i].out; + for (var x = 0; x < FIELD_COUNT; x++) { + leaf_hashers[i].data[x] <== new_data[i][x]; + } + } + + // do an inclusion proof for each epoch key + + component epoch_tree_proof[EPOCH_KEY_NONCE_PER_EPOCH]; + component epoch_tree_proof_valid[EPOCH_KEY_NONCE_PER_EPOCH]; + for (var x = 0; x < EPOCH_KEY_NONCE_PER_EPOCH; x++) { + epoch_tree_proof[x] = MerkleTreeInclusionProof(EPOCH_TREE_DEPTH); + epoch_tree_proof[x].leaf <== leaf_hashers[x].out; + for (var y = 0; y < EPOCH_TREE_DEPTH; y++) { + epoch_tree_proof[x].path_index[y] <== epoch_tree_indices[x][y]; + epoch_tree_proof[x].path_elements[y] <== epoch_tree_elements[x][y]; + } + epoch_tree_proof_valid[x] = IsEqual(); + epoch_tree_proof_valid[x].in[0] <== epoch_tree_root; + epoch_tree_proof_valid[x].in[1] <== epoch_tree_proof[x].root; + } + + component epk_out_hashers[EPOCH_KEY_NONCE_PER_EPOCH]; + for (var x = 0; x < EPOCH_KEY_NONCE_PER_EPOCH; x++) { + epk_out_hashers[x] = Poseidon(4); + epk_out_hashers[x].inputs[0] <== identity_secret; + epk_out_hashers[x].inputs[1] <== attester_id; + epk_out_hashers[x].inputs[2] <== from_epoch; + epk_out_hashers[x].inputs[3] <== epoch_tree_proof_valid[x].out * EPOCH_KEY_NONCE_PER_EPOCH + x; // nonce + epks[x] <== epk_out_hashers[x].out; + } + + // if an inclusion proof is not valid the newData must be 0 + component data_check[EPOCH_KEY_NONCE_PER_EPOCH][FIELD_COUNT]; + component proof_invalid[EPOCH_KEY_NONCE_PER_EPOCH]; + for (var x = 0; x < EPOCH_KEY_NONCE_PER_EPOCH; x++) { + proof_invalid[x] = IsZero(); + proof_invalid[x].in <== epoch_tree_proof_valid[x].out; + for (var y = 0; y < FIELD_COUNT; y++) { + data_check[x][y] = ForceEqualIfEnabled(); + data_check[x][y].enabled <== proof_invalid[x].out; + data_check[x][y].in[0] <== 0; + data_check[x][y].in[1] <== new_data[x][y]; + } + } + + /* End of check 2 */ + + /* 3. Calculate the new state tree leaf */ + + signal final_data[EPOCH_KEY_NONCE_PER_EPOCH][FIELD_COUNT]; + + var REPL_FIELD_COUNT = FIELD_COUNT - SUM_FIELD_COUNT; + + component index_check[EPOCH_KEY_NONCE_PER_EPOCH][REPL_FIELD_COUNT]; + component field_select[EPOCH_KEY_NONCE_PER_EPOCH][REPL_FIELD_COUNT]; + + for (var i = 0; i < EPOCH_KEY_NONCE_PER_EPOCH; i++) { + // first combine the sum data + for (var j = 0; j < SUM_FIELD_COUNT; j++) { + if (i == 0) { + final_data[i][j] <== data[j] + new_data[i][j]; + } else { + final_data[i][j] <== final_data[i-1][j] + new_data[i][j]; + } + } + // then combine the replacement data + for (var j = 0; j < REPL_FIELD_COUNT; j++) { + var field_i = SUM_FIELD_COUNT + j; + index_check[i][j] = UpperLessThan(REPL_NONCE_BITS); + index_check[i][j].in[0] <== new_data[i][field_i]; + if (i == 0) { + index_check[i][j].in[1] <== data[field_i]; + } else { + index_check[i][j].in[1] <== final_data[i-1][field_i]; + } + + field_select[i][j] = Mux1(); + field_select[i][j].s <== index_check[i][j].out; + if (i == 0) { + field_select[i][j].c[1] <== data[field_i]; + } else { + field_select[i][j].c[1] <== final_data[i-1][field_i]; + } + field_select[i][j].c[0] <== new_data[i][field_i]; + + final_data[i][field_i] <== field_select[i][j].out; + } + } + + component out_leaf_hasher = StateTreeLeaf(FIELD_COUNT); + out_leaf_hasher.identity_secret <== identity_secret; + out_leaf_hasher.attester_id <== attester_id; + out_leaf_hasher.epoch <== to_epoch; + for (var x = 0; x < FIELD_COUNT; x++) { + out_leaf_hasher.data[x] <== final_data[EPOCH_KEY_NONCE_PER_EPOCH - 1][x]; + } + state_tree_leaf <== out_leaf_hasher.out; + + /* End of check 3 */ +} diff --git a/tests/vulnerabilities/uo/UserStateTransition/test.json b/tests/vulnerabilities/uo/UserStateTransition/test.json new file mode 100644 index 0000000..78f89ed --- /dev/null +++ b/tests/vulnerabilities/uo/UserStateTransition/test.json @@ -0,0 +1,17 @@ +{ + "id": "UserStateTransition", + "source": "UniRep", + "project_url": "https://github.com/Unirep/Unirep/", + "commit": "0985a28c38c8b2e7b7a9e80f43e63179fdd08b89", + "internal": false, + "synthetic": false, + "tests": [ + { + "id": "UserStateTransition", + "file": "src/userStateTransition.circom", + "template": "UserStateTransition", + "main": "src/main.circom", + "description": "UserStateTransition template is missing range check, making output underconstrained." + } + ] +} diff --git a/tests/vulnerabilities/uo/WindowMulFix/src/main.circom b/tests/vulnerabilities/uo/WindowMulFix/src/main.circom index df07fce..bbc2e4e 100644 --- a/tests/vulnerabilities/uo/WindowMulFix/src/main.circom +++ b/tests/vulnerabilities/uo/WindowMulFix/src/main.circom @@ -1,5 +1,5 @@ pragma circom 2.0.0; -include "escalarmulany_dummy.circom"; +include "escalarmulfix_dummy.circom"; component main{public [in, base]} = WindowMulFixDummy();