diff --git a/Cargo.lock b/Cargo.lock index be8a0fc..20c9dec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -740,6 +740,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals", + "bitcoin_hashes", +] + [[package]] name = "base64" version = "0.13.1" @@ -770,6 +780,12 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "bech32" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" + [[package]] name = "bincode" version = "1.3.3" @@ -852,6 +868,96 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bitcoin" +version = "0.32.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf93e61f2dbc3e3c41234ca26a65e2c0b0975c52e0f069ab9893ebbede584d3" +dependencies = [ + "base58ck", + "bech32 0.11.1", + "bitcoin-internals", + "bitcoin-io", + "bitcoin-units", + "bitcoin_hashes", + "hex-conservative", + "hex_lit", + "secp256k1", + "serde", +] + +[[package]] +name = "bitcoin-internals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" +dependencies = [ + "serde", +] + +[[package]] +name = "bitcoin-io" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" + +[[package]] +name = "bitcoin-script" +version = "0.4.0" +source = "git+https://github.com/BitVM/rust-bitcoin-script#01b4cb66cbf5b525079cabe006f9f99627da97cd" +dependencies = [ + "bitcoin", + "script-macro", + "stdext", +] + +[[package]] +name = "bitcoin-script-stack" +version = "0.0.1" +source = "git+https://github.com/BitVM/rust-bitcoin-script-stack#643c5f1a44af448274849c01a5ae7fbdd54d8213" +dependencies = [ + "bitcoin", + "bitcoin-script", + "bitcoin-scriptexec", +] + +[[package]] +name = "bitcoin-scriptexec" +version = "0.0.0" +source = "git+https://github.com/BitVM/rust-bitcoin-scriptexec#ba96bc2bd76774c9d1b011461cb79d983c2c43a1" +dependencies = [ + "bitcoin", + "clap", + "console_error_panic_hook", + "getrandom 0.2.17", + "lazy_static", + "serde", + "serde-wasm-bindgen", + "serde_json", + "wasm-bindgen", +] + +[[package]] +name = "bitcoin-units" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346568ebaab2918487cea76dd55dae13c27bb618cdb737c952e69eb2017c4118" +dependencies = [ + "bitcoin-internals", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +dependencies = [ + "bitcoin-io", + "hex-conservative", + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -876,6 +982,37 @@ dependencies = [ "wyz", ] +[[package]] +name = "bitvm" +version = "0.1.0" +source = "git+https://github.com/GOATNetwork/BitVM.git?branch=GA#26b0bd61b61b24b50b2d2443a7fda8e58412edfa" +dependencies = [ + "ark-bn254", + "ark-crypto-primitives", + "ark-ec", + "ark-ff 0.5.0", + "ark-groth16", + "ark-relations", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "bitcoin", + "bitcoin-script", + "bitcoin-script-stack", + "bitcoin-scriptexec", + "blake3", + "colored", + "itertools 0.13.0", + "num-bigint 0.4.6", + "num-traits", + "paste", + "rand 0.8.5", + "rand_chacha 0.3.1", + "regex", + "serde", + "sha2", + "tqdm", +] + [[package]] name = "blake2" version = "0.10.6" @@ -1197,7 +1334,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5286a0843c21f8367f7be734f89df9b822e0321d8bcce8d6e735aadff7d74979" dependencies = [ "base64 0.21.7", - "bech32", + "bech32 0.9.1", "bs58", "digest 0.10.7", "generic-array 0.14.7", @@ -1239,6 +1376,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "const-default" version = "1.0.0" @@ -1375,6 +1522,31 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crossterm" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +dependencies = [ + "bitflags 1.3.2", + "crossterm_winapi", + "libc", + "mio 0.8.11", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crunchy" version = "0.2.4" @@ -1732,6 +1904,17 @@ dependencies = [ "spki", ] +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "git+https://github.com/zkMIPS-patches/signatures?branch=patch-ecdsa-0.16.9#da0cd8753b243e09acf82f8e58f8da0e28e7287d" +dependencies = [ + "digest 0.10.7", + "elliptic-curve", + "signature", + "spki", +] + [[package]] name = "educe" version = "0.6.0" @@ -2567,8 +2750,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -2815,6 +3000,21 @@ dependencies = [ "serde", ] +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + [[package]] name = "hickory-proto" version = "0.24.4" @@ -3417,7 +3617,7 @@ version = "0.13.4" source = "git+https://github.com/ziren-patches/elliptic-curves?branch=patch-k256-0.13.4#8266b228a39402a0ba68d644b7f26b85b5112fe3" dependencies = [ "cfg-if", - "ecdsa", + "ecdsa 0.16.9 (git+https://github.com/ziren-patches/signatures?branch=patch-ecdsa-0.16.9)", "elliptic-curve", "hex", "once_cell", @@ -3685,6 +3885,18 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "mio" version = "1.1.1" @@ -4004,7 +4216,7 @@ name = "p256" version = "0.13.2" source = "git+https://github.com/ziren-patches/elliptic-curves?branch=patch-p256-0.13.2#a6f1a1fb07020d00f627725a20dc336983be3946" dependencies = [ - "ecdsa", + "ecdsa 0.16.9 (git+https://github.com/ziren-patches/signatures?branch=patch-ecdsa-0.16.9)", "elliptic-curve", "hex", "primeorder", @@ -4730,6 +4942,30 @@ dependencies = [ "toml_edit 0.23.10+spec-1.0.0", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -5466,6 +5702,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "script-macro" +version = "0.4.0" +source = "git+https://github.com/BitVM/rust-bitcoin-script#01b4cb66cbf5b525079cabe006f9f99627da97cd" +dependencies = [ + "bitcoin", + "proc-macro-error", + "proc-macro2", + "quote", +] + [[package]] name = "scrypt" version = "0.10.0" @@ -5508,6 +5755,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "git+https://github.com/ziren-patches/rust-secp256k1?branch=patch-0.29.1#c7e39ee0732d0e6b5c33721038f8f1b789f77c36" +dependencies = [ + "bitcoin_hashes", + "cfg-if", + "ecdsa 0.16.9 (git+https://github.com/zkMIPS-patches/signatures?branch=patch-ecdsa-0.16.9)", + "rand 0.8.5", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.0" +source = "git+https://github.com/ziren-patches/rust-secp256k1?branch=patch-0.29.1#c7e39ee0732d0e6b5c33721038f8f1b789f77c36" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -5581,6 +5849,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_arrays" version = "0.1.0" @@ -5737,6 +6016,27 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio 0.8.11", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.8" @@ -5877,6 +6177,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stdext" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4af28eeb7c18ac2dbdb255d40bee63f203120e1db6b0024b177746ebec7049c1" + [[package]] name = "strength_reduce" version = "0.2.4" @@ -6200,7 +6506,7 @@ checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", - "mio", + "mio 1.1.1", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -6475,6 +6781,17 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" +[[package]] +name = "tqdm" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2d2932240205a99b65f15d9861992c95fbb8c9fb280b3a1f17a92db6dc611f" +dependencies = [ + "anyhow", + "crossterm", + "once_cell", +] + [[package]] name = "tracing" version = "0.1.44" @@ -6827,12 +7144,15 @@ dependencies = [ "ark-relations", "ark-serialize 0.5.0", "bincode", + "bitcoin", + "bitvm", "blake3", "cfg-if", "garbled-snark-verifier", "p3-maybe-rayon", "rand 0.8.5", "rand_chacha 0.3.1", + "rayon", "ripemd 0.2.0", "serde", "sha2", @@ -8233,11 +8553,6 @@ name = "rsa" version = "0.9.6" source = "git+https://github.com/ziren-patches/RustCrypto-RSA.git?branch=patch-rsa-0.9.6#df285475f9800a8e20f72a7f93de07539df0ed25" -[[patch.unused]] -name = "secp256k1" -version = "0.29.1" -source = "git+https://github.com/ziren-patches/rust-secp256k1?branch=patch-0.29.1#c7e39ee0732d0e6b5c33721038f8f1b789f77c36" - [[patch.unused]] name = "substrate-bn" version = "0.6.0" diff --git a/babe-programs/Cargo.lock b/babe-programs/Cargo.lock index 3d2f5a1..a9d7cb0 100644 --- a/babe-programs/Cargo.lock +++ b/babe-programs/Cargo.lock @@ -49,28 +49,6 @@ dependencies = [ "cpufeatures 0.2.17", ] -[[package]] -name = "aes-guest" -version = "1.1.0" -dependencies = [ - "ark-bn254", - "garbled-snark-verifier", - "verifiable-circuit-babe", - "zkm-zkvm", -] - -[[package]] -name = "aes-host" -version = "1.1.0" -dependencies = [ - "aes", - "ark-bn254", - "garbled-snark-verifier", - "verifiable-circuit-babe", - "zkm-build", - "zkm-sdk", -] - [[package]] name = "ahash" version = "0.8.12" @@ -546,6 +524,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" dependencies = [ + "colored", "num-traits", "rand 0.8.5", "rayon", @@ -761,6 +740,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals", + "bitcoin_hashes", +] + [[package]] name = "base64" version = "0.13.1" @@ -791,6 +780,12 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "bech32" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" + [[package]] name = "bincode" version = "1.3.3" @@ -850,6 +845,96 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bitcoin" +version = "0.32.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf93e61f2dbc3e3c41234ca26a65e2c0b0975c52e0f069ab9893ebbede584d3" +dependencies = [ + "base58ck", + "bech32 0.11.1", + "bitcoin-internals", + "bitcoin-io", + "bitcoin-units", + "bitcoin_hashes", + "hex-conservative", + "hex_lit", + "secp256k1", + "serde", +] + +[[package]] +name = "bitcoin-internals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" +dependencies = [ + "serde", +] + +[[package]] +name = "bitcoin-io" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" + +[[package]] +name = "bitcoin-script" +version = "0.4.0" +source = "git+https://github.com/BitVM/rust-bitcoin-script#01b4cb66cbf5b525079cabe006f9f99627da97cd" +dependencies = [ + "bitcoin", + "script-macro", + "stdext", +] + +[[package]] +name = "bitcoin-script-stack" +version = "0.0.1" +source = "git+https://github.com/BitVM/rust-bitcoin-script-stack#643c5f1a44af448274849c01a5ae7fbdd54d8213" +dependencies = [ + "bitcoin", + "bitcoin-script", + "bitcoin-scriptexec", +] + +[[package]] +name = "bitcoin-scriptexec" +version = "0.0.0" +source = "git+https://github.com/BitVM/rust-bitcoin-scriptexec#ba96bc2bd76774c9d1b011461cb79d983c2c43a1" +dependencies = [ + "bitcoin", + "clap", + "console_error_panic_hook", + "getrandom 0.2.17", + "lazy_static", + "serde", + "serde-wasm-bindgen", + "serde_json", + "wasm-bindgen", +] + +[[package]] +name = "bitcoin-units" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346568ebaab2918487cea76dd55dae13c27bb618cdb737c952e69eb2017c4118" +dependencies = [ + "bitcoin-internals", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +dependencies = [ + "bitcoin-io", + "hex-conservative", + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -874,6 +959,37 @@ dependencies = [ "wyz", ] +[[package]] +name = "bitvm" +version = "0.1.0" +source = "git+https://github.com/GOATNetwork/BitVM.git?branch=GA#26b0bd61b61b24b50b2d2443a7fda8e58412edfa" +dependencies = [ + "ark-bn254", + "ark-crypto-primitives", + "ark-ec", + "ark-ff 0.5.0", + "ark-groth16", + "ark-relations", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "bitcoin", + "bitcoin-script", + "bitcoin-script-stack", + "bitcoin-scriptexec", + "blake3", + "colored", + "itertools 0.13.0", + "num-bigint 0.4.6", + "num-traits", + "paste", + "rand 0.8.5", + "rand_chacha 0.3.1", + "regex", + "serde", + "sha2", + "tqdm", +] + [[package]] name = "blake2" version = "0.10.6" @@ -1198,7 +1314,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5286a0843c21f8367f7be734f89df9b822e0321d8bcce8d6e735aadff7d74979" dependencies = [ "base64 0.21.7", - "bech32", + "bech32 0.9.1", "bs58", "digest 0.10.7", "generic-array 0.14.9", @@ -1217,6 +1333,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + [[package]] name = "console" version = "0.15.11" @@ -1230,6 +1356,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "const-default" version = "1.0.0" @@ -1375,6 +1511,31 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crossterm" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +dependencies = [ + "bitflags 1.3.2", + "crossterm_winapi", + "libc", + "mio 0.8.11", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crunchy" version = "0.2.4" @@ -1732,6 +1893,17 @@ dependencies = [ "spki", ] +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "git+https://github.com/zkMIPS-patches/signatures?branch=patch-ecdsa-0.16.9#da0cd8753b243e09acf82f8e58f8da0e28e7287d" +dependencies = [ + "digest 0.10.7", + "elliptic-curve", + "signature", + "spki", +] + [[package]] name = "educe" version = "0.6.0" @@ -1798,39 +1970,6 @@ dependencies = [ "log", ] -[[package]] -name = "enc-setup-guest" -version = "1.1.0" -dependencies = [ - "ark-bn254", - "ark-ec", - "ark-ff 0.5.0", - "ark-serialize 0.5.0", - "blake3", - "sha2", - "zkm-zkvm", -] - -[[package]] -name = "enc-setup-host" -version = "1.1.0" -dependencies = [ - "ark-bn254", - "ark-crypto-primitives", - "ark-ec", - "ark-ff 0.5.0", - "ark-groth16", - "ark-relations", - "ark-serialize 0.5.0", - "hex", - "rand 0.8.5", - "sha2", - "tracing", - "verifiable-circuit-babe", - "zkm-build", - "zkm-sdk", -] - [[package]] name = "encode_unicode" version = "1.0.0" @@ -2534,40 +2673,10 @@ dependencies = [ "byteorder", ] -[[package]] -name = "garbled-circuit-guest" -version = "1.1.0" -dependencies = [ - "garbled-snark-verifier", - "zkm-zkvm", -] - -[[package]] -name = "garbled-circuit-host" -version = "1.1.0" -dependencies = [ - "ark-bn254", - "ark-ec", - "ark-ff 0.5.0", - "ark-serialize 0.5.0", - "bincode", - "garbled-snark-verifier", - "hex", - "indexmap 2.13.0", - "rand 0.8.5", - "sha2", - "tracing", - "verifiable-circuit-babe", - "verifiable-circuit-host", - "zkm-build", - "zkm-sdk", -] - [[package]] name = "garbled-snark-verifier" version = "0.1.0" dependencies = [ - "aes", "ark-bn254", "ark-crypto-primitives", "ark-ec", @@ -2588,7 +2697,6 @@ dependencies = [ "serde", "serde_json", "serial_test", - "sha2", ] [[package]] @@ -2626,8 +2734,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -2875,6 +2985,21 @@ dependencies = [ "serde", ] +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + [[package]] name = "hickory-proto" version = "0.24.4" @@ -3468,7 +3593,7 @@ version = "0.13.4" source = "git+https://github.com/ziren-patches/elliptic-curves?branch=patch-k256-0.13.4#8266b228a39402a0ba68d644b7f26b85b5112fe3" dependencies = [ "cfg-if", - "ecdsa", + "ecdsa 0.16.9 (git+https://github.com/ziren-patches/signatures?branch=patch-ecdsa-0.16.9)", "elliptic-curve", "hex", "once_cell", @@ -3730,6 +3855,18 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "mio" version = "1.1.1" @@ -4049,7 +4186,7 @@ name = "p256" version = "0.13.2" source = "git+https://github.com/ziren-patches/elliptic-curves?branch=patch-p256-0.13.2#a6f1a1fb07020d00f627725a20dc336983be3946" dependencies = [ - "ecdsa", + "ecdsa 0.16.9 (git+https://github.com/ziren-patches/signatures?branch=patch-ecdsa-0.16.9)", "elliptic-curve", "hex", "primeorder", @@ -4769,6 +4906,30 @@ dependencies = [ "toml_edit 0.25.4+spec-1.1.0", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -5528,6 +5689,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "script-macro" +version = "0.4.0" +source = "git+https://github.com/BitVM/rust-bitcoin-script#01b4cb66cbf5b525079cabe006f9f99627da97cd" +dependencies = [ + "bitcoin", + "proc-macro-error", + "proc-macro2", + "quote", +] + [[package]] name = "scrypt" version = "0.10.0" @@ -5570,6 +5742,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "git+https://github.com/ziren-patches/rust-secp256k1?branch=patch-0.29.1#c7e39ee0732d0e6b5c33721038f8f1b789f77c36" +dependencies = [ + "bitcoin_hashes", + "cfg-if", + "ecdsa 0.16.9 (git+https://github.com/zkMIPS-patches/signatures?branch=patch-ecdsa-0.16.9)", + "rand 0.8.5", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.0" +source = "git+https://github.com/ziren-patches/rust-secp256k1?branch=patch-0.29.1#c7e39ee0732d0e6b5c33721038f8f1b789f77c36" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -5643,6 +5836,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_arrays" version = "0.1.0" @@ -5799,6 +6003,27 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio 0.8.11", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.8" @@ -5924,6 +6149,7 @@ dependencies = [ "ark-relations", "garbled-snark-verifier", "rand 0.8.5", + "rand_chacha 0.3.1", "tracing", "verifiable-circuit-babe", "zkm-build", @@ -5964,6 +6190,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stdext" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4af28eeb7c18ac2dbdb255d40bee63f203120e1db6b0024b177746ebec7049c1" + [[package]] name = "strength_reduce" version = "0.2.4" @@ -6287,7 +6519,7 @@ checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", - "mio", + "mio 1.1.1", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -6562,6 +6794,17 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" +[[package]] +name = "tqdm" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2d2932240205a99b65f15d9861992c95fbb8c9fb280b3a1f17a92db6dc611f" +dependencies = [ + "anyhow", + "crossterm", + "once_cell", +] + [[package]] name = "tracing" version = "0.1.44" @@ -6904,42 +7147,21 @@ dependencies = [ "ark-relations", "ark-serialize 0.5.0", "bincode", + "bitcoin", + "bitvm", "blake3", "cfg-if", "garbled-snark-verifier", "p3-maybe-rayon", "rand 0.8.5", "rand_chacha 0.3.1", + "rayon", "ripemd 0.2.0", "serde", "sha2", "zkm-zkvm", ] -[[package]] -name = "verifiable-circuit-host" -version = "0.0.1" -dependencies = [ - "ark-bn254", - "ark-crypto-primitives", - "ark-ec", - "ark-ff 0.5.0", - "ark-groth16", - "ark-relations", - "ark-serialize 0.5.0", - "ark-std 0.5.0", - "bincode", - "garbled-snark-verifier", - "indexmap 2.13.0", - "num-bigint 0.4.6", - "rand 0.8.5", - "rand_chacha 0.3.1", - "tracing", - "tracing-subscriber 0.3.22", - "zkm-build", - "zkm-sdk", -] - [[package]] name = "version_check" version = "0.9.5" @@ -8227,11 +8449,6 @@ name = "rsa" version = "0.9.6" source = "git+https://github.com/ziren-patches/RustCrypto-RSA.git?branch=patch-rsa-0.9.6#df285475f9800a8e20f72a7f93de07539df0ed25" -[[patch.unused]] -name = "secp256k1" -version = "0.29.1" -source = "git+https://github.com/ziren-patches/rust-secp256k1?branch=patch-0.29.1#c7e39ee0732d0e6b5c33721038f8f1b789f77c36" - [[patch.unused]] name = "substrate-bn" version = "0.6.0" diff --git a/babe-programs/Cargo.toml b/babe-programs/Cargo.toml index 81a9d2d..6a4149f 100644 --- a/babe-programs/Cargo.toml +++ b/babe-programs/Cargo.toml @@ -1,11 +1,5 @@ [workspace] members = [ - "aes-enc/guest", - "aes-enc/host", - "garbled-circuit/guest", - "garbled-circuit/host", - "enc-setup/guest", - "enc-setup/host", "soldering/guest", "soldering/host", ] diff --git a/babe-programs/aes-enc/guest/Cargo.toml b/babe-programs/aes-enc/guest/Cargo.toml deleted file mode 100644 index c70a4e3..0000000 --- a/babe-programs/aes-enc/guest/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "aes-guest" -version.workspace = true -edition.workspace = true - -[dependencies] -zkm-zkvm = { workspace = true, features = ["embedded"] } -garbled-snark-verifier = { workspace = true } -verifiable-circuit-babe = { workspace = true } -ark-bn254 = { version = "0.5.0", features = ["curve", "scalar_field"], default-features = false } diff --git a/babe-programs/aes-enc/guest/src/main.rs b/babe-programs/aes-enc/guest/src/main.rs deleted file mode 100644 index 6e86abd..0000000 --- a/babe-programs/aes-enc/guest/src/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -//! A simple program that takes a number `n` as input, and writes the `n-1`th and `n`th fibonacci -//! number as an output. - -// These two lines are necessary for the program to properly compile. -// -// Under the hood, we wrap your main function with some extra code so that it behaves properly -// inside the zkVM. -#![no_std] -#![no_main] -extern crate alloc; - -zkm_zkvm::entrypoint!(main); -use garbled_snark_verifier::bag::S; -use verifiable_circuit_babe::gc::utils::aes_enc; - -fn main() { - let tmp = ark_bn254::Fq::from(20_u64); - let key = S::from_slice(&[0u8; 16]); - let res = aes_enc(&tmp, &key.0); - zkm_zkvm::io::commit::<[u8; 32]>(&res); -} diff --git a/babe-programs/aes-enc/host/Cargo.toml b/babe-programs/aes-enc/host/Cargo.toml deleted file mode 100644 index f8ed859..0000000 --- a/babe-programs/aes-enc/host/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "aes-host" -version.workspace = true -edition.workspace = true - -[dependencies] -zkm-sdk = { workspace = true } -garbled-snark-verifier = { workspace = true } -verifiable-circuit-babe = { workspace = true } -aes = "0.8" -ark-bn254 = { version = "0.5.0", features = ["curve", "scalar_field"], default-features = false } - -[build-dependencies] -zkm-build = { workspace = true } \ No newline at end of file diff --git a/babe-programs/aes-enc/host/build.rs b/babe-programs/aes-enc/host/build.rs deleted file mode 100644 index e7ea36a..0000000 --- a/babe-programs/aes-enc/host/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - zkm_build::build_program("../guest"); -} diff --git a/babe-programs/aes-enc/host/src/main.rs b/babe-programs/aes-enc/host/src/main.rs deleted file mode 100644 index e55f90a..0000000 --- a/babe-programs/aes-enc/host/src/main.rs +++ /dev/null @@ -1,32 +0,0 @@ -use zkm_sdk::{include_elf, utils, ProverClient, ZKMStdin}; -use garbled_snark_verifier::bag::S; - -const ELF: &[u8] = include_elf!("aes-guest"); - -fn main() { - utils::setup_logger(); - let stdin = ZKMStdin::new(); - - // Create a `ProverClient` method. - let client = ProverClient::new(); - - // Execute the program using the `ProverClient.execute` method, without generating a proof. - let (mut pub_val, report) = client.execute(ELF, &stdin).run().unwrap(); - println!("executed program with {} cycles", report.total_instruction_count()); - // Read and verify the output. - // - // Note that this output is read from values committed to in the program using - // `zkm_zkvm::io::commit`. - let public_input = pub_val.read::<[u8; 32]>(); - println!("public input: {:?}", public_input); - - // // Generate the proof for the given program and input. - let (pk, vk) = client.setup(ELF); - let mut proof = client.prove(&pk, stdin).run().unwrap(); - println!("generated proof"); - - let tmp = ark_bn254::Fq::from(20_u64); - let key = &[0u8; 16]; - let out = verifiable_circuit_babe::gc::utils::aes_enc(&tmp, &key); - println!("expected output: {:?}", out); -} diff --git a/babe-programs/enc-setup/guest/Cargo.toml b/babe-programs/enc-setup/guest/Cargo.toml deleted file mode 100644 index 8aa3f3d..0000000 --- a/babe-programs/enc-setup/guest/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "enc-setup-guest" -version.workspace = true -edition.workspace = true - -[dependencies] -zkm-zkvm = { workspace = true, features = ["embedded"] } -ark-bn254 = { version = "0.5.0", features = ["curve", "scalar_field"], default-features = false } -ark-ff = { version = "0.5.0", default-features = false } -ark-ec = { version = "0.5.0", default-features = false } -ark-serialize = { version = "0.5.0", default-features = false, features = ["derive"] } -sha2 = { workspace = true } -blake3 = { version = "1.6.1", default-features = false } \ No newline at end of file diff --git a/babe-programs/enc-setup/guest/src/main.rs b/babe-programs/enc-setup/guest/src/main.rs deleted file mode 100644 index 94f86d5..0000000 --- a/babe-programs/enc-setup/guest/src/main.rs +++ /dev/null @@ -1,155 +0,0 @@ -// Guest program: proves enc_setup of the BABE verifier. -// -// Proves that ct_setup = (ct2, ct3) was correctly computed as: -// ct2 = r · [delta]_2 -// rY = e(alpha_g1, r·beta_g2) + e(vk_x, r·gamma_g2) (additive GT notation) -// ct3 = blake3(rY_bytes) ⊕ msg -// -// Public outputs committed: -// ct2_bytes – compressed G2 encoding of r·delta_g2 -// ct3 – 32-byte masked message -// h_msg – SHA-256(msg) (hashlock) -// h_vk – SHA-256(raw VK bytes) -// h_public_inputs – SHA-256(raw public-input bytes) -#![no_std] -#![no_main] -extern crate alloc; - -use alloc::vec::Vec; - -zkm_zkvm::entrypoint!(main); - -use ark_bn254::{Bn254, Fq, Fq2, Fr, G1Affine, G1Projective, G2Affine}; -use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup}; -use ark_ff::PrimeField; -use ark_serialize::CanonicalSerialize; -use sha2::{Digest, Sha256}; - -// ── Field / curve helpers ───────────────────────────────────────────────────── - -fn fq_from_le(b: &[u8; 32]) -> Fq { - Fq::from_le_bytes_mod_order(b) -} - -fn fr_from_le(b: &[u8; 32]) -> Fr { - Fr::from_le_bytes_mod_order(b) -} - -/// Deserialize an uncompressed G1Affine from 64 raw LE bytes in a slice: x‖y. -fn g1_from_slice(b: &[u8]) -> G1Affine { - assert_eq!(b.len(), 64); - let x = fq_from_le(b[..32].try_into().unwrap()); - let y = fq_from_le(b[32..].try_into().unwrap()); - G1Affine::new_unchecked(x, y) -} - -/// Deserialize an uncompressed G2Affine from 128 raw LE bytes: x.c0‖x.c1‖y.c0‖y.c1. -fn g2_from_slice(b: &[u8]) -> G2Affine { - assert_eq!(b.len(), 128); - let x_c0 = fq_from_le(b[0..32].try_into().unwrap()); - let x_c1 = fq_from_le(b[32..64].try_into().unwrap()); - let y_c0 = fq_from_le(b[64..96].try_into().unwrap()); - let y_c1 = fq_from_le(b[96..128].try_into().unwrap()); - G2Affine::new_unchecked(Fq2::new(x_c0, x_c1), Fq2::new(y_c0, y_c1)) -} - -/// Random-oracle: blake3 hash of bytes → 32-byte mask (matches verifiable_circuit_babe::babe::h). -fn h(data: &[u8]) -> [u8; 32] { - *blake3::hash(data).as_bytes() -} - -// ── Main ────────────────────────────────────────────────────────────────────── - -fn main() { - // ── 1. Read private witnesses ───────────────────────────────────────────── - let r_bytes = zkm_zkvm::io::read::<[u8; 32]>(); - let msg = zkm_zkvm::io::read::<[u8; 32]>(); - - // ── 2. Read VK components as flat byte vectors ──────────────────────────── - // G1Affine = 64 bytes (x ‖ y, each 32-byte Fq LE) - // G2Affine = 128 bytes (x.c0 ‖ x.c1 ‖ y.c0 ‖ y.c1, each 32-byte Fq LE) - let alpha_g1_raw = zkm_zkvm::io::read_vec(); // 64 bytes - let beta_g2_raw = zkm_zkvm::io::read_vec(); // 128 bytes - let gamma_g2_raw = zkm_zkvm::io::read_vec(); // 128 bytes - let delta_g2_raw = zkm_zkvm::io::read_vec(); // 128 bytes - // gamma_abc_g1: flat array, n_pts × 64 bytes - let gamma_abc_raw = zkm_zkvm::io::read_vec(); // (n+1) * 64 bytes - - // ── 3. Read public inputs ───────────────────────────────────────────────── - // Each Fr is 32 bytes LE; total = n_inputs × 32 bytes - let public_inputs_raw = zkm_zkvm::io::read_vec(); // n * 32 bytes - - // ── 4. Reconstruct curve points and scalars ─────────────────────────────── - let r = fr_from_le(&r_bytes); - let alpha_g1 = g1_from_slice(&alpha_g1_raw); - let beta_g2 = g2_from_slice(&beta_g2_raw); - let gamma_g2 = g2_from_slice(&gamma_g2_raw); - let delta_g2 = g2_from_slice(&delta_g2_raw); - - assert_eq!(gamma_abc_raw.len() % 64, 0); - let n_abc = gamma_abc_raw.len() / 64; - let gamma_abc_g1: Vec = (0..n_abc) - .map(|i| g1_from_slice(&gamma_abc_raw[i * 64..(i + 1) * 64])) - .collect(); - - assert_eq!(public_inputs_raw.len() % 32, 0); - let n_inputs = public_inputs_raw.len() / 32; - let public_inputs: Vec = (0..n_inputs) - .map(|i| fr_from_le(public_inputs_raw[i * 32..(i + 1) * 32].try_into().unwrap())) - .collect(); - - // ── 5. Compute vk_x = gamma_abc_g1[0] + Σ gamma_abc_g1[i+1] · inp[i] ──── - assert_eq!(gamma_abc_g1.len(), public_inputs.len() + 1); - let mut vk_x = gamma_abc_g1[0].into_group(); - for (i, inp) in public_inputs.iter().enumerate() { - vk_x += gamma_abc_g1[i + 1].into_group() * *inp; - } - - // ── 6. Compute ct2 = r · delta_g2 (one G2 scalar mult, unavoidable) ────── - let r_delta = (delta_g2.into_group() * r).into_affine(); - let mut ct2_bytes = Vec::new(); - r_delta.serialize_compressed(&mut ct2_bytes).unwrap(); - - // ── 7. Compute rY via G1 scalar mults + one multi-pairing ──────────────── - // - // By bilinearity: e(alpha, r·beta) = e(r·alpha, beta) - // e(vk_x, r·gamma) = e(r·vk_x, gamma) - // - // This replaces 2 expensive G2 scalar mults (over Fq²) with 2 cheap G1 - // scalar mults (over Fq), and uses multi_pairing so the costly final - // exponentiation is computed only once instead of twice. - let g1_proj = [alpha_g1.into_group() * r, vk_x * r]; - // Batch-normalize: one shared field inversion instead of two. - let g1_aff = G1Projective::normalize_batch(&g1_proj); - let r_y = Bn254::multi_pairing(g1_aff, [beta_g2, gamma_g2]); - - // ── 8. Compute ct3 = blake3(rY) ⊕ msg ──────────────────────────────────── - let mut ry_bytes = Vec::new(); - r_y.serialize_compressed(&mut ry_bytes).unwrap(); - let mask = h(&ry_bytes); - let ct3: [u8; 32] = core::array::from_fn(|i| msg[i] ^ mask[i]); - - // ── 9. Compute public commitments ───────────────────────────────────────── - - // h_msg = SHA-256(msg) — hashlock value posted on Bitcoin - let h_msg: [u8; 32] = Sha256::digest(&msg).into(); - - // h_vk = SHA-256(all VK raw bytes in canonical order) - let mut vk_hasher = Sha256::new(); - vk_hasher.update(&alpha_g1_raw); - vk_hasher.update(&beta_g2_raw); - vk_hasher.update(&gamma_g2_raw); - vk_hasher.update(&delta_g2_raw); - vk_hasher.update(&gamma_abc_raw); - let h_vk: [u8; 32] = vk_hasher.finalize().into(); - - // h_public_inputs = SHA-256(concatenated raw Fr bytes) - let h_public_inputs: [u8; 32] = Sha256::digest(&public_inputs_raw).into(); - - // ── 10. Commit public outputs ───────────────────────────────────────────── - zkm_zkvm::io::commit::>(&ct2_bytes); - zkm_zkvm::io::commit::<[u8; 32]>(&ct3); - zkm_zkvm::io::commit::<[u8; 32]>(&h_msg); - zkm_zkvm::io::commit::<[u8; 32]>(&h_vk); - zkm_zkvm::io::commit::<[u8; 32]>(&h_public_inputs); -} \ No newline at end of file diff --git a/babe-programs/enc-setup/host/Cargo.toml b/babe-programs/enc-setup/host/Cargo.toml deleted file mode 100644 index 3754af8..0000000 --- a/babe-programs/enc-setup/host/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "enc-setup-host" -version.workspace = true -edition.workspace = true - -[dependencies] -zkm-sdk = { workspace = true } -verifiable-circuit-babe = { workspace = true } -ark-bn254 = { version = "0.5.0", features = ["curve", "scalar_field"], default-features = false } -ark-ff = { version = "0.5.0", default-features = false } -ark-ec = { version = "0.5.0", default-features = false } -ark-serialize = { version = "0.5.0" } -ark-groth16 = { version = "0.5.0" } -ark-crypto-primitives = { version = "0.5.0" } -ark-relations = { version = "0.5.0" } -sha2 = { workspace = true } -rand = "0.8" -hex = "0.4" -tracing = "0.1" - -[build-dependencies] -zkm-build = { workspace = true } \ No newline at end of file diff --git a/babe-programs/enc-setup/host/build.rs b/babe-programs/enc-setup/host/build.rs deleted file mode 100644 index 032c1d8..0000000 --- a/babe-programs/enc-setup/host/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - zkm_build::build_program("../guest"); -} \ No newline at end of file diff --git a/babe-programs/enc-setup/host/src/main.rs b/babe-programs/enc-setup/host/src/main.rs deleted file mode 100644 index 3e7a7c4..0000000 --- a/babe-programs/enc-setup/host/src/main.rs +++ /dev/null @@ -1,211 +0,0 @@ -// Host program: prepares enc_setup inputs, executes the guest inside the ZKM, -// and verifies the committed outputs match the natively computed values. -// -// Circuit being proved: enc_setup of the BABE verifier. -// ct2 = r · [delta]_2 -// rY = e(alpha_g1, r·beta_g2) + e(vk_x, r·gamma_g2) -// ct3 = blake3(rY) ⊕ msg -// -// Public outputs (committed by guest): -// ct2_bytes, ct3, h_msg, h_vk, h_public_inputs - -use std::time::Instant; - -use ark_bn254::{Bn254, Fq, Fq2, Fr, G1Affine, G2Affine}; -use ark_crypto_primitives::snark::{CircuitSpecificSetupSNARK, SNARK}; -use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup}; -use ark_ff::{PrimeField, UniformRand}; -use ark_groth16::Groth16; -use ark_relations::lc; -use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; -use ark_serialize::CanonicalSerialize; -use rand::RngCore; -use sha2::{Digest, Sha256}; -use tracing::info; -use zkm_sdk::{include_elf, utils as sdk_utils, ProverClient, ZKMStdin}; -use verifiable_circuit_babe::babe::DummyMulCircuit; - -const ELF: &[u8] = include_elf!("enc-setup-guest"); - -// ── Serialization helpers (raw LE bytes, matching guest deserialization) ────── - -/// Serialize Fq as 32 LE bytes. -fn fq_to_le(fq: &Fq) -> [u8; 32] { - let mut out = [0u8; 32]; - fq.serialize_uncompressed(&mut out[..]).unwrap(); - out -} - -/// Serialize Fr as 32 LE bytes. -fn fr_to_le(fr: &Fr) -> [u8; 32] { - let mut out = [0u8; 32]; - fr.serialize_uncompressed(&mut out[..]).unwrap(); - out -} - -/// Serialize G1Affine as 64 raw LE bytes: x‖y. -fn g1_to_bytes(pt: &G1Affine) -> Vec { - let mut out = Vec::with_capacity(64); - out.extend_from_slice(&fq_to_le(&pt.x)); - out.extend_from_slice(&fq_to_le(&pt.y)); - out -} - -/// Serialize G2Affine as 128 raw LE bytes: x.c0‖x.c1‖y.c0‖y.c1. -fn g2_to_bytes(pt: &G2Affine) -> Vec { - let mut out = Vec::with_capacity(128); - out.extend_from_slice(&fq_to_le(&pt.x.c0)); - out.extend_from_slice(&fq_to_le(&pt.x.c1)); - out.extend_from_slice(&fq_to_le(&pt.y.c0)); - out.extend_from_slice(&fq_to_le(&pt.y.c1)); - out -} - -// ── Native enc_setup computation (for expected-value verification) ──────────── - -fn compute_enc_setup( - vk: &ark_groth16::VerifyingKey, - public_inputs: &[Fr], - r: Fr, - msg: &[u8; 32], -) -> (Vec, [u8; 32]) { - // vk_x = gamma_abc_g1[0] + Σ gamma_abc_g1[i+1] · public_inputs[i] - let mut vk_x = vk.gamma_abc_g1[0].into_group(); - for (i, inp) in public_inputs.iter().enumerate() { - vk_x += vk.gamma_abc_g1[i + 1].into_group() * *inp; - } - - // ct2 = r · delta_g2 (compressed G2 bytes) - let r_delta = (vk.delta_g2.into_group() * r).into_affine(); - let mut ct2_bytes = Vec::new(); - r_delta.serialize_compressed(&mut ct2_bytes).unwrap(); - - // rY = e(alpha_g1, r·beta_g2) + e(vk_x, r·gamma_g2) - let r_beta = (vk.beta_g2.into_group() * r).into_affine(); - let r_gamma = (vk.gamma_g2.into_group() * r).into_affine(); - let t1 = Bn254::pairing(vk.alpha_g1, r_beta); - let t2 = Bn254::pairing(vk_x.into_affine(), r_gamma); - let r_y = t1 + t2; - - // ct3 = blake3(rY) ⊕ msg - let mut ry_bytes = Vec::new(); - r_y.serialize_compressed(&mut ry_bytes).unwrap(); - let mask = verifiable_circuit_babe::babe::h_256(&ry_bytes); - let ct3: [u8; 32] = core::array::from_fn(|i| msg[i] ^ mask[i]); - - (ct2_bytes, ct3) -} - -// ── Main ────────────────────────────────────────────────────────────────────── - -fn main() { - sdk_utils::setup_logger(); - let mut rng = rand::thread_rng(); - - // ── 1. Groth16 setup: a × b = c ────────────────────────────────────────── - let start = Instant::now(); - let a = Fr::from(3u64); - let b = Fr::from(7u64); - let (_, vk) = Groth16::::setup( - DummyMulCircuit { a: Some(a), b: Some(b) }, - &mut rng, - ) - .expect("groth16 setup"); - let public_inputs = vec![a * b]; // c = 21 - info!(elapsed = ?start.elapsed(), "Groth16 setup"); - - // ── 2. Sample private witnesses ─────────────────────────────────────────── - let r = Fr::rand(&mut rng); - let mut msg = [0u8; 32]; - rng.fill_bytes(&mut msg); - - // ── 3. Native enc_setup (reference values for post-execution check) ─────── - let start = Instant::now(); - let (expected_ct2, expected_ct3) = compute_enc_setup(&vk, &public_inputs, r, &msg); - let expected_h_msg: [u8; 32] = Sha256::digest(&msg).into(); - info!(elapsed = ?start.elapsed(), "enc_setup computed natively"); - - // ── 4. Serialize VK components as flat byte vectors ─────────────────────── - let r_bytes = fr_to_le(&r); - let alpha_g1_raw = g1_to_bytes(&vk.alpha_g1); - let beta_g2_raw = g2_to_bytes(&vk.beta_g2); - let gamma_g2_raw = g2_to_bytes(&vk.gamma_g2); - let delta_g2_raw = g2_to_bytes(&vk.delta_g2); - - // gamma_abc_g1: flat array of (n+1) × 64 bytes - let gamma_abc_raw: Vec = vk.gamma_abc_g1 - .iter() - .flat_map(|pt| g1_to_bytes(pt)) - .collect(); - - // public inputs: flat array of n × 32 bytes - let public_inputs_raw: Vec = public_inputs - .iter() - .flat_map(|fr| fr_to_le(fr)) - .collect(); - - // ── 5. Compute expected commitments ─────────────────────────────────────── - let mut vk_hasher = Sha256::new(); - vk_hasher.update(&alpha_g1_raw); - vk_hasher.update(&beta_g2_raw); - vk_hasher.update(&gamma_g2_raw); - vk_hasher.update(&delta_g2_raw); - vk_hasher.update(&gamma_abc_raw); - let expected_h_vk: [u8; 32] = vk_hasher.finalize().into(); - - let expected_h_public_inputs: [u8; 32] = Sha256::digest(&public_inputs_raw).into(); - - // ── 6. Write inputs to ZKMStdin ─────────────────────────────────────────── - let mut stdin = ZKMStdin::new(); - - // private witnesses - stdin.write::<[u8; 32]>(&r_bytes); - stdin.write::<[u8; 32]>(&msg); - - // VK components (as flat Vec) - stdin.write_vec(alpha_g1_raw); - stdin.write_vec(beta_g2_raw); - stdin.write_vec(gamma_g2_raw); - stdin.write_vec(delta_g2_raw); - stdin.write_vec(gamma_abc_raw); - - // public inputs - stdin.write_vec(public_inputs_raw); - - // ── 7. Execute guest ────────────────────────────────────────────────────── - let client = ProverClient::new(); - let start = Instant::now(); - let (mut pub_val, report) = client.execute(ELF, &stdin).run().unwrap(); - info!( - elapsed = ?start.elapsed(), - cycles = report.total_instruction_count(), - "guest executed" - ); - - // ── 8. Read and verify committed outputs ────────────────────────────────── - let ct2_out = pub_val.read::>(); - let ct3_out = pub_val.read::<[u8; 32]>(); - let h_msg_out = pub_val.read::<[u8; 32]>(); - let h_vk_out = pub_val.read::<[u8; 32]>(); - let h_pi_out = pub_val.read::<[u8; 32]>(); - - assert_eq!(ct2_out, expected_ct2, "ct2 mismatch"); - assert_eq!(ct3_out, expected_ct3, "ct3 mismatch"); - assert_eq!(h_msg_out, expected_h_msg, "h_msg mismatch"); - assert_eq!(h_vk_out, expected_h_vk, "h_vk mismatch"); - assert_eq!(h_pi_out, expected_h_public_inputs, "h_public_inputs mismatch"); - - println!("ct2: {}", hex::encode(&ct2_out)); - println!("ct3: {}", hex::encode(ct3_out)); - println!("h_msg: {}", hex::encode(h_msg_out)); - println!("h_vk: {}", hex::encode(h_vk_out)); - println!("h_public_inputs: {}", hex::encode(h_pi_out)); - println!("all enc_setup outputs verified ✓"); - - // ── 9. (Optional) Generate ZK proof ────────────────────────────────────── - // let (pk, vk_proof) = client.setup(ELF); - // let proof = client.prove(&pk, stdin).compressed().run().unwrap(); - // client.verify(&proof, &vk_proof).expect("proof verification failed"); - // proof.save("proof.bin").expect("saving proof failed"); - // println!("proof verified ✓"); -} diff --git a/babe-programs/garbled-circuit/guest/Cargo.toml b/babe-programs/garbled-circuit/guest/Cargo.toml deleted file mode 100644 index ec30988..0000000 --- a/babe-programs/garbled-circuit/guest/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "garbled-circuit-guest" -version.workspace = true -edition.workspace = true - -[dependencies] -zkm-zkvm = { workspace = true, features = ["embedded"] } -garbled-snark-verifier = { workspace = true } - -[features] -sha2 = ["garbled-snark-verifier/garbled", "garbled-snark-verifier/_sha2", "garbled-snark-verifier/_getrandom"] -poseidon2 = ["garbled-snark-verifier/garbled", "garbled-snark-verifier/_poseidon2", "garbled-snark-verifier/_getrandom"] -blake3 = ["garbled-snark-verifier/garbled", "garbled-snark-verifier/_blake3", "garbled-snark-verifier/_getrandom"] -aes = ["garbled-snark-verifier/garbled", "garbled-snark-verifier/_aes", "garbled-snark-verifier/_getrandom"] -default = ["poseidon2"] diff --git a/babe-programs/garbled-circuit/guest/src/main.rs b/babe-programs/garbled-circuit/guest/src/main.rs deleted file mode 100644 index 974a87f..0000000 --- a/babe-programs/garbled-circuit/guest/src/main.rs +++ /dev/null @@ -1,22 +0,0 @@ -// Under the hood, we wrap your main function with some extra code so that it behaves properly -// inside the zkVM. -#![no_std] -#![no_main] -extern crate alloc; - -zkm_zkvm::entrypoint!(main); - -use alloc::vec::Vec; -use garbled_snark_verifier::core::utils::{check_guest, SUB_INPUT_GATES_PARTS}; -use zkm_zkvm::lib::boolean_circuit_garble::boolean_circuit_garble; -fn main() { - let mut sub_gates: [Vec; SUB_INPUT_GATES_PARTS] = core::array::from_fn(|_| Vec::new()); - for i in 0..SUB_INPUT_GATES_PARTS { - sub_gates[i] = zkm_zkvm::io::read_vec(); - } - let sub_wires = zkm_zkvm::io::read_vec(); - let sub_ciphertexts = zkm_zkvm::io::read_vec(); - let input = check_guest(&sub_gates, &sub_wires, &sub_ciphertexts); - let output = boolean_circuit_garble(&input); - assert!(output); -} diff --git a/babe-programs/garbled-circuit/host/Cargo.toml b/babe-programs/garbled-circuit/host/Cargo.toml deleted file mode 100644 index f074716..0000000 --- a/babe-programs/garbled-circuit/host/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "garbled-circuit-host" -version.workspace = true -edition.workspace = true - -[dependencies] -zkm-sdk = { workspace = true } -verifiable-circuit-babe = { workspace = true } -garbled-snark-verifier = { workspace = true } -verifiable-circuit-host = { workspace = true } -ark-bn254 = { version = "0.5.0", features = ["curve", "scalar_field"], default-features = false } -ark-ff = { version = "0.5.0", default-features = false } -ark-ec = { version = "0.5.0", default-features = false } -ark-serialize = { version = "0.5.0" } -sha2 = { workspace = true } -rand = "0.8" -hex = "0.4" -tracing = "0.1.44" -indexmap = "2.12.0" -bincode = "1.3.3" - -[build-dependencies] -zkm-build = { workspace = true } diff --git a/babe-programs/garbled-circuit/host/build.rs b/babe-programs/garbled-circuit/host/build.rs deleted file mode 100644 index e7ea36a..0000000 --- a/babe-programs/garbled-circuit/host/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - zkm_build::build_program("../guest"); -} diff --git a/babe-programs/garbled-circuit/host/src/main.rs b/babe-programs/garbled-circuit/host/src/main.rs deleted file mode 100644 index ed01a69..0000000 --- a/babe-programs/garbled-circuit/host/src/main.rs +++ /dev/null @@ -1,144 +0,0 @@ -mod mem_fs; -mod utils; - -use std::time::Instant; -use ark_bn254::G1Affine; -use ark_ff::UniformRand; -use tracing::info; -use ark_ec::CurveGroup; - -use zkm_sdk::{ProverClient, ZKMProofWithPublicValues, ZKMStdin, include_elf, utils as sdk_utils}; - -use garbled_snark_verifier::dv_bn254::dv_snark::{dv_snark_verifier_bench_circuit}; -use garbled_snark_verifier::{bag::Circuit, dv_bn254::dv_ref::VerifierPayloadRef}; -use garbled_snark_verifier::core::utils::reset_gid; -use garbled_snark_verifier::dv_bn254::fq::Fq; -use verifiable_circuit_babe::gc::compile_babe_gc; -use crate::utils::{gen_sub_circuits, SUB_CIRCUIT_MAX_GATES, SUB_INPUT_GATES_PARTS}; - -/// The ELF we want to execute inside the zkVM. -const ELF: &[u8] = include_elf!("garbled-circuit-guest"); - -fn random_g1_affine() -> G1Affine { - let mut rng = rand::thread_rng(); - ark_bn254::G1Projective::rand(&mut rng).into_affine() -} - -fn custom_babe_garbled_circuit() -> Circuit { - - let pi = random_g1_affine(); - let g = random_g1_affine(); - - let witness: Vec = Fq::to_bits(pi.x) - .into_iter() - .chain(Fq::to_bits(pi.y).into_iter()) - .collect(); - info!("gen witness"); - - // build circuit - let start = Instant::now(); - reset_gid(); - let (bld, output_indices) = compile_babe_gc(g); - let mut circuit = bld.build(&witness); - let elapsed = start.elapsed(); - info!(step = "Gen circuit", elapsed = ?elapsed); - - let start = Instant::now(); - for gate in &mut circuit.1 { - gate.evaluate(); - } - let elapsed = start.elapsed(); - info!(step = "Eval circuit", elapsed = ?elapsed); - - circuit -} - -fn split_circuit() { - let mut circuit = custom_babe_garbled_circuit(); - circuit.gate_counts().print(); - println!("Wires: {}", circuit.0.len()); - gen_sub_circuits(&mut circuit, SUB_CIRCUIT_MAX_GATES, 4); -} - -fn main() { - // Setup logging. - sdk_utils::setup_logger(); - - let start_total = Instant::now(); - - let start = Instant::now(); - split_circuit(); - let elapsed = start.elapsed(); - info!(elapsed = ?elapsed, "split circuit"); - - // The input stream that the guest will read from using `zkm_zkvm::io::read`. Note that the - // types of the elements in the input stream must match the types being read in the guest. - let mut stdin = ZKMStdin::new(); - - let mut sub_gates: [Vec; SUB_INPUT_GATES_PARTS] = - std::array::from_fn(|_| Vec::new()); - for part in 0..SUB_INPUT_GATES_PARTS { - sub_gates[part] = mem_fs::MemFile::read(format!("babe_gates_{}.bin", part)).unwrap(); - // sub_gates[part] = std::fs::read(format!("babe_gates_{}.bin", part)).unwrap(); - info!("sub_gates part {} size: {:?} bytes", part, sub_gates[part].len()); - } - let sub_wires = mem_fs::MemFile::read("babe_wires.bin").unwrap(); - // let sub_wires = std::fs::read("babe_wires.bin").unwrap(); - info!("sub_wires size: {:?} bytes", sub_wires.len()); - - let sub_ciphertexts = mem_fs::MemFile::read("babe_ciphertexts.bin").unwrap(); - // let sub_ciphertexts = std::fs::read("babe_ciphertexts.bin").unwrap(); - info!("sub_ciphertexts size: {:?} bytes", sub_ciphertexts.len()); - - // Write the read sub-circuit to a file for inspection or later use. - for part in 0..SUB_INPUT_GATES_PARTS { - std::fs::write(format!("babe_gates_{}.bin", part), &sub_gates[part]) - .expect("Failed to write sub-gate to babe_gates.bin"); - } - std::fs::write("babe_wires.bin", &sub_wires) - .expect("Failed to write sub-wires to babe_wires.bin"); - std::fs::write("babe_ciphertexts.bin", &sub_ciphertexts) - .expect("Failed to write sub-ciphertexts to babe_ciphertexts.bin"); - info!("Saved sub-circuit to file"); - - // info!("Check guest"); - // garbled_snark_verifier::core::utils::check_guest(&ser_sc_0); - - for i in 0..SUB_INPUT_GATES_PARTS { - stdin.write_vec(sub_gates[i].clone()); - } - stdin.write_vec(sub_wires); - stdin.write_vec(sub_ciphertexts); - // Create a `ProverClient` method. - let client = ProverClient::new(); - - let start = Instant::now(); - // Execute the guest using the `ProverClient.execute` method, without generating a proof. - let (_public_values, report) = client.execute(ELF, &stdin).run().unwrap(); - - let elapsed = start.elapsed(); - info!(elapsed = ?elapsed, "executed program with {} cycles", report.total_instruction_count()); - - let start = Instant::now(); - // Generate the proof for the given guest and input. - let (pk, vk) = client.setup(ELF); - let proof = client.prove(&pk, stdin).compressed().run().unwrap(); - - let elapsed = start.elapsed(); - info!(step = "generated proof", elapsed =? elapsed, "finish proof generation"); - - // Verify proof and public values - client.verify(&proof, &vk).expect("verification failed"); - - // // Test a round trip of proof serialization and deserialization. - // proof.save("proof-with-pis.bin").expect("saving proof failed"); - // let deserialized_proof = - // ZKMProofWithPublicValues::load("proof-with-pis.bin").expect("loading proof failed"); - // - // // Verify the deserialized proof. - // client.verify(&deserialized_proof, &vk).expect("verification failed"); - // - // info!("successfully generated and verified proof for the program!"); - // let total_elapsed = start_total.elapsed(); - // info!(elapsed = ?total_elapsed, "total time"); -} \ No newline at end of file diff --git a/babe-programs/garbled-circuit/host/src/mem_fs.rs b/babe-programs/garbled-circuit/host/src/mem_fs.rs deleted file mode 100644 index bd06774..0000000 --- a/babe-programs/garbled-circuit/host/src/mem_fs.rs +++ /dev/null @@ -1,210 +0,0 @@ -use std::{ - cell::RefCell, - collections::HashMap, - io::{Error, ErrorKind, Read, Result, Write}, - sync::{Mutex, OnceLock}, -}; - -static GLOBAL_FS: OnceLock>>>> = OnceLock::new(); - -fn get_fs() -> &'static Mutex>>> { - GLOBAL_FS.get_or_init(|| Mutex::new(HashMap::new())) -} - -pub struct MemFile { - name: String, - cursor: usize, -} - -#[allow(dead_code)] -impl MemFile { - pub fn create>(name: S) -> Result { - let fs = get_fs(); - let mut map = fs.lock().unwrap(); - map.insert(name.as_ref().to_string(), RefCell::new(Vec::new())); - Ok(Self { name: name.as_ref().to_string(), cursor: 0 }) - } - - pub fn open>(name: S) -> Result { - let fs = get_fs(); - let map = fs.lock().unwrap(); - if map.contains_key(name.as_ref()) { - Ok(Self { name: name.as_ref().to_string(), cursor: 0 }) - } else { - Err(Error::new(ErrorKind::NotFound, "File not found")) - } - } - - pub fn read>(name: S) -> Result> { - let fs = get_fs(); - let map = fs.lock().unwrap(); - match map.get(name.as_ref()) { - Some(cell) => Ok(cell.borrow().clone()), - None => Err(Error::new(ErrorKind::NotFound, "File not found")), - } - } - - pub fn write>(name: S, data: &[u8]) -> Result<()> { - let fs = get_fs(); - let mut map = fs.lock().unwrap(); - let cell = map.entry(name.as_ref().to_string()).or_insert_with(|| RefCell::new(Vec::new())); - let mut content = cell.borrow_mut(); - content.clear(); - content.extend_from_slice(data); - Ok(()) - } - - pub fn print_fs() { - let fs = get_fs(); - let map = fs.lock().unwrap(); - println!("MemFS contains {} files:", map.len()); - for (name, cell) in map.iter() { - let size = cell.borrow().len(); - println!(" {} - {} bytes", name, size); - } - } -} - -impl Read for MemFile { - fn read(&mut self, buf: &mut [u8]) -> Result { - let fs = get_fs(); - let map = fs.lock().unwrap(); - let content = - map.get(&self.name).ok_or_else(|| Error::new(ErrorKind::NotFound, "File not found"))?; - let content = content.borrow(); - if self.cursor >= content.len() { - return Ok(0); - } - let len = std::cmp::min(buf.len(), content.len() - self.cursor); - buf[..len].copy_from_slice(&content[self.cursor..self.cursor + len]); - self.cursor += len; - Ok(len) - } -} - -impl Write for MemFile { - fn write(&mut self, buf: &[u8]) -> Result { - let fs = get_fs(); - let map = fs.lock().unwrap(); - let content_cell = - map.get(&self.name).ok_or_else(|| Error::new(ErrorKind::NotFound, "File not found"))?; - let mut content = content_cell.borrow_mut(); - - if self.cursor > content.len() { - content.resize(self.cursor, 0); - } - if self.cursor + buf.len() > content.len() { - content.resize(self.cursor + buf.len(), 0); - } - content[self.cursor..self.cursor + buf.len()].copy_from_slice(buf); - self.cursor += buf.len(); - Ok(buf.len()) - } - - fn flush(&mut self) -> Result<()> { - Ok(()) - } -} -#[cfg(test)] -mod tests { - use super::*; - use std::io::{Read, Write}; - - #[test] - fn test_create_and_write() { - let mut file = MemFile::create("test_create.txt").unwrap(); - let written = file.write(b"hello").unwrap(); - assert_eq!(written, 5); - - let contents = MemFile::read("test_create.txt").unwrap(); - assert_eq!(contents, b"hello"); - } - - #[test] - fn test_open_and_read() { - let mut file = MemFile::create("test_open.txt").unwrap(); - file.write_all(b"rustacean").unwrap(); - file.flush().unwrap(); - - let mut file2 = MemFile::open("test_open.txt").unwrap(); - let mut buf = vec![0u8; 9]; - let read_bytes = file2.read(&mut buf).unwrap(); - assert_eq!(read_bytes, 9); - assert_eq!(&buf, b"rustacean"); - } - - #[test] - fn test_read_nonexistent_file() { - let result = MemFile::read("no_such_file.txt"); - assert!(result.is_err()); - } - - #[test] - fn test_write_overwrites_existing() { - let mut file = MemFile::create("test_overwrite.txt").unwrap(); - file.write_all(b"old data").unwrap(); - - // Overwrite via static write - MemFile::write("test_overwrite.txt", b"new data").unwrap(); - - let content = MemFile::read("test_overwrite.txt").unwrap(); - assert_eq!(content, b"new data"); - } - - #[test] - fn test_multiple_reads_and_writes() { - let mut file = MemFile::create("test_multi.txt").unwrap(); - - // Write first chunk - file.write_all(b"abc").unwrap(); - // Write second chunk - file.write_all(b"defgh").unwrap(); - - file.flush().unwrap(); - - // Reset cursor manually for read test - let mut file2 = MemFile::open("test_multi.txt").unwrap(); - - let mut buf = vec![0u8; 8]; - let read_bytes = file2.read(&mut buf).unwrap(); - assert_eq!(read_bytes, 8); - assert_eq!(&buf[..read_bytes], b"abcdefgh"); - } - #[test] - fn test_1gb_read_write() { - use std::io::{Read, Write}; - - // 1GB = 1024 * 1024 * 1024 bytes - const ONE_GB: usize = 1024 * 1024 * 1024; - - // Create file - let mut file = MemFile::create("bigfile.bin").expect("create failed"); - - // Write 1GB of zero bytes - let chunk = vec![0u8; 1024 * 1024]; // 1 MB chunk - let mut written = 0; - while written < ONE_GB { - let write_size = std::cmp::min(chunk.len(), ONE_GB - written); - file.write_all(&chunk[..write_size]).expect("write failed"); - written += write_size; - } - file.flush().expect("flush failed"); - - // Read back and verify size - let mut file2 = MemFile::open("bigfile.bin").expect("open failed"); - let mut read_bytes = 0; - let mut buffer = vec![0u8; 1024 * 1024]; // 1MB buffer - - while read_bytes < ONE_GB { - let read_size = file2.read(&mut buffer).expect("read failed"); - if read_size == 0 { - break; - } - // Check all zeros in the buffer read - assert!(buffer[..read_size].iter().all(|&b| b == 0)); - read_bytes += read_size; - } - - assert_eq!(read_bytes, ONE_GB); - } -} diff --git a/babe-programs/garbled-circuit/host/src/utils.rs b/babe-programs/garbled-circuit/host/src/utils.rs deleted file mode 100644 index 56a3653..0000000 --- a/babe-programs/garbled-circuit/host/src/utils.rs +++ /dev/null @@ -1,163 +0,0 @@ -use std::collections::{HashMap, HashSet}; -use std::io::Write; -use crate::mem_fs; -use garbled_snark_verifier::bag::{Circuit, Wire}; -use garbled_snark_verifier::core::utils::{serialize_to_bytes, SerializableGate, SerializableSubCircuitGates, SerializableSubWires, SerializableWire}; -use std::time::Instant; -use tracing::info; -use indexmap::IndexMap; - -pub const SUB_CIRCUIT_MAX_GATES: usize = 2_000_000; -pub const SUB_INPUT_GATES_PART_SIZE: usize = 200_000; -pub const SUB_INPUT_GATES_PARTS: usize = 10; - -pub fn gen_sub_circuits( - circuit: &mut Circuit, - max_gates: usize, - finest_ratio_target: usize, -) { - let start = Instant::now(); - let mut garbled_gates = circuit.garbled_gates(); - let elapsed = start.elapsed(); - info!(step = "garble gates", elapsed =? elapsed, "garbled gates: {}", garbled_gates.len()); - - let size = circuit.1.len().div_ceil(max_gates); - - let start = Instant::now(); - let wires: Vec = circuit.0.iter().map(|w| w.borrow().clone()).collect(); - let mut finest = finest_ratio_target; - let mut finest_id = 0; - /// find the sub-circuit with the finest non-free gates ratio - circuit.1.chunks(max_gates).enumerate().zip(garbled_gates.chunks_mut(max_gates)).for_each( - |((i, w), garblings)| { - info!(step = "gen_sub_circuits", "Split batch {i}/{size}"); - let ciphertexts: Vec<_> = garblings - .iter() - .filter_map(|g| g.as_ref().cloned()) - .collect(); - - /// compute non-free gates ratio - let non_free_gates = ciphertexts.len(); - if non_free_gates != 0 { - let ratio = SUB_CIRCUIT_MAX_GATES / non_free_gates; - let dif = { - if finest_ratio_target > ratio { - finest_ratio_target - ratio - } else { - ratio - finest_ratio_target - } - }; - if dif < finest { - finest = dif; - finest_id = i; - } - } - } - ); - info!("finest id: {}, finest dif: {}", finest_id, finest); - // dump subcircuit with the finest ratio - circuit.1.chunks(max_gates).enumerate().zip(garbled_gates.chunks_mut(max_gates)).for_each( - |((i, w), garblings)| { - if i == finest_id { - info!(step = "gen_sub_circuits", "Dumping finest batch {i}/{size}"); - let ciphertexts: Vec<_> = garblings - .iter() - .filter_map(|g| g.as_ref().cloned()) - .collect(); - - // All of this should be removed. - let start = Instant::now(); - let mut sub_wires_map: IndexMap = IndexMap::new(); - let mut next_sub_id = 0; - for gate in w { - let wire_a_id = gate.wire_a.borrow().id.unwrap(); - sub_wires_map.entry(wire_a_id).or_insert_with(|| { - let id = next_sub_id; - next_sub_id += 1; - id - }); - let wire_b_id = gate.wire_b.borrow().id.unwrap(); - sub_wires_map.entry(wire_b_id).or_insert_with(|| { - let id = next_sub_id; - next_sub_id += 1; - id - }); - let wire_c_id = gate.wire_c.borrow().id.unwrap(); - sub_wires_map.entry(wire_c_id).or_insert_with(|| { - let id = next_sub_id; - next_sub_id += 1; - id - }); - } - // Build the vector of sub wires - let serialziable_wires: Vec<_> = sub_wires_map - .keys() - .map(|&id| { - SerializableWire { - label: wires[id as usize].label.unwrap(), - value: wires[id as usize].value, - } - }) - .collect(); - - let sub_wires = SerializableSubWires::from_serialzable_wires(&serialziable_wires); - let elapsed = start.elapsed(); - info!(step = "gen_sub_wires ", elapsed = ?elapsed); - - let mut gates: Vec<_> = w.iter().map(|w| SerializableGate { - gate_type: w.gate_type as u8, - wire_a_id: *sub_wires_map.get(&w.wire_a.borrow().id.unwrap()).unwrap(), - wire_b_id: *sub_wires_map.get(&w.wire_b.borrow().id.unwrap()).unwrap(), - wire_c_id: *sub_wires_map.get(&w.wire_c.borrow().id.unwrap()).unwrap(), - gid: w.gid, - } - ).collect(); - let last_gate = gates.last().unwrap().clone(); - let dummy_gate = SerializableGate { - gate_type: 8, - wire_a_id: last_gate.wire_a_id, - wire_b_id: last_gate.wire_b_id, - wire_c_id: last_gate.wire_c_id, - gid: last_gate.gid, - }; - while gates.len() < SUB_CIRCUIT_MAX_GATES { - gates.push(dummy_gate.clone()); - } - - for part in 0..SUB_INPUT_GATES_PARTS { - let start = part * SUB_INPUT_GATES_PART_SIZE; - let end = start + SUB_INPUT_GATES_PART_SIZE; - let mut array_gates: [SerializableGate; SUB_INPUT_GATES_PART_SIZE] = [SerializableGate::default(); SUB_INPUT_GATES_PART_SIZE]; - array_gates.copy_from_slice(&gates[start..end]); - - let sub_gates: SerializableSubCircuitGates = SerializableSubCircuitGates { - gates: array_gates, - }; - - // serialize each sub-gate array to its own file - let bytes = serialize_to_bytes(&sub_gates); - let mut file = mem_fs::MemFile::create(format!("babe_gates_{}.bin", part)).unwrap(); - file.write_all(&bytes).unwrap(); - } - - bincode::serialize_into( - mem_fs::MemFile::create(format!("babe_wires.bin")).unwrap(), - &sub_wires, - ) - .unwrap(); - - bincode::serialize_into( - mem_fs::MemFile::create(format!("babe_ciphertexts.bin")).unwrap(), - &ciphertexts, - ) - .unwrap(); - - let elapsed = start.elapsed(); - info!(step = "gen_sub_circuits", elapsed = ?elapsed, "Writing garbled_{i}"); - } - } - ); - - let elapsed = start.elapsed(); - info!(step = "gen_sub_circuits", elapsed =? elapsed, "total time"); -} \ No newline at end of file diff --git a/babe-programs/soldering/host/Cargo.toml b/babe-programs/soldering/host/Cargo.toml index e19718a..bb87455 100644 --- a/babe-programs/soldering/host/Cargo.toml +++ b/babe-programs/soldering/host/Cargo.toml @@ -14,6 +14,8 @@ ark-relations = "0.5.0" ark-crypto-primitives = "0.5.0" rand = "0.8.5" tracing = "0.1" +rand_chacha = { version = "0.3", default-features = false } + [build-dependencies] zkm-build = { workspace = true } \ No newline at end of file diff --git a/babe-programs/soldering/host/src/main.rs b/babe-programs/soldering/host/src/main.rs index fe667f7..6349837 100644 --- a/babe-programs/soldering/host/src/main.rs +++ b/babe-programs/soldering/host/src/main.rs @@ -1,12 +1,14 @@ use std::time::Instant; use ark_bn254::{Bn254, Fr}; -use ark_crypto_primitives::snark::CircuitSpecificSetupSNARK; +use ark_crypto_primitives::snark::{CircuitSpecificSetupSNARK, SNARK}; use ark_groth16::Groth16; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; use tracing::info; +use rand::SeedableRng; use zkm_sdk::{include_elf, utils as sdk_utils, ProverClient, ZKMStdin}; -use verifiable_circuit_babe::prover::BABEProver; +use verifiable_circuit_babe::babe::DummyMulCircuit; +use verifiable_circuit_babe::prover::{BABEProver, GROTH_16_SEED}; use verifiable_circuit_babe::soldering::{ build_soldered_wires_input, SolderedLabelsData, SolderedWiresInput, }; @@ -14,38 +16,33 @@ use verifiable_circuit_babe::verifier::BABEVerifier; const ELF: &[u8] = include_elf!("soldering-guest"); -const N_CC: usize = 4; // Number of C&C instances. +const M_CC: usize = 4; // Number of finalized C&C instances. -#[derive(Clone)] -struct TrivialCircuit; - -impl ConstraintSynthesizer for TrivialCircuit { - fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { - cs.new_input_variable(|| Ok(Fr::from(1u64)))?; - Ok(()) - } -} - -fn setup_vk() -> (ark_groth16::VerifyingKey, Vec) { - let mut rng = rand::thread_rng(); - let (_, vk) = - Groth16::::setup(TrivialCircuit, &mut rng).unwrap(); - (vk, vec![Fr::from(1u64)]) -} fn main() { sdk_utils::setup_logger(); - // 1. Setup VK and create N_CC instances - let start = Instant::now(); - let (vk, public_inputs) = setup_vk(); - info!(elapsed = ?start.elapsed(), "VK setup done"); - + // Prove with the same dummy circuit. + let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(GROTH_16_SEED); + let a = Fr::from(3u64); + let b = Fr::from(7u64); + let (pk, vk) = ark_groth16::Groth16::::setup( + DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, + ).unwrap(); + let proof = ark_groth16::Groth16::::prove( + &pk, + DummyMulCircuit:: { a: Some(a), b: Some(b) }, + &mut rng, + ).unwrap(); + let static_public_inputs = a * b; + let dynamic_public_inputs = a * a; + + // 1. Create N_CC instances let start = Instant::now(); - let verifier = BABEVerifier::new(N_CC, &vk, &public_inputs).expect("verifier setup failed"); - info!(elapsed = ?start.elapsed(), n_cc = N_CC, "BABEVerifier created"); + let verifier = BABEVerifier::new(M_CC, &vk, static_public_inputs).expect("verifier setup failed"); + info!(elapsed = ?start.elapsed(), n_cc = M_CC, "BABEVerifier created"); - // 2. C&C commit and derive finalized indices + // 2. C&C commit and derive finalized indices let package = verifier.commit(); let finalized_indices = vec![0, 1, 2, 3]; @@ -54,7 +51,7 @@ fn main() { let soldering_input = build_soldered_wires_input(&verifier, &finalized_indices); info!(elapsed = ?start.elapsed(), "SolderedWiresInput built"); - // 4. Feed input to zkVM guest + // 4. Feed input to zkVM guest let mut stdin = ZKMStdin::new(); stdin.write::(&soldering_input); @@ -75,7 +72,7 @@ fn main() { .expect("soldering output verification failed"); info!("commitment verification passed"); - // 6. Generate ZK proof + // 6. Generate ZK proof let start = Instant::now(); let (pk, vk_proof) = client.setup(ELF); let proof = client.prove(&pk, stdin).compressed().run().unwrap(); diff --git a/garbled-snark-verifier/src/core/circuit.rs b/garbled-snark-verifier/src/core/circuit.rs index 1558a76..b64ed9c 100644 --- a/garbled-snark-verifier/src/core/circuit.rs +++ b/garbled-snark-verifier/src/core/circuit.rs @@ -109,23 +109,20 @@ impl Circuit { } } - pub fn set_witness_value(&mut self, witness: &[bool]) { + pub fn set_witness_value(&mut self, witness: &[bool], skip: usize) { // Gate wires are Rc> sharing the same data as self.0, so this covers all. witness.iter() - .zip(self.0.iter().skip(2)) + .zip(self.0.iter().skip(skip)) .for_each(|(bit, wirex)| wirex.borrow_mut().set_value_for_uninitialized(*bit)); } - pub fn reset_circuit_except_constants(&mut self) { + pub fn reset_circuit_except_01_constants(&mut self) { for wirex in self.0.iter().skip(2) { wirex.borrow_mut().value = None; } for wirex in self.0.iter() { wirex.borrow_mut().label = None; } - // compute the size of circuit, because this resetted circuit will be reused for multiple proofs - let size = self.size_in_bytes(); - println!("Reset circuit, size: {} MB", size as f64 / 1_048_576.0); } pub fn is_fresh(&self) -> bool { diff --git a/garbled-snark-verifier/src/dv_bn254/g1.rs b/garbled-snark-verifier/src/dv_bn254/g1.rs index 7b91c31..fbb7325 100644 --- a/garbled-snark-verifier/src/dv_bn254/g1.rs +++ b/garbled-snark-verifier/src/dv_bn254/g1.rs @@ -19,6 +19,25 @@ pub struct G1Projective { pub z: Fq, } pub const G1_PROJECTIVE_LEN: usize = 3 * FQ_LEN; +/// Width of each scalar-mul window, in bits. w=8 gives 32 windows for a 254-bit scalar +/// (vs 64 for w=4), so the dominant Fq-mul cost drops roughly 2× at the price of a +/// 16× larger precomputed affine table (free in gates; only bandwidth). +pub const SCALAR_WINDOW_BITS: usize = 8; +pub const SCALAR_WINDOW_COUNT: usize = (Fr::N_BITS + SCALAR_WINDOW_BITS - 1) / SCALAR_WINDOW_BITS; +pub const SCALAR_WINDOW_ENTRIES: usize = 1 << SCALAR_WINDOW_BITS; + +// ─── Signed-digit scalar-mul parameters ──────────────────────────────────────── +// Scalar is recoded offline (Booth) into digits d_i ∈ [-128, 127], one extra window +// absorbs the carry. Each window carries (w-1)=7 index bits + 1 sign bit + 1 skip bit = 9 bits. +// Table is halved: 2^(w-1) = 128 entries per window, storing (j+1)·256^i·P for j ∈ [0, 127]. +// Saves ~50 % of the MUX cost vs. the unsigned version at the price of a conditional +// y-negation (near-free) and a single extra window. +pub const SIGNED_SCALAR_WINDOW_BITS: usize = 8; +pub const SIGNED_SCALAR_WINDOW_COUNT: usize = + (Fr::N_BITS + SIGNED_SCALAR_WINDOW_BITS - 1) / SIGNED_SCALAR_WINDOW_BITS + 1; +pub const SIGNED_SCALAR_WINDOW_ENTRIES: usize = 1 << (SIGNED_SCALAR_WINDOW_BITS - 1); +pub const SIGNED_DIGIT_BITS: usize = SIGNED_SCALAR_WINDOW_BITS + 1; +pub const SIGNED_SCALAR_DIGIT_TOTAL_BITS: usize = SIGNED_SCALAR_WINDOW_COUNT * SIGNED_DIGIT_BITS; impl G1Projective { pub fn as_montgomery(p: ark_bn254::G1Projective) -> ark_bn254::G1Projective { ark_bn254::G1Projective { @@ -356,6 +375,138 @@ impl G1Projective { res } + /// Mixed projective + affine addition in Montgomery form. + /// + /// `proj` — variable projective point (X·R : Y·R : Z·R), with Z != 0. + /// `affine` — variable affine point encoded as Montgomery wires (x·R, y·R). + /// + /// Returns (X3·R : Y3·R : Z3·R). + pub fn add_mixed_montgomery_no_inf( + bld: &mut T, + proj: &[usize], + affine: &[usize], + ) -> Vec { + assert_eq!(proj.len(), G1_PROJECTIVE_LEN); + assert_eq!(affine.len(), G1_AFFINE_LEN); + + let x1 = &proj[0..FQ_LEN]; + let y1 = &proj[FQ_LEN..2 * FQ_LEN]; + let z1 = &proj[2 * FQ_LEN..3 * FQ_LEN]; + let x2 = &affine[0..FQ_LEN]; + let y2 = &affine[FQ_LEN..2 * FQ_LEN]; + + // z2 = 1 (affine), so z2² = z2³ = 1 and u1 = x1, s1 = y1. + let z1s = Fq::square_montgomery(bld, z1); + let z1c = Fq::mul_montgomery(bld, &z1s, z1); + + let u2 = Fq::mul_montgomery(bld, x2, &z1s); + let s2 = Fq::mul_montgomery(bld, y2, &z1c); + + let h = Fq::sub(bld, x1, &u2); + let r = Fq::sub(bld, y1, &s2); + + let h2 = Fq::square_montgomery(bld, &h); + let g = Fq::mul_montgomery(bld, &h, &h2); + let v = Fq::mul_montgomery(bld, x1, &h2); + + let r2 = Fq::square_montgomery(bld, &r); + let r2g = Fq::add(bld, &r2, &g); + let vd = Fq::double(bld, &v); + let x3 = Fq::sub(bld, &r2g, &vd); + + let vx3 = Fq::sub(bld, &v, &x3); + let w = Fq::mul_montgomery(bld, &r, &vx3); + let s1g = Fq::mul_montgomery(bld, y1, &g); + let y3 = Fq::sub(bld, &w, &s1g); + + // z2 = 1, so z1·z2 = z1. + let z3 = Fq::mul_montgomery(bld, z1, &h); + + let mut res = Vec::new(); + res.extend(x3); + res.extend(y3); + res.extend(z3); + res + } + + /// Unsigned 8-bit windowed scalar multiplication with a garbler-private full affine table. + /// + /// `scalar` — 254 wire indices, raw (non-Montgomery) bits, LSB-first. + /// `table` — `SCALAR_WINDOW_COUNT` windows of `SCALAR_WINDOW_ENTRIES` affine points, + /// each encoded as Montgomery wires (x·R, y·R). + /// Window `w`, entry `j` represents `j * (256^w * P)` in affine form. + /// Entry j=0 is the point at infinity (addition skipped via window_nonzero guard). + pub fn scalar_mul_private_table_circuit( + bld: &mut T, + scalar: &[usize], + table: &[usize], + ) -> Vec { + assert_eq!(scalar.len(), Fr::N_BITS); + assert_eq!( + table.len(), + SCALAR_WINDOW_COUNT * SCALAR_WINDOW_ENTRIES * G1_AFFINE_LEN + ); + + let z_one = Fq::wires_set(bld, Fq::as_montgomery(ark_bn254::Fq::from(1u64))).0.to_vec(); + let inf_wires = G1Projective::wires_set( + bld, + G1Projective::as_montgomery(ark_bn254::G1Projective::default()), + ) + .to_vec_wires(); + + let mut acc: Vec = inf_wires.clone(); + let mut acc_is_inf: usize = bld.one(); + let zero_wire = bld.zero(); + + let bit = |i: usize| -> usize { + if i < Fr::N_BITS { scalar[i] } else { zero_wire } + }; + + for w in 0..SCALAR_WINDOW_COUNT { + let base = w * SCALAR_WINDOW_BITS; + let sel: Vec = (0..SCALAR_WINDOW_BITS).map(|k| bit(base + k)).collect(); + + // window_nonzero = OR of all 8 selector bits + let mut window_nonzero = sel[0]; + for &b in &sel[1..] { + window_nonzero = bld.or_wire(window_nonzero, b); + } + + let window_base = w * SCALAR_WINDOW_ENTRIES * G1_AFFINE_LEN; + let ws = &table[window_base..window_base + SCALAR_WINDOW_ENTRIES * G1_AFFINE_LEN]; + + let tab_x: Vec> = (0..SCALAR_WINDOW_ENTRIES) + .map(|i| ws[i * G1_AFFINE_LEN..i * G1_AFFINE_LEN + FQ_LEN].to_vec()) + .collect(); + let tab_y: Vec> = (0..SCALAR_WINDOW_ENTRIES) + .map(|i| ws[i * G1_AFFINE_LEN + FQ_LEN..(i + 1) * G1_AFFINE_LEN].to_vec()) + .collect(); + + let affine_x = Fq::multiplexer(bld, &tab_x, &sel, SCALAR_WINDOW_BITS); + let affine_y = Fq::multiplexer(bld, &tab_y, &sel, SCALAR_WINDOW_BITS); + + let mut affine = Vec::with_capacity(G1_AFFINE_LEN); + affine.extend_from_slice(&affine_x); + affine.extend_from_slice(&affine_y); + + let mut affine_proj = Vec::with_capacity(G1_PROJECTIVE_LEN); + affine_proj.extend_from_slice(&affine_x); + affine_proj.extend_from_slice(&affine_y); + affine_proj.extend_from_slice(&z_one); + + let mixed = Self::add_mixed_montgomery_no_inf(bld, &acc, &affine); + + let candidate = + Self::selector_projective_montgomery(bld, &affine_proj, &mixed, acc_is_inf); + acc = Self::selector_projective_montgomery(bld, &candidate, &acc, window_nonzero); + + let not_nonzero = not(bld, window_nonzero); + acc_is_inf = bld.and_wire(acc_is_inf, not_nonzero); + } + + acc + } + pub fn msm_montgomery_circuit( bld: &mut T, scalars: &[Vec], diff --git a/verifiable-circuit-babe/Cargo.toml b/verifiable-circuit-babe/Cargo.toml index e0d5eaf..0b95ae7 100644 --- a/verifiable-circuit-babe/Cargo.toml +++ b/verifiable-circuit-babe/Cargo.toml @@ -23,7 +23,13 @@ garbled-snark-verifier = { workspace = true, features = ["_poseidon2", "_getrand aes = "0.8" cfg-if = "1.0.4" ripemd = "0.2.0" -[target.'cfg(all(target_os = "zkvm"))'.dependencies] +rayon = "1.11.0" + +[target.'cfg(not(target_os = "zkvm"))'.dependencies] +bitvm = { git = "https://github.com/GOATNetwork/BitVM.git", branch = "GA" } +bitcoin = { version = "0.32.5", features = ["rand-std"] } + +[target.'cfg(target_os = "zkvm")'.dependencies] zkm-zkvm = { workspace = true } [features] diff --git a/verifiable-circuit-babe/src/babe.rs b/verifiable-circuit-babe/src/babe.rs index 990b4da..a2ad630 100644 --- a/verifiable-circuit-babe/src/babe.rs +++ b/verifiable-circuit-babe/src/babe.rs @@ -1,8 +1,9 @@ -use ark_bn254::{Bn254, Fr, G1Affine}; -use ark_ec::AffineRepr; +use ark_bn254::{Bn254, Fr}; use ark_ff::PrimeField; use ark_groth16::{Proof as Groth16Proof, VerifyingKey as Groth16VerifyingKey}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_groth16::ProvingKey as Groth16ProvingKey; + +use ark_serialize::CanonicalSerialize; use ark_relations::lc; use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; use rand::SeedableRng; @@ -15,34 +16,28 @@ use crate::cac::{ cac_finalize_indices, verify_finalized_instances, verify_opened_instances, CACSetupPackage, FinalizedInstanceData, }; -use crate::lamport::{lamport_keygen, lamport_sign, lamport_verify, LamportPk, LamportSk}; -use crate::prover::BABEProver; + +use crate::wots::{wots96_verify, Wots96, Wots96PublicKey, Wots96Secret}; +use crate::utils::pi1_xd_to_wots96_msg; +use bitvm::signatures::Wots; +use crate::prover::{BABEProver, GROTH_16_SEED}; use crate::soldering::{build_soldered_wires_input, soldering_guest_compute, SolderingData, SolderingProof}; -use crate::transactions::{OnchainSize, TxAssertWitness, TxChallengeAssertOutputLock, TxChallengeAssertWitness, TxDepositLock, TxNoWithdrawWitness, TxWithdrawWitness, TxWronglyChallengedWitness}; +use crate::transactions::{OnchainSize, TxAssertWitness, TxChallengeAssertOutputLock, TxChallengeAssertWitness, TxDepositLock, TxWronglyChallengedWitness}; pub use crate::utils::{derive_hashlock, g1_from_ser_checked, g1_to_ser, g2_from_ser_checked, g2_to_ser, groth16_vk_x, h_256, ro_from_pairing_bytes}; use crate::verifier::BABEVerifier; // ─── Constants ──────────────────────────────────────────────────────────────── - -/// Number of bits in π₁ (G1Affine): 254 bits for x + 254 bits for y. -pub const LAMPORT_N: usize = 508; - /// Total number of C&C instances the Verifier creates and commits to. /// In practice, N_CC = 181. -pub const N_CC: usize = 10; +pub const N_CC: usize = 4; /// Number of instances the Prover finalizes (keeps hidden); rest are opened. -pub const M_CC: usize = 4; - -/// Byte size of a Lamport signature on-chain: LAMPORT_N revealed 16-byte secrets. -pub const LAMPORT_SIG_BYTES: usize = LAMPORT_N * 16; +/// In practice, M_CC = 4. +pub const M_CC: usize = 2; /// Byte size of a Bitcoin signature placeholder (64 bytes in production). pub const BTC_SIG_BYTES: usize = 32; -/// Byte size of a compressed G1Affine point (π₁). -pub const PI1_BYTES: usize = 33; - /// Byte size of the secret message. pub const MSG_BYTES: usize = 32; @@ -54,6 +49,8 @@ pub struct BtcPk(pub [u8; 33]); /// Named Bitcoin signature placeholders (64-byte Schnorr in production). #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum BabeBtcSig { + // In practice, this is divided into 2 Txns: ProverPresigChallengeAssert_1 + // and ProverPresigChallengeAssert_2 ProverPresigChallengeAssert, ProverPresigNoWithdraw, VerifierPresigAssert, @@ -68,20 +65,11 @@ pub enum BabeBtcSig { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct EncodingKeyPublic(pub Vec<[[u8; 20]; 2]>); -pub fn compute_epk_with_delta(encoding_keys: &[S], delta: S) -> EncodingKeyPublic { - let pairs = encoding_keys.iter().map(|&key| [derive_hashlock(&key.0), derive_hashlock(&(key ^ delta).0)]).collect(); - EncodingKeyPublic(pairs) -} - -pub fn compute_epk(encoding_keys: &[S]) -> EncodingKeyPublic { - use garbled_snark_verifier::core::utils::NON_CAC_DELTA; - compute_epk_with_delta(encoding_keys, NON_CAC_DELTA) -} - // ─── Presig structs ─────────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub struct ProverPresigs { + // In practice, this is divided into 2 sigs, for ChallengeAssert1 and CHallengeAssert2 pub sig_challenge_assert: BabeBtcSig, pub sig_no_withdraw: BabeBtcSig, } @@ -103,13 +91,16 @@ pub struct WeKnownPi1SetupCt { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct WeKnownPi1ProveCt { pub ct1_r_pi1: Vec, + pub ct1_prime: Vec, // r * Q } // ─── Setup state ───────────────────────────────────────────────────────────── /// Everything the Prover stores after completing the setup phase. +/// In practice, prover has the commitment of input labels from the Verifier Txn skeletons. +/// (Verifier just need to commit the base instance input labels) pub struct ProverSetupState { - pub lsk_p: LamportSk, + pub wots_sk_p: Wots96Secret, pub finalized: Vec, pub soldering: SolderingData, /// h_msg per finalized instance, in finalized-index order. @@ -122,7 +113,7 @@ pub struct VerifierSetupState { pub verifier: BABEVerifier, pub package: CACSetupPackage, pub finalized_indices: Vec, - pub lpk_p: LamportPk, + pub wots_pk_p: Wots96PublicKey, pub presigs_p: ProverPresigs, } @@ -130,6 +121,7 @@ pub struct VerifierSetupState { pub struct BabeCACE2ERun { pub deposit_lock: TxDepositLock, pub assert_witness: TxAssertWitness, + // In practice, this is divided into 2 Txn witness: challenge_assert1/2 pub challenge_assert_witness: TxChallengeAssertWitness, pub wrongly_challenged_witness: TxWronglyChallengedWitness, } @@ -140,9 +132,9 @@ pub struct BabeCACE2ERun { /// and the public `CACSetupPackage` to send to the Prover. pub fn babe_verifier_cac_setup( vk: &Groth16VerifyingKey, - public_inputs: &[Fr], + static_public_inputs: Fr, ) -> (BABEVerifier, CACSetupPackage) { - let verifier = BABEVerifier::new(N_CC, vk, public_inputs).expect("verifier CAC setup failed"); + let verifier = BABEVerifier::new(N_CC, vk, static_public_inputs).expect("verifier CAC setup failed"); println!("Verifier: committing all instances.."); let package = verifier.commit(); (verifier, package) @@ -153,8 +145,8 @@ pub fn babe_verifier_cac_setup( pub fn babe_verifier_open_and_solder( verifier: &BABEVerifier, finalized_indices: &[usize], -) -> (Vec<(usize, u64)>, Vec, SolderingData, [u8; 20]) { - let (opened, finalized) = verifier.open(finalized_indices); +) -> (Vec<(usize, u64)>, Vec, SolderingData) { + let (opened, finalized) = verifier.open(finalized_indices).expect("verifier open failed"); // Note that this part will be replaced by generating soldering proof in production. let soldered_input = build_soldered_wires_input(verifier, finalized_indices); @@ -164,8 +156,8 @@ pub fn babe_verifier_open_and_solder( finalized_indices: finalized_indices.to_vec(), soldering_proof: SolderingProof { soldered_output, _proof: PhantomData }, }; - - (opened, finalized, soldering, derive_hashlock(&verifier.temp_val)) + + (opened, finalized, soldering) } /// Prover: verify the opened instances, the finalized instances, and the soldering proof. @@ -175,9 +167,9 @@ pub fn babe_prover_verify_setup( finalized: &[FinalizedInstanceData], soldering: &SolderingData, vk: &Groth16VerifyingKey, - public_inputs: &[Fr], + static_public_inputs: Fr, ) -> Result<(), String> { - verify_opened_instances(package, opened, vk, public_inputs)?; + verify_opened_instances(package, opened, vk, static_public_inputs)?; verify_finalized_instances(package, finalized)?; BABEProver::verify_soldering_output(package, soldering)?; Ok(()) @@ -199,11 +191,17 @@ pub fn babe_verifier_presign() -> VerifierPresigs { } } +// Prover verifies verifier presigns. In practice, this should check +// the Txn skeletons. Additional check: hardcoded prover_lpk in the ChallengeAssert_1/2 +// is correct. pub fn babe_verify_verifier_presigs(presigs_v: &VerifierPresigs) -> bool { presigs_v.sig_assert == BabeBtcSig::VerifierPresigAssert && presigs_v.sig_withdraw == BabeBtcSig::VerifierPresigWithdraw } +// Verifier verifies prover presigns. In practice, this should check +// the Txn skeletons. Additional check: hardcoded verifier_lpk in the ChallengeAssert_1/2 +// is correct pub fn babe_verify_prover_presigs( prover_presigs: &ProverPresigs, challenge_assert_outlock: &TxChallengeAssertOutputLock, @@ -219,10 +217,11 @@ pub fn babe_verify_prover_presigs( let keys_valid = challenge_assert_outlock.pk_p == *prover_pkey && challenge_assert_outlock.pk_v == *verifier_pkey; + // check that the h_msg is correct. let h_msgs_valid = challenge_assert_outlock.h_msgs.len() == finalized_indices.len() && challenge_assert_outlock.h_msgs.iter().zip(finalized_indices.iter()).all(|(&h_msg, &idx)| { - h_msg == package.commits[idx].h_msg - }); + h_msg == package.commits[idx].h_msg + }); presigs_valid && keys_valid && h_msgs_valid } @@ -233,15 +232,14 @@ pub fn babe_build_deposit_lock(pk_p: BtcPk, pk_v: BtcPk, amount: u64) -> TxDepos TxDepositLock { pk_p, pk_v, amount } } -// ─── Assert phase (Prover posts π₁) ───────────────────────────────────────── +// ─── Assert phase (Prover posts π₁ and x_d) ───────────────────────────────────────── -/// Prover: sign π₁ with lsk_P and build the assert witness. -pub fn babe_prover_assert(proof: &Groth16Proof, lsk_p: &LamportSk) -> TxAssertWitness { +/// Prover: sign π₁ and x_d with wots_sk_P and build the assert witness. +pub fn babe_prover_assert(proof: &Groth16Proof, wots_sk: &Wots96Secret, x_d: ark_bn254::Fr) -> TxAssertWitness { let pi1 = proof.a; - let mut pi1_bytes = Vec::new(); - pi1.serialize_compressed(&mut pi1_bytes).expect("serialize π₁"); - let lamport_sig = lamport_sign(lsk_p, &pi1); - TxAssertWitness { pi1: pi1_bytes, lamport_sig } + let msg = pi1_xd_to_wots96_msg(&pi1, x_d); + let wots_sig = Wots96::sign(wots_sk, &msg); + TxAssertWitness { wots_sig } } // ─── ChallengeAssert phase (Verifier reveals base-instance labels) ──────────── @@ -258,30 +256,46 @@ pub fn build_ca_outlock( } } -/// Verifier: verify Lamport sig in assert_witness, then compute input labels for π₁ +/// Verifier: verify Wots96 sig in assert_witness, then compute input labels for π₁ and x_d /// from the base finalized instance and return them in the ChallengeAssert witness. pub fn babe_verifier_challenge_assert_cac( assert_witness: &TxAssertWitness, verifier_state: &VerifierSetupState, sig_p_presig: BabeBtcSig, ) -> Option { - let pi1 = G1Affine::deserialize_compressed(assert_witness.pi1.as_slice()).ok()?; + let (pi1, x_d) = assert_witness.recover_pi1_xd_without_verify()?; - println!("Verifier: Checking the Lamport signature in tx_Assert witness against pi1 and lpk_P..."); - if !lamport_verify(&verifier_state.lpk_p, &pi1, &assert_witness.lamport_sig) { + let msg = pi1_xd_to_wots96_msg(&pi1, x_d); + println!("Verifier: Checking Wots96 signature in tx_Assert against pi1, x_d and wots_pk_p..."); + if !wots96_verify(&verifier_state.wots_pk_p, &msg, &assert_witness.wots_sig) { return None; } // Derive labels from the base instance (finalized_indices[0]). let base_idx = verifier_state.finalized_indices[0]; - let base_inst = &verifier_state.verifier.instances[base_idx]; - let all_labels = base_inst.compute_pi1_labels_based_on_value(pi1); - // all_labels[0..2] are constant-wire labels; [2..] are π₁ input labels. - let input_labels: Vec<[u8; 16]> = all_labels[2..].iter().map(|s| s.0).collect(); + + // compute_pi1_labels returns 508 labels: [0..254] for pi1.x, [254..508] for pi1.y. + // compute_x_d_labels returns 254 labels. + // Interleave 6 dummy labels at the 2 MSB padding positions of each 256-bit field: + // pi1.x[254] | dummy[2] | pi1.y[254] | dummy[2] | x_d[254] | dummy[2] = 768 + // Dummy value [0u8; 16] is consistent: derive_hashlock(&[0u8; 16]) == epk[i][0] for + // the dummy EPK entries computed in compute_epk_with_delta. Note that Prover & Verifier should + // embed this hashlock in the challengeAssert script in order to make the check passed. + let pi1_labels = verifier_state.verifier.compute_pi1_labels(base_idx, pi1); + let x_d_labels = verifier_state.verifier.compute_x_d_labels(base_idx, x_d); + let dummy = S([0u8; 16]); + let input_labels: Vec<[u8; 16]> = pi1_labels[..254].iter() + .chain([dummy, dummy].iter()) + .chain(pi1_labels[254..].iter()) + .chain([dummy, dummy].iter()) + .chain(x_d_labels.iter()) + .chain([dummy, dummy].iter()) + .map(|s| s.0) + .collect(); Some(TxChallengeAssertWitness { input_labels, - lamport_sig: assert_witness.lamport_sig.clone(), + wots_sig: assert_witness.wots_sig, sig_v: BabeBtcSig::VerifierLiveSig, sig_p: sig_p_presig, }) @@ -292,16 +306,27 @@ pub fn babe_verifier_challenge_assert_cac( /// Prover: given the labels from TxChallengeAssert, evaluate the GC across all finalized /// instances (base first, then non-base via soldering deltas) and decrypt the msg. pub fn babe_prover_wrongly_challenged_cac( + pk: &Groth16ProvingKey, + dyn_pubin: Fr, challenge_witness: &TxChallengeAssertWitness, proof: &Groth16Proof, prover_state: &ProverSetupState, ) -> Option<(TxWronglyChallengedWitness, usize)> { let base_input_labels: Vec = challenge_witness.input_labels.iter().map(|&b| S(b)).collect(); - - let mut prover = BABEProver::new(proof.clone()); + assert_eq!(base_input_labels.len(), 768); + // Layout: pi1.x[0..254] | dummy[254..256] | pi1.y[256..510] | dummy[510..512] + // | x_d[512..766] | dummy[766..768] + // Strip the 6 dummy labels before passing to the GC (which has 762 real wires). + let pi1_labels: Vec = base_input_labels[..254].iter() + .chain(base_input_labels[256..510].iter()) + .copied() + .collect(); + let x_d_labels: Vec = base_input_labels[512..766].to_vec(); + let mut prover = BABEProver::new(pk.clone(), proof.clone(), dyn_pubin); let found = prover.check_compute_msg( &prover_state.finalized, - &base_input_labels, + &pi1_labels, + &x_d_labels, &prover_state.soldering, &prover_state.h_msgs, ); @@ -312,76 +337,33 @@ pub fn babe_prover_wrongly_challenged_cac( }, prover.valid_finalized_id.unwrap())) } -// ─── No-withdraw / Withdraw phases ─────────────────────────────────────────── - -pub fn babe_verifier_no_withdraw(sig_p_presig: BabeBtcSig) -> TxNoWithdrawWitness { - TxNoWithdrawWitness { - input0_sig_p: sig_p_presig, - input0_sig_v: BabeBtcSig::VerifierLiveSig, - input1_sig_v: BabeBtcSig::VerifierLiveSig, - } -} - -pub fn babe_prover_withdraw(sig_v_presig: BabeBtcSig) -> TxWithdrawWitness { - TxWithdrawWitness { - input0_sig_p: BabeBtcSig::ProverLiveSig, - input0_sig_v: sig_v_presig.clone(), - input1_sig_p: BabeBtcSig::ProverLiveSig, - input1_sig_v: sig_v_presig, - } -} - -// ─── Enc_ functions ──────────────────────────────────────────────────────── - -/// Encsetup(crs, x, msg; r): ctsetup = (r·[delta]_2, RO(rY) ⊕ msg). -pub fn we_known_pi1_encsetup( - vk: &Groth16VerifyingKey, - public_inputs: &[Fr], - msg: &[u8], - r_bytes: [u8; 32], -) -> Option { - let r = Fr::from_le_bytes_mod_order(&r_bytes); - let vk_x = groth16_vk_x(vk, public_inputs)?; - let r_delta = vk.delta_g2.into_group() * r; - - let t1 = Bn254::pairing(vk.alpha_g1, vk.beta_g2.into_group() * r); - let t2 = Bn254::pairing(vk_x, vk.gamma_g2.into_group() * r); - let r_y = t1 + t2; - - let mut ry_bytes = Vec::new(); - r_y.serialize_compressed(&mut ry_bytes).ok()?; - let mask = ro_from_pairing_bytes(&ry_bytes, msg.len()); - let ct3 = msg.iter().zip(mask.iter()).map(|(a, b)| a ^ b).collect(); - - Some(WeKnownPi1SetupCt { ct2_r_delta_g2: g2_to_ser(r_delta), ct3_masked_msg: ct3 }) -} - -/// Encprove(crs, π₁; r): ctprove = r·π₁. -pub fn we_known_pi1_encprove(pi1: ark_bn254::G1Projective, r_bytes: [u8; 32]) -> WeKnownPi1ProveCt { - let r = Fr::from_le_bytes_mod_order(&r_bytes); - WeKnownPi1ProveCt { ct1_r_pi1: g1_to_ser(pi1 * r) } -} - -/// Dec(ctsetup, ctprove, π₂, π₃): msg = ct3 ⊕ RO(e(ct1,π₂) - e(π₃,ct2)). +/// Dec*(vk, ctsetup, ctprove, c1', π₂, π₃): +/// Q_blind = e(c1', γ) where c1' = r·P_D + r·B (DSGC output) +/// mask = e(r·π₁, π₂) - e(π₃, r·δ) - Q_blind = Y_S^r - e(r·B, γ) pub fn we_known_pi1_dec( - ctsetup: &WeKnownPi1SetupCt, - ctprove: &WeKnownPi1ProveCt, + vk: &Groth16VerifyingKey, + ct_setup: &WeKnownPi1SetupCt, + ct_prove: &WeKnownPi1ProveCt, pi2: ark_bn254::G2Projective, pi3: ark_bn254::G1Projective, ) -> Option> { - let ct1 = g1_from_ser_checked(&ctprove.ct1_r_pi1)?; - let ct2 = g2_from_ser_checked(&ctsetup.ct2_r_delta_g2)?; + let ct1 = g1_from_ser_checked(&ct_prove.ct1_r_pi1)?; + let ct1_prime = g1_from_ser_checked(&ct_prove.ct1_prime)?; + let ct2 = g2_from_ser_checked(&ct_setup.ct2_r_delta_g2)?; + let r_y = Bn254::pairing(ct1, pi2) - Bn254::pairing(pi3, ct2); + let q_blind = Bn254::pairing(ct1_prime, vk.gamma_g2); + let mask_gt = r_y - q_blind; - let mut ry_bytes = Vec::new(); - r_y.serialize_compressed(&mut ry_bytes).ok()?; - let mask = ro_from_pairing_bytes(&ry_bytes, ctsetup.ct3_masked_msg.len()); - Some(ctsetup.ct3_masked_msg.iter().zip(mask.iter()).map(|(a, b)| a ^ b).collect()) + let mut mask_bytes = Vec::new(); + mask_gt.serialize_compressed(&mut mask_bytes).ok()?; + let mask = ro_from_pairing_bytes(&mask_bytes, ct_setup.ct3_masked_msg.len()); + Some(ct_setup.ct3_masked_msg.iter().zip(mask.iter()).map(|(a, b)| a ^ b).collect()) } // ─── BABE C&C Soldering E2E flow ───────────────────────────────────────────────────── pub fn run_babe_e2e_cac() -> BabeCACE2ERun { - let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(42); + let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(GROTH_16_SEED); let a = Fr::from(7u64); let b = Fr::from(9u64); @@ -393,7 +375,8 @@ pub fn run_babe_e2e_cac() -> BabeCACE2ERun { DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, ).expect("groth16 prove"); - let public_inputs = vec![a * b]; + let static_public_inputs = a * b; + let dynamic_public_inputs = a * a; // ── Setup phase ─────────────────────────────────────────────────────────── @@ -406,7 +389,7 @@ pub fn run_babe_e2e_cac() -> BabeCACE2ERun { println!("Verifier generating BTC keys"); let pk_v = BtcPk([1u8; 33]); println!("Verifier: building {} instances...", N_CC); - let (verifier, package) = babe_verifier_cac_setup(&vk, &public_inputs); + let (verifier, package) = babe_verifier_cac_setup(&vk, static_public_inputs); // Verifier sends vk and package to Prover println!("Verifier: sending pk_v and {} instance commitment to Prover", N_CC); @@ -417,15 +400,16 @@ pub fn run_babe_e2e_cac() -> BabeCACE2ERun { // Verifier opens non-finalized instances and generates soldering proof. println!("Verifier: opening and soldering..."); - let (opened, finalized, soldering, _hash_temp_val) = babe_verifier_open_and_solder(&verifier, &finalized_indices); + let (opened, finalized, soldering) = babe_verifier_open_and_solder(&verifier, &finalized_indices); println!("Prover: verifying opening and soldering proof..."); // Prover verifies everything. - babe_prover_verify_setup(&package, &opened, &finalized, &soldering, &vk, &public_inputs) + babe_prover_verify_setup(&package, &opened, &finalized, &soldering, &vk, static_public_inputs) .expect("prover setup verification failed"); - println!("Prover: generating Lamport signature..."); - let (lsk_p, lpk_p) = lamport_keygen(&mut rng); + println!("Prover: generating Wots96 signing key..."); + let wots_sk_p = Wots96::generate_secret_key(); + let wots_pk_p = Wots96::generate_public_key(&wots_sk_p); // ── Create Txn Set and Presign ────────────────────────────────────────────────── println!("Prover: creating Tx Set and pre sign..."); @@ -453,7 +437,7 @@ pub fn run_babe_e2e_cac() -> BabeCACE2ERun { let verifier_presigs = babe_verifier_presign(); println!("Verifier: sending presigs_v to Verifier..."); - println!("Prover: verifying presigs_p..."); + println!("Prover: verifying presigs_v..."); assert!(babe_verify_verifier_presigs(&verifier_presigs), "verifier presigs invalid"); // ── Deposit ─────────────────────────────────────────────────────────────── @@ -462,7 +446,7 @@ pub fn run_babe_e2e_cac() -> BabeCACE2ERun { let deposit_lock = babe_build_deposit_lock(pk_p, pk_v, 100_000); // Both parties persist their setup state. let prover_state = ProverSetupState { - lsk_p, + wots_sk_p, finalized, soldering, h_msgs: tx_challenge_assert_outlock_p.h_msgs, @@ -472,14 +456,14 @@ pub fn run_babe_e2e_cac() -> BabeCACE2ERun { verifier, package, finalized_indices, - lpk_p, + wots_pk_p, presigs_p: prover_presigs, }; // ── Proving phase ───────────────────────────────────────────────────────── - // Assert: Prover posts π₁ + Lamport sig on-chain. - let assert_witness = babe_prover_assert(&proof, &prover_state.lsk_p); + // Assert: Prover posts π₁ + x_d and Wots96 sig on-chain. + let assert_witness = babe_prover_assert(&proof, &prover_state.wots_sk_p, dynamic_public_inputs); println!("Prover: posting tx_Assert..."); println!("tx_Assert witness: {} bytes", assert_witness.size_bytes()); @@ -488,7 +472,7 @@ pub fn run_babe_e2e_cac() -> BabeCACE2ERun { &assert_witness, &verifier_state, verifier_state.presigs_p.sig_challenge_assert.clone(), - ).expect("Lamport sig invalid in assert witness"); + ).expect("Wots96 sig invalid in assert witness"); println!("Verifier: posting tx_ChallengeAssert..."); println!("tx_ChallengeAssert witness: {} bytes", challenge_witness.size_bytes()); @@ -496,6 +480,8 @@ pub fn run_babe_e2e_cac() -> BabeCACE2ERun { // WronglyChallenged: Prover evaluates GC (base first, then non-base if needed). println!("Prover: Finding msg..."); let (wc_witness, instance_id) = babe_prover_wrongly_challenged_cac( + &groth16_pk, + dynamic_public_inputs, &challenge_witness, &proof, &prover_state, @@ -527,7 +513,9 @@ impl ConstraintSynthesizer for DummyMulCircuit { let a = cs.new_witness_variable(|| self.a.ok_or(SynthesisError::AssignmentMissing))?; let b = cs.new_witness_variable(|| self.b.ok_or(SynthesisError::AssignmentMissing))?; let c = cs.new_input_variable(|| Ok(self.a.unwrap() * self.b.unwrap()))?; + let d = cs.new_input_variable(|| Ok(self.a.unwrap() * self.a.unwrap()))?; cs.enforce_constraint(lc!() + a, lc!() + b, lc!() + c)?; + cs.enforce_constraint(lc!() + a, lc!() + a, lc!() + d)?; Ok(()) } } @@ -536,11 +524,12 @@ impl ConstraintSynthesizer for DummyMulCircuit { #[cfg(test)] mod tests { + use ark_bn254::G1Affine; use super::*; - use ark_bn254::{Bn254, Fr}; - use ark_crypto_primitives::snark::{CircuitSpecificSetupSNARK, SNARK}; use ark_ff::UniformRand; use rand::SeedableRng; + use crate::wots::{wots96_verify, Wots96}; + use bitvm::signatures::Wots; #[test] fn hashlock_roundtrip() { @@ -551,36 +540,26 @@ mod tests { } #[test] - fn lamport_sign_verify_roundtrip() { - let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(1); + fn wots96_sign_verify_roundtrip() { + let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(2); let pi1 = G1Affine::from(ark_bn254::G1Projective::rand(&mut rng)); - let (lsk, lpk) = lamport_keygen(&mut rng); - let sig = lamport_sign(&lsk, &pi1); - assert!(lamport_verify(&lpk, &pi1, &sig)); + let x_d = ark_bn254::Fr::rand(&mut rng); + + let sk = Wots96::generate_secret_key(); + let pk = Wots96::generate_public_key(&sk); + let msg = pi1_xd_to_wots96_msg(&pi1, x_d); + let sig = Wots96::sign(&sk, &msg); + + assert!(wots96_verify(&pk, &msg, &sig)); + + // Wrong pi1 must fail. let pi1_other = G1Affine::from(ark_bn254::G1Projective::rand(&mut rng)); - assert!(!lamport_verify(&lpk, &pi1_other, &sig)); - } + let msg_other = pi1_xd_to_wots96_msg(&pi1_other, x_d); + assert!(!wots96_verify(&pk, &msg_other, &sig)); - #[test] - fn we_encsetup_dec_roundtrip() { - let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(42); - let a = Fr::from(3u64); - let b = Fr::from(7u64); - let (pk, vk) = ark_groth16::Groth16::::setup( - DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, - ).unwrap(); - let proof = ark_groth16::Groth16::::prove( - &pk, - DummyMulCircuit:: { a: Some(a), b: Some(b) }, - &mut rng, - ).unwrap(); - let public_inputs = vec![a * b]; - let secret = b"test-secret-32by"; - let r_bytes = h_256(b"r-test"); - - let ct_setup = we_known_pi1_encsetup(&vk, &public_inputs, secret, r_bytes).unwrap(); - let ctprove = we_known_pi1_encprove(proof.a.into_group(), r_bytes); - let decrypted = we_known_pi1_dec(&ct_setup, &ctprove, proof.b.into_group(), proof.c.into_group()).unwrap(); - assert_eq!(decrypted, secret); + // Wrong x_d must fail. + let x_d_other = ark_bn254::Fr::rand(&mut rng); + let msg_xd_other = pi1_xd_to_wots96_msg(&pi1, x_d_other); + assert!(!wots96_verify(&pk, &msg_xd_other, &sig)); } -} \ No newline at end of file +} diff --git a/verifiable-circuit-babe/src/cac.rs b/verifiable-circuit-babe/src/cac.rs index 0114898..6e92120 100644 --- a/verifiable-circuit-babe/src/cac.rs +++ b/verifiable-circuit-babe/src/cac.rs @@ -1,4 +1,4 @@ -use ark_bn254::Fr; +use ark_bn254::{Fr, G1Affine}; use ark_groth16::VerifyingKey as Groth16VerifyingKey; use crate::babe::WeKnownPi1SetupCt; use crate::instance::commit::CACInstanceCommit; @@ -8,7 +8,8 @@ use rand::Rng; use rand_chacha::ChaCha12Rng; use rand::SeedableRng; use sha2::{Digest, Sha256}; -use crate::instance::BABEInstance; +use garbled_snark_verifier::dv_bn254::fq::Fq; +use crate::instance::CACInstance; use crate::utils::h_256; /// What the Verifier sends to the Prover during the C&C commit phase. @@ -17,6 +18,8 @@ pub struct CACSetupPackage { } /// Derive the finalized instance indices deterministically from the committed values. +/// Note that in practice, prover doesnt need to use this. Instead, he can generate indices +/// using random. pub fn cac_finalize_indices(package: &CACSetupPackage, m_cc: usize) -> Vec { let n_cc = package.commits.len(); assert!(m_cc <= n_cc, "m_cc ({m_cc}) must be <= n_cc ({n_cc})"); @@ -27,14 +30,22 @@ pub fn cac_finalize_indices(package: &CACSetupPackage, m_cc: usize) -> Vec Vec>, - pub adaptor_table: SparseAdaptorTable, + pub ciphertext_sets: [Vec>; 3], + pub adaptor_tables: [SparseAdaptorTable; 2], pub ct_setup: WeKnownPi1SetupCt, /// [0-label of wire-0 (constant false), 1-label of wire-1 (constant true)]. - pub constant_labels: [S; 2], + pub constant_labels_0: [S; 2], + /// value-based labels + pub constant_labels_1: [S; 510], + pub b: G1Affine, } + pub fn verify_opened_instances( package: &CACSetupPackage, opened: &[(usize, u64)], vk: &Groth16VerifyingKey, - public_inputs: &[Fr], + static_public_inputs: Fr, ) -> Result<(), String> { use p3_maybe_rayon::prelude::*; - opened - .par_iter() - .map(|&(idx, seed)| { - let mut inst = BABEInstance::new_from_seed(seed); - inst.enc_setup(vk, public_inputs) - .map_err(|e| format!("instance {idx}: enc_setup failed: {e}"))?; - - let recomputed = inst.commit(); - let committed = &package.commits[idx]; - - if recomputed.epk != committed.epk { - return Err(format!("instance {idx}: input_commits mismatch")); - } - if recomputed.constant_commits != committed.constant_commits { - return Err(format!("instance {idx}: constant_commits mismatch")); - } - if recomputed.h_msg != committed.h_msg { - return Err(format!("instance {idx}: h_msg mismatch")); - } - if recomputed.h_ct_setup != committed.h_ct_setup { - return Err(format!("instance {idx}: ct_setup mismatch")); - } - if recomputed.com_adaptor != committed.com_adaptor { - return Err(format!("instance {idx}: com_adaptor mismatch")); - } - if recomputed.com_gc != committed.com_gc { - return Err(format!("instance {idx}: com_gc mismatch")); - } - Ok(()) - }) - .collect::>() - .into_iter() - .collect::, _>>()?; + + const BATCH_SIZE: usize = 10; + + let pool = rayon::ThreadPoolBuilder::new() + .num_threads(BATCH_SIZE) + .build() + .map_err(|e| e.to_string())?; + + for batch in opened.chunks(BATCH_SIZE) { + // Process up to BATCH_SIZE instances in parallel. commit_from_seed stream-hashes + // ciphertexts and adaptor tables without materializing them, so peak memory per + // batch is BATCH_SIZE × O(circuit_size) instead of BATCH_SIZE × ~6 GB. + let results: Vec> = pool.install(|| { + batch + .par_iter() + .map(|&(idx, seed)| { + let (recomputed, _secrets) = + CACInstance::commit_from_seed(seed, vk, static_public_inputs)?; + let committed = &package.commits[idx]; + + if recomputed.epk != committed.epk { + return Err(format!("instance {idx}: input_commits mismatch")); + } + if recomputed.constant_commits_0 != committed.constant_commits_0 + || recomputed.constant_commits_1 != committed.constant_commits_1 + { + return Err(format!("instance {idx}: constant_commits mismatch")); + } + if recomputed.b_blind_commit != committed.b_blind_commit { + return Err(format!("instance {idx}: b_bind_commit mismatch")); + } + if recomputed.h_msg != committed.h_msg { + return Err(format!("instance {idx}: h_msg mismatch")); + } + if recomputed.h_ct_setup != committed.h_ct_setup { + return Err(format!("instance {idx}: ct_setup mismatch")); + } + if recomputed.com_adaptor != committed.com_adaptor { + return Err(format!("instance {idx}: com_adaptor mismatch")); + } + if recomputed.com_gc != committed.com_gc { + return Err(format!("instance {idx}: com_gc mismatch")); + } + Ok(()) + }) + .collect() + }); + + for result in results { + result?; + } + } Ok(()) } @@ -111,16 +145,37 @@ pub fn verify_finalized_instances( let idx = data.index; let committed = &package.commits[idx]; - if gc_ciphertexts_commit(&data.gc_ciphertexts) != committed.com_gc { - return Err(format!("instance {idx}: gc_ciphertexts do not match com_gc")); + for i in 0..3 { + if gc_ciphertexts_commit(&data.ciphertext_sets[i]) != committed.com_gc[i] { + return Err(format!("instance {idx}: gc_ciphertexts do not match com_gc")); + } } - if data.adaptor_table.commit() != committed.com_adaptor { + + if data.adaptor_tables[0].commit() != committed.com_adaptor[0] + || data.adaptor_tables[1].commit() != committed.com_adaptor[1] { return Err(format!("instance {idx}: adaptor_table does not match com_adaptor")); } - if h_256(&data.constant_labels[0].0) != committed.constant_commits[0][0] || - h_256(&data.constant_labels[1].0) != committed.constant_commits[1][1] { + + // verify constant labels + if h_256(&data.constant_labels_0[0].0) != committed.constant_commits_0[0][0] || + h_256(&data.constant_labels_0[1].0) != committed.constant_commits_0[1][1] || + h_256(&data.constant_labels_1[0].0) != committed.constant_commits_1[0][0] || + h_256(&data.constant_labels_1[1].0) != committed.constant_commits_1[1][1] { return Err(format!("instance {idx}: constant_commits do not match")); } + let x_bits = Fq::to_bits(Fq::as_montgomery(data.b.x)); + let y_bits = Fq::to_bits(Fq::as_montgomery(data.b.y)); + for (i, bit) in x_bits.iter().enumerate() { + if h_256(&data.constant_labels_1[i + 2].0) != committed.constant_commits_1[i + 2][*bit as usize] { + return Err(format!("instance {idx}: constant_commits do not match")); + } + } + for (i, bit) in y_bits.iter().enumerate() { + if h_256(&data.constant_labels_1[i + 2 + 254].0) != committed.constant_commits_1[i + 2 + 254][*bit as usize] { + return Err(format!("instance {idx}: constant_commits do not match")); + } + } + let mut ct_bytes = Vec::new(); ct_bytes.extend_from_slice(&data.ct_setup.ct2_r_delta_g2); ct_bytes.extend_from_slice(&data.ct_setup.ct3_masked_msg); @@ -138,25 +193,27 @@ mod tests { use ark_crypto_primitives::snark::CircuitSpecificSetupSNARK; use rand::SeedableRng; use crate::babe::DummyMulCircuit; + use crate::prover::GROTH_16_SEED; use crate::verifier::BABEVerifier; - const TEST_N_CC: usize = 10; + const TEST_N_CC: usize = 50; const TEST_M_CC: usize = 4; #[test] fn test_cac_commit_open_verify() { - let mut rng = ChaCha12Rng::seed_from_u64(42); + let mut rng = ChaCha12Rng::seed_from_u64(GROTH_16_SEED); let a = Fr::from(3u64); let b = Fr::from(7u64); let (_, vk) = ark_groth16::Groth16::::setup( DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, ).expect("groth16 setup"); - let public_inputs = vec![a * b]; + let static_public_inputs = a * b; + let _dynamic_public_inputs = a * a; // Verifier creates TEST_N_CC instances and commits. let now = std::time::Instant::now(); - let verifier = BABEVerifier::new(TEST_N_CC, &vk, &public_inputs) + let verifier = BABEVerifier::new(TEST_N_CC, &vk, static_public_inputs) .expect("BABEVerifier::new failed"); let elapsed = now.elapsed(); println!("Verifier setup for {TEST_N_CC} instances took {elapsed:.2?}"); @@ -171,7 +228,7 @@ mod tests { // Verifier opens: seeds for the rest, GC data for finalized. let now = std::time::Instant::now(); - let (opened, finalized) = verifier.open(&finalized_indices); + let (opened, finalized) = verifier.open(&finalized_indices).expect("verifier open failed"); assert_eq!(opened.len(), TEST_N_CC - TEST_M_CC); assert_eq!(finalized.len(), TEST_M_CC); let elapsed = now.elapsed(); @@ -179,7 +236,7 @@ mod tests { let now = std::time::Instant::now(); // Prover verifies opened instances by re-deriving from seed. - verify_opened_instances(&package, &opened, &vk, &public_inputs) + verify_opened_instances(&package, &opened, &vk, static_public_inputs) .expect("opened instance verification failed"); let elapsed = now.elapsed(); println!("Prover verification of opened instances took {elapsed:.2?}"); diff --git a/verifiable-circuit-babe/src/dre/matrices.rs b/verifiable-circuit-babe/src/dre/matrices.rs index ed122ba..d60d655 100644 --- a/verifiable-circuit-babe/src/dre/matrices.rs +++ b/verifiable-circuit-babe/src/dre/matrices.rs @@ -1,7 +1,7 @@ use std::sync::LazyLock; use ark_bn254::{Fq, G1Affine}; use ark_ff::{BigInteger, One, PrimeField, Zero}; -use crate::dre::{L, N}; +use crate::dre::{U_BAR_SIZE, N}; /// Structural nonzero block flags per row (columns 0..=5 of C). const NONZERO_BLOCKS: [[bool; 6]; 3] = [ @@ -131,7 +131,7 @@ fn u_vec(pi: &G1Affine) -> [Fq; 6] { /// Layout: (1, bits(x), bits(y), bits(x²), bits(y²), bits(xy)) in LSB-first pub fn u_bar_vec(pi: &G1Affine) -> Vec { let u = u_vec(pi); - let mut u_bar = Vec::with_capacity(L); + let mut u_bar = Vec::with_capacity(U_BAR_SIZE); u_bar.push(Fq::one()); // u_0 = 1 for u_i in &u[1..] { let bits = u_i.into_bigint().to_bits_le(); @@ -210,7 +210,7 @@ mod tests { let u_bar = u_bar_vec(&pi); // Check length - assert_eq!(u_bar.len(), L); + assert_eq!(u_bar.len(), U_BAR_SIZE); // First entry must be 1 assert_eq!(u_bar[0], Fq::one()); diff --git a/verifiable-circuit-babe/src/dre/mod.rs b/verifiable-circuit-babe/src/dre/mod.rs index 1a259b5..c385d41 100644 --- a/verifiable-circuit-babe/src/dre/mod.rs +++ b/verifiable-circuit-babe/src/dre/mod.rs @@ -3,7 +3,8 @@ use ark_bn254::Fq; pub mod utils; pub mod matrices; pub const N: usize = 254; -pub const L: usize = 1 + 5 * N; // 1271 +pub const U_BAR_SIZE: usize = 1 + 5 * N; // 1271 — ū(π) binary decomposition +pub const Q_SIZE: usize = 2 * N; // 508 — standard affine (x,y) of x_d·L_2 + B /// Decoding: f_i = r_i·π + ρ_i (Jacobian coords) pub struct DREDecoding { diff --git a/verifiable-circuit-babe/src/gc/adaptor.rs b/verifiable-circuit-babe/src/gc/adaptor.rs index 077661a..b2b50f7 100644 --- a/verifiable-circuit-babe/src/gc/adaptor.rs +++ b/verifiable-circuit-babe/src/gc/adaptor.rs @@ -2,7 +2,7 @@ use ark_bn254::{Fq, Fr, G1Affine}; use ark_ff::Zero; use ark_serialize::CanonicalSerialize; use sha2::{Digest, Sha256}; -use crate::dre::{DREDecoding, L, N}; +use crate::dre::{DREDecoding, N, U_BAR_SIZE}; use crate::dre::matrices::{build_d_i_sparse, nonzero_col_indices}; use crate::gc::utils::{aes_dec, aes_enc, prf_fq}; @@ -32,19 +32,19 @@ pub struct SparseAdaptorTable { } impl SparseAdaptorTable { - pub fn build_from_r_and_labels( + pub fn build_from_r_and_u_bar_labels( r: Fr, labels: &[[u8; 16]], rhos: &[G1Affine], fq_deltas: &[Fq], ) -> Self { - assert_eq!(labels.len(), 2 * L); + assert_eq!(labels.len(), 2 * U_BAR_SIZE); assert_eq!(rhos.len(), N); assert_eq!(fq_deltas.len(), N); let r_bits = garbled_snark_verifier::dv_bn254::fr::Fr::to_bits(r); let col_indices = nonzero_col_indices(); - let prf_cache: Vec = (0..L).map(|k| prf_fq(&labels[2 * k + 1])).collect(); + let prf_cache: Vec = (0..U_BAR_SIZE).map(|k| prf_fq(&labels[2 * k + 1])).collect(); let entries = (0..N) .map(|i| { @@ -76,6 +76,48 @@ impl SparseAdaptorTable { SparseAdaptorTable { entries } } + /// Build the adaptor table entry-by-entry, hashing each row on-the-fly without + /// materializing the full `Vec`. Produces the same hash as + /// `SparseAdaptorTable::build_from_r_and_u_bar_labels(...).commit()`. + pub fn build_and_hash( + r: Fr, + labels: &[[u8; 16]], + rhos: &[G1Affine], + fq_deltas: &[Fq], + ) -> [u8; 32] { + assert_eq!(labels.len(), 2 * U_BAR_SIZE); + assert_eq!(rhos.len(), N); + assert_eq!(fq_deltas.len(), N); + + let r_bits = garbled_snark_verifier::dv_bn254::fr::Fr::to_bits(r); + let col_indices = nonzero_col_indices(); + let prf_cache: Vec = (0..U_BAR_SIZE).map(|k| prf_fq(&labels[2 * k + 1])).collect(); + + let mut hasher = Sha256::new(); + let mut buf = Vec::new(); + + for i in 0..N { + let r_i = Fq::from(r_bits[i] as u8); + let d = build_d_i_sparse(fq_deltas[i], r_i, &rhos[i]); + + for j in 0..3 { + let mut offset = Fq::zero(); + for (&k, &d_val) in col_indices[j].iter().zip(d[j].iter()) { + let s_k = prf_cache[k] - d_val; + offset += s_k; + let ct = aes_enc(&s_k, &labels[2 * k]); + hasher.update(ct.as_slice()); + } + buf.clear(); + offset.serialize_compressed(&mut buf).expect("serialize Fq offset"); + hasher.update(&buf); + // row data is never stored — dropped here + } + } + + hasher.finalize().into() + } + /// SHA256 over all ciphertexts and Fq offsets in entry/row order. /// Used by the Prover to verify an opened C&C instance's adaptor table. pub fn commit(&self) -> [u8; 32] { @@ -95,8 +137,8 @@ impl SparseAdaptorTable { /// Decrypt the adaptor table and sum each row to recover Jacobian coords of r_i·π + ρ_i. pub fn eval(&self, labels: &[[u8; 16]], u_bar: &[Fq]) -> Vec { - assert_eq!(labels.len(), L); - assert_eq!(u_bar.len(), L); + assert_eq!(labels.len(), U_BAR_SIZE); + assert_eq!(u_bar.len(), U_BAR_SIZE); let col_indices = nonzero_col_indices(); @@ -135,7 +177,6 @@ mod tests { use garbled_snark_verifier::core::s::S; // Todo: change to use own delta, instead of NON_CAC_DELTA use garbled_snark_verifier::core::utils::NON_CAC_DELTA; - use crate::dre::L; use crate::dre::matrices::u_bar_vec; #[test] @@ -146,7 +187,7 @@ mod tests { let u_bar_pi = u_bar_vec(&pi); - let labels: Vec<[u8; 16]> = (0..L) + let labels: Vec<[u8; 16]> = (0..U_BAR_SIZE) .flat_map(|_| { let l0 = S::random(); let l1 = l0 ^ NON_CAC_DELTA; @@ -161,9 +202,9 @@ mod tests { if !Zero::is_zero(&v) { break v; } }) .collect(); - let table = SparseAdaptorTable::build_from_r_and_labels(r, &labels, &rhos, &fq_deltas); + let table = SparseAdaptorTable::build_from_r_and_u_bar_labels(r, &labels, &rhos, &fq_deltas); - let eval_labels: Vec<[u8; 16]> = (0..L) + let eval_labels: Vec<[u8; 16]> = (0..U_BAR_SIZE) .map(|k| if u_bar_pi[k].is_zero() { labels[2 * k] } else { labels[2 * k + 1] }) .collect(); diff --git a/verifiable-circuit-babe/src/gc/circuit.rs b/verifiable-circuit-babe/src/gc/circuit.rs index b386cbd..c79f9b8 100644 --- a/verifiable-circuit-babe/src/gc/circuit.rs +++ b/verifiable-circuit-babe/src/gc/circuit.rs @@ -1,85 +1,177 @@ use ark_bn254::G1Affine; +use ark_ec::CurveGroup; +use ark_ff::{AdditiveGroup, Zero}; use sha2::{Digest, Sha256}; use garbled_snark_verifier::circuits::sect233k1::builder::{CircuitAdapter, CircuitTrait}; use garbled_snark_verifier::dv_bn254::basic::selector; use garbled_snark_verifier::dv_bn254::fp254impl::Fp254Impl; -use garbled_snark_verifier::dv_bn254::fq::Fq; - -use crate::dre::{L, N}; - -/// Compile the BABE circuit structure without fixing witness values. -pub fn compile_babe_gc(g: G1Affine) -> (CircuitAdapter, Vec) { +use garbled_snark_verifier::dv_bn254::{fq::Fq, fr::Fr}; +use garbled_snark_verifier::dv_bn254::g1::{projective_to_affine_montgomery, G1Projective as GcG1Projective, G1Projective}; + +use crate::dre::{N, Q_SIZE, U_BAR_SIZE}; + +// Unsigned 8-bit windowed scalar-mul parameters. Must match the `SCALAR_WINDOW_*` +// constants in garbled-snark-verifier's dv_bn254::g1. With w=8 we do 32 mixed-adds +// over a full 256-entry precomputed table. x_d is supplied as plain Fr bits (LSB-first). +pub const WINDOW_BITS: usize = 8; +pub const WINDOW_COUNT: usize = (Fr::N_BITS + WINDOW_BITS - 1) / WINDOW_BITS; // 32 +pub const WINDOW_ENTRIES: usize = 1 << WINDOW_BITS; // 256 +pub const PRECOMP_TABLE_BITS: usize = WINDOW_COUNT * WINDOW_ENTRIES * 2 * N; +pub const SGC_PART1_CONSTANT_SIZE: usize = 2 + 2 * N; // 0/1 + B. + + +// ── Circuit 1 / FGC ──────────────────────────────────────────────────────── + +/// Compile the FGC: evaluates the ū(π) subcircuit. +/// +/// Input wire layout: +/// [0..N) π_x — x-coordinate of π (evaluator input) +/// [N..2·N) π_y — y-coordinate of π (evaluator input) +/// +/// `g` is the fallback point, embedded as constant wires. +/// +/// Output: U_BAR_SIZE bits — ū(π) when π is on the curve, ū(g) otherwise. +pub fn compile_fgc(g: G1Affine) -> (CircuitAdapter, Vec) { let mut bld = CircuitAdapter::default(); - // Allocate input wire + let pi_x = Fq::wires(&mut bld); let pi_y = Fq::wires(&mut bld); - let output_indices = emit_babe_gc(&mut bld, &pi_x.0, &pi_y.0, g); + + let output_indices = emit_fgc(&mut bld, &pi_x.0, &pi_y.0, g); (bld, output_indices) } -/// Garbled circuit for BABE: -/// 0. Input wire indices: pi_x (bits 0..N), pi_y (bits N..2N) — LSB first. -/// 1. Validates input point π = (x, y) lies on BN254 curve E: y² = x³ + 3 (mod p) -/// 2. If valid: outputs binary decomposition ū(π) = bits(1, x, y, x², y², xy) -/// If invalid: outputs ū(g) for a fixed fallback point g ∈ G₁ -fn emit_babe_gc( +fn emit_fgc( bld: &mut CircuitAdapter, pi_x: &[usize], pi_y: &[usize], g: G1Affine, ) -> Vec { - // R² mod p — multiply by this constant to convert to Montgomery form + // R² mod p — converts normal → Montgomery form let r_sq = Fq::as_montgomery(Fq::as_montgomery(ark_bn254::Fq::from(1u64))); - // Convert x, y to Montgomery form using constant-multiplication (avoids Karatsuba) let x_m = Fq::mul_by_constant_montgomery(bld, pi_x, r_sq); let y_m = Fq::mul_by_constant_montgomery(bld, pi_y, r_sq); - // Compute x², y², xy, x³ — all in Montgomery form let x_sq_m = Fq::square_montgomery(bld, &x_m); let y_sq_m = Fq::square_montgomery(bld, &y_m); - let xy_m = Fq::mul_montgomery(bld, &x_m, &y_m); + let xy_m = Fq::mul_montgomery(bld, &x_m, &y_m); let x_cu_m = Fq::mul_montgomery(bld, &x_sq_m, &x_m); - // Curve check: y² ≡ x³ + 3 (mod p) let three_mont = Fq::as_montgomery(ark_bn254::Fq::from(3u64)); - let rhs_m = Fq::add_constant(bld, &x_cu_m, three_mont); + let rhs_m = Fq::add_constant(bld, &x_cu_m, three_mont); let on_curve = Fq::equal(bld, &y_sq_m, &rhs_m); - // Convert x², y², xy from Montgomery back to standard form. - // mul_by_constant_montgomery(A·R, 1) = montgomery_reduce(A·R ‖ 0) = A·R·R⁻¹ = A + // montgomery_reduce(A·R ‖ 0) = A — converts back to standard form let x_sq = Fq::mul_by_constant_montgomery(bld, &x_sq_m, ark_bn254::Fq::from(1u64)); let y_sq = Fq::mul_by_constant_montgomery(bld, &y_sq_m, ark_bn254::Fq::from(1u64)); - let xy = Fq::mul_by_constant_montgomery(bld, &xy_m, ark_bn254::Fq::from(1u64)); + let xy = Fq::mul_by_constant_montgomery(bld, &xy_m, ark_bn254::Fq::from(1u64)); - // Build ū(π) - let mut pi_u_bar: Vec = Vec::with_capacity(L); + let mut pi_u_bar: Vec = Vec::with_capacity(U_BAR_SIZE); pi_u_bar.push(bld.one()); pi_u_bar.extend_from_slice(pi_x); pi_u_bar.extend_from_slice(pi_y); pi_u_bar.extend(x_sq); pi_u_bar.extend(y_sq); pi_u_bar.extend(xy); - assert_eq!(pi_u_bar.len(), L); + assert_eq!(pi_u_bar.len(), U_BAR_SIZE); - // Build constant ū(g) for the fallback point let g_u_bar = g_u_bar_indices(bld, g); - // Output: select ū(π) if on_curve, else ū(g) - (0..L) + (0..U_BAR_SIZE) .map(|k| selector(bld, pi_u_bar[k], g_u_bar[k], on_curve)) .collect() } -/// Build constant wire indices for ū(g) +// ── Circuit 2 Part 1 / SGC Part 1 ────────────────────────────────────────── + +/// Compile SGC Part 1: computes Q = x_d · L_2 + B. +/// +/// `l2` is fixed (like `g` in FGC) and is embedded as constant wires. +/// B is garbler-private and supplied as input wires. +/// +/// Input wire layout: +/// [0..Fr::N_BITS) B_x — x-coordinate of B, Montgomery form (garbler-private) +/// [Fr::N_BITS..Fr::N_BITS+N) B_y — y-coordinate of B, Montgomery form (garbler-private) +/// [Fr::N_BITS+N..Fr::N_BITS+2·N) x_d — scalar bits, LSB-first (evaluator input) +/// +/// Output: Q_SIZE = 2·N bits — (x, y) of Q in standard affine form. +pub fn compile_sgc_part1(l2: G1Affine) -> (CircuitAdapter, Vec) { + let mut bld = CircuitAdapter::default(); + + // B — garbler-private affine point, Montgomery form + let b_x = Fq::wires(&mut bld); + let b_y = Fq::wires(&mut bld); + + // x_d — evaluator input + let x_d: Vec = (0..Fr::N_BITS).map(|_| bld.fresh_one()).collect(); + + // L_2 table — embedded as constant wires, same pattern as g in compile_fgc + let table_wires: Vec = build_l2_table_bits(&l2) + .into_iter() + .map(|bit| if bit { bld.one() } else { bld.zero() }) + .collect(); + assert_eq!(table_wires.len(), PRECOMP_TABLE_BITS); + + let output_indices = emit_scalar_mul_then_add(&mut bld, &x_d, &b_x.0, &b_y.0, &table_wires); + (bld, output_indices) +} + +// ── Shared inner ──────────────────────────────────────────────────────────── + +/// Emit: scalar_point = x_d · L_2 (windowed private table) then add affine point P. +/// +/// Wire indices for the affine point and table may be input or constant wires. +/// Output: Q_SIZE bits — (x, y) in standard affine form. +fn emit_scalar_mul_then_add( + bld: &mut CircuitAdapter, + x_d: &[usize], + p_x_wires: &[usize], + p_y_wires: &[usize], + table_wires: &[usize], +) -> Vec { + assert_eq!(table_wires.len(), PRECOMP_TABLE_BITS); + + let prod_proj_m = GcG1Projective::scalar_mul_private_table_circuit(bld, x_d, table_wires); + + let mut p_affine_m: Vec = p_x_wires.to_vec(); + p_affine_m.extend_from_slice(p_y_wires); + let result_proj_m = + GcG1Projective::add_mixed_montgomery_no_inf(bld, &prod_proj_m, &p_affine_m); + + // Convert projective Montgomery (X·R, Y·R, Z·R) → standard affine (x, y) + // x = X/Z, y = Y/Z via Fermat inversion of Z in the Montgomery domain + let x_m: [usize; N] = result_proj_m[..N].try_into().unwrap(); + let y_m: [usize; N] = result_proj_m[N..2 * N].try_into().unwrap(); + let z_m: [usize; N] = result_proj_m[2 * N..].try_into().unwrap(); + let mont_res_proj = G1Projective { + x: Fq(x_m), + y: Fq(y_m), + z: Fq(z_m), + }; + let (mont_res_affine, _is_valid) = projective_to_affine_montgomery(bld, &mont_res_proj); + // convert back to standard form + let x_q = Fq::mul_by_constant_montgomery(bld, &mont_res_affine.x.0, ark_bn254::Fq::from(1u64)); + let y_q = Fq::mul_by_constant_montgomery(bld, &mont_res_affine.y.0, ark_bn254::Fq::from(1u64)); + + let mut output = Vec::with_capacity(Q_SIZE); + output.extend_from_slice(&x_q); + output.extend_from_slice(&y_q); + assert_eq!(output.len(), Q_SIZE); + output +} + +// ── Shared helpers ────────────────────────────────────────────────────────── + +/// Build constant wire indices for ū(g). fn g_u_bar_indices(bld: &mut CircuitAdapter, g: G1Affine) -> Vec { - let x = g.x; - let y = g.y; + let x = g.x; + let y = g.y; let x_sq = x * x; let y_sq = y * y; - let xy = x * y; + let xy = x * y; - let mut indices: Vec = Vec::with_capacity(L); + let mut indices: Vec = Vec::with_capacity(U_BAR_SIZE); indices.push(bld.one()); for val in [x, y, x_sq, y_sq, xy] { @@ -89,263 +181,200 @@ fn g_u_bar_indices(bld: &mut CircuitAdapter, g: G1Affine) -> Vec { } } - assert_eq!(indices.len(), L); + assert_eq!(indices.len(), U_BAR_SIZE); indices } /// SHA256 commitment to a `Vec>` GC ciphertext list. -/// None entries contribute a 0x00 byte; Some(s) contributes 0x01 || s.0. pub fn gc_ciphertexts_commit(ciphertexts: &[Option]) -> [u8; 32] { let mut hasher = Sha256::new(); for ct in ciphertexts { match ct { - None => hasher.update([0u8]), + None => hasher.update([0u8]), + Some(s) => { hasher.update([1u8]); hasher.update(s.0); } + } + } + hasher.finalize().into() +} + +/// Garble all gates and hash their ciphertexts on-the-fly without materializing +/// the full `Vec>`. Produces the same hash as +/// `gc_ciphertexts_commit(&circuit.garbled_gates_with_delta(delta))`. +pub fn gc_garble_and_hash( + circuit: &garbled_snark_verifier::bag::Circuit, + delta: garbled_snark_verifier::bag::S, +) -> [u8; 32] { + let mut hasher = Sha256::new(); + for (i, gate) in circuit.1.iter().enumerate() { + if i.is_multiple_of(1000000) { + println!("Garble batch: {}/{}", i, circuit.1.len()); + } + match gate.garbled_with_delta(delta) { + None => hasher.update([0u8]), Some(s) => { hasher.update([1u8]); hasher.update(s.0); } } } hasher.finalize().into() } +/// Build the unsigned w=8 Base table. +/// +/// Layout: window `i` (i = 0..WINDOW_COUNT), entry `j` (j = 0..WINDOW_ENTRIES) stores +/// `j · 256^i · Base` in affine Montgomery form. Entry j=0 is the point at infinity. +pub fn build_l2_table_bits(base: &G1Affine) -> Vec { + let mut bits = Vec::with_capacity(PRECOMP_TABLE_BITS); + let mut window_base = ark_bn254::G1Projective::from(base.clone()); + + for _ in 0..WINDOW_COUNT { + let mut multiple = ark_bn254::G1Projective::zero(); // j=0: infinity + for _ in 0..WINDOW_ENTRIES { + let aff = multiple.into_affine(); + bits.extend(Fq::to_bits(Fq::as_montgomery(aff.x))); + bits.extend(Fq::to_bits(Fq::as_montgomery(aff.y))); + multiple += window_base; + } + + for _ in 0..WINDOW_BITS { + window_base.double_in_place(); + } + } + bits +} + #[cfg(test)] mod tests { use super::*; use ark_ec::CurveGroup; - use ark_ff::{UniformRand, Zero}; - use garbled_snark_verifier::bag::{Circuit, S}; + use ark_ff::UniformRand; use garbled_snark_verifier::core::utils::reset_gid; - use crate::dre::matrices::u_bar_vec; + use crate::dre::Q_SIZE; fn random_g1_affine() -> G1Affine { let mut rng = rand::thread_rng(); ark_bn254::G1Projective::rand(&mut rng).into_affine() } - #[test] - fn test_babe_gc_on_curve() { - let pi = random_g1_affine(); - let g = random_g1_affine(); + // ── FGC witness / tests ───────────────────────────────────────────────── - let witness: Vec = Fq::to_bits(pi.x) + /// Witness for compile_fgc: only π_x and π_y. + fn fgc_witness(pi: &G1Affine) -> Vec { + Fq::to_bits(pi.x) .into_iter() - .chain(Fq::to_bits(pi.y).into_iter()) - .collect(); + .chain(Fq::to_bits(pi.y)) + .collect() + } - // build circuit + fn eval_fgc(pi: &G1Affine, g: G1Affine) -> Vec { + let witness = fgc_witness(pi); reset_gid(); - let (bld, output_indices) = compile_babe_gc(g); + let (bld, output_indices) = compile_fgc(g); + assert_eq!(output_indices.len(), U_BAR_SIZE); let mut circuit = bld.build(&witness); - circuit.gate_counts().print(); + for gate in &mut circuit.1 { gate.evaluate(); } + output_indices.iter().map(|&i| circuit.0[i].borrow().get_value()).collect() + } - // Evaluate the circuit - for gate in &mut circuit.1 { - gate.evaluate(); + #[test] + fn test_fgc_on_curve() { + let pi = random_g1_affine(); + let g = random_g1_affine(); + let output = eval_fgc(&pi, g); + + assert_eq!(output.len(), U_BAR_SIZE); + assert!(output[0], "u₀ must be 1"); + + for (k, (chunk, expected_bits)) in output[1..].chunks(N).zip([ + Fq::to_bits(pi.x), + Fq::to_bits(pi.y), + Fq::to_bits(pi.x * pi.x), + Fq::to_bits(pi.y * pi.y), + Fq::to_bits(pi.x * pi.y), + ]).enumerate() { + for (j, (&got, &exp)) in chunk.iter().zip(expected_bits.iter()).enumerate() { + assert_eq!(got, exp, "field element {k} bit {j} mismatch"); + } } + } - let output: Vec = output_indices - .iter() - .map(|&i| circuit.0[i].borrow().get_value()) - .collect(); - - assert_eq!(output.len(), L); - - // u₀ = 1 - assert!(output[0]); - - // bits 1..=N match bits of x - let x_bits = Fq::to_bits(pi.x); - for k in 0..N { - assert_eq!(output[1 + k], x_bits[k], "x bit {k} mismatch"); - } + #[test] + fn test_fgc_off_curve_falls_back_to_g() { + let pi = random_g1_affine(); + let g = random_g1_affine(); - // bits N+1..=2N match bits of y - let y_bits = Fq::to_bits(pi.y); - for k in 0..N { - assert_eq!(output[1 + N + k], y_bits[k], "y bit {k} mismatch"); - } + let mut off_pi = pi; + off_pi.y += ark_bn254::Fq::from(1u64); + let output = eval_fgc(&off_pi, g); - // bits 2N+1..=3N match bits of x² - let x_sq_bits = Fq::to_bits(pi.x * pi.x); - for k in 0..N { - assert_eq!(output[1 + 2 * N + k], x_sq_bits[k], "x² bit {k} mismatch"); - } + assert_eq!(output.len(), U_BAR_SIZE); + assert!(output[0], "u₀ must be 1"); - // bits 3N+1..=4N match bits of y² - let y_sq_bits = Fq::to_bits(pi.y * pi.y); + let gx_bits = Fq::to_bits(g.x); for k in 0..N { - assert_eq!(output[1 + 3 * N + k], y_sq_bits[k], "y² bit {k} mismatch"); + assert_eq!(output[1 + k], gx_bits[k], "g.x bit {k} mismatch"); } - - // bits 4N+1..=5N match bits of xy - let xy_bits = Fq::to_bits(pi.x * pi.y); + let gy_bits = Fq::to_bits(g.y); for k in 0..N { - assert_eq!(output[1 + 4 * N + k], xy_bits[k], "xy bit {k} mismatch"); + assert_eq!(output[1 + N + k], gy_bits[k], "g.y bit {k} mismatch"); } } - #[test] - fn test_babe_gc_off_curve_falls_back_to_g() { - let pi = random_g1_affine(); - let g = random_g1_affine(); + // ── SGC Part 1 witness / tests ────────────────────────────────────────── - // Perturb y to force off-curve - let bad_y = pi.y + ark_bn254::Fq::from(1u64); - let witness: Vec = Fq::to_bits(pi.x) + /// Witness for compile_sgc_part1: x_d | B_x (Montgomery) | B_y (Montgomery). + /// L_2 table is baked into the circuit as constants, so it is not part of the witness. + fn sgc_part1_witness(x_d: ark_bn254::Fr, b: &G1Affine) -> Vec { + Fq::to_bits(Fq::as_montgomery(b.x)) .into_iter() - .chain(Fq::to_bits(bad_y).into_iter()) - .collect(); + .chain(Fq::to_bits(Fq::as_montgomery(b.y))) + .chain(Fr::to_bits(x_d)) + .collect() + } - // circuit + fn eval_sgc_part1(l2: G1Affine, b: G1Affine, x_d: ark_bn254::Fr) -> ark_bn254::G1Affine { + let witness = sgc_part1_witness(x_d, &b); reset_gid(); - let (bld, output_indices) = compile_babe_gc(g); + let (bld, output_indices) = compile_sgc_part1(l2); + assert_eq!(output_indices.len(), Q_SIZE); + let mut circuit = bld.build(&witness); - for gate in &mut circuit.1 { - gate.evaluate(); - } + circuit.gate_counts().print(); + for gate in &mut circuit.1 { gate.evaluate(); } - let output: Vec = output_indices + let bits: Vec = output_indices .iter() .map(|&i| circuit.0[i].borrow().get_value()) .collect(); - assert_eq!(output.len(), L); - - // u₀ = 1 - assert!(output[0]); - - // Should fall back to g - let gx_bits = Fq::to_bits(g.x); - for k in 0..N { - assert_eq!(output[1 + k], gx_bits[k], "g.x bit {k} mismatch"); - } - let gy_bits = Fq::to_bits(g.y); - for k in 0..N { - assert_eq!(output[1 + N + k], gy_bits[k], "g.y bit {k} mismatch"); - } + let x = Fq::from_bits(bits[..N].to_vec()); + let y = Fq::from_bits(bits[N..2 * N].to_vec()); + ark_bn254::G1Affine::new_unchecked(x, y) } - /// Tests the full garbled circuit flow for BABE GC. - /// With the `garbled` feature, Wire::new() auto-assigns random labels. - #[cfg(feature = "garbled")] #[test] - fn test_babe_gc_garbled_labels() { - let pi = random_g1_affine(); - let g = random_g1_affine(); - - reset_gid(); - let (bld, output_indices) = compile_babe_gc(g); - let mut circuit = bld.build(&vec![]); - - let witness: Vec = Fq::to_bits(pi.x) - .into_iter() - .chain(Fq::to_bits(pi.y).into_iter()) - .collect(); + fn test_sgc_part1_random_xd() { + let mut rng = rand::thread_rng(); + let l2 = random_g1_affine(); + let b = random_g1_affine(); + let x_d = ark_bn254::Fr::rand(&mut rng); - circuit.set_witness_value(&witness); - for gate in &mut circuit.1 { - gate.evaluate(); - } - let garblings = circuit.garbled_gates(); - let _ = circuit.garbled_evaluate(&garblings); + let got = eval_sgc_part1(l2, b, x_d); + let expected = (ark_bn254::G1Projective::from(l2) * x_d + + ark_bn254::G1Projective::from(b)) + .into_affine(); - let output_labels: Vec = output_indices - .iter() - .map(|&i| { - let w = &circuit.0[i]; - w.borrow().select(w.borrow().get_value()) - }).collect(); - assert_eq!(output_labels.len(), L); - - // compute expected u_bar(π) for the given π - let u_bar = u_bar_vec(&pi); - assert_eq!(u_bar.len(), L); - for k in 0..L { - let expected_bit = !u_bar[k].is_zero(); - let expected_label = circuit.0[output_indices[k]].borrow().select(expected_bit); - assert_eq!( - output_labels[k], - expected_label, - "garbled output label mismatch at ū[{k}]" - ); - } + assert_eq!(got, expected, "Q = x_d · L₂ + B affine mismatch"); } - #[cfg(feature = "garbled")] #[test] - fn test_output_labels() { - // Todo: change to use own Delta instead of NON_CAC_DELTA - use garbled_snark_verifier::core::utils::NON_CAC_DELTA; - - let g = random_g1_affine(); + fn test_sgc_part1_xd_one() { + let l2 = random_g1_affine(); + let b = random_g1_affine(); - // Generate the circuit. - reset_gid(); - let (bld, output_indices) = compile_babe_gc(g); - let mut circuit = bld.build(&[]); - - // Encoding keys: random 0-labels for each of the 2*N input wires (x bits then y bits). - // The 1-label for wire i is encoding_keys[i] XOR DELTA (Free XOR). - let encoding_keys: Vec = (0..2 * crate::dre::N).map(|_| S::random()).collect(); - // Override the circuit's input wire labels with our encoding keys. - for (i, &key) in encoding_keys.iter().enumerate() { - circuit.0[2 + i].borrow_mut().label = Some(key); - } + let got = eval_sgc_part1(l2, b, ark_bn254::Fr::from(1u64)); + let expected = (ark_bn254::G1Projective::from(l2) + + ark_bn254::G1Projective::from(b)) + .into_affine(); - // Two random G1 points. - let mut rng = rand::thread_rng(); - let p1a = ark_bn254::G1Projective::rand(&mut rng).into_affine(); - let p1b = ark_bn254::G1Projective::rand(&mut rng).into_affine(); - - // Evaluate the circuit for a given point and return the output labels. - // For each output wire k, select the label corresponding to the output bit value. - let eval = |circuit: &mut Circuit, p: &ark_bn254::G1Affine| -> Vec { - let witness: Vec = Fq::to_bits(p.x) - .into_iter() - .chain(Fq::to_bits(p.y).into_iter()) - .collect(); - - // Reset all non-constant wire values so the circuit can be re-evaluated. - for wire in circuit.0.iter().skip(2) { - wire.borrow_mut().value = None; - } - circuit.set_witness_value(&witness); - for gate in &mut circuit.1 { - gate.evaluate(); - } - let garblings = circuit.garbled_gates(); - let _ = circuit.garbled_evaluate(&garblings); - - output_indices - .iter() - .map(|&i| { - let w = &circuit.0[i]; - w.borrow().select(w.borrow().get_value()) - }) - .collect() - }; - - let labels_a = eval(&mut circuit, &p1a); - let labels_b = eval(&mut circuit, &p1b); - - // Verify the Free XOR property on output labels: - // same u_bar bit → labels are equal - // different u_bar → labels XOR DELTA - let u_bar_a = u_bar_vec(&p1a); - let u_bar_b = u_bar_vec(&p1b); - assert_eq!(u_bar_a.len(), L); - assert_eq!(u_bar_b.len(), L); - - for k in 0..L { - let bit_a = !u_bar_a[k].is_zero(); - let bit_b = !u_bar_b[k].is_zero(); - if bit_a == bit_b { - assert_eq!(labels_a[k], labels_b[k], "k={k}: same u_bar bit → equal output labels"); - } else { - assert_eq!( - labels_a[k] ^ labels_b[k], - NON_CAC_DELTA, - "k={k}: different u_bar bits → labels must differ by DELTA" - ); - } - } + assert_eq!(got, expected, "Q = 1·L₂ + B should equal L₂ + B"); } } diff --git a/verifiable-circuit-babe/src/gc/mod.rs b/verifiable-circuit-babe/src/gc/mod.rs index 231c4e8..a9ca2fe 100644 --- a/verifiable-circuit-babe/src/gc/mod.rs +++ b/verifiable-circuit-babe/src/gc/mod.rs @@ -15,21 +15,41 @@ use garbled_snark_verifier::core::gate::GateType; use garbled_snark_verifier::core::utils::{reset_gid, SerializableGate}; pub use utils::*; -fn gc_gates_path() -> String { - std::env::var("GC_GATES_PATH").unwrap_or_else(|_| "./babe_gc_gates.bin".to_string()) +fn fgc_gates_path() -> String { + std::env::var("FGC_GATES_PATH").unwrap_or_else(|_| "./fgc_gates.bin".to_string()) } -fn gc_indices_path() -> String { - std::env::var("GC_INDICES_PATH").unwrap_or_else(|_| "./babe_gc_indices.bin".to_string()) +fn fgc_indices_path() -> String { + std::env::var("FGC_OUT_INDICES_PATH").unwrap_or_else(|_| "./fgc_out_indices.bin".to_string()) +} + +fn sgc_part1_gates_path() -> String { + std::env::var("SGC_GATES_PATH").unwrap_or_else(|_| "./sgc_gates.bin".to_string()) +} + +fn sgc_part1_indices_path() -> String { + std::env::var("SGC_OUT_INDICES_PATH").unwrap_or_else(|_| "./sgc_out_indices.bin".to_string()) } /// Raw circuit bytes cached on first read. -static CIRCUIT_BYTES: OnceLock<(Vec, Vec)> = OnceLock::new(); +static CIRCUIT_1_BYTES: OnceLock<(Vec, Vec)> = OnceLock::new(); +static CIRCUIT_2_BYTES: OnceLock<(Vec, Vec)> = OnceLock::new(); + +pub fn read_fresh_gc() -> (Circuit, Vec, Circuit, Vec) { + let (fgc_bytes, fgc_indices_bytes) = CIRCUIT_1_BYTES.get_or_init(|| { + let gates_path = fgc_gates_path(); + let indices_path = fgc_indices_path(); + let g = fs::read(&gates_path) + .unwrap_or_else(|_| panic!("'{}' not found — run function generate_and_write_fresh_circuit() to generate it.", gates_path)); + let i = fs::read(&indices_path) + .unwrap_or_else(|_| panic!("'{}' not found — run function generate_and_write_fresh_circuit() to generate it.", indices_path)); + (g, i) + }); + let (fgc, fgc_indices) = deserialize_circuit(fgc_bytes, fgc_indices_bytes); -pub fn read_fresh_circuit() -> (Circuit, Vec) { - let (gates_bytes, indices_bytes) = CIRCUIT_BYTES.get_or_init(|| { - let gates_path = gc_gates_path(); - let indices_path = gc_indices_path(); + let (sgc_bytes, sgc_indices_bytes) = CIRCUIT_2_BYTES.get_or_init(|| { + let gates_path = sgc_part1_gates_path(); + let indices_path = sgc_part1_indices_path(); let g = fs::read(&gates_path) .unwrap_or_else(|_| panic!("'{}' not found — run function generate_and_write_fresh_circuit() to generate it.", gates_path)); let i = fs::read(&indices_path) @@ -37,10 +57,16 @@ pub fn read_fresh_circuit() -> (Circuit, Vec) { (g, i) }); + let (sgc, sgc_indices) = deserialize_circuit(sgc_bytes, sgc_indices_bytes); + + (fgc, fgc_indices, sgc, sgc_indices) +} + +fn deserialize_circuit(gates_bytes: &[u8], output_indices_bytes: &[u8]) -> (Circuit, Vec) { let (num_wires, gates_read): (u32, Vec) = bincode::deserialize(gates_bytes).expect("deserialize gates"); let output_indices: Vec = - bincode::deserialize(indices_bytes).expect("deserialize indices"); + bincode::deserialize(output_indices_bytes).expect("deserialize indices"); let wires: Vec<_> = (0..num_wires) .map(|id| Rc::new(RefCell::new(Wire { label: None, value: None, id: Some(id) }))) @@ -61,15 +87,26 @@ pub fn read_fresh_circuit() -> (Circuit, Vec) { (Circuit(wires, gates), output_indices) } -pub fn generate_and_write_fresh_circuit() { +pub fn generate_and_write_fresh_circuit(l2_point: ark_bn254::G1Affine) { reset_gid(); let g = G1Affine::generator(); - let (bld, output_indices) = compile_babe_gc(g); - let circuit = bld.build(&[]); + let (bld, fgc_output_indices) = compile_fgc(g); + let fgc_circuit = bld.build(&[]); + + let (bld, sgc_output_indices) = compile_sgc_part1(l2_point); + let sgc_circuit = bld.build(&[]); // --- Serialize --- + write_fresh_circuit(fgc_circuit, fgc_output_indices, fgc_gates_path(), fgc_indices_path()); + write_fresh_circuit(sgc_circuit, sgc_output_indices, sgc_part1_gates_path(), sgc_part1_indices_path()); +} - // File 1: (num_wires: u32, gates: Vec) +fn write_fresh_circuit( + circuit: Circuit, + output_indices: Vec, + gates_path: String, + output_indices_path: String, +) { let num_wires = circuit.0.len() as u32; let gates: Vec = circuit.1.iter().map(|gate| SerializableGate { gate_type: gate.gate_type as u8, @@ -79,11 +116,11 @@ pub fn generate_and_write_fresh_circuit() { gid: gate.gid, }).collect(); let gates_bytes = bincode::serialize(&(num_wires, &gates)).expect("serialize gates"); - fs::write(gc_gates_path(), &gates_bytes).expect("write gates"); + fs::write(gates_path, &gates_bytes).expect("write gates"); // File 2: Vec output indices let indices_bytes = bincode::serialize(&output_indices).expect("serialize indices"); - fs::write(gc_indices_path(), &indices_bytes).expect("write indices"); + fs::write(output_indices_path, &indices_bytes).expect("write indices"); } #[cfg(test)] @@ -91,28 +128,40 @@ mod tests { use ark_bn254::G1Affine; use ark_ec::AffineRepr; use garbled_snark_verifier::core::utils::reset_gid; - use super::{compile_babe_gc, generate_and_write_fresh_circuit}; + use super::{compile_fgc, compile_sgc_part1, generate_and_write_fresh_circuit}; #[test] #[ignore] fn test_babe_gc_serialize_roundtrip() { - generate_and_write_fresh_circuit(); + let l2_point: G1Affine = G1Affine::generator(); + generate_and_write_fresh_circuit(l2_point); reset_gid(); let g = G1Affine::generator(); - let (bld, output_indices) = compile_babe_gc(g); - let circuit = bld.build(&[]); - let num_wires = circuit.0.len() as u32; + let (bld, fgc_output_indices) = compile_fgc(g); + let f_circuit = bld.build(&[]); + let (bld, sgc_output_indices) = compile_sgc_part1(l2_point); + let s_circuit = bld.build(&[]); // --- Reconstruct Circuit --- - let (circuit_reconstructed, _) = super::read_fresh_circuit(); - + let (fgc, fgc_indices, sgc, sgc_indices) = super::read_fresh_gc(); - assert_eq!(circuit_reconstructed.0.len(), circuit.0.len(), "reconstructed wire count mismatch"); - assert_eq!(circuit_reconstructed.1.len(), circuit.1.len(), "reconstructed gate count mismatch"); + assert_eq!(fgc.0.len(), f_circuit.0.len(), "reconstructed wire count mismatch"); + assert_eq!(sgc.0.len(), s_circuit.0.len(), "reconstructed wire count mismatch"); + assert_eq!(fgc.1.len(), f_circuit.1.len(), "reconstructed gate count mismatch"); + assert_eq!(sgc.1.len(), s_circuit.1.len(), "reconstructed gate count mismatch"); + assert_eq!(fgc_indices, fgc_output_indices, "reconstructed output wire mismatch"); + assert_eq!(sgc_indices, sgc_output_indices, "reconstructed output wire mismatch"); + for (i, (orig, rec)) in f_circuit.1.iter().zip(fgc.1.iter()).enumerate() { + assert_eq!(orig.gate_type, rec.gate_type, "reconstructed gate[{i}] type mismatch"); + assert_eq!(orig.wire_a.borrow().id, rec.wire_a.borrow().id, "reconstructed gate[{i}] wire_a id mismatch"); + assert_eq!(orig.wire_b.borrow().id, rec.wire_b.borrow().id, "reconstructed gate[{i}] wire_b id mismatch"); + assert_eq!(orig.wire_c.borrow().id, rec.wire_c.borrow().id, "reconstructed gate[{i}] wire_c id mismatch"); + assert_eq!(orig.gid, rec.gid, "reconstructed gate[{i}] gid mismatch"); + } - for (i, (orig, rec)) in circuit.1.iter().zip(circuit_reconstructed.1.iter()).enumerate() { + for (i, (orig, rec)) in s_circuit.1.iter().zip(sgc.1.iter()).enumerate() { assert_eq!(orig.gate_type, rec.gate_type, "reconstructed gate[{i}] type mismatch"); assert_eq!(orig.wire_a.borrow().id, rec.wire_a.borrow().id, "reconstructed gate[{i}] wire_a id mismatch"); assert_eq!(orig.wire_b.borrow().id, rec.wire_b.borrow().id, "reconstructed gate[{i}] wire_b id mismatch"); @@ -120,7 +169,6 @@ mod tests { assert_eq!(orig.gid, rec.gid, "reconstructed gate[{i}] gid mismatch"); } - println!("wires={num_wires}, gates={}, output_indices={}", circuit.1.len(), output_indices.len()); println!("Circuit reconstructed successfully from serialized data."); } -} \ No newline at end of file +} diff --git a/verifiable-circuit-babe/src/instance/commit.rs b/verifiable-circuit-babe/src/instance/commit.rs index e61c97a..5c1c4e0 100644 --- a/verifiable-circuit-babe/src/instance/commit.rs +++ b/verifiable-circuit-babe/src/instance/commit.rs @@ -1,6 +1,6 @@ -use crate::babe::compute_epk_with_delta; +use ark_serialize::CanonicalSerialize; use crate::gc::gc_ciphertexts_commit; -use crate::instance::BABEInstance; +use crate::instance::CACInstance; use crate::utils::{derive_hashlock, h_256}; /// Per-instance commitment sent from Verifier to Prover during C&C commit phase. @@ -9,39 +9,69 @@ pub struct CACInstanceCommit { /// RIPEMD(SHA256)(label0) and RIPEMD(SHA256)(label1) for each input wire. /// We need to use RIPEMD(SHA256) to put this on skeleton Txn. pub epk: Vec<[[u8; 20]; 2]>, - /// SHA256(label0) and SHA256(label1) for each of the 2 constant wires. - pub constant_commits: [[[u8; 32]; 2]; 2], + /// SHA256(label0) and SHA256(label1) for each of the 2 constant wires in fgc. + pub constant_commits_0: [[[u8; 32]; 2]; 2], + /// constant commit for sgc, size = 2 + 2*N + pub constant_commits_1: [[[u8; 32]; 2]; 510], + /// commitment of b_blind in this instance. + pub b_blind_commit: [u8; 32], pub h_msg: [u8; 20], /// RO(ct_setup) = SHA256(ct2_r_delta_g2 || ct3_masked_msg). pub h_ct_setup: [u8; 32], /// SHA256 commitment to the adaptor table. - pub com_adaptor: [u8; 32], + pub com_adaptor: [[u8; 32]; 2], /// SHA256 commitment to the GC gate ciphertexts. - pub com_gc: [u8; 32], + pub com_gc: [[u8; 32]; 3], } impl CACInstanceCommit { - pub fn from_instance(instance: &BABEInstance) -> Self { + pub fn from_instance(instance: &CACInstance) -> Self { let delta = instance.secrets.delta; - let input_commits = compute_epk_with_delta(&instance.secrets.encoding_keys, delta).0; + let input_commits = { + let fgc_pairs: Vec<[[u8; 20]; 2]> = instance.secrets.encoding_keys[0] + .iter() + .map(|&key| [derive_hashlock(&key.0), derive_hashlock(&(key ^ delta[0]).0)]) + .collect(); + let sgc_pairs: Vec<[[u8; 20]; 2]> = instance.secrets.encoding_keys[1] + .iter() + .map(|&key| [derive_hashlock(&key.0), derive_hashlock(&(key ^ delta[1]).0)]) + .collect(); + let pairs: Vec<[[u8; 20]; 2]> = fgc_pairs.into_iter().chain(sgc_pairs).collect(); + pairs + }; - let constant_commits = std::array::from_fn(|w| { - let l0 = instance.secrets.constant_0labels[w]; - [h_256(&l0.0), h_256(&(l0 ^ delta).0)] + let constant_commits_0 = std::array::from_fn(|w| { + let l0 = instance.secrets.constant_0labels[0][w]; + [h_256(&l0.0), h_256(&(l0 ^ delta[0]).0)] + }); + let constant_commits_1 = std::array::from_fn(|w| { + let l0 = instance.secrets.constant_0labels[1][w]; + [h_256(&l0.0), h_256(&(l0 ^ delta[1]).0)] }); let mut ct_setup_bytes = Vec::new(); ct_setup_bytes.extend_from_slice(&instance.ct_setup.ct2_r_delta_g2); ct_setup_bytes.extend_from_slice(&instance.ct_setup.ct3_masked_msg); + let mut b_blind_bytes = Vec::new(); + instance.secrets.b.serialize_compressed(&mut b_blind_bytes).expect("serialize r·G1P"); + + let b_blind_commit = h_256(&b_blind_bytes); + CACInstanceCommit { epk: input_commits, - constant_commits, + constant_commits_0, + constant_commits_1, + b_blind_commit, h_msg: derive_hashlock(&instance.secrets.msg), h_ct_setup: h_256(&ct_setup_bytes), - com_adaptor: instance.adaptor_table.commit(), - com_gc: gc_ciphertexts_commit(&instance.ciphertexts), + com_adaptor: [instance.adaptor_tables[0].commit(), instance.adaptor_tables[1].commit()], + com_gc: [ + gc_ciphertexts_commit(&instance.ciphertexts_sets[0]), + gc_ciphertexts_commit(&instance.ciphertexts_sets[1]), + gc_ciphertexts_commit(&instance.ciphertexts_sets[2]), + ] } } } diff --git a/verifiable-circuit-babe/src/instance/mod.rs b/verifiable-circuit-babe/src/instance/mod.rs index e49f41b..d2cb123 100644 --- a/verifiable-circuit-babe/src/instance/mod.rs +++ b/verifiable-circuit-babe/src/instance/mod.rs @@ -2,154 +2,455 @@ use ark_bn254::Fr; use ark_ec::{AffineRepr, CurveGroup}; use ark_ec::pairing::Pairing; use ark_ff::UniformRand; -use garbled_snark_verifier::bag::S; -use garbled_snark_verifier::circuits::bn254::fq::Fq; +use garbled_snark_verifier::bag::{Circuit, S}; use crate::babe::WeKnownPi1SetupCt; -use crate::gc::SparseAdaptorTable; +use crate::gc::{gc_garble_and_hash, SparseAdaptorTable, SGC_PART1_CONSTANT_SIZE}; use crate::instance::secret::InstanceSecrets; use ark_groth16::VerifyingKey as Groth16VerifyingKey; use ark_serialize::CanonicalSerialize; +use garbled_snark_verifier::dv_bn254::fq::Fq as DvFq; +use garbled_snark_verifier::dv_bn254::fr::Fr as DvFr; +use crate::dre::{Q_SIZE, U_BAR_SIZE}; use crate::instance::commit::CACInstanceCommit; -use crate::utils::{g2_to_ser, groth16_vk_x, h_256}; +use crate::utils::{derive_hashlock, g2_to_ser, h_256, ro_from_pairing_bytes}; pub mod secret; pub mod commit; -pub struct BABEInstance { +pub struct CACInstance { pub seed: u64, pub secrets: InstanceSecrets, pub ct_setup: WeKnownPi1SetupCt, - pub adaptor_table: SparseAdaptorTable, - pub ciphertexts: Vec>, + pub adaptor_tables: [SparseAdaptorTable; 2], + /// for fgc, sgc part 1, sgc part 2 + pub ciphertexts_sets: [Vec>; 3] } -impl BABEInstance { - /// Construct a BABE instance fully determined by `seed`. +impl CACInstance { + /// Construct a instance fully determined by `seed`. /// W/O ct_setup. - pub fn new_from_seed(seed: u64) -> Self { + pub fn new_from_seed( + seed: u64, + vk: &Groth16VerifyingKey, + static_inputs: Fr, + ) -> Result { use ark_bn254::G1Projective; - use ark_ff::Zero; - use crate::dre::matrices::u_bar_vec; + + if vk.gamma_abc_g1.len() != 3 { + return Err("static/dynamic split does not match vk".to_string()); + } + + // generate artifacts. + // crate::gc::generate_and_write_fresh_circuit(vk.gamma_abc_g1[2]); let secrets = InstanceSecrets::new_from_seed(seed); // Load fresh circuit structure from pre-serialized files; drop after use. - let (mut circuit, gc_output_indices) = crate::gc::read_fresh_circuit(); + let (mut fgc, fgc_indices, mut sgc, sgc_indices) = crate::gc::read_fresh_gc(); - // Apply encoding keys and constant 0-labels to input wires. - circuit.0[0].borrow_mut().label = Some(secrets.constant_0labels[0]); - circuit.0[1].borrow_mut().label = Some(secrets.constant_0labels[1]); - for (i, &key) in secrets.encoding_keys.iter().enumerate() { - circuit.0[2 + i].borrow_mut().label = Some(key); + // Apply encoding keys as 0-labels for evaluator input wires (pi_x, pi_y, x_d). + for (i, &key) in secrets.encoding_keys[0].iter().enumerate() { + fgc.0[2 + i].borrow_mut().label = Some(key); + } + for (i, &key) in secrets.encoding_keys[1].iter().enumerate() { + sgc.0[SGC_PART1_CONSTANT_SIZE + i].borrow_mut().label = Some(key); + } + + // Compute B bit representation (Montgomery form) for use as constant wires. + let b_x_bits: Vec = DvFq::to_bits(DvFq::as_montgomery(secrets.b.x)); + let b_y_bits: Vec = DvFq::to_bits(DvFq::as_montgomery(secrets.b.y)); + // Set constant wire value for sgc part 1 + for (i, bit) in b_x_bits.iter().enumerate() { + sgc.0[2 + i].borrow_mut().value = Some(*bit); + } + for (i, bit) in b_y_bits.iter().enumerate() { + sgc.0[2 + 254 + i].borrow_mut().value = Some(*bit); } - // Evaluate circuit at a random pi1 to obtain output labels. + // set constant labels + set_gc_const_labels(&mut fgc, &secrets.constant_0labels[0]); + set_gc_const_labels(&mut sgc, &secrets.constant_0labels[1]); + + // Evaluate circuit at a random pi1 and random x_d to garble let pi1 = G1Projective::rand(&mut rand::thread_rng()).into_affine(); - let witness: Vec = Fq::to_bits(pi1.x) + let x_d = ark_bn254::Fr::rand(&mut rand::thread_rng()); + + // Fgc + let fgc_witness: Vec = DvFq::to_bits(pi1.x) .into_iter() - .chain(Fq::to_bits(pi1.y).into_iter()) + .chain(DvFq::to_bits(pi1.y)) .collect(); - circuit.set_witness_value(&witness); - for gate in &mut circuit.1 { - gate.evaluate(); + let (fgc_ciphertext, fgc_output_labels) = get_ciphertext_and_output_labels( + &mut fgc, + &fgc_indices, + &fgc_witness, + secrets.delta[0], + 2 + ); + assert_eq!(fgc_output_labels.len(), 2 * U_BAR_SIZE); + println!("cac instance fgc done"); + // Sgc - part 1 + let sgc_part1_witness: Vec = DvFr::to_bits(x_d); + let (sgc_ciphertext_1, sgc_output_labels_1) = get_ciphertext_and_output_labels( + &mut sgc, + &sgc_indices, + &sgc_part1_witness, + secrets.delta[1], + SGC_PART1_CONSTANT_SIZE, + ); + println!("cac instance sgc part1 done"); + assert_eq!(sgc_output_labels_1.len(), 2 * Q_SIZE); + // Sgc - part 2 + // Reuse the fgc structure, by setting up the input & constant labels again, then evaluate. + fgc.reset_circuit_except_01_constants(); + // set label of part2 as output of part1 + for (i, &key) in sgc_output_labels_1.iter().step_by(2).enumerate() { + fgc.0[2 + i].borrow_mut().label = Some(S(key)); } - let ciphertexts = circuit.garbled_gates_with_delta(secrets.delta); - - // Recover label0 for each output wire. - let delta = secrets.delta; - let u_bar_pi1 = u_bar_vec(&pi1); - let output_labels: Vec<[u8; 16]> = gc_output_indices - .iter() - .zip(u_bar_pi1.iter()) - .flat_map(|(idx, u)| { - let current = circuit.0[*idx] - .borrow() - .select_with_delta(circuit.0[*idx].borrow().get_value(), delta); - let label_0 = if u.is_zero() { current } else { current ^ delta }; - [label_0.0, (label_0 ^ delta).0] - }) - .collect(); - - let adaptor_table = SparseAdaptorTable::build_from_r_and_labels( + // set constant for part2 + set_gc_const_labels(&mut fgc, &secrets.constant_0labels[1][0..2]); + // random eval + let (sgc_ciphertext_2, sgc_output_labels_2) = get_ciphertext_and_output_labels( + &mut fgc, + &fgc_indices, + &fgc_witness, + secrets.delta[1], + 2 + ); + assert_eq!(sgc_output_labels_2.len(), 2 * U_BAR_SIZE); + println!("cac instance sgc part 2 done"); + // generate adaptor table + // fgc + let fgc_adaptor_table = SparseAdaptorTable::build_from_r_and_u_bar_labels( + secrets.r, + &fgc_output_labels, + &secrets.rhos[0], + &secrets.fq_deltas[0], + ); + + let sgc_adaptor_table = SparseAdaptorTable::build_from_r_and_u_bar_labels( secrets.r, - &output_labels, - &secrets.rhos, - &secrets.fq_deltas, + &sgc_output_labels_2, + &secrets.rhos[1], + &secrets.fq_deltas[1], ); + let ct_setup = Self::enc_setup( + &secrets, + vk, + static_inputs, + )?; + // circuit is dropped here — not stored in the instance. - Self { + Ok(Self { seed, secrets, - ct_setup: WeKnownPi1SetupCt { ct2_r_delta_g2: vec![], ct3_masked_msg: vec![] }, - adaptor_table, - ciphertexts, + ct_setup, + adaptor_tables: [fgc_adaptor_table, sgc_adaptor_table], + // adaptor_tables: [fgc_adaptor_table.clone(), fgc_adaptor_table], + ciphertexts_sets: [fgc_ciphertext, sgc_ciphertext_1, sgc_ciphertext_2], + // ciphertexts_sets: [fgc_ciphertext.clone(), fgc_ciphertext.clone(), fgc_ciphertext], + }) + } + + /// Commitment-only path: same circuit setup as `new_from_seed` but ciphertexts and + /// adaptor tables are stream-hashed without being stored. Produces identical + /// `CACInstanceCommit` values while keeping peak memory at O(circuit_size) instead + /// of O(circuit_size + ciphertexts + adaptor_tables). + /// + /// Returns the full `InstanceSecrets` alongside the commit so the caller can + /// extract encoding keys and deltas without a second derivation. + pub fn commit_from_seed( + seed: u64, + vk: &Groth16VerifyingKey, + static_inputs: Fr, + ) -> Result<(CACInstanceCommit, InstanceSecrets), String> { + use ark_bn254::G1Projective; + + if vk.gamma_abc_g1.len() != 3 { + return Err("static/dynamic split does not match vk".to_string()); + } + + let secrets = InstanceSecrets::new_from_seed(seed); + let (mut fgc, fgc_indices, mut sgc, sgc_indices) = crate::gc::read_fresh_gc(); + + for (i, &key) in secrets.encoding_keys[0].iter().enumerate() { + fgc.0[2 + i].borrow_mut().label = Some(key); } + for (i, &key) in secrets.encoding_keys[1].iter().enumerate() { + sgc.0[SGC_PART1_CONSTANT_SIZE + i].borrow_mut().label = Some(key); + } + + let b_x_bits = DvFq::to_bits(DvFq::as_montgomery(secrets.b.x)); + let b_y_bits = DvFq::to_bits(DvFq::as_montgomery(secrets.b.y)); + for (i, bit) in b_x_bits.iter().enumerate() { + sgc.0[2 + i].borrow_mut().value = Some(*bit); + } + for (i, bit) in b_y_bits.iter().enumerate() { + sgc.0[2 + 254 + i].borrow_mut().value = Some(*bit); + } + + set_gc_const_labels(&mut fgc, &secrets.constant_0labels[0]); + set_gc_const_labels(&mut sgc, &secrets.constant_0labels[1]); + + let pi1 = G1Projective::rand(&mut rand::thread_rng()).into_affine(); + let x_d = ark_bn254::Fr::rand(&mut rand::thread_rng()); + + let fgc_witness: Vec = DvFq::to_bits(pi1.x).into_iter().chain(DvFq::to_bits(pi1.y)).collect(); + + // FGC: stream-hash ciphertexts, collect output label pairs (0-labels are deterministic). + let (com_fgc, fgc_output_labels) = garble_hash_and_output_labels( + &mut fgc, &fgc_indices, &fgc_witness, secrets.delta[0], 2, + ); + assert_eq!(fgc_output_labels.len(), 2 * U_BAR_SIZE); + + // SGC part 1. + let sgc_part1_witness: Vec = DvFr::to_bits(x_d); + let (com_sgc_1, sgc_output_labels_1) = garble_hash_and_output_labels( + &mut sgc, &sgc_indices, &sgc_part1_witness, secrets.delta[1], SGC_PART1_CONSTANT_SIZE, + ); + assert_eq!(sgc_output_labels_1.len(), 2 * Q_SIZE); + + // SGC part 2: reuse fgc (same pattern as new_from_seed). + fgc.reset_circuit_except_01_constants(); + for (i, &key) in sgc_output_labels_1.iter().step_by(2).enumerate() { + fgc.0[2 + i].borrow_mut().label = Some(S(key)); + } + set_gc_const_labels(&mut fgc, &secrets.constant_0labels[1][0..2]); + let (com_sgc_2, sgc_output_labels_2) = garble_hash_and_output_labels( + &mut fgc, &fgc_indices, &fgc_witness, secrets.delta[1], 2, + ); + assert_eq!(sgc_output_labels_2.len(), 2 * U_BAR_SIZE); + + // Adaptor tables: build and hash on-the-fly, never materialized. + let com_adaptor_0 = SparseAdaptorTable::build_and_hash( + secrets.r, &fgc_output_labels, &secrets.rhos[0], &secrets.fq_deltas[0], + ); + let com_adaptor_1 = SparseAdaptorTable::build_and_hash( + secrets.r, &sgc_output_labels_2, &secrets.rhos[1], &secrets.fq_deltas[1], + ); + + // ct_setup (small). + let ct_setup = Self::enc_setup(&secrets, vk, static_inputs)?; + let mut ct_setup_bytes = Vec::new(); + ct_setup_bytes.extend_from_slice(&ct_setup.ct2_r_delta_g2); + ct_setup_bytes.extend_from_slice(&ct_setup.ct3_masked_msg); + + // Build the commit from hashes and small secret data — no GC data retained. + let delta = secrets.delta; + let epk: Vec<[[u8; 20]; 2]> = secrets.encoding_keys[0] + .iter() + .map(|&key| [derive_hashlock(&key.0), derive_hashlock(&(key ^ delta[0]).0)]) + .chain(secrets.encoding_keys[1].iter().map(|&key| { + [derive_hashlock(&key.0), derive_hashlock(&(key ^ delta[1]).0)] + })) + .collect(); + + let constant_commits_0: [[[u8; 32]; 2]; 2] = std::array::from_fn(|w| { + let l0 = secrets.constant_0labels[0][w]; + [h_256(&l0.0), h_256(&(l0 ^ delta[0]).0)] + }); + let constant_commits_1: [[[u8; 32]; 2]; 510] = std::array::from_fn(|w| { + let l0 = secrets.constant_0labels[1][w]; + [h_256(&l0.0), h_256(&(l0 ^ delta[1]).0)] + }); + + let mut b_blind_bytes = Vec::new(); + secrets.b.serialize_compressed(&mut b_blind_bytes).expect("serialize b"); + + let commit = CACInstanceCommit { + epk, + constant_commits_0, + constant_commits_1, + b_blind_commit: h_256(&b_blind_bytes), + h_msg: derive_hashlock(&secrets.msg), + h_ct_setup: h_256(&ct_setup_bytes), + com_adaptor: [com_adaptor_0, com_adaptor_1], + com_gc: [com_fgc, com_sgc_1, com_sgc_2], + }; + + // fgc, sgc circuits (and their wire data) are dropped here. + Ok((commit, secrets)) } - /// Encsetup(crs, x, msg; r): ctsetup = (r·[delta]_2, RO(rY) ⊕ msg), - /// where Y = e([alpha]_1, [beta]_2) · e(vk_x, [gamma]_2). - pub fn enc_setup( - &mut self, + /// Enc*(crs, x_S, |D|, msg; r, B): + /// P_S = gamma_abc[0] + Σ_{k} x_S[k]·gamma_abc[k+1] + /// mask = Y_S^r - e(r·B, γ) where Y_S^r = e(α, r·β) + e(P_S, r·γ) + fn enc_setup( + secrets: &InstanceSecrets, vk: &Groth16VerifyingKey, - public_inputs: &[Fr], - ) -> Result<(), String> { - let vk_x = groth16_vk_x(vk, public_inputs).ok_or("Failed to get vk_x")?; + static_inputs: Fr, + ) -> Result { + let r = secrets.r; + let p_s = vk.gamma_abc_g1[0].into_group() + vk.gamma_abc_g1[1].into_group() * static_inputs; - let r = self.secrets.r; + let r_b = secrets.b * r; let r_delta = vk.delta_g2.into_group() * r; let t1 = ark_bn254::Bn254::pairing(vk.alpha_g1, vk.beta_g2.into_group() * r); - let t2 = ark_bn254::Bn254::pairing(vk_x, vk.gamma_g2.into_group() * r); - let r_y = t1 + t2; + let t2 = ark_bn254::Bn254::pairing(p_s, vk.gamma_g2.into_group() * r); + let y_s_r = t1 + t2; + + let q_b = ark_bn254::Bn254::pairing(r_b, vk.gamma_g2); + let mask_gt = y_s_r - q_b; - let mut ry_bytes = Vec::new(); - r_y.serialize_compressed(&mut ry_bytes).or(Err("Failed to serialize ry_bytes"))?; - let mask = h_256(&ry_bytes); - let ct3 = self.secrets.msg.iter().zip(mask.iter()).map(|(a, b)| a ^ b).collect::>(); + let mut mask_bytes = Vec::new(); + mask_gt.serialize_compressed(&mut mask_bytes).or(Err("Failed to serialize mask_bytes"))?; + let mask = ro_from_pairing_bytes(&mask_bytes, secrets.msg.len()); + let ct3 = secrets.msg.iter().zip(mask.iter()).map(|(a, b)| a ^ b).collect::>(); - self.ct_setup = WeKnownPi1SetupCt { + Ok(WeKnownPi1SetupCt { ct2_r_delta_g2: g2_to_ser(r_delta), ct3_masked_msg: ct3, - }; - Ok(()) + }) } /// Returns the input labels given the bits of pi1. + /// Use for testing. pub fn compute_pi1_labels_based_on_value(&self, pi1: ark_bn254::G1Affine) -> Vec { - let x_bits = Fq::to_bits(pi1.x); - let y_bits = Fq::to_bits(pi1.y); - let witness: Vec = x_bits.into_iter().chain(y_bits.into_iter()).collect(); - let delta = self.secrets.delta; + let x_bits = DvFq::to_bits(pi1.x); + let y_bits = DvFq::to_bits(pi1.y); + let witness: Vec = x_bits.into_iter().chain(y_bits).collect(); + let delta = self.secrets.delta[0]; - let mut labels = Vec::new(); - labels.push(self.secrets.constant_0labels[0]); - labels.push(self.secrets.constant_0labels[1] ^ delta); - let tail: Vec = witness.iter().enumerate().map(|(i, &b)| { - let key = self.secrets.encoding_keys[i]; + let labels: Vec = witness.iter().enumerate().map(|(i, &b)| { + let key = self.secrets.encoding_keys[0][i]; if b { key ^ delta } else { key } }).collect(); - labels.extend(tail); labels } + /// Returns the input labels given the bits of pi1. + /// Use for testing. + pub fn compute_x_d_labels_based_on_value(&self, x_d: Fr) -> Vec { + let witness = DvFr::to_bits(x_d); + let delta = self.secrets.delta[1]; + + let labels: Vec = witness.iter().enumerate().map(|(i, &b)| { + let key = self.secrets.encoding_keys[1][i]; + if b { key ^ delta } else { key } + }).collect(); + labels + } + + pub fn get_b_value_labels(&self) -> Vec { + let b_x_bits: Vec = DvFq::to_bits(DvFq::as_montgomery(self.secrets.b.x)); + let b_y_bits: Vec = DvFq::to_bits(DvFq::as_montgomery(self.secrets.b.y)); + let mut labels = Vec::new(); + for (i, bit) in b_x_bits.iter().enumerate() { + if *bit { + labels.push(self.secrets.constant_0labels[1][i + 2] ^ self.secrets.delta[1]); + } else { + labels.push(self.secrets.constant_0labels[1][i + 2]); + } + } + + for (i, bit) in b_y_bits.iter().enumerate() { + if *bit { + labels.push(self.secrets.constant_0labels[1][i + 2 + 254] ^ self.secrets.delta[1]); + } else { + labels.push(self.secrets.constant_0labels[1][i + 2 + 254]); + } + } + + labels + } + + // Labels of constants in both circuits + pub fn get_2_circuit_constant_labels(&self) -> [Vec; 2] { + let f_01_labels = [ + self.secrets.constant_0labels[0][0], self.secrets.constant_0labels[0][1] ^ self.secrets.delta[0] + ]; + + let s_01_labels = [ + self.secrets.constant_0labels[1][0], self.secrets.constant_0labels[1][1] ^ self.secrets.delta[1] + ]; + let s_b_labels = self.get_b_value_labels(); + let s_constant_labels: Vec = s_01_labels + .to_vec().into_iter().chain(s_b_labels).collect(); + [f_01_labels.to_vec(), s_constant_labels] + } + pub fn commit(&self) -> CACInstanceCommit { CACInstanceCommit::from_instance(self) } } +pub fn set_gc_const_labels( + circuit: &mut Circuit, + constant_labels: &[S], +) { + for i in 0..constant_labels.len() { + circuit.0[i].borrow_mut().label = Some(constant_labels[i]); + } +} + +/// Streaming variant: evaluate + stream-hash ciphertexts without materializing the Vec. +/// Returns `(com_gc, output_label_pairs)` where output_label_pairs has 2 entries per +/// output wire — the 0-label and the 1-label (0-label XOR delta). +fn garble_hash_and_output_labels( + circuit: &mut Circuit, + output_indices: &[usize], + random_witness: &[bool], + delta: S, + const_skip: usize, +) -> ([u8; 32], Vec<[u8; 16]>) { + circuit.set_witness_value(random_witness, const_skip); + for gate in &mut circuit.1 { + gate.evaluate(); + } + let com_gc = gc_garble_and_hash(circuit, delta); + let output_labels: Vec<[u8; 16]> = output_indices + .iter() + .flat_map(|idx| { + let l0 = circuit.0[*idx].borrow().label.unwrap(); + [l0.0, (l0 ^ delta).0] + }) + .collect(); + (com_gc, output_labels) +} + +/// Generate ciphertexts and all output labels +fn get_ciphertext_and_output_labels( + circuit: &mut Circuit, + output_indices: &[usize], + random_witness: &[bool], + delta: S, + const_skip: usize, +) -> (Vec>, Vec<[u8; 16]>) { + circuit.set_witness_value(&random_witness, const_skip); + for gate in &mut circuit.1 { + gate.evaluate(); + } + let ciphertexts = circuit.garbled_gates_with_delta(delta); + + // size = gc_output_indices x 2 + let output_labels: Vec<[u8; 16]> = output_indices + .iter() + .flat_map(|idx| { + let l0 = circuit.0[*idx].borrow().label.unwrap(); + [l0.0, (l0 ^ delta).0] + }) + .collect(); + + (ciphertexts, output_labels) +} + #[cfg(test)] mod tests { use super::*; use ark_crypto_primitives::snark::{CircuitSpecificSetupSNARK, SNARK}; - use ark_serialize::CanonicalDeserialize; use rand::SeedableRng; + use garbled_snark_verifier::circuits::bn254::g1::G1Affine; use crate::babe::DummyMulCircuit; + use crate::prover::{BABEProver, GROTH_16_SEED}; #[test] fn enc_setup_prove_dec_roundtrip() { - let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(42); + use crate::babe::{we_known_pi1_dec, WeKnownPi1ProveCt}; + + let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(GROTH_16_SEED); let a = Fr::from(3u64); let b = Fr::from(7u64); @@ -160,35 +461,117 @@ mod tests { &pk, DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, - ) - .expect("groth16 prove"); - let public_inputs = vec![a * b]; - - let mut instance = BABEInstance::new_from_seed(42); - instance.enc_setup(&vk, &public_inputs).unwrap(); - - let ct1 = proof.a.into_group() * instance.secrets.r; - - let ct2 = ark_bn254::G2Affine::deserialize_compressed( - instance.ct_setup.ct2_r_delta_g2.as_slice(), - ) - .expect("deserialize ct2") - .into_group(); - let lhs = ark_bn254::Bn254::pairing(ct1, proof.b); - let rhs = ark_bn254::Bn254::pairing(proof.c, ct2); - let r_y = lhs - rhs; - - let mut ry_bytes = Vec::new(); - r_y.serialize_compressed(&mut ry_bytes).unwrap(); - let mask = h_256(&ry_bytes); - - let decrypted: [u8; 32] = instance.ct_setup.ct3_masked_msg.iter() - .zip(mask.iter()) - .map(|(c, m)| c ^ m) - .collect::>() - .try_into() - .unwrap(); - - assert_eq!(decrypted, instance.secrets.msg); + ).expect("groth16 prove"); + + // |S|=1 (a*b static), |D|=1 (a*a dynamic) + let static_inputs = a * b; + let dynamic_inputs = a * a; + + let instance = CACInstance::new_from_seed(2, &vk, static_inputs) + .expect("new_from_seed"); + println!("generate instance done"); + + let r = instance.secrets.r; + let b_blind = instance.secrets.b; + let pi1 = proof.a; + let constant_labels = instance.get_2_circuit_constant_labels(); + let pi1_labels = instance.compute_pi1_labels_based_on_value(proof.a); + let x_d_labels = instance.compute_x_d_labels_based_on_value(dynamic_inputs); + let (mut fgc, fgc_indices, mut sgc, sgc_indices) = crate::gc::read_fresh_gc(); + + // evaluate the fgc to get the r * pi_1 + set_gc_const_labels(&mut fgc, &constant_labels[0]); + for (i, &lbl) in pi1_labels.iter().enumerate() { + fgc.0[i + 2].borrow_mut().label = Some(lbl); + } + let fgc_witness: Vec = DvFq::to_bits(pi1.x) + .into_iter() + .chain(DvFq::to_bits(pi1.y).into_iter()) + .collect(); + let fgc_output_labels = BABEProver::eval_circuit_with_ciphertext( + &mut fgc, + &fgc_indices, + &fgc_witness, + &instance.ciphertexts_sets[0], + 2 + ); + let ct1_bytes = BABEProver::eval_adaptor_table( + &fgc_output_labels, pi1, &instance.adaptor_tables[0] + ); + let mut expected_ct1_bytes = Vec::new(); + (pi1 * r).into_affine().serialize_compressed(&mut expected_ct1_bytes).expect("serialize r·G1P"); + assert_eq!(ct1_bytes, expected_ct1_bytes, "fgc is wrong"); + println!("fgc test done"); + + // evaluate the sgc part 1 to get the Q + set_gc_const_labels(&mut sgc, &constant_labels[1]); + for (i, &lbl) in x_d_labels.iter().enumerate() { + sgc.0[i + SGC_PART1_CONSTANT_SIZE].borrow_mut().label = Some(lbl); + } + let b_x_bits: Vec = DvFq::to_bits(DvFq::as_montgomery(b_blind.x)); + let b_y_bits: Vec = DvFq::to_bits(DvFq::as_montgomery(b_blind.y)); + for (i, bit) in b_x_bits.iter().enumerate() { + sgc.0[2 + i].borrow_mut().value = Some(*bit); + } + for (i, bit) in b_y_bits.iter().enumerate() { + sgc.0[2 + 254 + i].borrow_mut().value = Some(*bit); + } + let sgc_part1_witness: Vec = DvFr::to_bits(dynamic_inputs); + let sgc_output_labels_1 = BABEProver::eval_circuit_with_ciphertext( + &mut sgc, + &sgc_indices, + &sgc_part1_witness, + &instance.ciphertexts_sets[1], + SGC_PART1_CONSTANT_SIZE + ); + // check that sgc computed correctly + let q_proj = vk.gamma_abc_g1[2].into_group() * dynamic_inputs + b_blind; + let q_affine = q_proj.into_affine(); + let q_value_bits = G1Affine::to_bits(q_affine); + for (k, &idx) in sgc_indices.iter().enumerate() { + let w_val = sgc.0[idx].borrow().get_value(); + let q_val = q_value_bits[k]; + assert_eq!(w_val, q_val, "sgc part 1: mismatch at k={k}: wire={w_val}, q_val={q_val}"); + } + println!("sgc part 1 test done"); + + // evaluate the sgc part2 to get the r * Q + fgc.reset_circuit_except_01_constants(); + // set label of part2 as output of part1 (evaluator has one active label per wire) + for (i, &key) in sgc_output_labels_1.iter().enumerate() { + fgc.0[2 + i].borrow_mut().label = Some(S(key)); + } + set_gc_const_labels(&mut fgc, &constant_labels[1][0..2]); + let sgc_witness: Vec = DvFq::to_bits(q_affine.x) + .into_iter() + .chain(DvFq::to_bits(q_affine.y).into_iter()) + .collect(); + let sgc_output_labels_2 = BABEProver::eval_circuit_with_ciphertext( + &mut fgc, + &fgc_indices, + &sgc_witness, + &instance.ciphertexts_sets[2], + 2 + ); + let ct1_prime = BABEProver::eval_adaptor_table( + &sgc_output_labels_2, q_affine, &instance.adaptor_tables[1] + ); + + let mut expected_ct1_prime_bytes = Vec::new(); + (q_affine * r).into_affine().serialize_compressed(&mut expected_ct1_prime_bytes).expect("serialize r·G1P"); + assert_eq!(ct1_prime, expected_ct1_prime_bytes, "sgc part2 is wrong"); + println!("sgc part 2 test done"); + + let ctprove = WeKnownPi1ProveCt { + ct1_r_pi1: ct1_bytes, + ct1_prime, + }; + let decrypted = we_known_pi1_dec( + &vk, &instance.ct_setup, &ctprove, + proof.b.into_group(), proof.c.into_group(), + ).unwrap(); + + println!("decrypted done"); + assert_eq!(decrypted.as_slice(), &instance.secrets.msg); } -} \ No newline at end of file +} diff --git a/verifiable-circuit-babe/src/instance/secret.rs b/verifiable-circuit-babe/src/instance/secret.rs index 2dd6a78..9356430 100644 --- a/verifiable-circuit-babe/src/instance/secret.rs +++ b/verifiable-circuit-babe/src/instance/secret.rs @@ -3,61 +3,78 @@ use ark_ff::{UniformRand, Zero}; use garbled_snark_verifier::bag::S; use rand::SeedableRng; use rand_chacha::ChaCha20Rng; +use rand_chacha::rand_core::RngCore; use crate::dre::{N, utils::sample_rhos}; pub struct InstanceSecrets { - pub delta: S, + /// 2 deltas for 2 garbled circuits + pub delta: [S; 2], pub r: Fr, pub msg: [u8; 32], - /// label0 per input wire, size = 2 * N - pub encoding_keys: Vec, - /// Two constant-wire 0-labels - pub constant_0labels: [S; 2], - pub rhos: Vec, - pub fq_deltas: Vec, + /// label0 per input wire, for 2 circuits + /// fgc input size = 2 * N // pi_1 + /// sgc input size = N // x_d + pub encoding_keys: [Vec; 2], + /// Constant 0labels. + /// fgc size = 2 (0/1) + /// sgc size = 2 + 2 * N (for B) + pub constant_0labels: [Vec; 2], + pub rhos: [Vec; 2], + pub fq_deltas: [Vec; 2], + pub b: G1Affine, } impl InstanceSecrets { pub fn new_from_seed(seed: u64) -> Self { let mut rng = ChaCha20Rng::seed_from_u64(seed); - let mut delta_bytes = [0u8; 16]; - rand::RngCore::fill_bytes(&mut rng, &mut delta_bytes); - delta_bytes[15] |= 1; - let delta = S(delta_bytes); + let delta = [gen_s(&mut rng, 1)[0], gen_s(&mut rng, 1)[0]]; let r = Fr::rand(&mut rng); let mut msg = [0u8; 32]; - rand::RngCore::fill_bytes(&mut rng, &mut msg); - - let encoding_keys: Vec = (0..2 * N) - .map(|_| { - let mut b = [0u8; 16]; - rand::RngCore::fill_bytes(&mut rng, &mut b); - S(b) - }) - .collect(); - - let constant_0labels = { - let mut b0 = [0u8; 16]; - let mut b1 = [0u8; 16]; - rand::RngCore::fill_bytes(&mut rng, &mut b0); - rand::RngCore::fill_bytes(&mut rng, &mut b1); - [S(b0), S(b1)] - }; - - let rhos = sample_rhos(&mut rng); - - let fq_deltas: Vec = (0..N) - .map(|_| loop { - let v = Fq::rand(&mut rng); - if !v.is_zero() { - break v; - } - }) - .collect(); - - Self { delta, r, msg, encoding_keys, constant_0labels, rhos, fq_deltas } + rng.fill_bytes(&mut msg); + + let encoding_keys = [gen_s(&mut rng, 2 * N), gen_s(&mut rng, N)]; + + let constant_val_labels = [gen_s(&mut rng, 2), gen_s(&mut rng, 2 + 2 * N)]; + + let rhos = [sample_rhos(&mut rng), sample_rhos(&mut rng)]; + + let fq_deltas = [gen_fq_deltas(&mut rng, N), gen_fq_deltas(&mut rng, N)]; + + let b = G1Affine::rand(&mut rng); + + Self { + delta, + r, + msg, + encoding_keys, + constant_0labels: constant_val_labels, + rhos, + fq_deltas, + b, + } } } + +fn gen_s(rng: &mut R, n: usize) -> Vec { + (0..n) + .map(|_| { + let mut b = [0u8; 16]; + rng.fill_bytes(&mut b); + S(b) + }) + .collect() +} + +fn gen_fq_deltas(rng: &mut R, n: usize) -> Vec { + (0..n) + .map(|_| loop { + let v = Fq::rand(rng); + if !v.is_zero() { + break v; + } + }) + .collect() +} diff --git a/verifiable-circuit-babe/src/lamport.rs b/verifiable-circuit-babe/src/lamport.rs deleted file mode 100644 index 3ba4d6c..0000000 --- a/verifiable-circuit-babe/src/lamport.rs +++ /dev/null @@ -1,49 +0,0 @@ -// ─── Lamport Signature Scheme ───────────────────────────────────────────────── - -use ark_bn254::G1Affine; -use rand::RngCore; -use serde::{Deserialize, Serialize}; -use crate::babe::LAMPORT_N; -use crate::utils::{derive_hashlock, pi1_to_bits}; - -/// Lamport signing key: LAMPORT_N pairs of 16-byte secrets. -/// Each secret has the same width as a GC input label. -#[derive(Debug, Clone)] -pub struct LamportSk(pub Vec<[[u8; 16]; 2]>); - -/// Lamport verification key: pk[i][b] = RIPEMD160(SHA256((sk[i][b])). -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct LamportPk(pub Vec<[[u8; 20]; 2]>); - -/// Lamport signature: sig[i] = sk[i][bit_i(π₁)], for i in 0..LAMPORT_N. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct LamportSig(pub Vec<[u8; 16]>); - -pub fn lamport_keygen(rng: &mut impl RngCore) -> (LamportSk, LamportPk) { - let mut sk_entries = Vec::with_capacity(LAMPORT_N); - let mut pk_entries = Vec::with_capacity(LAMPORT_N); - for _ in 0..LAMPORT_N { - let mut s0 = [0u8; 16]; - let mut s1 = [0u8; 16]; - rng.fill_bytes(&mut s0); - rng.fill_bytes(&mut s1); - pk_entries.push([derive_hashlock(&s0), derive_hashlock(&s1)]); - sk_entries.push([s0, s1]); - } - (LamportSk(sk_entries), LamportPk(pk_entries)) -} - -/// Sign π₁: reveal sk[i][bit_i(π₁)] for each bit. -pub fn lamport_sign(sk: &LamportSk, pi1: &G1Affine) -> LamportSig { - let bits = pi1_to_bits(pi1); - LamportSig(bits.iter().enumerate().map(|(i, &b)| sk.0[i][b as usize]).collect()) -} - -/// Verify a Lamport signature against lpk_P and π₁. -pub fn lamport_verify(pk: &LamportPk, pi1: &G1Affine, sig: &LamportSig) -> bool { - if sig.0.len() != LAMPORT_N { - return false; - } - let bits = pi1_to_bits(pi1); - bits.iter().enumerate().all(|(i, &b)| derive_hashlock(&sig.0[i]) == pk.0[i][b as usize]) -} diff --git a/verifiable-circuit-babe/src/lib.rs b/verifiable-circuit-babe/src/lib.rs index 14b446c..56c935f 100644 --- a/verifiable-circuit-babe/src/lib.rs +++ b/verifiable-circuit-babe/src/lib.rs @@ -1,11 +1,19 @@ -pub mod babe; pub mod dre; pub mod gc; -pub mod instance; -pub mod prover; -pub mod verifier; -pub mod cac; pub mod utils; -pub mod transactions; pub mod soldering; -pub mod lamport; + +#[cfg(not(target_os = "zkvm"))] +pub mod babe; +#[cfg(not(target_os = "zkvm"))] +pub mod wots; +#[cfg(not(target_os = "zkvm"))] +pub mod transactions; +#[cfg(not(target_os = "zkvm"))] +pub mod cac; +#[cfg(not(target_os = "zkvm"))] +pub mod prover; +#[cfg(not(target_os = "zkvm"))] +pub mod instance; +#[cfg(not(target_os = "zkvm"))] +pub mod verifier; diff --git a/verifiable-circuit-babe/src/prover.rs b/verifiable-circuit-babe/src/prover.rs index ee332ed..e4ff2de 100644 --- a/verifiable-circuit-babe/src/prover.rs +++ b/verifiable-circuit-babe/src/prover.rs @@ -5,25 +5,39 @@ use ark_ff::{One, Zero}; use ark_groth16::Proof as Groth16Proof; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use garbled_snark_verifier::bag::{Circuit, S}; -use garbled_snark_verifier::dv_bn254::fq::Fq; +use ark_groth16::ProvingKey as Groth16ProvingKey; +use garbled_snark_verifier::dv_bn254::fq::Fq as DvFq; +use garbled_snark_verifier::dv_bn254::fr::Fr as DvFr; use crate::babe::{WeKnownPi1ProveCt, WeKnownPi1SetupCt}; use crate::cac::{CACSetupPackage, FinalizedInstanceData}; use crate::dre::matrices::u_bar_vec; -use crate::gc::SparseAdaptorTable; +use crate::dre::N; +use crate::gc::{SparseAdaptorTable, SGC_PART1_CONSTANT_SIZE}; +use crate::instance::set_gc_const_labels; use crate::soldering::{SolderedLabelsData, SolderingData}; -use crate::utils::{derive_hashlock, h_160, h_256}; +use crate::utils::{derive_hashlock, h_160, h_256, ro_from_pairing_bytes}; + +pub const GROTH_16_SEED: u64 = 42; pub struct BABEProver { groth16_proof: Groth16Proof, + dyn_pubin: ark_bn254::Fr, + pub pk: Groth16ProvingKey, pub valid_msg: Option<[u8; 32]>, pub valid_ct_prove: Option, pub valid_finalized_id: Option, } impl BABEProver { - pub fn new(groth16_proof: Groth16Proof) -> Self { + pub fn new( + pk: Groth16ProvingKey, + groth16_proof: Groth16Proof, + dyn_pubin: ark_bn254::Fr + ) -> Self { Self { groth16_proof, + dyn_pubin, + pk, valid_msg: None, valid_ct_prove: None, valid_finalized_id: None, @@ -78,15 +92,22 @@ impl BABEProver { pub fn check_compute_msg( &mut self, finalized: &[FinalizedInstanceData], - base_input_labels: &[S], + pi1_labels: &[S], + x_d_labels: &[S], soldering: &SolderingData, h_msgs_onchain: &[[u8; 20]], ) -> bool { let sld = &soldering.soldering_proof.soldered_output; + let (mut fgc, fgc_indices, mut sgc, sgc_indices) = crate::gc::read_fresh_gc(); println!("Trying base instance..."); - let base_full = Self::build_full_labels(&finalized[0].constant_labels, base_input_labels); - let base_res = self.try_evaluate_instance(&finalized[0], &base_full, h_msgs_onchain[0]); + let base_res = self.try_evaluate_instance( + &finalized[0], + &pi1_labels, + &x_d_labels, + h_msgs_onchain[0], + &mut fgc, &fgc_indices, &mut sgc, &sgc_indices, + ); match base_res { Ok(true) => return true, Ok(false) => eprintln!("Warning: base instance did not yield valid msg"), @@ -97,21 +118,43 @@ impl BABEProver { println!("Trying non-base instance..."); for i in 1..finalized.len() { let deltas_i = &sld.deltas[i - 1]; - let instance_labels: Vec = base_input_labels + + let instance_pi1_labels: Vec = pi1_labels .iter() .enumerate() - .map(|(j, &base_lbl)| { + .map(|(j, &lbl)| { let (d0, d1) = deltas_i[j]; - if h_256(&base_lbl.0) == sld.base_commitment[j].0 { - base_lbl ^ S(d0) + if h_256(&lbl.0) == sld.base_commitment[j].0 { + lbl ^ S(d0) + } else { + lbl ^ S(d1) + } + }) + .collect(); + + let instance_x_d_labels: Vec = x_d_labels + .iter() + .enumerate() + .map(|(j, &lbl)| { + let idx = 2 * N + j; + let (d0, d1) = deltas_i[idx]; + if h_256(&lbl.0) == sld.base_commitment[idx].0 { + lbl ^ S(d0) } else { - base_lbl ^ S(d1) + lbl ^ S(d1) } }) .collect(); - let full = Self::build_full_labels(&finalized[i].constant_labels, &instance_labels); - let temp = self.try_evaluate_instance(&finalized[i], &full, h_msgs_onchain[i]); + fgc.reset_circuit_except_01_constants(); + sgc.reset_circuit_except_01_constants(); + let temp = self.try_evaluate_instance( + &finalized[i], + &instance_pi1_labels, + &instance_x_d_labels, + h_msgs_onchain[i], + &mut fgc, &fgc_indices, &mut sgc, &sgc_indices, + ); match temp { Ok(true) => return true, Ok(false) => eprintln!("Warning: non-base instance: {i} did not yield valid msg"), @@ -122,33 +165,34 @@ impl BABEProver { false } - fn build_full_labels(constant_labels: &[S; 2], input_labels: &[S]) -> Vec { - let mut v = Vec::with_capacity(2 + input_labels.len()); - v.push(constant_labels[0]); - v.push(constant_labels[1]); - v.extend_from_slice(input_labels); - v - } - /// Evaluate the garbled circuit for one instance, decrypt the message, and check it /// against `h_msg_onchain`. On success, stores the result fields and returns `Ok(true)`. fn try_evaluate_instance( &mut self, data: &FinalizedInstanceData, - full_labels: &[S], + pi1_labels: &[S], + x_d_labels: &[S], h_msg_onchain: [u8; 20], + fgc: &mut Circuit, + fgc_indices: &[usize], + sgc: &mut Circuit, + sgc_indices: &[usize], ) -> Result { - let (mut circuit, gc_output_indices) = crate::gc::read_fresh_circuit(); let ct_prove = self.compute_ct_prove( - &mut circuit, - &gc_output_indices, - full_labels, - &data.gc_ciphertexts, - &data.adaptor_table, + fgc, + fgc_indices, + sgc, + sgc_indices, + &[data.constant_labels_0.to_vec(), data.constant_labels_1.to_vec()], + &pi1_labels, + &x_d_labels, + &data.ciphertext_sets, + &data.adaptor_tables, + &data.b, ); - drop(circuit); + println!("compute ct_prove done"); - let msg = Self::compute_msg(&self.groth16_proof, &ct_prove, &data.ct_setup)?; + let msg = Self::compute_msg(&self.groth16_proof, &ct_prove, &data.ct_setup, &self.pk.vk)?; // found the valid one. if derive_hashlock(&msg) == h_msg_onchain { self.valid_msg = Some(msg); @@ -163,36 +207,119 @@ impl BABEProver { /// Garble-evaluate the circuit and return the resulting `ct1 = r·π₁`. pub fn compute_ct_prove( &self, - garbled_circuit: &mut Circuit, - gc_output_indices: &[usize], - input_labels: &[S], - ciphertext: &[Option], - adaptor_table: &SparseAdaptorTable, + fgc: &mut Circuit, + fgc_indices: &[usize], + sgc: &mut Circuit, + sgc_indices: &[usize], + const_labels: &[Vec; 2], + p1_labels: &[S], + x_d_labels: &[S], + ciphertext_sets: &[Vec>; 3], + adaptor_tables: &[SparseAdaptorTable; 2], + b: &ark_bn254::G1Affine, ) -> WeKnownPi1ProveCt { let pi1 = self.groth16_proof.a; - for (i, &lbl) in input_labels.iter().enumerate() { - garbled_circuit.0[i].borrow_mut().label = Some(lbl); + let x_d = self.dyn_pubin; + + // --------Compute ct1-------------- + set_gc_const_labels(fgc, &const_labels[0]); + for (i, &lbl) in p1_labels.iter().enumerate() { + fgc.0[i + 2].borrow_mut().label = Some(lbl); + } + let fgc_witness: Vec = DvFq::to_bits(pi1.x) + .into_iter() + .chain(DvFq::to_bits(pi1.y).into_iter()) + .collect(); + let fgc_output_labels = BABEProver::eval_circuit_with_ciphertext( + fgc, + fgc_indices, + &fgc_witness, + &ciphertext_sets[0], + 2 + ); + let ct1 = BABEProver::eval_adaptor_table( + &fgc_output_labels, pi1, &adaptor_tables[0] + ); + + // --------Compute ct1'-------------- + // Part1: compute Q = x_d * L2 + B. + set_gc_const_labels(sgc, &const_labels[1]); + for (i, &lbl) in x_d_labels.iter().enumerate() { + sgc.0[i + SGC_PART1_CONSTANT_SIZE].borrow_mut().label = Some(lbl); + } + // Compute B bit representation (Montgomery form) for use as constant wires. + // Todo: move it to a function in instance.mod.rs + let b_x_bits: Vec = DvFq::to_bits(DvFq::as_montgomery(b.x)); + let b_y_bits: Vec = DvFq::to_bits(DvFq::as_montgomery(b.y)); + for (i, bit) in b_x_bits.iter().enumerate() { + sgc.0[2 + i].borrow_mut().value = Some(*bit); + } + for (i, bit) in b_y_bits.iter().enumerate() { + sgc.0[2 + 254 + i].borrow_mut().value = Some(*bit); } + let sgc_part1_witness: Vec = DvFr::to_bits(x_d); + let sgc_output_labels_1 = BABEProver::eval_circuit_with_ciphertext( + sgc, + sgc_indices, + &sgc_part1_witness, + &ciphertext_sets[1], + SGC_PART1_CONSTANT_SIZE + ); - let witness: Vec = Fq::to_bits(pi1.x) + // Part2: compute rQ + let q = self.pk.vk.gamma_abc_g1[2] * self.dyn_pubin + b; + let q_affine = q.into_affine(); + fgc.reset_circuit_except_01_constants(); + // set label of part2 as output of part1 (evaluator has one active label per wire) + for (i, &key) in sgc_output_labels_1.iter().enumerate() { + fgc.0[2 + i].borrow_mut().label = Some(S(key)); + } + set_gc_const_labels(fgc, &const_labels[1][0..2]); + let sgc_witness: Vec = DvFq::to_bits(q_affine.x) .into_iter() - .chain(Fq::to_bits(pi1.y).into_iter()) + .chain(DvFq::to_bits(q_affine.y).into_iter()) .collect(); + let sgc_output_labels_2 = BABEProver::eval_circuit_with_ciphertext( + fgc, + fgc_indices, + &sgc_witness, + &ciphertext_sets[2], + 2 + ); + let ct1_prime = BABEProver::eval_adaptor_table( + &sgc_output_labels_2, q_affine, &adaptor_tables[1] + ); - garbled_circuit.set_witness_value(&witness); - for gate in &mut garbled_circuit.1 { + WeKnownPi1ProveCt { ct1_r_pi1: ct1, ct1_prime } + } + + pub(crate) fn eval_circuit_with_ciphertext( + circuit: &mut Circuit, + output_indices: &[usize], + witness: &[bool], + ciphertext: &[Option], + const_skip: usize, + ) -> Vec<[u8; 16]> { + circuit.set_witness_value(&witness, const_skip); + for gate in &mut circuit.1 { gate.evaluate(); } + circuit.garbled_evaluate_without_delta(&ciphertext); - garbled_circuit.garbled_evaluate_without_delta(ciphertext); - - let output_labels: Vec<[u8; 16]> = gc_output_indices + let output_labels: Vec<[u8; 16]> = output_indices .iter() - .map(|i| garbled_circuit.0[*i].borrow().get_label().0) + .map(|i| circuit.0[*i].borrow().get_label().0) .collect(); + output_labels + } - let u_bar = u_bar_vec(&pi1); - let decrypted_encodings = adaptor_table.eval(&output_labels, &u_bar); + pub(crate) fn eval_adaptor_table( + u_bar_labels: &[[u8; 16]], + u: G1Affine, + adaptor_table: &SparseAdaptorTable, + ) -> Vec { + let u_bar = u_bar_vec(&u); + let decrypted_encodings = adaptor_table.eval(u_bar_labels, &u_bar); let mut sum = G1Projective::zero(); let mut weight = ark_bn254::Fr::one(); @@ -202,9 +329,10 @@ impl BABEProver { weight += weight; } - let mut ct1 = Vec::new(); - sum.into_affine().serialize_compressed(&mut ct1).expect("serialize r·π₁"); - WeKnownPi1ProveCt { ct1_r_pi1: ct1 } + let mut ct = Vec::new(); + sum.into_affine().serialize_compressed(&mut ct).expect("serialize r·G1P"); + ct + } /// Decrypt the message from `ct_prove` and `ct_setup` using the Groth16 proof. @@ -212,19 +340,24 @@ impl BABEProver { proof: &Groth16Proof, ct_prove: &WeKnownPi1ProveCt, ct_setup: &WeKnownPi1SetupCt, + vk: &ark_groth16::VerifyingKey, ) -> Result<[u8; 32], String> { let ct1 = G1Affine::deserialize_compressed(ct_prove.ct1_r_pi1.as_slice()) .map_err(|e| format!("deserialize ct1: {e}"))? .into_group(); + let ct1_prime = G1Affine::deserialize_compressed(ct_prove.ct1_prime.as_slice()) + .map_err(|e| format!("deserialize ct1_prime: {e}"))?; let ct2 = G2Affine::deserialize_compressed(ct_setup.ct2_r_delta_g2.as_slice()) .map_err(|e| format!("deserialize ct2: {e}"))? .into_group(); let r_y = Bn254::pairing(ct1, proof.b) - Bn254::pairing(proof.c, ct2); + let q_blind = Bn254::pairing(ct1_prime, vk.gamma_g2); + let mask_gt = r_y - q_blind; - let mut ry_bytes = Vec::new(); - r_y.serialize_compressed(&mut ry_bytes).map_err(|e| format!("serialize r_y: {e}"))?; - let mask = h_256(&ry_bytes); + let mut mask_bytes = Vec::new(); + mask_gt.serialize_compressed(&mut mask_bytes).map_err(|e| format!("serialize mask_gt: {e}"))?; + let mask = ro_from_pairing_bytes(&mask_bytes, 32); let ct3 = &ct_setup.ct3_masked_msg; if ct3.len() != 32 { @@ -248,7 +381,7 @@ mod tests { use garbled_snark_verifier::core::utils::reset_gid; use crate::babe::DummyMulCircuit; use crate::cac::cac_finalize_indices; - use crate::instance::BABEInstance; + use crate::instance::CACInstance; use crate::soldering::{build_soldered_wires_input, soldering_guest_compute, SolderingProof}; use crate::verifier::BABEVerifier; @@ -258,7 +391,7 @@ mod tests { #[test] fn test_prover_ct_prove_decrypts_message() { reset_gid(); - let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(42); + let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(GROTH_16_SEED); // 1. Groth16 setup and prove: a * b = c. let a = Fr::from(3u64); @@ -272,29 +405,47 @@ mod tests { DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, ).unwrap(); - let public_inputs = vec![a * b]; + let static_public_inputs = a * b; + let dynamic_public_inputs = a * a; // x_d // 2. Verifier enc_setup. - let mut verifier = BABEInstance::new_from_seed(rand::random()); - verifier.enc_setup(&vk, &public_inputs).unwrap(); + let verifier = CACInstance::new_from_seed( + rand::random(), + &vk, + static_public_inputs, + ).unwrap(); - // 3. Full labels: [const_0, const_1, pi1_bits...]. - let full_labels = verifier.compute_pi1_labels_based_on_value(proof.a); + // 3. Input labels: [pi1_xbits, pi1_ybits, x_dbits...]. + let pi1_labels = verifier.compute_pi1_labels_based_on_value(proof.a); + let x_d_labels = verifier.compute_x_d_labels_based_on_value(dynamic_public_inputs); + let constant_labels = verifier.get_2_circuit_constant_labels(); // 4. Prover evaluates the garbled circuit. - let prover = BABEProver::new(proof.clone()); - let (mut circuit, gc_output_indices) = crate::gc::read_fresh_circuit(); + // Prover has: 2 circuits, input labels, constants labels & value + let prover = BABEProver::new( + pk, + proof.clone(), + dynamic_public_inputs + ); + let (mut fgc, fgc_indices, mut sgc, sgc_indices) = crate::gc::read_fresh_gc(); + let ct_prove = prover.compute_ct_prove( - &mut circuit, - &gc_output_indices, - &full_labels, - &verifier.ciphertexts, - &verifier.adaptor_table, + &mut fgc, + &fgc_indices, + &mut sgc, + &sgc_indices, + &constant_labels, + &pi1_labels, + &x_d_labels, + &verifier.ciphertexts_sets, + &verifier.adaptor_tables, + &verifier.secrets.b, ); - drop(circuit); + drop(fgc); + drop(sgc); // 5. Decrypt and verify. - let msg = BABEProver::compute_msg(&proof, &ct_prove, &verifier.ct_setup).unwrap(); + let msg = BABEProver::compute_msg(&proof, &ct_prove, &verifier.ct_setup, &vk).unwrap(); assert_eq!(msg, verifier.secrets.msg); } @@ -307,18 +458,19 @@ mod tests { Vec, SolderingData, ) { - let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(1); + let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(GROTH_16_SEED); let a = Fr::from(3u64); let b = Fr::from(7u64); let (_, vk) = ark_groth16::Groth16::::setup( DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, ).unwrap(); - let public_inputs = vec![a * b]; + let static_public_inputs = a * b; + let _dynamic_public_inputs = a * a; - let verifier = BABEVerifier::new(TEST_N_CC, &vk, &public_inputs).unwrap(); + let verifier = BABEVerifier::new(TEST_N_CC, &vk, static_public_inputs).unwrap(); let package = verifier.commit(); let finalized_indices = cac_finalize_indices(&package, TEST_M_CC); - let (_, finalized) = verifier.open(&finalized_indices); + let (_temp, finalized) = verifier.open(&finalized_indices).expect("verifier open failed"); let soldered_input = build_soldered_wires_input(&verifier, &finalized_indices); let soldered_output = soldering_guest_compute(&soldered_input); @@ -348,7 +500,7 @@ mod tests { setup_cac_soldering(); // Prove with the same dummy circuit. - let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(1); + let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(GROTH_16_SEED); let a = Fr::from(3u64); let b = Fr::from(7u64); let (pk, _) = ark_groth16::Groth16::::setup( @@ -359,19 +511,21 @@ mod tests { DummyMulCircuit:: { a: Some(a), b: Some(b) }, &mut rng, ).unwrap(); + let _static_public_inputs = a * b; + let dynamic_public_inputs = a * a; + // base_input_labels: active labels for the 508 π₁ input wires of the base instance. let base_idx = finalized_indices[0]; - let all_labels = verifier.instances[base_idx].compute_pi1_labels_based_on_value(proof.a); - let base_input_labels = &all_labels[2..]; // strip constant-wire labels - - let mut prover = BABEProver::new(proof.clone()); + let pi1_labels = verifier.compute_pi1_labels(base_idx, proof.a); + let x_d_labels = verifier.compute_x_d_labels(base_idx, dynamic_public_inputs); + let mut prover = BABEProver::new(pk.clone(), proof.clone(), dynamic_public_inputs); // Extract h_msgs from bitcoin script of WronglyChallenged Txn // But in this test, we just get from package let mut h_msgs_onchain: Vec<[u8; 20]> = finalized_indices.iter().map(|&idx| package.commits[idx].h_msg).collect(); let found = prover.check_compute_msg( - &finalized, base_input_labels, &soldering, &h_msgs_onchain, + &finalized, &pi1_labels, &x_d_labels, &soldering, &h_msgs_onchain, ); assert!(found, "expected a valid msg to be found"); @@ -380,10 +534,11 @@ mod tests { assert!(prover.valid_finalized_id.is_some()); // change the base msg to access non-base instance - let mut prover = BABEProver::new(proof); + println!("change the base msg to access non-base instance"); + let mut prover = BABEProver::new(pk, proof, dynamic_public_inputs); h_msgs_onchain[0] = [0u8; 20]; let found = prover.check_compute_msg( - &finalized, base_input_labels, &soldering, &h_msgs_onchain, + &finalized, &pi1_labels, &x_d_labels, &soldering, &h_msgs_onchain, ); assert!(found, "expected a valid msg to be found"); assert!(prover.valid_msg.is_some()); @@ -391,4 +546,4 @@ mod tests { assert!(prover.valid_finalized_id.is_some()); assert_ne!(prover.valid_finalized_id.unwrap(), finalized_indices[0]); } -} \ No newline at end of file +} diff --git a/verifiable-circuit-babe/src/soldering.rs b/verifiable-circuit-babe/src/soldering.rs index 45122aa..8b1ff16 100644 --- a/verifiable-circuit-babe/src/soldering.rs +++ b/verifiable-circuit-babe/src/soldering.rs @@ -74,6 +74,7 @@ pub fn soldering_guest_compute(input: &SolderedWiresInput) -> SolderedLabelsData } +#[cfg(not(target_os = "zkvm"))] /// Build `SolderedWiresInput` from the verifier's finalized instances. /// `instances[0]` = base (finalized_indices[0]), `instances[1..]` = non-base in order. pub fn build_soldered_wires_input( @@ -84,13 +85,17 @@ pub fn build_soldered_wires_input( finalized_indices .iter() .map(|&idx| { - let inst = &verifier.instances[idx]; - let delta = inst.secrets.delta; - inst.secrets - .encoding_keys - .iter() - .map(|&ek| (ek.0, (ek ^ delta).0)) - .collect() + let ls = &verifier.light_secrets[idx]; + let delta = &ls.delta; + let encoding_keys = &ls.encoding_keys; + + (0..2) + .flat_map(|i| { + encoding_keys[i] + .iter() + .map(move |&ek| (ek.0, (ek ^ delta[i]).0)) + }) + .collect::>() }) .collect(), ) diff --git a/verifiable-circuit-babe/src/transactions.rs b/verifiable-circuit-babe/src/transactions.rs index fd161b8..b7fb3ce 100644 --- a/verifiable-circuit-babe/src/transactions.rs +++ b/verifiable-circuit-babe/src/transactions.rs @@ -1,8 +1,11 @@ // ─── Transaction locking script ─────────────────────────────────────────────── +use ark_bn254::{Fq, Fr, G1Affine}; +use ark_serialize::CanonicalDeserialize; use serde::{Deserialize, Serialize}; -use crate::babe::{BabeBtcSig, BtcPk, BTC_SIG_BYTES, LAMPORT_N, LAMPORT_SIG_BYTES, MSG_BYTES, PI1_BYTES}; -use crate::lamport::LamportSig; +use crate::babe::{BabeBtcSig, BtcPk, BTC_SIG_BYTES, MSG_BYTES}; +use crate::wots::{Wots96, Wots96Sig}; +use bitvm::signatures::Wots; /// Constants embedded in the locking script of tx_Deposit output 0. /// Script: CheckSig(pk_P) ∧ CheckSig(pk_V) @@ -23,27 +26,43 @@ pub struct TxChallengeAssertOutputLock { // ─── Transaction witnesses (on-chain data) ──────────────────────────────────── /// tx_Assert — witness for input 0. -/// Input spends a UTXO with: CheckLampSig(lpk_P) -/// Script verifies: SHA256(μ[i]) == lpk_P[i][bit_i(π₁)] for all i. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +/// Input spends a UTXO with: CheckWotsSig(wots_pk_P) +/// Script verifies the Wots96 signature over the 96-byte message (π₁.x ∥ π₁.y ∥ x_d). +#[derive(Debug, Clone, PartialEq, Eq)] pub struct TxAssertWitness { - /// Compressed G1Affine, 33 bytes — the asserted proof element. - pub pi1: Vec, - /// μ₁…μ_ℓ — Lamport signature, LAMPORT_N × 16 bytes. - pub lamport_sig: LamportSig, + /// Wots96 signature over the 96-byte message (π₁.x LE-32 ∥ π₁.y LE-32 ∥ x_d LE-32). + /// π₁ and x_d are recoverable from the digit values embedded in this signature. + pub wots_sig: Wots96Sig, +} + +impl TxAssertWitness { + /// Extract π₁ and x_d from the digit values embedded in the Wots96 signature. + /// The 96-byte message layout is: π₁.x (LE-32) ∥ π₁.y (LE-32) ∥ x_d (LE-32). + /// Does NOT verify the signature — caller must call wots96_verify separately. + pub fn recover_pi1_xd_without_verify(&self) -> Option<(G1Affine, Fr)> { + let msg = Wots96::signature_to_message(&self.wots_sig); + let x = Fq::deserialize_uncompressed(&msg[0..32]).ok()?; + let y = Fq::deserialize_uncompressed(&msg[32..64]).ok()?; + let pi1 = G1Affine::new(x, y); + let x_d = Fr::deserialize_uncompressed(&msg[64..96]).ok()?; + Some((pi1, x_d)) + } } /// tx_ChallengeAssert — witness for input 0. -/// Input spends tx_Assert output 1: CheckLampSigsMatch(lpk_P, lpk_V) ∧ CheckSig(pk_V) ∧ CheckSig(pk_P) +/// Input spends tx_Assert output 1: CheckSigsConsistent(wots_pk_P, epk_V) ∧ CheckSig(pk_V) ∧ CheckSig(pk_P) /// Script verifies: -/// (a) SHA256(μ[i]) == lpk_P[i][bit_i] — μ is a valid Lamport sig for some π₁ -/// (b) blake3(L[i]) == lpk_V[i][bit_i] — L[i] is the correct GC label for bit_i under epk -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +/// (a) Wots96 sig is valid for some 96-byte message m — binds π₁ and x_d to the prover +/// (b) SHA256(L[i]) == epk_V[i][bit_i(m)] — L[i] is the correct GC label for bit_i under epk +/// (c) Wots96 sig and epk labels are consistent over the same message m — both sign/encode the same bits +#[derive(Debug, Clone, PartialEq, Eq)] pub struct TxChallengeAssertWitness { - /// L₁…L_ℓ — one GC input label per π₁ bit, LAMPORT_N × 16 bytes. + /// L₁…L_M — one GC input label per bit of π₁ and x_d, LAMPORT_N × 16 bytes. pub input_labels: Vec<[u8; 16]>, - /// μ₁…μ_ℓ — Lamport sig re-posted to bind L to π₁. - pub lamport_sig: LamportSig, + /// Wots96 sig re-posted from TxAssertWitness to bind the labels to π₁ and x_d. + /// The script checks that the bit sequence recovered from this sig matches the + /// bit indices used to select each label in input_labels. + pub wots_sig: Wots96Sig, /// VerifierLiveSig pub sig_v: BabeBtcSig, /// ProverPresigChallengeAssert @@ -97,19 +116,17 @@ pub trait OnchainSize { impl OnchainSize for TxAssertWitness { fn size_bytes(&self) -> usize { - PI1_BYTES // pi1: 33 bytes - + LAMPORT_SIG_BYTES // lamport_sig: 8,128 bytes - // total: 8,161 bytes + // Signature size + Wots96::TOTAL_DIGIT_LEN as usize * 21 } } impl OnchainSize for TxChallengeAssertWitness { fn size_bytes(&self) -> usize { - LAMPORT_N * 16 // input_labels: 8,128 bytes - + LAMPORT_SIG_BYTES // lamport_sig: 8,128 bytes - + BTC_SIG_BYTES // sig_v: 32 bytes - + BTC_SIG_BYTES // sig_p: 32 bytes - // total: 16,320 bytes + self.input_labels.len() * 16 // LAMPORT_N × 16 bytes + + Wots96::TOTAL_DIGIT_LEN as usize * 20 // wots_sig preimages + + BTC_SIG_BYTES // sig_v: 32 bytes + + BTC_SIG_BYTES // sig_p: 32 bytes } } @@ -117,7 +134,6 @@ impl OnchainSize for TxWronglyChallengedWitness { fn size_bytes(&self) -> usize { BTC_SIG_BYTES // sig_p: 32 bytes + MSG_BYTES // msg: 32 bytes - // total: 64 bytes } } @@ -132,51 +148,3 @@ impl OnchainSize for TxWithdrawWitness { BTC_SIG_BYTES * 4 // 4 sigs × 32 bytes = 128 bytes } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::babe::{BabeBtcSig, LAMPORT_N, LAMPORT_SIG_BYTES, PI1_BYTES}; - use crate::transactions::{TxAssertWitness, TxChallengeAssertWitness, TxWronglyChallengedWitness}; - - #[test] - fn onchain_sizes() { - assert_eq!(LAMPORT_SIG_BYTES, 508 * 16); - - // Construct minimal witnesses to call size_bytes(). - let dummy_sig = BabeBtcSig::ProverLiveSig; - let dummy_lamport = LamportSig(vec![[0u8; 16]; LAMPORT_N]); - - let assert_w = TxAssertWitness { - pi1: vec![0u8; PI1_BYTES], - lamport_sig: dummy_lamport.clone(), - }; - assert_eq!(assert_w.size_bytes(), 8161); - - let challenge_w = TxChallengeAssertWitness { - input_labels: vec![[0u8; 16]; LAMPORT_N], - lamport_sig: dummy_lamport, - sig_v: dummy_sig.clone(), - sig_p: dummy_sig.clone(), - }; - assert_eq!(challenge_w.size_bytes(), 16320); - - let wc_w = TxWronglyChallengedWitness { sig_p: dummy_sig.clone(), msg: [0u8; 32] }; - assert_eq!(wc_w.size_bytes(), 64); - - let nw_w = TxNoWithdrawWitness { - input0_sig_p: dummy_sig.clone(), - input0_sig_v: dummy_sig.clone(), - input1_sig_v: dummy_sig.clone(), - }; - assert_eq!(nw_w.size_bytes(), 96); - - let wd_w = TxWithdrawWitness { - input0_sig_p: dummy_sig.clone(), - input0_sig_v: dummy_sig.clone(), - input1_sig_p: dummy_sig.clone(), - input1_sig_v: dummy_sig, - }; - assert_eq!(wd_w.size_bytes(), 128); - } -} diff --git a/verifiable-circuit-babe/src/utils.rs b/verifiable-circuit-babe/src/utils.rs index 00437a6..79a6bb8 100644 --- a/verifiable-circuit-babe/src/utils.rs +++ b/verifiable-circuit-babe/src/utils.rs @@ -47,6 +47,28 @@ pub fn g2_from_ser_checked(v: &[u8]) -> Option { Some(a.into_group()) } +/// Encode pi1 and x_d into a 96-byte Wots96 message. +/// Layout: pi1.x LE-32 || pi1.y LE-32 || x_d LE-32. +/// Bit packing: bits[i] = (msg[i/8] >> (i%8)) & 1 (LSB-first within each byte). +/// Bits 254-255 of each 32-byte chunk are always 0 (BN254 fields are < 2^254). +pub fn pi1_xd_to_wots96_msg(pi1: &G1Affine, x_d: Fr) -> [u8; 96] { + let mut msg = [0u8; 96]; + let mut tmp = Vec::new(); + + pi1.x.serialize_uncompressed(&mut tmp).expect("serialize pi1.x"); + msg[..32].copy_from_slice(&tmp); + + tmp.clear(); + pi1.y.serialize_uncompressed(&mut tmp).expect("serialize pi1.y"); + msg[32..64].copy_from_slice(&tmp); + + tmp.clear(); + x_d.serialize_uncompressed(&mut tmp).expect("serialize x_d"); + msg[64..96].copy_from_slice(&tmp); + + msg +} + pub fn pi1_to_bits(pi1: &G1Affine) -> Vec { use garbled_snark_verifier::dv_bn254::fq::Fq as GcFq; GcFq::to_bits(pi1.x).into_iter().chain(GcFq::to_bits(pi1.y)).collect() diff --git a/verifiable-circuit-babe/src/verifier.rs b/verifiable-circuit-babe/src/verifier.rs index e117110..fd999e7 100644 --- a/verifiable-circuit-babe/src/verifier.rs +++ b/verifiable-circuit-babe/src/verifier.rs @@ -1,80 +1,186 @@ -use ark_bn254::Fr; +use ark_bn254::{Fr, G1Affine}; use ark_groth16::VerifyingKey as Groth16VerifyingKey; -use rand::Rng; -use crate::instance::BABEInstance; +use garbled_snark_verifier::bag::S; +use garbled_snark_verifier::dv_bn254::fq::Fq as DvFq; +use garbled_snark_verifier::dv_bn254::fr::Fr as DvFr; +use crate::instance::CACInstance; +use crate::instance::commit::CACInstanceCommit; + +/// Number of instances generated in parallel per batch during the commitment phase. +/// Tune to match available RAM: peak ≈ BATCH_SIZE × ~6 GB. +const BATCH_SIZE: usize = 10; + +/// Minimal per-instance secrets retained after commitment phase. +/// Only encoding keys and deltas are kept — all heavy GC data is dropped. +pub struct InstanceLightSecrets { + pub delta: [S; 2], + pub encoding_keys: [Vec; 2], +} /// The C&C Verifier: manages N_CC garbled-circuit instances for Cut-and-Choose. +/// +/// Instances are generated in batches of BATCH_SIZE. After each batch the heavy +/// GC data (ciphertexts, adaptor tables) is dropped immediately; only the +/// commitment hash and minimal secrets are retained. Peak memory is therefore +/// BATCH_SIZE × ~6 GB rather than N_CC × ~6 GB. pub struct BABEVerifier { - /// All N_CC instances, each fully derived from its own seed. - pub instances: Vec, - /// value used for CA_2 Txn - pub temp_val: [u8; 32], + seeds: Vec, + commits: Vec, + /// Encoding keys and deltas for every instance — needed for label computation + /// without re-deriving the full garbled circuit. + pub light_secrets: Vec, + vk: Groth16VerifyingKey, + static_public_inputs: Fr, } impl BABEVerifier { - /// Create `n_cc` fresh instances and run `enc_setup` on each. + /// Create `n_cc` fresh instances processed in batches of BATCH_SIZE. /// - /// Each instance gets a random seed. `enc_setup` binds each instance's - /// secret message to the given Groth16 verifying key and public inputs. + /// For each batch: instances are generated in parallel, their commitments + /// and minimal secrets extracted, then the heavy GC data is dropped. pub fn new( n_cc: usize, vk: &Groth16VerifyingKey, - public_inputs: &[Fr], + static_public_inputs: Fr, ) -> Result { use p3_maybe_rayon::prelude::*; + let seeds: Vec = (0..n_cc).map(|_| rand::random()).collect(); - let instances = seeds - .par_iter() - .map(|&seed| { - let mut inst = BABEInstance::new_from_seed(seed); - inst.enc_setup(vk, public_inputs)?; - Ok::(inst) - }) - .collect::>() - .into_iter() - .collect::, _>>()?; - let rng = &mut rand::thread_rng(); - let mut temp_val = [0u8; 32]; - rng.fill(&mut temp_val); - Ok(Self { instances, temp_val }) + + let pool = rayon::ThreadPoolBuilder::new() + .num_threads(BATCH_SIZE) + .build() + .map_err(|e| e.to_string())?; + + let mut commits = Vec::with_capacity(n_cc); + let mut light_secrets = Vec::with_capacity(n_cc); + + for batch_seeds in seeds.chunks(BATCH_SIZE) { + // Generate up to BATCH_SIZE instances in parallel. For each instance, + // stream-hash the ciphertexts and adaptor table without materializing them. + // Peak memory per batch: BATCH_SIZE × O(circuit_size) instead of + // BATCH_SIZE × O(circuit_size + ciphertexts + adaptor_tables). + let batch_results: Vec> = + pool.install(|| { + batch_seeds + .par_iter() + .map(|&seed| { + let (commit, secrets) = + CACInstance::commit_from_seed(seed, vk, static_public_inputs)?; + let ls = InstanceLightSecrets { + delta: secrets.delta, + encoding_keys: secrets.encoding_keys, + }; + Ok((commit, ls)) + }) + .collect() + }); + + for result in batch_results { + let (commit, ls) = result?; + commits.push(commit); + light_secrets.push(ls); + } + } + + Ok(Self { + seeds, + commits, + light_secrets, + vk: vk.clone(), + static_public_inputs, + }) } - /// Build the C&C commit package: one `CACInstanceCommit` per instance. - /// Sent to the Prover at the start of the C&C protocol. + /// Return the C&C commit package from pre-computed commitments. pub fn commit(&self) -> crate::cac::CACSetupPackage { crate::cac::CACSetupPackage { - commits: self.instances.iter().map(|inst| inst.commit()).collect(), + commits: self.commits.clone(), } } - /// After receiving the Prover's finalized indices, reveal the open round: - /// - seeds for instance not in I - /// - GC data for every instance in I + /// After receiving the finalized indices, reveal: + /// - seeds for the non-finalized instances (N_CC - M_CC) + /// - full GC data for the M_CC finalized instances, regenerated one-by-one + /// + /// Peak memory during this call: 1 × ~6 GB (sequential regeneration). pub fn open( &self, finalized_indices: &[usize], - ) -> (Vec<(usize, u64)>, Vec) { - let mut opened = Vec::new(); - for (i, inst) in self.instances.iter().enumerate() { - if !finalized_indices.contains(&i) { - opened.push((i, inst.seed)); - } - } + ) -> Result<(Vec<(usize, u64)>, Vec), String> { + let finalized_set: std::collections::HashSet = + finalized_indices.iter().copied().collect(); - let mut finalized = Vec::new(); - for &i in finalized_indices { - let inst = &self.instances[i]; - let constant_labels = - [inst.secrets.constant_0labels[0], inst.secrets.constant_0labels[1] ^ inst.secrets.delta]; - finalized.push(crate::cac::FinalizedInstanceData { - index: i, - gc_ciphertexts: inst.ciphertexts.clone(), - adaptor_table: inst.adaptor_table.clone(), - ct_setup: inst.ct_setup.clone(), - constant_labels - }); - } + let opened: Vec<(usize, u64)> = (0..self.seeds.len()) + .filter(|i| !finalized_set.contains(i)) + .map(|i| (i, self.seeds[i])) + .collect(); + + // Regenerate all M_CC finalized instances in parallel (M_CC <= 4, + // so peak memory is at most 4 × ~6 GB). + use p3_maybe_rayon::prelude::*; + let finalized: Vec> = finalized_indices + .par_iter() + .map(|&i| { + let inst = CACInstance::new_from_seed( + self.seeds[i], + &self.vk, + self.static_public_inputs, + )?; + + let constant_labels_0 = [ + inst.secrets.constant_0labels[0][0], + inst.secrets.constant_0labels[0][1] ^ inst.secrets.delta[0], + ]; + let mut constant_labels_1 = vec![ + inst.secrets.constant_0labels[1][0], + inst.secrets.constant_0labels[1][1] ^ inst.secrets.delta[1], + ]; + constant_labels_1.extend(inst.get_b_value_labels()); + + Ok(crate::cac::FinalizedInstanceData { + index: i, + ciphertext_sets: inst.ciphertexts_sets, + adaptor_tables: inst.adaptor_tables, + ct_setup: inst.ct_setup, + constant_labels_0, + constant_labels_1: constant_labels_1.try_into().unwrap(), + b: inst.secrets.b, + }) + // inst is dropped here + }) + .collect(); + let finalized = finalized.into_iter().collect::, _>>()?; + + Ok((opened, finalized)) + } + + /// Compute the active π₁ input labels for instance `idx` given a concrete π₁. + pub fn compute_pi1_labels(&self, idx: usize, pi1: G1Affine) -> Vec { + let ls = &self.light_secrets[idx]; + let delta = ls.delta[0]; + DvFq::to_bits(pi1.x) + .into_iter() + .chain(DvFq::to_bits(pi1.y)) + .enumerate() + .map(|(i, b)| { + let key = ls.encoding_keys[0][i]; + if b { key ^ delta } else { key } + }) + .collect() + } - (opened, finalized) + /// Compute the active x_d input labels for instance `idx` given a concrete x_d. + pub fn compute_x_d_labels(&self, idx: usize, x_d: Fr) -> Vec { + let ls = &self.light_secrets[idx]; + let delta = ls.delta[1]; + DvFr::to_bits(x_d) + .into_iter() + .enumerate() + .map(|(i, b)| { + let key = ls.encoding_keys[1][i]; + if b { key ^ delta } else { key } + }) + .collect() } -} \ No newline at end of file +} diff --git a/verifiable-circuit-babe/src/wots.rs b/verifiable-circuit-babe/src/wots.rs new file mode 100644 index 0000000..cf1ad4e --- /dev/null +++ b/verifiable-circuit-babe/src/wots.rs @@ -0,0 +1,149 @@ +use bitcoin::script::read_scriptint; +use bitcoin::Witness; +use bitvm::signatures::utils::bitcoin_representation; +pub use bitvm::signatures::winternitz::{Parameters, VoidConverter}; +pub use bitvm::signatures::{CompactWots, WinternitzSecret, Wots}; + +pub const WOTS96_LOG2_BASE: u32 = 8; +pub const WOTS96_BASE: u32 = 1 << WOTS96_LOG2_BASE; + +pub struct Wots96; + +impl Wots for Wots96 { + type Converter = VoidConverter; + type PublicKey = [[u8; 20]; Self::TOTAL_DIGIT_LEN as usize]; + type Message = [u8; Self::MSG_BYTE_LEN as usize]; + type Signature = [[u8; 21]; Self::TOTAL_DIGIT_LEN as usize]; + + const MSG_BYTE_LEN: u32 = 96; + const PARAMETERS: Parameters = + Parameters::new_by_bit_length(Self::MSG_BYTE_LEN * 8, WOTS96_LOG2_BASE); + + fn raw_witness_to_signature(witness: &Witness) -> Self::Signature { + assert_eq!(witness.len(), 2 * Self::TOTAL_DIGIT_LEN as usize); + + let mut digit_signatures = Vec::with_capacity(Self::TOTAL_DIGIT_LEN as usize); + for i in (0..witness.len()).step_by(2) { + assert_eq!( + witness[i].len(), + 20, + "the digit signature should be constant 20 bytes" + ); + assert!( + witness[i + 1].len() <= 2, + "the base256 digit should fit in minimally encoded script bytes" + ); + + let digit_value = read_scriptint(&witness[i + 1]).unwrap(); + assert!( + (0..WOTS96_BASE as i64).contains(&digit_value), + "the digit should be in the valid Wots96 base range" + ); + + let mut digit_signature = [0u8; 21]; + digit_signature[..20].copy_from_slice(&witness[i]); + digit_signature[20] = digit_value as u8; + digit_signatures.push(digit_signature); + } + + Self::Signature::try_from(digit_signatures).unwrap() + } + + fn signature_to_raw_witness(signature: &Self::Signature) -> Witness { + let mut witness = Witness::new(); + for digit_signature in signature.as_ref() { + witness.push(&digit_signature[..20]); + witness.push(bitcoin_representation(i32::from(digit_signature[20]))); + } + witness + } + + fn signature_to_message(signature: &Self::Signature) -> Self::Message { + if WOTS96_LOG2_BASE == 8 { + let bytes = signature + .as_ref() + .iter() + .map(|digit_sig| digit_sig[20]) + .take(Self::MSG_BYTE_LEN as usize) + .rev() + .collect::>(); + return Self::Message::try_from(bytes).unwrap(); + } + + let digits = signature + .as_ref() + .iter() + .map(|digit_sig| digit_sig[20]) + .take(((Self::MSG_BYTE_LEN * 8).div_ceil(WOTS96_LOG2_BASE)) as usize) + .rev() + .collect::>(); + + let mut bytes = Vec::with_capacity(Self::MSG_BYTE_LEN as usize); + let mut byte = 0u8; + let mut used_bits = 0u32; + for digit in digits { + byte |= digit << used_bits; + used_bits += WOTS96_LOG2_BASE; + if used_bits >= 8 { + bytes.push(byte); + byte = digit >> (8 - (used_bits - WOTS96_LOG2_BASE)); + used_bits -= 8; + } + } + bytes.truncate(Self::MSG_BYTE_LEN as usize); + + Self::Message::try_from(bytes).unwrap() + } +} + +impl CompactWots for Wots96 { + type CompactSignature = [[u8; 20]; Self::TOTAL_DIGIT_LEN as usize]; +} + +pub type Wots96Secret = WinternitzSecret; +pub type Wots96PublicKey = ::PublicKey; +pub type Wots96Sig = ::Signature; + +/// Verify a Wots96 signature against a public key and message. +/// +/// Mirrors `lamport_verify`: for each digit position i, hash `sig[i][..20]` +/// exactly `(max_digit - d_i)` more times and compare against `pk[i]`. +/// Digits are derived from `msg` directly (not trusted from sig[i][20]). +pub fn wots96_verify(pk: &Wots96PublicKey, msg: &[u8; 96], sig: &Wots96Sig) -> bool { + use bitcoin::hashes::{hash160, Hash}; + + let base = WOTS96_BASE as usize; // 256 + let max_digit = base - 1; // 255 + let msg_digit_len = Wots96::MSG_BYTE_LEN as usize; // 96 (one byte = one digit) + let total_digit_len = Wots96::TOTAL_DIGIT_LEN as usize; // 98 + let chk_digit_len = total_digit_len - msg_digit_len; // 2 + + // Step 1: message digits — each byte is one digit, reversed (matches message_to_digits internals) + let mut digits: Vec = msg.iter().rev().map(|&b| b as usize).collect(); + + // Step 2: checksum = max_digit * msg_digit_len - sum(message_digits) + let checksum: usize = max_digit * msg_digit_len - digits.iter().sum::(); + + // Step 3: encode checksum as big-endian base-256 digits and append + let mut chk = checksum; + let mut chk_digits = vec![0usize; chk_digit_len]; + for d in chk_digits.iter_mut().rev() { + *d = chk % base; + chk /= base; + } + digits.extend(chk_digits); + + // Step 4: for each digit position i, verify the hash chain against pk[i] + for i in 0..total_digit_len { + let d_i = digits[i]; + let mut h = hash160::Hash::from_byte_array(sig[i][..20].try_into().unwrap()); + for _ in 0..(max_digit - d_i) { + h = hash160::Hash::hash(h.as_byte_array()); + } + if h.as_byte_array() != &pk[i] { + return false; + } + } + + true +} \ No newline at end of file diff --git a/verifiable-circuit-babe/tests/babe_e2e.rs b/verifiable-circuit-babe/tests/babe_e2e.rs index aa43087..0510513 100644 --- a/verifiable-circuit-babe/tests/babe_e2e.rs +++ b/verifiable-circuit-babe/tests/babe_e2e.rs @@ -4,7 +4,7 @@ use verifiable_circuit_babe::babe::run_babe_e2e_cac; fn e2e_babe_cac() { use verifiable_circuit_babe::transactions::OnchainSize; let run = run_babe_e2e_cac(); - assert_eq!(run.assert_witness.size_bytes(), 8161); - assert_eq!(run.challenge_assert_witness.size_bytes(), 16320); + assert_eq!(run.assert_witness.size_bytes(), 2058); // 98 * 21 + assert_eq!(run.challenge_assert_witness.size_bytes(), 14312); assert_eq!(run.wrongly_challenged_witness.size_bytes(), 64); }