diff --git a/test/fixtures/handleCommand/HonkVerifier.sol b/test/fixtures/handleCommand/HonkVerifier.sol index ef858e6..d67cda3 100644 --- a/test/fixtures/handleCommand/HonkVerifier.sol +++ b/test/fixtures/handleCommand/HonkVerifier.sol @@ -13,72 +13,72 @@ library HonkVerificationKey { logCircuitSize: uint256(19), publicInputsSize: uint256(155), ql: Honk.G1Point({ - x: uint256(0x1a7210ba96c8b6c29b36aee5b50314f5c4158b938bc01af8a806d891cfff6480), - y: uint256(0x145831f09c7528cf686ccfaa6690596a8a8fd2976c9e7f915fdfcd7af416c3d9) + x: uint256(0x18b752a9719df385402e0da2aa1c616ae952f68c8b431ba8dcf6584037877b68), + y: uint256(0x0625809b75796192402ace9b56a94630f1c0dd55b13a6fb879ae033a8d70bcdd) }), qr: Honk.G1Point({ - x: uint256(0x2c23a7bf1a4c0e1f8f6bd815fff6d2bc1e4adb170987d98727899a9dd7ecd5f7), - y: uint256(0x14ea777c20aab015bef566f664204487995b714ba5565f8b8f84ba1bd72f22b2) + x: uint256(0x2c871af167ea728c3865fdf6294e27265837f78b4e60f157c44eb4f1ffd7b4fb), + y: uint256(0x208930bc8277809ab17c2d07733265a87459802b600e1293b2b29915923b589b) }), qo: Honk.G1Point({ - x: uint256(0x16f662e2c4345028c82ac5a5b0121d0e7e4ee0b2085fa7fad70b2c17a998cc7d), - y: uint256(0x0ce1d12dd14f5209aa10a93c87701ded85802b3bd41ef7822e43e08402a4da5b) + x: uint256(0x0486c6e33ac2d2b106d4a2e9605592d30404e8574126fc46af221d52eaf1b96f), + y: uint256(0x25653d543494e349cdcf9a3ac26dbb7d14303c5a3f9ba2fa85f963244f98e60a) }), q4: Honk.G1Point({ - x: uint256(0x208969616f7d5aedbffa302e7090aea6af18d65a9d83d69df6ef1facd353d0e7), - y: uint256(0x179f6562d9adb58dd4103b16efc77c7d5e61ff80c77f1f5f26aded02c6d733f0) + x: uint256(0x176f1a95319910e6af5816e4b2dd2297653d31e34488a542461db10110a64010), + y: uint256(0x1059d39bd270befb1c302945e0227a0ffe2e565892996f5e41a4104da6d3e5ef) }), qm: Honk.G1Point({ - x: uint256(0x12921b19b24447ba841027ebe25dd5428781bda07af9d8ef6ff480946dd75e92), - y: uint256(0x2e9aa3501ceba3ac369602fd299c109e915891cb4a5f04e9b567c366c75c3b1f) + x: uint256(0x17616374b4d19ee9dbf0b5fd4f55c056a01a2c37f13582179123c029a6464cf3), + y: uint256(0x29430f9a9ad7a78521af10a66ae8d872598143bd9b3906b1b18708653c136673) }), qc: Honk.G1Point({ - x: uint256(0x0ce49c89c2833a08c0dfa578860de87188278085f2d6cde77a385e1b8f72785a), - y: uint256(0x068bb8e2a0ac69b84d51bf0f7f5a23a92ef5cf2a65a38781ffd24a5d8a7ba24e) + x: uint256(0x16b93f6a34d9be63f3fb39641a24754fabf66b3ef84719510184d31e88960e14), + y: uint256(0x078e786ba4aa4450fa85d791bf770410a2e02cfcb696b0151cd81fb45223b20f) }), qArith: Honk.G1Point({ - x: uint256(0x2e44810bb8254da0223f05a48f676b6cf9d937b1cbc9593d263743a76d017cec), - y: uint256(0x12ebaa449b5153bdeefeefd18db82c08315d2ec6e95610c898ed771fb953b4a8) + x: uint256(0x05e7db9a108d3b31d1426f3648f73c1f9872d546f8737230f21d3a3357ae3a0b), + y: uint256(0x2e2bd25caa71f691af0ec0d3b96e0635ab151246eae11c1a2cf5a4e18344db63) }), qDeltaRange: Honk.G1Point({ - x: uint256(0x0255d78987e5cb213de5ae10e81a36f0d218936d5f5b396787d5dce64118837a), - y: uint256(0x23fb776efa1c16e8f911648fb199823fb996aa54a7852b07f33c0cde5585b7e1) + x: uint256(0x302c0cfa4b8c81fa85cf576c9f3433b57034525d4454b169990f7046dc975bea), + y: uint256(0x1cf64074d693e617ad141cf8c79bc05f08d5ce400e490119ce2d3204ebc3398a) }), qElliptic: Honk.G1Point({ - x: uint256(0x175d1b3a92888ac3b21d5460919d7a8dee522fc3f7e7588bb7937356f31318a6), - y: uint256(0x258d10d36596389fef6524bbe933afff25a338f8a1be8ea6eff462ebb699df6f) + x: uint256(0x150dabe9217c03a5987bffbaa48d037f72e1f6b6ecd6d65a6827838e0c86c543), + y: uint256(0x049ba9c2e39c978e5863586319f34d777f8ae326b651a7d2b691ce1b29e6c15f) }), qAux: Honk.G1Point({ - x: uint256(0x25f6b4c4b1d0bf5b8bc82298369a1c49d41c83e922502d9ee6cefa2f1f2b1ae4), - y: uint256(0x0fca6cf0671751857d3f332e12041afd19dc4aa03cc341516bdf30c2d52f9787) + x: uint256(0x12205baf40241ba21dcba60acebfce1181053038de948e34d02ddd2295f14112), + y: uint256(0x1b0e8530cef504029f1a1c58f2501a3204727409a3d45173427aea12ed3b9bc5) }), qLookup: Honk.G1Point({ x: uint256(0x076d3f92dff00e732053b3b7248f2949cf66e2ff39fe4800f14a23555881d08f), y: uint256(0x19f2844ef71502fada8390017b2881c376c839f0a71748129279029ec262c939) }), qPoseidon2External: Honk.G1Point({ - x: uint256(0x27965a98c47f39936cae1d648b2ea446f3bfcb7c72e041f56d2c6f0a00d01f0b), - y: uint256(0x2855b07d2683d0ec1b7636353522722b2027eb33e77d1b7c78857ec1efdcef08) + x: uint256(0x0e99bacc30295c9188e6c98128019b67744160964d42a476b88537e6aad7c5d2), + y: uint256(0x07e1e801e0d538482fee19b60a407d68b00cab9cee5fff1dfc3a08ec357076e4) }), qPoseidon2Internal: Honk.G1Point({ - x: uint256(0x1e9291bd098e69bc19943506da6c5fa5930482c111625bb10561bc7dd2b74216), - y: uint256(0x22a4094030024039e3a35ed18c464923e19ece17a26a11c593a43d93113f4cf7) + x: uint256(0x12a0f64b474eb0aadce0889f8c874e1e515e4b6d9b23c4b3d784d6a274d110ae), + y: uint256(0x05cf51aa01ce37bf94abec725fc27235c5576c15a75d12b5b4b55271f1a3a592) }), s1: Honk.G1Point({ - x: uint256(0x15dc27b81554a6ddb3a649a3e101c0a224fa707e1dc302fad9a69eaa08bd4f38), - y: uint256(0x129a625be0f4d4897a4166d8941edcb4eaae32867d331292f88e8e44cd38d155) + x: uint256(0x2337c1e994c3f461dee70c5fbb9252de9c4c5625bbf00554b2568e7f8ad11c7d), + y: uint256(0x168652c411ef0f224873b9351dc16c0772bfe76cac941febfb32769b24e46595) }), s2: Honk.G1Point({ - x: uint256(0x29512e721dcdbe168ff49b6f5c03e9448bb0b84a2b3a2775a1458ad7e2c1c32e), - y: uint256(0x13df76c9ac079116a26bed197bcb59c44b868ce6a09e7d720e1c16a403fc1726) + x: uint256(0x18acce41f1d7ff23338dfdc9dbc5ecf83778a0b8ae5137cbffbfa47ae161e101), + y: uint256(0x209f48d6f3f0bbe319d10695e8e93042bcd9cc0f84faaf2f79ca573ea0f19f38) }), s3: Honk.G1Point({ - x: uint256(0x2aa14957fae029f300cd9212dabece20eab126fe10b485a34a9f0362e306f76f), - y: uint256(0x2f3ca257e590e53f7c5211db28e97a10cf0caa33966acc9ba02c3b3d43c420c6) + x: uint256(0x1a59b92da6637092373550f26d237355030bbb28830cb30341fa9f26bcbd2350), + y: uint256(0x2fac8c4d54269aae9c43ffdd443aaa5c6ff7474b3ccd32cec5a64db2bc899b8a) }), s4: Honk.G1Point({ - x: uint256(0x15f830463f615fd2f06f785f54414e650ae5843a97ab6a66a226821cdbe40d30), - y: uint256(0x05ec0391af74def03aebc53630b022f0fdd01f00198aa189a995813eac300a97) + x: uint256(0x2846d21246c001ffeb8602f48e6f4d60db3d4d2174bb0760970ad85c39c75027), + y: uint256(0x28321db79bd22880a9d3d04c97e1ce3a9283b24d6697afdca4f1ad2892b33440) }), t1: Honk.G1Point({ x: uint256(0x0dd222939afbd6003c7c4f993cb8c1febbcc78eabdd8f8c64b3cc99d67e7bc60), @@ -97,28 +97,28 @@ library HonkVerificationKey { y: uint256(0x16e024c7a0985ad98c91599ff8b5478356f289b60c9cb110eaa18ea464d89faa) }), id1: Honk.G1Point({ - x: uint256(0x191750aa8336e364f0d08db18bb521f1bce1b46700e71f791eda547d98aff253), - y: uint256(0x2a68390660612925ccc0abb53a966562d3402e3673cfd27b431d39f34f1da311) + x: uint256(0x07d606554d33deb837a2c7f39f5ed5146ceb803fef03be296568da276ab15bcc), + y: uint256(0x2c005c83adb9c45d6ab5a98df45f55cf7f6430809af4ca6a02549430f0b4469f) }), id2: Honk.G1Point({ - x: uint256(0x07a4f4a3b0922201179fabd2354553d553480b0d7868d8a70744072e8bde71b3), - y: uint256(0x063feb660361d9bc79992f7b1ceb8bf1836944190bcd45e80fc47d79cfc1b61e) + x: uint256(0x1ca2feb02fade345a94f8277e8734eb508c32f75e252f9502e7bc1e3271be32b), + y: uint256(0x2baec32f3d1c057178d4a2219b514d1811b65f8d5786bf8899a1de58ac5d1c11) }), id3: Honk.G1Point({ - x: uint256(0x1bc6c2aa9d5c73803e9245b07f86394fcb8c721d5acaeeef31e177be41463e50), - y: uint256(0x0192e119993e4aa355eb6183a27377b0363927cbc3868bdbb0cc609d9d73afb3) + x: uint256(0x132d52191c3a5a7390f22fd19ce96f540da2c30612ad857e79e8cb1b9d21e9ea), + y: uint256(0x03ad25ea91998a74ef9a5385f00f4004a20d6deaf23ec859c40ae83b9ab01b35) }), id4: Honk.G1Point({ - x: uint256(0x151189c9e62b0ef38907ab84cb472ccedb531721266dea883dbf250245b6251b), - y: uint256(0x1f1416bb4654f31e85e2049a732b5dfd90f036689bce26ee5417779abe44f1e9) + x: uint256(0x099e9f8770819c2a0e56de0f2fd35ef5163546e91fe8b59196322e562bb39e29), + y: uint256(0x1e3210741e94c195e02037858a13caea9645356d3fb135fe54f0e3e97b4baa9c) }), lagrangeFirst: Honk.G1Point({ x: uint256(0x0000000000000000000000000000000000000000000000000000000000000001), y: uint256(0x0000000000000000000000000000000000000000000000000000000000000002) }), lagrangeLast: Honk.G1Point({ - x: uint256(0x1e813ba4f3d08a3289ed528ef9425189ae1240e7c2e086dd4c267edd6b729705), - y: uint256(0x1bb8bb58399caa55cccf0b1fb45ca0a9ca863c2769aacb6f79bf6839f8b142a6) + x: uint256(0x27f7011ea2a9c76b1ba1d6f4757ca17cc5a88f6696d5ce783175f86556d1b842), + y: uint256(0x300d1a0de2a13886a3c842b4b2af5735b84a55e2884a77546620e7597cf67bd9) }) }); return vk; diff --git a/test/fixtures/handleCommand/circuit/src/main.nr b/test/fixtures/handleCommand/circuit/src/main.nr index 0bf2d04..f6f84f4 100644 --- a/test/fixtures/handleCommand/circuit/src/main.nr +++ b/test/fixtures/handleCommand/circuit/src/main.nr @@ -56,6 +56,18 @@ fn main( remove_soft_line_breaks(body.storage(), decoded_body.storage()), "Decoded body does not properly remove soft line breaks", ); + // NOTE: the DKIM signature covers header[0..header.len()) and the body hash covers + // body[0..body.len()). Every regex below must read only those signed bytes (see + // `signed_decoded_len` for why storage past len() cannot be trusted). + assert( + x_handle_match_start + x_handle_match_length <= signed_decoded_len(body), + "Body regex match must lie within the signed body", + ); + assert( + sender_domain_match_start + sender_domain_match_length <= header.len(), + "Header regex match must lie within the signed header", + ); + let pubkey_hash = pubkey.hash(); let email_nullifier = poseidon::bn254::hash_1([poseidon_large(signature)]); let x_handle_capture_1 = x_handle_regex::regex_match::<1920, 64>( @@ -83,3 +95,33 @@ fn main( x_handle_capture_1, sender_domain_capture_1, ) } + +/** + * Number of leading bytes of the soft-line-break-decoded body that come from signed body bytes. + * + * REASON: BoundedVec storage past len() is a free prover input. sha256_var and + * partial_sha256_var_end only hash bytes [0, len()), zk-regex's select_subarray only bounds a + * match by the array capacity, and remove_soft_line_breaks decodes the whole storage, so bytes past + * body.len() also reach decoded_body. A regex match must therefore end before the decoded image of + * body.len(), otherwise the public outputs need not come from the signed email. + * + * NOTE: we bound matches instead of asserting the storage tail is zero, because legitimate input + * generators fill it differently: relayer-utils zero-pads, but zkemail.nr's JS generator leaves + * SHA-256 padding there and sets decoded_body.len() past the signed content. decoded_body.len() is + * therefore not trusted either. Each "=\r\n" that starts inside the signed body removes 3 bytes. A + * break that straddles body.len() is also counted, which only makes the bound stricter. + */ +fn signed_decoded_len(body: BoundedVec) -> u32 { + let storage = body.storage(); + let len = body.len(); + let mut removed: u32 = 0; + for i in 0..N - 2 { + let is_break = (storage[i] == 0x3D) & (storage[i + 1] == 0x0D) & (storage[i + 2] == 0x0A); + removed += ((is_break & (i < len)) as u32) * 3; + } + if removed > len { + 0 + } else { + len - removed + } +} diff --git a/test/fixtures/handleCommand/files/claimX/proof b/test/fixtures/handleCommand/files/claimX/proof index cee998d..be0ac75 100644 Binary files a/test/fixtures/handleCommand/files/claimX/proof and b/test/fixtures/handleCommand/files/claimX/proof differ diff --git a/test/fixtures/linkHandleCommand/check_signed_bounds.py b/test/fixtures/linkHandleCommand/check_signed_bounds.py new file mode 100755 index 0000000..02dabd9 --- /dev/null +++ b/test/fixtures/linkHandleCommand/check_signed_bounds.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 +"""Regression test: regex matches must come from DKIM-signed bytes. + +Runs `nargo execute` on a circuit with: +- the sample inputs, and the same email laid out the way other input generators + lay it out (SHA-256 padding left in the storage tail), which must succeed; +- inputs that copy a regex match into BoundedVec storage past `len()`, which + must fail. + +NOTE: bytes of `header`, `body` and `decoded_body` at index >= len() are not +covered by the DKIM signature or the body hash, so a circuit that lets a regex +read them would output values that are not in the signed email. + +Usage (needs nargo 1.0.0-beta.5 on PATH): + ./check_signed_bounds.py twitter + ./check_signed_bounds.py +""" + +import os +import re +import shutil +import subprocess +import sys +import tempfile + +HERE = os.path.dirname(os.path.abspath(__file__)) +PRESETS = { + "twitter": (os.path.join(HERE, "twitter"), "Prover.sample.toml", "x_handle"), +} + + +def section(text, name): + m = re.search(r"(\[%s\]\s*\n)(.*?)(?=\n\[|\Z)" % re.escape(name), text, re.S) + assert m, name + return m + + +def get_list(body, key): + raw = re.search(r"(?m)^%s\s*=\s*\[([^\]]*)\]" % key, body).group(1) + return [int(x.strip().strip('"'), 0) for x in raw.split(",") if x.strip()] + + +def get_int(text, key): + return int(re.search(r'(?m)^%s\s*=\s*"?(\d+)"?' % key, text).group(1)) + + +def set_list(body, key, values): + return re.sub( + r"(?m)^(%s\s*=\s*)\[[^\]]*\]" % key, + lambda m: m.group(1) + "[" + ", ".join('"%d"' % v for v in values) + "]", + body, + count=1, + ) + + +def set_int(text, key, value): + return re.sub(r'(?m)^(%s\s*=\s*)"?\d+"?' % key, lambda m: m.group(1) + '"%d"' % value, text, count=1) + + +def write_tail(text, name, payload, at=None): + """Write payload into `name`'s storage at `at` (default: len), leaving len unchanged.""" + m = section(text, name) + storage = get_list(m.group(2), "storage") + start = get_int(m.group(2), "len") if at is None else at + storage[start : start + len(payload)] = list(payload) + body = set_list(m.group(2), "storage", storage) + return text[: m.start(2)] + body + text[m.end(2) :], start + + +def body_tail_case(text, regex): + # Copy the signed body match (same NFA states) into the unsigned tail of both body arrays, + # then point the body regex at the copy in decoded_body. + m = section(text, "decoded_body") + storage = get_list(m.group(2), "storage") + start, length = get_int(text, regex + "_match_start"), get_int(text, regex + "_match_length") + payload = bytes(storage[start : start + length]) + text, _ = write_tail(text, "body", payload) + text, tail = write_tail(text, "decoded_body", payload) + return set_int(text, regex + "_match_start", tail) + + +def header_tail_case(text, _regex): + m = section(text, "header") + storage = get_list(m.group(2), "storage") + start, length = get_int(text, "sender_domain_match_start"), get_int(text, "sender_domain_match_length") + text, tail = write_tail(text, "header", bytes(storage[start : start + length])) + return set_int(text, "sender_domain_match_start", tail) + + +def sha_pad_tail(storage, length, total_len): + """SHA-256-pad storage[length:] in place, the way sha256Pad-based generators do.""" + storage[length] = 0x80 + end = ((length + 1 + 8 + 63) // 64) * 64 + if end <= len(storage): + storage[end - 8 : end] = list((total_len * 8).to_bytes(8, "big")) + + +def decode_soft_breaks(storage): + out, i = [], 0 + while i < len(storage): + if i + 2 < len(storage) and storage[i : i + 3] == [0x3D, 0x0D, 0x0A]: + i += 3 + else: + out.append(storage[i]) + i += 1 + return out + [0] * (len(storage) - len(out)), len(out) + + +def js_generator_layout(text, _regex): + # zkemail.nr's JS generator leaves SHA-256 padding past header.len() and body.len(), decodes + # soft line breaks over the whole storage, and sets decoded_body.len() to the decoded length + # of that whole storage. Valid email, so it must still prove. + for name in ("header", "body"): + m = section(text, name) + storage = get_list(m.group(2), "storage") + length = get_int(m.group(2), "len") + sha_pad_tail(storage, length, length) + text = text[: m.start(2)] + set_list(m.group(2), "storage", storage) + text[m.end(2) :] + if name == "body": + decoded, count = decode_soft_breaks(storage) + d = section(text, "decoded_body") + body = set_list(d.group(2), "storage", decoded) + body = re.sub(r'(?m)^(len\s*=\s*)"?\d+"?', lambda mm: mm.group(1) + '"%d"' % count, body) + text = text[: d.start(2)] + body + text[d.end(2) :] + return text + + +def run(circuit_dir, prover_text, label): + with tempfile.TemporaryDirectory() as tmp: + work = os.path.join(tmp, "circuit") + shutil.copytree(circuit_dir, work, ignore=shutil.ignore_patterns("target", "files")) + with open(os.path.join(work, "Prover.toml"), "w") as f: + f.write(prover_text) + res = subprocess.run(["nargo", "execute"], cwd=work, capture_output=True, text=True) + print("[%s] nargo execute -> exit %d" % (label, res.returncode)) + return res.returncode == 0, res.stdout + res.stderr + + +def main(): + if len(sys.argv) == 2: + circuit_dir, prover, regex = PRESETS[sys.argv[1]] + elif len(sys.argv) == 4: + circuit_dir, prover, regex = sys.argv[1], sys.argv[2], sys.argv[3] + else: + sys.exit(__doc__) + with open(os.path.join(circuit_dir, prover)) as f: + base = f.read() + + for label, text in (("valid", base), ("valid-sha-padded-tail", js_generator_layout(base, regex))): + ok, out = run(circuit_dir, text, label) + if not ok: + sys.exit("%s inputs failed:\n%s" % (label, out[-3000:])) + + failures = [] + for label, mutate, expected in ( + ("body-tail", body_tail_case, "Body regex match must lie within the signed body"), + ("header-tail", header_tail_case, "Header regex match must lie within the signed header"), + ): + ok, out = run(circuit_dir, mutate(base, regex), label) + if ok or expected not in out: + failures.append("%s: expected failure '%s'\n%s" % (label, expected, out[-2000:])) + if failures: + sys.exit("\n".join(failures)) + print("all signed-bounds checks passed") + + +if __name__ == "__main__": + main() diff --git a/test/fixtures/linkHandleCommand/discord/src/main.nr b/test/fixtures/linkHandleCommand/discord/src/main.nr index 8504808..a84b8ca 100644 --- a/test/fixtures/linkHandleCommand/discord/src/main.nr +++ b/test/fixtures/linkHandleCommand/discord/src/main.nr @@ -59,6 +59,18 @@ fn main( remove_soft_line_breaks(body.storage(), decoded_body.storage()), "Decoded body does not properly remove soft line breaks", ); + // NOTE: the DKIM signature covers header[0..header.len()) and the body hash covers + // body[0..body.len()). Every regex below must read only those signed bytes (see + // `signed_decoded_len` for why storage past len() cannot be trusted). + assert( + handle_match_start + handle_match_length <= signed_decoded_len(body), + "Body regex match must lie within the signed body", + ); + assert( + sender_domain_match_start + sender_domain_match_length <= header.len(), + "Header regex match must lie within the signed header", + ); + let pubkey_hash = pubkey.hash(); let email_nullifier = poseidon::bn254::hash_1([poseidon_large(signature)]); let handle_capture_1 = handle_regex::regex_match::<16384, 64>( @@ -83,3 +95,33 @@ fn main( ); (pubkey_hash, email_nullifier, header_hash[0], header_hash[1], prover_address, command, handle_capture_1, sender_domain_capture_1) } + +/** + * Number of leading bytes of the soft-line-break-decoded body that come from signed body bytes. + * + * REASON: BoundedVec storage past len() is a free prover input. sha256_var and + * partial_sha256_var_end only hash bytes [0, len()), zk-regex's select_subarray only bounds a + * match by the array capacity, and remove_soft_line_breaks decodes the whole storage, so bytes past + * body.len() also reach decoded_body. A regex match must therefore end before the decoded image of + * body.len(), otherwise the public outputs need not come from the signed email. + * + * NOTE: we bound matches instead of asserting the storage tail is zero, because legitimate input + * generators fill it differently: relayer-utils zero-pads, but zkemail.nr's JS generator leaves + * SHA-256 padding there and sets decoded_body.len() past the signed content. decoded_body.len() is + * therefore not trusted either. Each "=\r\n" that starts inside the signed body removes 3 bytes. A + * break that straddles body.len() is also counted, which only makes the bound stricter. + */ +fn signed_decoded_len(body: BoundedVec) -> u32 { + let storage = body.storage(); + let len = body.len(); + let mut removed: u32 = 0; + for i in 0..N - 2 { + let is_break = (storage[i] == 0x3D) & (storage[i + 1] == 0x0D) & (storage[i + 2] == 0x0A); + removed += ((is_break & (i < len)) as u32) * 3; + } + if removed > len { + 0 + } else { + len - removed + } +} diff --git a/test/fixtures/linkHandleCommand/discord/target/HonkVerifier.sol b/test/fixtures/linkHandleCommand/discord/target/HonkVerifier.sol index 353694b..5103462 100644 --- a/test/fixtures/linkHandleCommand/discord/target/HonkVerifier.sol +++ b/test/fixtures/linkHandleCommand/discord/target/HonkVerifier.sol @@ -12,72 +12,72 @@ library HonkVerificationKey { logCircuitSize: uint256(22), publicInputsSize: uint256(155), ql: Honk.G1Point({ - x: uint256(0x23afee21da447fb5a76c4e253fba8b95865b01fd9101e1e602db6fe5ff0ec998), - y: uint256(0x062493e3352ca171155cb388ccb245fac1ac265216d3541b33baaaa579952515) + x: uint256(0x115dca9784ed228363579ae48e72f56ab8343f6d1dbe75a49cab33b47bb60d3a), + y: uint256(0x05725dbd6b47b3d827776b886bd5927bf2e3c946b79e19cbb7b0c44c9f45a3a1) }), qr: Honk.G1Point({ - x: uint256(0x1d498504c07f6ff3e2d99c0da4bd9e83807cd43018f6ef032511f4aea5866441), - y: uint256(0x1e5bb8bc4ff02dc7ad84d33b2dc0e43efab6db0815a44454e2cdfb12b34d65d8) + x: uint256(0x2fc8369260bb12f635d53030594bd7aef370d3b650129efb3929dcccedac3965), + y: uint256(0x24d17c8117dfcda2057c7d75b69855bc90a23e9159e2056dc7a82ba3ca1d4d6e) }), qo: Honk.G1Point({ - x: uint256(0x11d5abff7c939aaf4fa6b73c196245077a299d47db60416e8dc3a25856bbbb0d), - y: uint256(0x25d9b51635456b5e8e8ff3f3bedf5a70089d0798d5b0b6aed3738672d8a036c9) + x: uint256(0x15a7b093df5b2c8fe0123ffbd65a02efd486a86a4fa88494a0b2877f7bde0649), + y: uint256(0x2ba9376e8f604335927431f9f17463f17fb0d85740494e640dfd9b2f1cfa1f50) }), q4: Honk.G1Point({ - x: uint256(0x01dd78b86a39ee41b7f485f02684fd8004bfdb7c4533682055b1b22fa45393ac), - y: uint256(0x01ac0ae7f2e2a64313f38f48d3a7ddfca95109d3a6cd1c3eaead38525b02859e) + x: uint256(0x11b39881c33b57e85e760906193ce666ead02561b4574cb8e3d22d16fbb13ad1), + y: uint256(0x07066041efb2c4a5785e12ada58db442ea8f989d7049d0b3b52c9e32c2a897f3) }), qm: Honk.G1Point({ - x: uint256(0x108587dd9a12fd7bd93e6683550ed3bbc698ef1d57c35440d26c72f198e612eb), - y: uint256(0x1cca0587bd22c8cea145f522c24c5489a94e982bc1ef380309b0986f35175e39) + x: uint256(0x2d89ec64cc9362a38a98f0c2ecf640f845004b29b11365116a389694e2d4f449), + y: uint256(0x2cb0b910c4ca4a6ac6a2ae2f21115ddf94c500b901b72cef9a2ec565adbeb285) }), qc: Honk.G1Point({ - x: uint256(0x115b3e4f5c998c3f6f81d7f278154a23f33a049d028ae9a2303c93d76ea25311), - y: uint256(0x006a89772112a53de280bdbca8b0eda2d64dd0b03078db8fda5a5ae98161ce05) + x: uint256(0x291d7989a3cd5dd336c9ccd7ef0e59320f038333dd9c7041add9181f86bbb472), + y: uint256(0x0e1ade07dfb6607c8cebdb2a54e9ac7cc543a3be0d1ccee04451f12be9aaa03d) }), qArith: Honk.G1Point({ - x: uint256(0x0a41049029fd6e3f10fe7dd68c3f0525ccce38544cc1113dad004305ceaf09e3), - y: uint256(0x1d0e7c70353b718acf9203e1ff5df94e9d856f7d0c6fa2645bf11ba8f6c129b7) + x: uint256(0x1b526ddd2f1458394c4cac95e5fbb93484b2706636847c8536fb40caf8889edb), + y: uint256(0x01e42a7ee3b4b11ad33451de03cc5f40746f538dc207bc66c05ab86a5ddd30ad) }), qDeltaRange: Honk.G1Point({ - x: uint256(0x0228b025cf350c932689e2396b574319522f92294354e46126cc3bda5dbb50a3), - y: uint256(0x0599283f6241e9ec0e12b125d22602b85acec61492b262e86c0d363bf8911249) + x: uint256(0x2c3f5d9cf51d2d7d40d8a2c78121c9a782f53ee13ecabb876ce764c4e56e9c98), + y: uint256(0x273d8a02d0adc0652bd85b7153447d340857e591be08ca3799d65f285c8220e6) }), qElliptic: Honk.G1Point({ - x: uint256(0x226e44cbb149593bbd86a4e274c493bc02f2b2645dfd6736e97bdbd39673b099), - y: uint256(0x0c41108a58d4edef45295bc88cc5c0abb90be6f9054a0dca30f7507a9bfa802a) + x: uint256(0x1ffce0832029ff62226bc78933a1358c6c0d3fc7ead64b9eaa1b391bae28b493), + y: uint256(0x155e850946abb1455a2384687112a9ab8a1c2825ae70258bb7948ef5a42f507b) }), qAux: Honk.G1Point({ - x: uint256(0x1207c0ff706ccf7a8c2055244bd4c26ecc7a93488767cc7823b3ccc54b8c0114), - y: uint256(0x22cd0538a772a0a025fb3ab6ab06f710d0b5a71e79d01ca8abae94182a2121b4) + x: uint256(0x0e740fb73fb4109ccbd0aaa091676ce8045f54741109904cafb8f759b206f054), + y: uint256(0x0789f877bd8e26f1dda757a15db78087d1eb7dbc350dcba988c5a1726751509e) }), qLookup: Honk.G1Point({ x: uint256(0x2e15b1c9a5d33749d695e65ba23ea6c681fab487a1ee82024b53a1452bd3f1ff), y: uint256(0x14087d9d9572a9b15f4d902599dd65c96ac2c21ae70a5b42fd80c00fc0ad744b) }), qPoseidon2External: Honk.G1Point({ - x: uint256(0x0c0158a38a22ca8b5de90a21d6366e76b18ec7d13c755b0ae4de8551246a6029), - y: uint256(0x1d49bb0373f7b06b7638c618bed271d1086ff93f899b1a9f434066f24c3c3e9d) + x: uint256(0x2b074aa51cff735348c2b49e8d5951a41e2980fec1db0cf13615d9fcc7243ece), + y: uint256(0x2ff4d2b545213693f0bb672851229dc42cd4801fd4dc76eb124a6176a57c9f95) }), qPoseidon2Internal: Honk.G1Point({ - x: uint256(0x2eb6fe698e81182b1fcba226bda490e7090689cae69b40e2832bbc16b00dcb8d), - y: uint256(0x0f094096c577182e6034426e96d4ca171efc67d98edd3f01baa1e3fef9f5ec39) + x: uint256(0x17969c435dab903de948631bfa80df568b8017dfe0cd1e8b9a3f921ee262bd02), + y: uint256(0x049b88da3c047aed78574a6f9b8cd0f7f4c54f020e4d5dfcb671bd249a868e88) }), s1: Honk.G1Point({ - x: uint256(0x161e274ea92285d35f15d04821887cdff535318246275e1b8212786fc79daa1b), - y: uint256(0x291eec225637937c3aba2dbb7cd524a5eed50ec31a7f5445c8bbd0f21325fb8b) + x: uint256(0x05a221b859cf2b4ffc17405fa6b5f5042106a058bd9099de23fe13dd9bed5d9d), + y: uint256(0x04d1434c6a3d61bdc90206eb4fdbf1d7f3f2b2f0650b3df01f3e85da205e868b) }), s2: Honk.G1Point({ - x: uint256(0x1fa6c416867313ad17362922eeea34561599dfbdaed668be31b15d132cbf70a5), - y: uint256(0x21b90a6f68b9b7b12f5aa10d32d8b4dacd810021212c8c9b04fcfaef4f1eebed) + x: uint256(0x10451524764cf2d3acc2bc6114f85e3710a0792c5b94fe9fa8f31fc62b62fa6e), + y: uint256(0x09149519ba3252f4d69071763beca0516720c099250a5128943e2b2cf43ee1e8) }), s3: Honk.G1Point({ - x: uint256(0x011b2fb021ae5a26944add06c1433c542df4d9eb22da4c0b32b8259c7a01fb44), - y: uint256(0x179c398a122d3c2ad44f9d350fcdacdfe2af5d187e0f3e3b258ccd0f3e4c7216) + x: uint256(0x185d1444764989c41650fa01486bdbe7cc03415a0358ce897be6420084e29e86), + y: uint256(0x26a888d1cdc379780f502a6b2db5fbe209876d28e15bddfc54f99dab78a81301) }), s4: Honk.G1Point({ - x: uint256(0x23492f99f6e11bddc58ece856f01dc1d26f6334ee717694c1d4b786cc0739c8a), - y: uint256(0x18170fd99f335c49c2beb4fcfa94fbff6f5c8fcd6d7f09c80ad3874fc03de5e5) + x: uint256(0x1d1a2c2678a1afe47b0badce67cef95a535e405f24cfa8635769aa0343845541), + y: uint256(0x0fa1ffa90c732f404d38c40c7b0e385ebcd35561468df2b49efc00f0c52ed892) }), t1: Honk.G1Point({ x: uint256(0x0dd222939afbd6003c7c4f993cb8c1febbcc78eabdd8f8c64b3cc99d67e7bc60), @@ -96,28 +96,28 @@ library HonkVerificationKey { y: uint256(0x16e024c7a0985ad98c91599ff8b5478356f289b60c9cb110eaa18ea464d89faa) }), id1: Honk.G1Point({ - x: uint256(0x061f9901934b05b6b06e897381c65486e27e8e4681c90cbf07b6421bfb0274fa), - y: uint256(0x0365db65577f2069eb26cbdb2ea2aabbd8f0266f8ebe89816a12b0dc740a53cf) + x: uint256(0x238f5f9d0e2b6df790106cf3616ba809a52eced4ef808295230952660cfdca99), + y: uint256(0x10c26379bcd673c85a16507de1438fde9c9ecf297f17ecbd0415ddcf133c772a) }), id2: Honk.G1Point({ - x: uint256(0x138270ada2b5fff86ef723defff4eb2806ce7bd719318120cf12b2d0f29a3a57), - y: uint256(0x1a22908eb5901d54f106747391e93ac0374102830d58f0e1f0ad1857c8b5770c) + x: uint256(0x01e0bf9ef8852a539b6f1d7c037f66b620c5575cfa3ac64b634dd98935f45b21), + y: uint256(0x2231f893161bc1ceb830cd06e18aba59891622c826082f26a844dbdae291a181) }), id3: Honk.G1Point({ - x: uint256(0x0cc579974f6e77395dd25119e16a69a1d8807cccc581d08cf04302edf4833647), - y: uint256(0x24c80e8e606bf6716c8d869979ce7fb55ec602f760a940e87960f85edf0eff2e) + x: uint256(0x24db22c49cb4ec6b3e35dcf1e69fc607e32c78f477cd9432bdb923e80b075781), + y: uint256(0x26d03ea64c45cf79159065a73b48ff4de79c5b133471c350c0cc6ebb5706f87f) }), id4: Honk.G1Point({ - x: uint256(0x1d3586f6822e7789532bd6cc53f79ac51fe228bc37f88e0dc68d45b537f448a7), - y: uint256(0x187ea8ec171b05f6248cbf301683e89a40da838c6c0ac615bb5e59f373bf9328) + x: uint256(0x15ca9e3c5c0d7458cbf546d192eb5fbb46d74ef5c2d173bda34bad288675ca72), + y: uint256(0x2604e8946fdd3fec6c89fea0b3261fb8c94b84bfe6c4c04fbc483704eec0998d) }), lagrangeFirst: Honk.G1Point({ x: uint256(0x0000000000000000000000000000000000000000000000000000000000000001), y: uint256(0x0000000000000000000000000000000000000000000000000000000000000002) }), lagrangeLast: Honk.G1Point({ - x: uint256(0x2eaa57566d8cc9032927ebb0b47305163f5dcb232599ad9cb5f086622f37f9e9), - y: uint256(0x1068c3cd1838f6e2fdd17a86f167e2189259e7be3826607ae5af823c0bbd54b4) + x: uint256(0x18ac03a664f9ca70120bf949fd10437086f263ab2a789ce077df7e4c0aaeb016), + y: uint256(0x18222960bae82ff4dc79fa0cae40ab0b45042318c57f831949162cba464fa96b) }) }); return vk; diff --git a/test/fixtures/linkHandleCommand/twitter/files/proof b/test/fixtures/linkHandleCommand/twitter/files/proof index 58a4dbd..60e667f 100644 Binary files a/test/fixtures/linkHandleCommand/twitter/files/proof and b/test/fixtures/linkHandleCommand/twitter/files/proof differ diff --git a/test/fixtures/linkHandleCommand/twitter/src/main.nr b/test/fixtures/linkHandleCommand/twitter/src/main.nr index 7263c3a..c59eca5 100644 --- a/test/fixtures/linkHandleCommand/twitter/src/main.nr +++ b/test/fixtures/linkHandleCommand/twitter/src/main.nr @@ -59,6 +59,18 @@ fn main( remove_soft_line_breaks(body.storage(), decoded_body.storage()), "Decoded body does not properly remove soft line breaks", ); + // NOTE: the DKIM signature covers header[0..header.len()) and the body hash covers + // body[0..body.len()). Every regex below must read only those signed bytes (see + // `signed_decoded_len` for why storage past len() cannot be trusted). + assert( + x_handle_match_start + x_handle_match_length <= signed_decoded_len(body), + "Body regex match must lie within the signed body", + ); + assert( + sender_domain_match_start + sender_domain_match_length <= header.len(), + "Header regex match must lie within the signed header", + ); + let pubkey_hash = pubkey.hash(); let email_nullifier = poseidon::bn254::hash_1([poseidon_large(signature)]); let x_handle_capture_1 = x_handle_regex::regex_match::<1920, 64>( @@ -83,3 +95,33 @@ fn main( ); (pubkey_hash, email_nullifier, header_hash[0], header_hash[1], prover_address, command, x_handle_capture_1, sender_domain_capture_1) } + +/** + * Number of leading bytes of the soft-line-break-decoded body that come from signed body bytes. + * + * REASON: BoundedVec storage past len() is a free prover input. sha256_var and + * partial_sha256_var_end only hash bytes [0, len()), zk-regex's select_subarray only bounds a + * match by the array capacity, and remove_soft_line_breaks decodes the whole storage, so bytes past + * body.len() also reach decoded_body. A regex match must therefore end before the decoded image of + * body.len(), otherwise the public outputs need not come from the signed email. + * + * NOTE: we bound matches instead of asserting the storage tail is zero, because legitimate input + * generators fill it differently: relayer-utils zero-pads, but zkemail.nr's JS generator leaves + * SHA-256 padding there and sets decoded_body.len() past the signed content. decoded_body.len() is + * therefore not trusted either. Each "=\r\n" that starts inside the signed body removes 3 bytes. A + * break that straddles body.len() is also counted, which only makes the bound stricter. + */ +fn signed_decoded_len(body: BoundedVec) -> u32 { + let storage = body.storage(); + let len = body.len(); + let mut removed: u32 = 0; + for i in 0..N - 2 { + let is_break = (storage[i] == 0x3D) & (storage[i + 1] == 0x0D) & (storage[i + 2] == 0x0A); + removed += ((is_break & (i < len)) as u32) * 3; + } + if removed > len { + 0 + } else { + len - removed + } +} diff --git a/test/fixtures/linkHandleCommand/twitter/target/HonkVerifier.sol b/test/fixtures/linkHandleCommand/twitter/target/HonkVerifier.sol index 28bd7b2..ebcac60 100644 --- a/test/fixtures/linkHandleCommand/twitter/target/HonkVerifier.sol +++ b/test/fixtures/linkHandleCommand/twitter/target/HonkVerifier.sol @@ -12,72 +12,72 @@ library HonkVerificationKey { logCircuitSize: uint256(19), publicInputsSize: uint256(155), ql: Honk.G1Point({ - x: uint256(0x1a7210ba96c8b6c29b36aee5b50314f5c4158b938bc01af8a806d891cfff6480), - y: uint256(0x145831f09c7528cf686ccfaa6690596a8a8fd2976c9e7f915fdfcd7af416c3d9) + x: uint256(0x18b752a9719df385402e0da2aa1c616ae952f68c8b431ba8dcf6584037877b68), + y: uint256(0x0625809b75796192402ace9b56a94630f1c0dd55b13a6fb879ae033a8d70bcdd) }), qr: Honk.G1Point({ - x: uint256(0x2c23a7bf1a4c0e1f8f6bd815fff6d2bc1e4adb170987d98727899a9dd7ecd5f7), - y: uint256(0x14ea777c20aab015bef566f664204487995b714ba5565f8b8f84ba1bd72f22b2) + x: uint256(0x2c871af167ea728c3865fdf6294e27265837f78b4e60f157c44eb4f1ffd7b4fb), + y: uint256(0x208930bc8277809ab17c2d07733265a87459802b600e1293b2b29915923b589b) }), qo: Honk.G1Point({ - x: uint256(0x16f662e2c4345028c82ac5a5b0121d0e7e4ee0b2085fa7fad70b2c17a998cc7d), - y: uint256(0x0ce1d12dd14f5209aa10a93c87701ded85802b3bd41ef7822e43e08402a4da5b) + x: uint256(0x0486c6e33ac2d2b106d4a2e9605592d30404e8574126fc46af221d52eaf1b96f), + y: uint256(0x25653d543494e349cdcf9a3ac26dbb7d14303c5a3f9ba2fa85f963244f98e60a) }), q4: Honk.G1Point({ - x: uint256(0x208969616f7d5aedbffa302e7090aea6af18d65a9d83d69df6ef1facd353d0e7), - y: uint256(0x179f6562d9adb58dd4103b16efc77c7d5e61ff80c77f1f5f26aded02c6d733f0) + x: uint256(0x176f1a95319910e6af5816e4b2dd2297653d31e34488a542461db10110a64010), + y: uint256(0x1059d39bd270befb1c302945e0227a0ffe2e565892996f5e41a4104da6d3e5ef) }), qm: Honk.G1Point({ - x: uint256(0x12921b19b24447ba841027ebe25dd5428781bda07af9d8ef6ff480946dd75e92), - y: uint256(0x2e9aa3501ceba3ac369602fd299c109e915891cb4a5f04e9b567c366c75c3b1f) + x: uint256(0x17616374b4d19ee9dbf0b5fd4f55c056a01a2c37f13582179123c029a6464cf3), + y: uint256(0x29430f9a9ad7a78521af10a66ae8d872598143bd9b3906b1b18708653c136673) }), qc: Honk.G1Point({ - x: uint256(0x0ce49c89c2833a08c0dfa578860de87188278085f2d6cde77a385e1b8f72785a), - y: uint256(0x068bb8e2a0ac69b84d51bf0f7f5a23a92ef5cf2a65a38781ffd24a5d8a7ba24e) + x: uint256(0x16b93f6a34d9be63f3fb39641a24754fabf66b3ef84719510184d31e88960e14), + y: uint256(0x078e786ba4aa4450fa85d791bf770410a2e02cfcb696b0151cd81fb45223b20f) }), qArith: Honk.G1Point({ - x: uint256(0x2e44810bb8254da0223f05a48f676b6cf9d937b1cbc9593d263743a76d017cec), - y: uint256(0x12ebaa449b5153bdeefeefd18db82c08315d2ec6e95610c898ed771fb953b4a8) + x: uint256(0x05e7db9a108d3b31d1426f3648f73c1f9872d546f8737230f21d3a3357ae3a0b), + y: uint256(0x2e2bd25caa71f691af0ec0d3b96e0635ab151246eae11c1a2cf5a4e18344db63) }), qDeltaRange: Honk.G1Point({ - x: uint256(0x0255d78987e5cb213de5ae10e81a36f0d218936d5f5b396787d5dce64118837a), - y: uint256(0x23fb776efa1c16e8f911648fb199823fb996aa54a7852b07f33c0cde5585b7e1) + x: uint256(0x302c0cfa4b8c81fa85cf576c9f3433b57034525d4454b169990f7046dc975bea), + y: uint256(0x1cf64074d693e617ad141cf8c79bc05f08d5ce400e490119ce2d3204ebc3398a) }), qElliptic: Honk.G1Point({ - x: uint256(0x175d1b3a92888ac3b21d5460919d7a8dee522fc3f7e7588bb7937356f31318a6), - y: uint256(0x258d10d36596389fef6524bbe933afff25a338f8a1be8ea6eff462ebb699df6f) + x: uint256(0x150dabe9217c03a5987bffbaa48d037f72e1f6b6ecd6d65a6827838e0c86c543), + y: uint256(0x049ba9c2e39c978e5863586319f34d777f8ae326b651a7d2b691ce1b29e6c15f) }), qAux: Honk.G1Point({ - x: uint256(0x25f6b4c4b1d0bf5b8bc82298369a1c49d41c83e922502d9ee6cefa2f1f2b1ae4), - y: uint256(0x0fca6cf0671751857d3f332e12041afd19dc4aa03cc341516bdf30c2d52f9787) + x: uint256(0x12205baf40241ba21dcba60acebfce1181053038de948e34d02ddd2295f14112), + y: uint256(0x1b0e8530cef504029f1a1c58f2501a3204727409a3d45173427aea12ed3b9bc5) }), qLookup: Honk.G1Point({ x: uint256(0x076d3f92dff00e732053b3b7248f2949cf66e2ff39fe4800f14a23555881d08f), y: uint256(0x19f2844ef71502fada8390017b2881c376c839f0a71748129279029ec262c939) }), qPoseidon2External: Honk.G1Point({ - x: uint256(0x27965a98c47f39936cae1d648b2ea446f3bfcb7c72e041f56d2c6f0a00d01f0b), - y: uint256(0x2855b07d2683d0ec1b7636353522722b2027eb33e77d1b7c78857ec1efdcef08) + x: uint256(0x0e99bacc30295c9188e6c98128019b67744160964d42a476b88537e6aad7c5d2), + y: uint256(0x07e1e801e0d538482fee19b60a407d68b00cab9cee5fff1dfc3a08ec357076e4) }), qPoseidon2Internal: Honk.G1Point({ - x: uint256(0x1e9291bd098e69bc19943506da6c5fa5930482c111625bb10561bc7dd2b74216), - y: uint256(0x22a4094030024039e3a35ed18c464923e19ece17a26a11c593a43d93113f4cf7) + x: uint256(0x12a0f64b474eb0aadce0889f8c874e1e515e4b6d9b23c4b3d784d6a274d110ae), + y: uint256(0x05cf51aa01ce37bf94abec725fc27235c5576c15a75d12b5b4b55271f1a3a592) }), s1: Honk.G1Point({ - x: uint256(0x15dc27b81554a6ddb3a649a3e101c0a224fa707e1dc302fad9a69eaa08bd4f38), - y: uint256(0x129a625be0f4d4897a4166d8941edcb4eaae32867d331292f88e8e44cd38d155) + x: uint256(0x2337c1e994c3f461dee70c5fbb9252de9c4c5625bbf00554b2568e7f8ad11c7d), + y: uint256(0x168652c411ef0f224873b9351dc16c0772bfe76cac941febfb32769b24e46595) }), s2: Honk.G1Point({ - x: uint256(0x29512e721dcdbe168ff49b6f5c03e9448bb0b84a2b3a2775a1458ad7e2c1c32e), - y: uint256(0x13df76c9ac079116a26bed197bcb59c44b868ce6a09e7d720e1c16a403fc1726) + x: uint256(0x18acce41f1d7ff23338dfdc9dbc5ecf83778a0b8ae5137cbffbfa47ae161e101), + y: uint256(0x209f48d6f3f0bbe319d10695e8e93042bcd9cc0f84faaf2f79ca573ea0f19f38) }), s3: Honk.G1Point({ - x: uint256(0x2aa14957fae029f300cd9212dabece20eab126fe10b485a34a9f0362e306f76f), - y: uint256(0x2f3ca257e590e53f7c5211db28e97a10cf0caa33966acc9ba02c3b3d43c420c6) + x: uint256(0x1a59b92da6637092373550f26d237355030bbb28830cb30341fa9f26bcbd2350), + y: uint256(0x2fac8c4d54269aae9c43ffdd443aaa5c6ff7474b3ccd32cec5a64db2bc899b8a) }), s4: Honk.G1Point({ - x: uint256(0x15f830463f615fd2f06f785f54414e650ae5843a97ab6a66a226821cdbe40d30), - y: uint256(0x05ec0391af74def03aebc53630b022f0fdd01f00198aa189a995813eac300a97) + x: uint256(0x2846d21246c001ffeb8602f48e6f4d60db3d4d2174bb0760970ad85c39c75027), + y: uint256(0x28321db79bd22880a9d3d04c97e1ce3a9283b24d6697afdca4f1ad2892b33440) }), t1: Honk.G1Point({ x: uint256(0x0dd222939afbd6003c7c4f993cb8c1febbcc78eabdd8f8c64b3cc99d67e7bc60), @@ -96,28 +96,28 @@ library HonkVerificationKey { y: uint256(0x16e024c7a0985ad98c91599ff8b5478356f289b60c9cb110eaa18ea464d89faa) }), id1: Honk.G1Point({ - x: uint256(0x191750aa8336e364f0d08db18bb521f1bce1b46700e71f791eda547d98aff253), - y: uint256(0x2a68390660612925ccc0abb53a966562d3402e3673cfd27b431d39f34f1da311) + x: uint256(0x07d606554d33deb837a2c7f39f5ed5146ceb803fef03be296568da276ab15bcc), + y: uint256(0x2c005c83adb9c45d6ab5a98df45f55cf7f6430809af4ca6a02549430f0b4469f) }), id2: Honk.G1Point({ - x: uint256(0x07a4f4a3b0922201179fabd2354553d553480b0d7868d8a70744072e8bde71b3), - y: uint256(0x063feb660361d9bc79992f7b1ceb8bf1836944190bcd45e80fc47d79cfc1b61e) + x: uint256(0x1ca2feb02fade345a94f8277e8734eb508c32f75e252f9502e7bc1e3271be32b), + y: uint256(0x2baec32f3d1c057178d4a2219b514d1811b65f8d5786bf8899a1de58ac5d1c11) }), id3: Honk.G1Point({ - x: uint256(0x1bc6c2aa9d5c73803e9245b07f86394fcb8c721d5acaeeef31e177be41463e50), - y: uint256(0x0192e119993e4aa355eb6183a27377b0363927cbc3868bdbb0cc609d9d73afb3) + x: uint256(0x132d52191c3a5a7390f22fd19ce96f540da2c30612ad857e79e8cb1b9d21e9ea), + y: uint256(0x03ad25ea91998a74ef9a5385f00f4004a20d6deaf23ec859c40ae83b9ab01b35) }), id4: Honk.G1Point({ - x: uint256(0x151189c9e62b0ef38907ab84cb472ccedb531721266dea883dbf250245b6251b), - y: uint256(0x1f1416bb4654f31e85e2049a732b5dfd90f036689bce26ee5417779abe44f1e9) + x: uint256(0x099e9f8770819c2a0e56de0f2fd35ef5163546e91fe8b59196322e562bb39e29), + y: uint256(0x1e3210741e94c195e02037858a13caea9645356d3fb135fe54f0e3e97b4baa9c) }), lagrangeFirst: Honk.G1Point({ x: uint256(0x0000000000000000000000000000000000000000000000000000000000000001), y: uint256(0x0000000000000000000000000000000000000000000000000000000000000002) }), lagrangeLast: Honk.G1Point({ - x: uint256(0x1e813ba4f3d08a3289ed528ef9425189ae1240e7c2e086dd4c267edd6b729705), - y: uint256(0x1bb8bb58399caa55cccf0b1fb45ca0a9ca863c2769aacb6f79bf6839f8b142a6) + x: uint256(0x27f7011ea2a9c76b1ba1d6f4757ca17cc5a88f6696d5ce783175f86556d1b842), + y: uint256(0x300d1a0de2a13886a3c842b4b2af5735b84a55e2884a77546620e7597cf67bd9) }) }); return vk; diff --git a/test/fixtures/redditHandleCommand/HonkVerifier.sol b/test/fixtures/redditHandleCommand/HonkVerifier.sol index ef8afb0..ce8c71f 100644 --- a/test/fixtures/redditHandleCommand/HonkVerifier.sol +++ b/test/fixtures/redditHandleCommand/HonkVerifier.sol @@ -2,83 +2,83 @@ // Copyright 2022 Aztec pragma solidity >=0.8.21; -uint256 constant N = 524_288; -uint256 constant LOG_N = 19; +uint256 constant N = 1_048_576; +uint256 constant LOG_N = 20; uint256 constant NUMBER_OF_PUBLIC_INPUTS = 155; library HonkVerificationKey { function loadVerificationKey() internal pure returns (Honk.VerificationKey memory) { Honk.VerificationKey memory vk = Honk.VerificationKey({ - circuitSize: uint256(524_288), - logCircuitSize: uint256(19), + circuitSize: uint256(1_048_576), + logCircuitSize: uint256(20), publicInputsSize: uint256(155), ql: Honk.G1Point({ - x: uint256(0x170da2a71ac64b4258834d3f40d3d6ff01b3839bf265848cab15453d810a4c49), - y: uint256(0x067ecb7e6d4c4a7abdce246b0438464c128ad810ee58ea394aa28c5d88dd54ed) + x: uint256(0x21af862f102fc86024d88797b44940042bfafae1b6f79afe166c8c15d7527e6a), + y: uint256(0x1b48ff44bd0c40df1520e8353248c5a02b8c753fb0b1691695ba419057ef7c7b) }), qr: Honk.G1Point({ - x: uint256(0x0b8d243031372315aec9db8da8684da45904779bc41f56b757ab43befb302a60), - y: uint256(0x01d681f05c4ed69d069314a34386c5b76c17ff078b7b85948249bb7c80801e60) + x: uint256(0x087affd7adf4f6a5fef2c1f6bf0737ce07f9550aad7607bb4a1e52c9a9afc421), + y: uint256(0x21c5f7301d39914395cfbc8119c0cc88148affecd4f163b70048e26f02f78d98) }), qo: Honk.G1Point({ - x: uint256(0x2041f56deb356c85ff44a91cef20587925d836a3bbfa1e6f6f99da705fd93621), - y: uint256(0x0cd34c5bd846688c76e9dd3c949c54978b2c9fcc7a73d756f3176cdd1593c3f5) + x: uint256(0x1425a73ca29eda26cd3bb17a39ea0c52114b8fa2b014e0cb7b0decbc3ec85e10), + y: uint256(0x240c7e5dff0bdeb7d8641205038fe0d7b4729e172d3ecb37a157ee2ca57a83b3) }), q4: Honk.G1Point({ - x: uint256(0x215d81d31dc82abadc6cfdee9a0b45398b672ca616a35328c326a19a0864e712), - y: uint256(0x1cb3710367b580eda9f73681b68b0f2b8f399507a77a946952f77c9572e2b040) + x: uint256(0x2c0f4540f2a310bc15bfdee5b498d90a2e71bc2954da11fb0cf9f0800cd7bc22), + y: uint256(0x061e03d0a9319939d6005078a8a4d41d5a0c91cf91bafb9024bdcfd5e397c4c3) }), qm: Honk.G1Point({ - x: uint256(0x1a75c361648f97cb211f33aedfd0fc3b00516e92c1cafac3e90b0bd32e5c17a7), - y: uint256(0x0b0fd1c09a3eb45a087e89e550e307a99f28075d0d6dbdeccb8963ae2eaf2b65) + x: uint256(0x2b402147ffc7aa5b88d56ef5af9f1ba464b7b038ca04dee99022c0920624801b), + y: uint256(0x1c30fcdb7f52be4263359bf570f82317ac59f96f5cfc6ba21e44970a62d96a3a) }), qc: Honk.G1Point({ - x: uint256(0x06d53c9ca68ecbb0081f9bb811318f8a36d30eaf9c184ec943fcdeff7e24ca4c), - y: uint256(0x1e9082521e15660567c820057a74c268b961a8c67fe80c114768191d128e2196) + x: uint256(0x0b14bc7b8122d6f0a09b3c73051b5712e5cd01ac8705d1d4498d56debfd97ce4), + y: uint256(0x2b0b7d0a9b346265786b2ab69bb58893bfd42c5bb21f13888cf82d836c6686b6) }), qArith: Honk.G1Point({ - x: uint256(0x0dd958c2bdaa509d527bab0031256d1d21b2d381dc9f0be5e07b689ccb987acc), - y: uint256(0x2c95de936c402de5efd1930edf49dd7a46b2683ae3bfaae6ea17632eabb456b7) + x: uint256(0x0d34a6d68e3c6c3bd0c5695e3b8529befdf0227c81799a084a16af778cf33ee0), + y: uint256(0x0919ba3e38a6719892159ab7208a15f2a297e30cb761992b3cf856d4b6cb9299) }), qDeltaRange: Honk.G1Point({ - x: uint256(0x2c68eaf355789c4bd4456367232be41f78a2911ff18146afac83ca341e1cdd51), - y: uint256(0x1c4368cd7e1a8dacea0a5c40f79d6e947eb006f26c74144c5a1fe8894e0d6fbf) + x: uint256(0x22b3e273dd370b7817ab16bc47ed1acae007fd5ffb78caabd3f483493371226f), + y: uint256(0x15a4871d1f46ce0affc1a178716778a2e80ac06a301ae195adbab468e360a0ed) }), qElliptic: Honk.G1Point({ - x: uint256(0x26b6c66a6e2130f43be6a0cfe79d1eb7d77e65001268dad174f3e832c442481f), - y: uint256(0x00df8a5996cb9ae795f72db3bec74dd7b28f765e81174e59f58f0508e8a8cc56) + x: uint256(0x0ed82c19181db0f801b5fedd507a2bf5bf2443efaddaf15be4de0d1436f2605e), + y: uint256(0x036e55e4f1d341199fc19949321332b49513051b579fde16f6f83200f024d9e6) }), qAux: Honk.G1Point({ - x: uint256(0x0a5c7c34cbd5577f8c911cb066306658b61a34d75c8806a6ef1666ce3e95dd6c), - y: uint256(0x018a23a307b1235ef2375dcb647ca5358a12c8e83ca06173897d531d30126ae2) + x: uint256(0x1d6d9a6f4362d78f0ddcc1c007336d7a11ce880d7dd20d390c650f7d08904355), + y: uint256(0x085e9dc74e279c63a1dc819d5785d6c62cc292724f5bb89f83ac88f7b8086806) }), qLookup: Honk.G1Point({ x: uint256(0x24bb4293b5c1d14778e7a307a8d87759581ccd29a3ce628e5725732d87e8f718), y: uint256(0x12be64a95a1e73bebeb21d831e970ee03c5de0e78de64414cf3be1dac190ac8c) }), qPoseidon2External: Honk.G1Point({ - x: uint256(0x2dfc4a36fa1be777404df294222435d4303e2db64670c16695cfe10fa93d9bea), - y: uint256(0x1328421375e49aa7844f0ad258da8b9f7a1d0eb905e2a405161d58037f14e3f6) + x: uint256(0x0fc1a95f56e2bda0b28d879fe2356921668baee2bb8cc8539100837cf2050d60), + y: uint256(0x16cab98c4c305c8377b61a4fd8fe0937a63507eb24e8071324549c53649a3c0e) }), qPoseidon2Internal: Honk.G1Point({ - x: uint256(0x2e9d1c5b60cccdb7b0c2335400726128da2840a52aded0b27218002aa6ee89b5), - y: uint256(0x23993c7a0cf2d06962ca66a72ba34f6bbeedaaff7f7c3eeb63b9cee2ad5053ff) + x: uint256(0x2693b5dc807b61882c490783e842058fed80bf7856663d0c9e2f1b125eb50a20), + y: uint256(0x1f6d9ef4174cc6c85ccd1e5d49dca0e583894c71d3241742f7c934665a4f084e) }), s1: Honk.G1Point({ - x: uint256(0x0c17cd19073bc1c79bdfcc4f4a5154aa1919683d75e32beb092968e018027134), - y: uint256(0x1fb0041e89dd097ae68cd53131d351647198f8bc1da8024c8d559979a9c8a0e3) + x: uint256(0x280f7c3ecd823c0fc2b7a36880bb21e6dc78d993be0f0a49d9e946f7e46de978), + y: uint256(0x0d7bae69461bd9237808821b58e8e47bdfaeb434b099a715b4c470bc69fbbe2a) }), s2: Honk.G1Point({ - x: uint256(0x1f052f6ea6e2eb327cea8f1a83420c8c95cddb9fb0c314a88fe0c168f8165d94), - y: uint256(0x0cc9cc98ba99fd6cc813d02cd32ac7fc2f88cd6d4b940773bdfc944edba03310) + x: uint256(0x0f7af34b0c0352bea2169e790834d0c05e5b77666c8f766a1dc2b1f7b963ad5f), + y: uint256(0x2c3ddde0d4de59bd237716717affcdc64e33ddf8103e9e99a0f6b166ae5e9672) }), s3: Honk.G1Point({ - x: uint256(0x28d76333580be4dec6b83d309ee925925dae393f96a71aa887ed5ba957df9636), - y: uint256(0x0165eef2701deb589fff0d5d4eeac930965093f61b2ff5bdbc1c10402130914e) + x: uint256(0x1711494f4722b138c8cf9e358266d8e20aa08de4190d0ca55e3500d9d3b0118a), + y: uint256(0x25e1e5e93f3240b7268e41c0afe53951492941ca7a8b17bb5348eda984a39683) }), s4: Honk.G1Point({ - x: uint256(0x130d1129ac0e7591374cc9d14139963daa6481aa97aa115ea5f54861138e07c4), - y: uint256(0x3023592f997254a591ee3bee5a2da64ba29e2522daf26811ac9f03202c53f275) + x: uint256(0x251d2b536db084371f28bc5c172941995cfa341e7b41a08a1b0dd1fb1820421e), + y: uint256(0x2e7632266586b22aa9a536e1b8d4f2ad093c585dd53afcb517411b113a631510) }), t1: Honk.G1Point({ x: uint256(0x0dd222939afbd6003c7c4f993cb8c1febbcc78eabdd8f8c64b3cc99d67e7bc60), @@ -97,28 +97,28 @@ library HonkVerificationKey { y: uint256(0x16e024c7a0985ad98c91599ff8b5478356f289b60c9cb110eaa18ea464d89faa) }), id1: Honk.G1Point({ - x: uint256(0x2cdb3f876871adbb40f67e32e0b23b97b347c4b41d1a6f22da55b476e2a8197d), - y: uint256(0x0dcac6afcae225f15c4f292c087357f30060fc6021d04ab2ff0697844ce75246) + x: uint256(0x2e18aa162039ba77c9c4d524e2975ce5cd1afd6a6a1503b14f40e122a20b8370), + y: uint256(0x143fff2bdc15cae92fdeb7afa1ef2e520125454dcae0a13e7e1bb5000012ab8f) }), id2: Honk.G1Point({ - x: uint256(0x1f1a304bbfea0605e56edcf21447ee529b21f46cdc08556908d528bdad5f2623), - y: uint256(0x2bfa633f29bd03552c1287caf2ce85589d0eef6e0fa9d9c4a6173d0590bb07b2) + x: uint256(0x1edeb9ad8b8a366a21b7d5b5c98659932d5c9bbf8fe6891c57acd508209a3f9f), + y: uint256(0x211208175c52b63e846158389e49e18984dcf532f4976121c6ed16ee66bea3d5) }), id3: Honk.G1Point({ - x: uint256(0x01cf5793a59a5d85ed202a58b04ea7ffaadc5951cdcf9e37b5910c9505dd82e9), - y: uint256(0x114dd7dc09ccd15123089b73f198d93d857c52c599de103c9614f35fbd03160e) + x: uint256(0x2bafebc1fd22b87c843a7feacc0399dc3bcc77c36ac8cd6b67a6f45cabd37161), + y: uint256(0x2551303c27691be39bb87bd2ba9c4be3257057f14d4fe5a32d7b5f89063518b3) }), id4: Honk.G1Point({ - x: uint256(0x1e6d6a7d3fe1709a556d6055fe5c439d8fd605e8676eb6e85e05c96aa0ac18ef), - y: uint256(0x0b4d76219a806efd88a72980cb31aa1227e88c12dc52ffa7a4ebf31a012eec17) + x: uint256(0x25d374902382dd63a67d0ecf14a1bc560eb45dda19decd8a233ae6cff0e86483), + y: uint256(0x0595681e30b1e29ca53f92086e657b22aece06f112e5f6226fd161a9d12b902e) }), lagrangeFirst: Honk.G1Point({ x: uint256(0x0000000000000000000000000000000000000000000000000000000000000001), y: uint256(0x0000000000000000000000000000000000000000000000000000000000000002) }), lagrangeLast: Honk.G1Point({ - x: uint256(0x0a347f5edc9a058228225c2cbddfc87effdb203a4a80bc19d5c19f495bfc8d3c), - y: uint256(0x0199c2cb5285441ba3e4ea8263357d74606f99a4061d8f2217fde20c9df3ce8d) + x: uint256(0x0f7e8ac14d0aed975e810053678df221dc4f81ea298660c5f944fb3230fe4698), + y: uint256(0x1efc84f8ebe7b8b0d8427de6901bf18b2e94c23b81f6c3d086b6c1f2f2dbb5a8) }) }); return vk; diff --git a/test/fixtures/redditHandleCommand/circuit/src/main.nr b/test/fixtures/redditHandleCommand/circuit/src/main.nr index 62ebdb2..ea4f462 100644 --- a/test/fixtures/redditHandleCommand/circuit/src/main.nr +++ b/test/fixtures/redditHandleCommand/circuit/src/main.nr @@ -59,6 +59,18 @@ fn main( remove_soft_line_breaks(body.storage(), decoded_body.storage()), "Decoded body does not properly remove soft line breaks", ); + // NOTE: the DKIM signature covers header[0..header.len()) and the body hash covers + // body[0..body.len()). Every regex below must read only those signed bytes (see + // `signed_decoded_len` for why storage past len() cannot be trusted). + assert( + handle_match_start + handle_match_length <= signed_decoded_len(body), + "Body regex match must lie within the signed body", + ); + assert( + sender_domain_match_start + sender_domain_match_length <= header.len(), + "Header regex match must lie within the signed header", + ); + let pubkey_hash = pubkey.hash(); let email_nullifier = poseidon::bn254::hash_1([poseidon::bn254::hash_9(signature)]); let handle_capture_1 = handle_regex::regex_match::<2048, 175>( @@ -83,3 +95,33 @@ fn main( ); (pubkey_hash, email_nullifier, header_hash[0], header_hash[1], prover_address, command, handle_capture_1, sender_domain_capture_1) } + +/** + * Number of leading bytes of the soft-line-break-decoded body that come from signed body bytes. + * + * REASON: BoundedVec storage past len() is a free prover input. sha256_var and + * partial_sha256_var_end only hash bytes [0, len()), zk-regex's select_subarray only bounds a + * match by the array capacity, and remove_soft_line_breaks decodes the whole storage, so bytes past + * body.len() also reach decoded_body. A regex match must therefore end before the decoded image of + * body.len(), otherwise the public outputs need not come from the signed email. + * + * NOTE: we bound matches instead of asserting the storage tail is zero, because legitimate input + * generators fill it differently: relayer-utils zero-pads, but zkemail.nr's JS generator leaves + * SHA-256 padding there and sets decoded_body.len() past the signed content. decoded_body.len() is + * therefore not trusted either. Each "=\r\n" that starts inside the signed body removes 3 bytes. A + * break that straddles body.len() is also counted, which only makes the bound stricter. + */ +fn signed_decoded_len(body: BoundedVec) -> u32 { + let storage = body.storage(); + let len = body.len(); + let mut removed: u32 = 0; + for i in 0..N - 2 { + let is_break = (storage[i] == 0x3D) & (storage[i + 1] == 0x0D) & (storage[i + 2] == 0x0A); + removed += ((is_break & (i < len)) as u32) * 3; + } + if removed > len { + 0 + } else { + len - removed + } +}