From aca121621649be8df0df974a7cb00c2efc17b6fc Mon Sep 17 00:00:00 2001 From: stephenh-axiom-xyz Date: Fri, 13 Dec 2024 18:36:25 -0500 Subject: [PATCH 01/69] feat: default paths for CLI keygen, proving, and verification (#1027) * feat: default paths for CLI keygen, proving, and verification * chore: setup checks for solc, EvmVerifier serialization not bson * feat: CLI setup downloads kzg files * fix: cargo lock * fix: cargo fmt * chore: use bitcode instead of bson --- Cargo.lock | 1141 +++++++++++++++-- Cargo.toml | 2 +- crates/cli/Cargo.toml | 8 +- crates/cli/example/Cargo.toml | 7 + crates/cli/example/src/main.rs | 18 + crates/cli/src/bin/cargo-openvm.rs | 5 +- crates/cli/src/commands/build.rs | 17 +- crates/cli/src/commands/keygen.rs | 19 +- crates/cli/src/commands/prove.rs | 20 +- crates/cli/src/commands/setup.rs | 76 +- crates/cli/src/commands/verify.rs | 12 +- crates/cli/src/default.rs | 10 + crates/cli/src/lib.rs | 1 + crates/cli/tests/app_e2e.rs | 102 +- crates/sdk/Cargo.toml | 3 +- crates/sdk/src/fs.rs | 60 +- extensions/native/recursion/Cargo.toml | 2 +- extensions/native/recursion/src/halo2/mod.rs | 86 +- .../native/recursion/src/halo2/tests/mod.rs | 5 +- .../native/recursion/src/halo2/wrapper.rs | 12 + 20 files changed, 1380 insertions(+), 226 deletions(-) create mode 100644 crates/cli/example/Cargo.toml create mode 100644 crates/cli/example/src/main.rs create mode 100644 crates/cli/src/default.rs diff --git a/Cargo.lock b/Cargo.lock index e3ee5b28ec..7dadd1ec53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,6 +50,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "alloy-eip2930" version = "0.1.0" @@ -369,6 +375,389 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "aws-config" +version = "1.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b49afaa341e8dd8577e1a2200468f98956d6eda50bcf4a53246cc00174ba924" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json 0.60.7", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http 0.2.12", + "ring", + "time", + "tokio", + "tracing", + "url", + "zeroize", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60e8f6b615cb5fc60a98132268508ad104310f0cfb25a1c22eee76efdf9154da" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-runtime" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5ac934720fbb46206292d2c75b57e67acfc56fe7dfd34fb9a02334af08409ea" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-s3" +version = "1.65.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3ba2c5c0f2618937ce3d4a5ad574b86775576fa24006bcb3128c6e2cbf3c34e" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-checksums", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-json 0.61.1", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "bytes", + "fastrand", + "hex", + "hmac", + "http 0.2.12", + "http-body 0.4.6", + "lru", + "once_cell", + "percent-encoding", + "regex-lite", + "sha2", + "tracing", + "url", +] + +[[package]] +name = "aws-sdk-sso" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ca43a4ef210894f93096039ef1d6fa4ad3edfabb3be92b80908b9f2e4b4eab" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json 0.61.1", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-ssooidc" +version = "1.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abaf490c2e48eed0bb8e2da2fb08405647bd7f253996e0f93b981958ea0f73b0" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json 0.61.1", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b68fde0d69c8bfdc1060ea7da21df3e39f6014da316783336deff0a9ec28f4bf" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json 0.61.1", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d3820e0c08d0737872ff3c7c1f21ebbb6693d832312d6152bf18ef50a5471c2" +dependencies = [ + "aws-credential-types", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "crypto-bigint 0.5.5", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.12", + "http 1.2.0", + "once_cell", + "p256", + "percent-encoding", + "ring", + "sha2", + "subtle", + "time", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62220bc6e97f946ddd51b5f1361f78996e704677afc518a4ff66b7a72ea1378c" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-checksums" +version = "0.60.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1a71073fca26775c8b5189175ea8863afb1c9ea2cceb02a5de5ad9dfbaa795" +dependencies = [ + "aws-smithy-http", + "aws-smithy-types", + "bytes", + "crc32c", + "crc32fast", + "hex", + "http 0.2.12", + "http-body 0.4.6", + "md-5", + "pin-project-lite", + "sha1", + "sha2", + "tracing", +] + +[[package]] +name = "aws-smithy-eventstream" +version = "0.60.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef7d0a272725f87e51ba2bf89f8c21e4df61b9e49ae1ac367a6d69916ef7c90" +dependencies = [ + "aws-smithy-types", + "bytes", + "crc32fast", +] + +[[package]] +name = "aws-smithy-http" +version = "0.60.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8bc3e8fdc6b8d07d976e301c02fe553f72a39b7a9fea820e023268467d7ab6" +dependencies = [ + "aws-smithy-eventstream", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4683df9469ef09468dad3473d129960119a0d3593617542b7d52086c8486f2d6" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-json" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4e69cc50921eb913c6b662f8d909131bb3e6ad6cb6090d3a39b66fc5c52095" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f20685047ca9d6f17b994a07f629c813f08b5bce65523e47124879e60103d45" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "http-body 1.0.1", + "httparse", + "hyper", + "hyper-rustls", + "once_cell", + "pin-project-lite", + "pin-utils", + "rustls", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92165296a47a812b267b4f41032ff8069ab7ff783696d217f0994a0d7ab585cd" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.2.0", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fbd94a32b3a7d55d3806fe27d98d3ad393050439dd05eb53ece36ec5e3d3510" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.2.0", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5221b91b3e441e6675310829fd8984801b772cb1546ef6c0e54dec9f1ac13fef" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version 0.4.1", + "tracing", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -385,6 +774,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + [[package]] name = "base16ct" version = "0.2.0" @@ -397,6 +792,22 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + [[package]] name = "base64ct" version = "1.6.0" @@ -446,6 +857,16 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bitcode" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee1bce7608560cd4bf0296a4262d0dbf13e6bcec5ff2105724c8ab88cc7fc784" +dependencies = [ + "bytemuck", + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -562,27 +983,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "bson" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068208f2b6fcfa27a7f1ee37488d2bb8ba2640f68f5475d08e1d9130696aba59" -dependencies = [ - "ahash", - "base64", - "bitvec", - "hex", - "indexmap", - "js-sys", - "once_cell", - "rand", - "serde", - "serde_bytes", - "serde_json", - "time", - "uuid", -] - [[package]] name = "bumpalo" version = "3.16.0" @@ -613,6 +1013,16 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + [[package]] name = "c-kzg" version = "1.0.3" @@ -662,7 +1072,8 @@ name = "cargo-openvm" version = "0.1.0" dependencies = [ "anstyle", - "bincode 2.0.0-rc.3", + "aws-config", + "aws-sdk-s3", "capstone", "cargo_metadata", "clap", @@ -673,6 +1084,7 @@ dependencies = [ "hex", "openvm-build", "openvm-circuit", + "openvm-cli-example-test", "openvm-keccak256-circuit", "openvm-keccak256-transpiler", "openvm-native-recursion", @@ -689,6 +1101,7 @@ dependencies = [ "target-lexicon", "tempfile", "textwrap", + "tokio", "toml", "tracing", "vergen", @@ -712,7 +1125,7 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", "cargo-platform", - "semver 1.0.23", + "semver 1.0.24", "serde", "serde_json", "thiserror 1.0.69", @@ -726,9 +1139,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.3" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" +checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" dependencies = [ "jobserver", "libc", @@ -877,6 +1290,22 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpp_demangle" version = "0.4.4" @@ -895,6 +1324,24 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32c" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" +dependencies = [ + "rustc_version 0.4.1", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "criterion" version = "0.5.1" @@ -999,6 +1446,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-bigint" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -1110,6 +1569,16 @@ dependencies = [ "uuid", ] +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "der" version = "0.7.9" @@ -1271,18 +1740,30 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +[[package]] +name = "ecdsa" +version = "0.14.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +dependencies = [ + "der 0.6.1", + "elliptic-curve 0.12.3", + "rfc6979 0.3.1", + "signature 1.6.4", +] + [[package]] name = "ecdsa" version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "der", + "der 0.7.9", "digest 0.10.7", - "elliptic-curve", - "rfc6979", - "signature", - "spki", + "elliptic-curve 0.13.8", + "rfc6979 0.4.0", + "signature 2.2.0", + "spki 0.7.3", ] [[package]] @@ -1297,21 +1778,41 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" +[[package]] +name = "elliptic-curve" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +dependencies = [ + "base16ct 0.1.1", + "crypto-bigint 0.4.9", + "der 0.6.1", + "digest 0.10.7", + "ff 0.12.1", + "generic-array", + "group 0.12.1", + "pkcs8 0.9.0", + "rand_core", + "sec1 0.3.0", + "subtle", + "zeroize", +] + [[package]] name = "elliptic-curve" version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ - "base16ct", - "crypto-bigint", + "base16ct 0.2.0", + "crypto-bigint 0.5.5", "digest 0.10.7", "ff 0.13.0", "generic-array", "group 0.13.0", - "pkcs8", + "pkcs8 0.10.2", "rand_core", - "sec1", + "sec1 0.7.3", "subtle", "zeroize", ] @@ -1541,6 +2042,45 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + [[package]] name = "gcd" version = "2.3.0" @@ -1640,6 +2180,25 @@ dependencies = [ "subtle", ] +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.4.1" @@ -1846,6 +2405,8 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ + "allocator-api2", + "equivalent", "foldhash", ] @@ -1880,15 +2441,123 @@ dependencies = [ name = "hex-literal" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.2.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] [[package]] -name = "hmac" -version = "0.12.1" +name = "hyper-rustls" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ - "digest 0.10.7", + "futures-util", + "http 0.2.12", + "hyper", + "log", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", ] [[package]] @@ -2185,8 +2854,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", - "ecdsa", - "elliptic-curve", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", "once_cell", "sha2", ] @@ -2315,6 +2984,15 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.2", +] + [[package]] name = "matchers" version = "0.1.0" @@ -2334,6 +3012,16 @@ dependencies = [ "rayon", ] +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", +] + [[package]] name = "memchr" version = "2.7.4" @@ -2419,6 +3107,17 @@ dependencies = [ "adler2", ] +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.52.0", +] + [[package]] name = "nibble_vec" version = "0.1.0" @@ -2632,6 +3331,12 @@ version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + [[package]] name = "openvm" version = "0.1.0" @@ -2928,6 +3633,13 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "openvm-cli-example-test" +version = "0.0.0" +dependencies = [ + "openvm", +] + [[package]] name = "openvm-ecc-circuit" version = "0.1.0" @@ -2961,8 +3673,8 @@ dependencies = [ name = "openvm-ecc-guest" version = "0.1.0" dependencies = [ - "ecdsa", - "elliptic-curve", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", "group 0.13.0", "halo2curves-axiom 0.7.0", "hex-literal", @@ -3197,7 +3909,7 @@ dependencies = [ name = "openvm-native-recursion" version = "0.1.0" dependencies = [ - "bson", + "bitcode", "cfg-if", "itertools 0.13.0", "lazy_static", @@ -3399,9 +4111,8 @@ name = "openvm-sdk" version = "0.1.0" dependencies = [ "async-trait", - "bincode 2.0.0-rc.3", + "bitcode", "bon", - "bson", "derivative", "derive_more 1.0.0", "eyre", @@ -3444,7 +4155,7 @@ dependencies = [ [[package]] name = "openvm-stark-backend" version = "0.1.0-alpha" -source = "git+ssh://git@github.com/openvm-org/stark-backend#79fcdbe321c21f2b146e1b4d58a88662c2abd6b4" +source = "git+ssh://git@github.com/openvm-org/stark-backend#75f79da9da485c2091637207201fc40f0985ef14" dependencies = [ "async-trait", "cfg-if", @@ -3470,7 +4181,7 @@ dependencies = [ [[package]] name = "openvm-stark-sdk" version = "0.1.0-alpha" -source = "git+ssh://git@github.com/openvm-org/stark-backend#79fcdbe321c21f2b146e1b4d58a88662c2abd6b4" +source = "git+ssh://git@github.com/openvm-org/stark-backend#75f79da9da485c2091637207201fc40f0985ef14" dependencies = [ "derive_more 0.99.18", "ff 0.13.0", @@ -3577,12 +4288,29 @@ dependencies = [ "num-traits", ] +[[package]] +name = "outref" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" + [[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p256" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" +dependencies = [ + "ecdsa 0.14.8", + "elliptic-curve 0.12.3", + "sha2", +] + [[package]] name = "p3-air" version = "0.1.0" @@ -4017,14 +4745,30 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der 0.6.1", + "spki 0.6.0", +] + [[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der", - "spki", + "der 0.7.9", + "spki 0.7.3", ] [[package]] @@ -4222,9 +4966,9 @@ dependencies = [ [[package]] name = "quanta" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" +checksum = "773ce68d0bb9bc7ef20be3536ffe94e223e1f365bd374108b2659fac0c65cfe6" dependencies = [ "crossbeam-utils", "libc", @@ -4404,6 +5148,12 @@ dependencies = [ "regex-syntax 0.8.5", ] +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + [[package]] name = "regex-syntax" version = "0.6.29" @@ -4480,6 +5230,17 @@ dependencies = [ "serde", ] +[[package]] +name = "rfc6979" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint 0.4.9", + "hmac", + "zeroize", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -4499,6 +5260,21 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "ripemd" version = "0.1.3" @@ -4609,7 +5385,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.23", + "semver 1.0.24", ] [[package]] @@ -4625,6 +5401,49 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.18" @@ -4658,6 +5477,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -4684,16 +5512,40 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct 0.1.1", + "der 0.6.1", + "generic-array", + "pkcs8 0.9.0", + "subtle", + "zeroize", +] + [[package]] name = "sec1" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ - "base16ct", - "der", + "base16ct 0.2.0", + "der 0.7.9", "generic-array", - "pkcs8", + "pkcs8 0.10.2", "subtle", "zeroize", ] @@ -4717,6 +5569,29 @@ dependencies = [ "cc", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "0.11.0" @@ -4728,9 +5603,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" dependencies = [ "serde", ] @@ -4771,15 +5646,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_bytes" -version = "0.11.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" -dependencies = [ - "serde", -] - [[package]] name = "serde_derive" version = "1.0.216" @@ -4813,6 +5679,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + [[package]] name = "sha2" version = "0.10.8" @@ -4859,6 +5736,25 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +dependencies = [ + "digest 0.10.7", + "rand_core", +] + [[package]] name = "signature" version = "2.2.0" @@ -4875,6 +5771,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -4931,12 +5836,32 @@ dependencies = [ "snark-verifier", ] +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der 0.6.1", +] + [[package]] name = "spki" version = "0.7.3" @@ -4944,7 +5869,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der", + "der 0.7.9", ] [[package]] @@ -5034,7 +5959,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2c04b93fc15d79b39c63218f15e3fdffaa4c227830686e3b7c5f41244eb3e50" dependencies = [ - "base64", + "base64 0.13.1", "proc-macro2", "quote", "syn 1.0.109", @@ -5347,8 +6272,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", + "bytes", + "libc", + "mio", "pin-project-lite", + "signal-hook-registry", + "socket2", "tokio-macros", + "windows-sys 0.52.0", ] [[package]] @@ -5362,6 +6293,29 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.8.19" @@ -5396,6 +6350,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.41" @@ -5480,6 +6440,12 @@ dependencies = [ "strength_reduce", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typenum" version = "1.17.0" @@ -5544,6 +6510,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.4" @@ -5555,6 +6527,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf16_iter" version = "1.0.5" @@ -5578,10 +6556,6 @@ name = "uuid" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" -dependencies = [ - "getrandom", - "serde", -] [[package]] name = "valuable" @@ -5620,6 +6594,12 @@ version = "0.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dcc60c0624df774c82a0ef104151231d37da4962957d691c011c852b2473314" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "wait-timeout" version = "0.2.0" @@ -5639,6 +6619,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -5918,6 +6907,12 @@ dependencies = [ "tap", ] +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + [[package]] name = "yansi" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index 109c11cfee..00f57edc13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -200,13 +200,13 @@ static_assertions = "1.1.0" async-trait = "0.1.83" getset = "0.1.3" rrs-lib = "0.1.0" -bson = "2.13.0" rand = { version = "0.8.5", default-features = false } hex = { version = "0.4.3", default-features = false } # default-features = false for no_std for use in guest programs itertools = { version = "0.13.0", default-features = false } bincode = { version = "2.0.0-rc.3", default-features = false } +bitcode = { version = "0.6.3", default-features = false, features = ["serde"] } serde = { version = "1.0.201", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1", default-features = false } bytemuck = { version = "1.20.0", default-features = false } diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 906aff0b37..759bb9624d 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -22,6 +22,10 @@ openvm-rv32im-transpiler = { workspace = true } openvm-sdk = { workspace = true } openvm-keccak256-transpiler = { workspace = true } openvm-stark-sdk.workspace = true + +aws-sdk-s3 = "1.20.0" +aws-config = "1.0.0" +tokio = { version = "1.41.1", features = ["rt", "rt-multi-thread", "macros"] } cargo_metadata = "0.18.1" clap = { version = "4.5.9", features = ["derive", "env"] } eyre.workspace = true @@ -42,9 +46,11 @@ regex = "1.5.4" prettytable-rs = "0.10" textwrap = "0.16.0" ctrlc = "3.4.2" -bincode = { workspace = true, features = ["serde", "std"] } toml = { workspace = true } +[dev-dependencies] +openvm-cli-example-test = { path = "example" } + [features] default = ["parallel", "mimalloc"] parallel = ["openvm-circuit/parallel"] diff --git a/crates/cli/example/Cargo.toml b/crates/cli/example/Cargo.toml new file mode 100644 index 0000000000..94088f4b61 --- /dev/null +++ b/crates/cli/example/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "openvm-cli-example-test" +version = "0.0.0" +edition = "2021" + +[dependencies] +openvm = { path = "../../toolchain/openvm" } diff --git a/crates/cli/example/src/main.rs b/crates/cli/example/src/main.rs new file mode 100644 index 0000000000..b8568d74a5 --- /dev/null +++ b/crates/cli/example/src/main.rs @@ -0,0 +1,18 @@ +#![cfg_attr(target_os = "zkvm", no_main)] +#![cfg_attr(target_os = "zkvm", no_std)] + +openvm::entry!(main); + +pub fn main() { + let n = core::hint::black_box(1 << 10); + let mut a: u32 = 0; + let mut b: u32 = 1; + for _ in 1..n { + let sum = a + b; + a = b; + b = sum; + } + if a == 0 { + panic!(); + } +} diff --git a/crates/cli/src/bin/cargo-openvm.rs b/crates/cli/src/bin/cargo-openvm.rs index 61437b0f4e..1f98e12d21 100644 --- a/crates/cli/src/bin/cargo-openvm.rs +++ b/crates/cli/src/bin/cargo-openvm.rs @@ -30,7 +30,8 @@ pub enum VmCliCommands { Verify(VerifyCmd), } -fn main() -> Result<()> { +#[tokio::main] +async fn main() -> Result<()> { let Cargo::OpenVm(args) = Cargo::parse(); let command = args.command; match command { @@ -39,7 +40,7 @@ fn main() -> Result<()> { VmCliCommands::Run(cmd) => cmd.run(), VmCliCommands::Keygen(cmd) => cmd.run(), VmCliCommands::Prove(cmd) => cmd.run(), - VmCliCommands::Setup(cmd) => cmd.run(), + VmCliCommands::Setup(cmd) => cmd.run().await, VmCliCommands::Verify(cmd) => cmd.run(), } } diff --git a/crates/cli/src/commands/build.rs b/crates/cli/src/commands/build.rs index d5156c4a59..30bce3f11f 100644 --- a/crates/cli/src/commands/build.rs +++ b/crates/cli/src/commands/build.rs @@ -16,7 +16,7 @@ use openvm_sdk::{ }; use openvm_transpiler::{elf::Elf, openvm_platform::memory::MEM_SIZE, transpiler::Transpiler}; -use crate::util::read_to_struct_toml; +use crate::{default::DEFAULT_MANIFEST_DIR, util::read_to_struct_toml}; #[derive(Parser)] #[command(name = "build", about = "Compile an OpenVM program")] @@ -36,9 +36,10 @@ impl BuildCmd { pub struct BuildArgs { #[arg( long, - help = "Path to the directory containing the Cargo.toml file for the guest code (relative to the current directory)" + help = "Path to the directory containing the Cargo.toml file for the guest code (relative to the current directory)", + default_value = DEFAULT_MANIFEST_DIR )] - pub manifest_dir: Option, + pub manifest_dir: PathBuf, #[arg(long, value_delimiter = ',', help = "Feature flags passed to cargo")] pub features: Vec, @@ -109,19 +110,17 @@ pub(crate) fn build(build_args: &BuildArgs) -> Result> { None }, }; - let pkg_dir = build_args - .manifest_dir - .clone() - .unwrap_or_else(|| std::env::current_dir().unwrap()); let guest_options = GuestOptions { features: build_args.features.clone(), ..Default::default() }; - let pkg = get_package(&pkg_dir); + let pkg = get_package(&build_args.manifest_dir); // We support builds of libraries with 0 or >1 executables. let elf_path = match build_guest_package(&pkg, &guest_options, None) { - Ok(target_dir) => find_unique_executable(&pkg_dir, &target_dir, &target_filter), + Ok(target_dir) => { + find_unique_executable(&build_args.manifest_dir, &target_dir, &target_filter) + } Err(None) => { return Err(eyre::eyre!("Failed to build guest")); } diff --git a/crates/cli/src/commands/keygen.rs b/crates/cli/src/commands/keygen.rs index bcb37129f4..2d1b2f67de 100644 --- a/crates/cli/src/commands/keygen.rs +++ b/crates/cli/src/commands/keygen.rs @@ -8,7 +8,10 @@ use openvm_sdk::{ Sdk, }; -use crate::util::read_to_struct_toml; +use crate::{ + default::{DEFAULT_APP_PK_PATH, DEFAULT_APP_VK_PATH}, + util::read_to_struct_toml, +}; #[derive(Parser)] #[command(name = "keygen", about = "Generate an application proving key")] @@ -16,10 +19,20 @@ pub struct KeygenCmd { #[clap(long, action, help = "Path to app config TOML file")] config: PathBuf, - #[clap(long, action, help = "Path to output app proving key file")] + #[clap( + long, + action, + help = "Path to output app proving key file", + default_value = DEFAULT_APP_PK_PATH + )] output: PathBuf, - #[clap(long, action, help = "Path to output app verifying key file")] + #[clap( + long, + action, + help = "Path to output app verifying key file", + default_value = DEFAULT_APP_VK_PATH + )] vk_output: PathBuf, } diff --git a/crates/cli/src/commands/prove.rs b/crates/cli/src/commands/prove.rs index a657afb6ee..4ade86d73e 100644 --- a/crates/cli/src/commands/prove.rs +++ b/crates/cli/src/commands/prove.rs @@ -15,7 +15,10 @@ use openvm_sdk::{ }; use crate::{ - commands::AGG_PK_PATH, + default::{ + DEFAULT_AGG_PK_PATH, DEFAULT_APP_PK_PATH, DEFAULT_APP_PROOF_PATH, DEFAULT_EVM_PROOF_PATH, + DEFAULT_PARAMS_DIR, + }, util::{read_to_stdin, Input}, }; @@ -29,7 +32,7 @@ pub struct ProveCmd { #[derive(Parser)] enum ProveSubCommand { App { - #[clap(long, action, help = "Path to app proving key")] + #[clap(long, action, help = "Path to app proving key", default_value = DEFAULT_APP_PK_PATH)] app_pk: PathBuf, #[clap(long, action, help = "Path to OpenVM executable")] @@ -38,11 +41,11 @@ enum ProveSubCommand { #[clap(long, value_parser, help = "Input to OpenVM program")] input: Option, - #[clap(long, action, help = "Path to output proof")] + #[clap(long, action, help = "Path to output proof", default_value = DEFAULT_APP_PROOF_PATH)] output: PathBuf, }, Evm { - #[clap(long, action, help = "Path to app proving key")] + #[clap(long, action, help = "Path to app proving key", default_value = DEFAULT_APP_PK_PATH)] app_pk: PathBuf, #[clap(long, action, help = "Path to OpenVM executable")] @@ -51,7 +54,7 @@ enum ProveSubCommand { #[clap(long, value_parser, help = "Input to OpenVM program")] input: Option, - #[clap(long, action, help = "Path to output proof")] + #[clap(long, action, help = "Path to output proof", default_value = DEFAULT_EVM_PROOF_PATH)] output: PathBuf, }, } @@ -75,12 +78,11 @@ impl ProveCmd { input, output, } => { - // FIXME: read path from config. - let params_reader = CacheHalo2ParamsReader::new_with_default_params_dir(); + let params_reader = CacheHalo2ParamsReader::new(DEFAULT_PARAMS_DIR); let (app_pk, committed_exe, input) = Self::prepare_execution(app_pk, exe, input)?; println!("Generating EVM proof, this may take a lot of compute and memory..."); - let agg_pk = read_agg_pk_from_file(AGG_PK_PATH).map_err(|e| { - eyre::eyre!("Failed to read aggregation proving key: {}\nPlease run 'cargo openvm evm-proving-setup' first", e) + let agg_pk = read_agg_pk_from_file(DEFAULT_AGG_PK_PATH).map_err(|e| { + eyre::eyre!("Failed to read aggregation proving key: {}\nPlease run 'cargo openvm setup' first", e) })?; let evm_proof = Sdk.generate_evm_proof(¶ms_reader, app_pk, committed_exe, agg_pk, input)?; diff --git a/crates/cli/src/commands/setup.rs b/crates/cli/src/commands/setup.rs index 5d73f2f211..d5fd7fd2b2 100644 --- a/crates/cli/src/commands/setup.rs +++ b/crates/cli/src/commands/setup.rs @@ -1,7 +1,12 @@ -use std::path::PathBuf; +use std::{ + fs::{create_dir_all, write}, + path::PathBuf, +}; +use aws_config::{defaults, BehaviorVersion, Region}; +use aws_sdk_s3::Client; use clap::Parser; -use eyre::Result; +use eyre::{eyre, Result}; use openvm_native_recursion::halo2::utils::CacheHalo2ParamsReader; use openvm_sdk::{ config::AggConfig, @@ -9,8 +14,7 @@ use openvm_sdk::{ Sdk, }; -pub const AGG_PK_PATH: &str = "~/.openvm/agg.pk"; -pub const VERIFIER_PATH: &str = "~/.openvm/verifier.sol"; +use crate::default::{DEFAULT_AGG_PK_PATH, DEFAULT_PARAMS_DIR, DEFAULT_VERIFIER_PATH}; #[derive(Parser)] #[command( @@ -20,18 +24,70 @@ pub const VERIFIER_PATH: &str = "~/.openvm/verifier.sol"; pub struct EvmProvingSetupCmd {} impl EvmProvingSetupCmd { - pub fn run(&self) -> Result<()> { - if PathBuf::from(AGG_PK_PATH).exists() && PathBuf::from(VERIFIER_PATH).exists() { + pub async fn run(&self) -> Result<()> { + if PathBuf::from(DEFAULT_AGG_PK_PATH).exists() + && PathBuf::from(DEFAULT_VERIFIER_PATH).exists() + { println!("Aggregation proving key and verifier contract already exist"); return Ok(()); + } else if !Self::check_solc_installed() { + return Err(eyre!( + "solc is not installed, please install solc to continue" + )); } - // FIXME: read path from config. - let params_reader = CacheHalo2ParamsReader::new_with_default_params_dir(); + + Self::download_params(10, 24).await?; + let params_reader = CacheHalo2ParamsReader::new(DEFAULT_PARAMS_DIR); let agg_config = AggConfig::default(); + + println!("Generating proving key..."); let agg_pk = Sdk.agg_keygen(agg_config, ¶ms_reader)?; + + println!("Generating verifier contract..."); let verifier = Sdk.generate_snark_verifier_contract(¶ms_reader, &agg_pk)?; - write_agg_pk_to_file(agg_pk, AGG_PK_PATH)?; - write_evm_verifier_to_file(verifier, VERIFIER_PATH)?; + + println!("Writing proving key to file..."); + write_agg_pk_to_file(agg_pk, DEFAULT_AGG_PK_PATH)?; + + println!("Writing verifier contract to file..."); + write_evm_verifier_to_file(verifier, DEFAULT_VERIFIER_PATH)?; + + Ok(()) + } + + fn check_solc_installed() -> bool { + std::process::Command::new("solc") + .arg("--version") + .output() + .is_ok() + } + + async fn download_params(min_k: u32, max_k: u32) -> Result<()> { + create_dir_all(DEFAULT_PARAMS_DIR)?; + let config = defaults(BehaviorVersion::latest()) + .region(Region::new("us-east-1")) + .no_credentials() + .load() + .await; + let client = Client::new(&config); + + for k in min_k..=max_k { + let file_name = format!("kzg_bn254_{}.srs", k); + let local_file_path = PathBuf::from(DEFAULT_PARAMS_DIR).join(&file_name); + if !local_file_path.exists() { + println!("Downloading {}", file_name); + let key = format!("challenge_0085/{}", file_name); + let resp = client + .get_object() + .bucket("axiom-crypto") + .key(&key) + .send() + .await?; + let data = resp.body.collect().await?; + write(local_file_path, data.into_bytes())?; + } + } + Ok(()) } } diff --git a/crates/cli/src/commands/verify.rs b/crates/cli/src/commands/verify.rs index c56092f10f..ef02f34510 100644 --- a/crates/cli/src/commands/verify.rs +++ b/crates/cli/src/commands/verify.rs @@ -10,7 +10,9 @@ use openvm_sdk::{ Sdk, }; -use crate::commands::VERIFIER_PATH; +use crate::default::{ + DEFAULT_APP_PROOF_PATH, DEFAULT_APP_VK_PATH, DEFAULT_EVM_PROOF_PATH, DEFAULT_VERIFIER_PATH, +}; #[derive(Parser)] #[command(name = "verify", about = "Verify a proof")] @@ -22,14 +24,14 @@ pub struct VerifyCmd { #[derive(Parser)] enum VerifySubCommand { App { - #[clap(long, action, help = "Path to app verifying key")] + #[clap(long, action, help = "Path to app verifying key", default_value = DEFAULT_APP_VK_PATH)] app_vk: PathBuf, - #[clap(long, action, help = "Path to app proof")] + #[clap(long, action, help = "Path to app proof", default_value = DEFAULT_APP_PROOF_PATH)] proof: PathBuf, }, Evm { - #[clap(long, action, help = "Path to EVM proof")] + #[clap(long, action, help = "Path to EVM proof", default_value = DEFAULT_EVM_PROOF_PATH)] proof: PathBuf, }, } @@ -43,7 +45,7 @@ impl VerifyCmd { Sdk.verify_app_proof(&app_vk, &app_proof)?; } VerifySubCommand::Evm { proof } => { - let evm_verifier = read_evm_verifier_from_file(VERIFIER_PATH).map_err(|e| { + let evm_verifier = read_evm_verifier_from_file(DEFAULT_VERIFIER_PATH).map_err(|e| { eyre::eyre!("Failed to read EVM verifier: {}\nPlease run 'cargo openvm evm-proving-setup' first", e) })?; let evm_proof = read_evm_proof_from_file(proof)?; diff --git a/crates/cli/src/default.rs b/crates/cli/src/default.rs new file mode 100644 index 0000000000..539152e4b8 --- /dev/null +++ b/crates/cli/src/default.rs @@ -0,0 +1,10 @@ +pub const DEFAULT_MANIFEST_DIR: &str = "."; + +pub const DEFAULT_AGG_PK_PATH: &str = concat!(env!("HOME"), "/.openvm/agg.pk"); +pub const DEFAULT_VERIFIER_PATH: &str = concat!(env!("HOME"), "/.openvm/verifier.sol"); +pub const DEFAULT_PARAMS_DIR: &str = concat!(env!("HOME"), "/.openvm/params/"); + +pub const DEFAULT_APP_PK_PATH: &str = "./openvm/app.pk"; +pub const DEFAULT_APP_VK_PATH: &str = "./openvm/app.vk"; +pub const DEFAULT_APP_PROOF_PATH: &str = "./openvm/app.proof"; +pub const DEFAULT_EVM_PROOF_PATH: &str = "./openvm/evm.proof"; diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index b332aecc71..5c95cafb7e 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -1,4 +1,5 @@ pub mod commands; +pub mod default; mod util; use std::process::{Command, Stdio}; diff --git a/crates/cli/tests/app_e2e.rs b/crates/cli/tests/app_e2e.rs index 22f9a1e4ce..c68a1b661b 100644 --- a/crates/cli/tests/app_e2e.rs +++ b/crates/cli/tests/app_e2e.rs @@ -6,21 +6,7 @@ use tempfile::tempdir; #[test] fn test_cli_app_e2e() -> Result<()> { let temp_dir = tempdir()?; - let package_dir = env::current_dir()?; - let prefix = "[test cli e2e]"; - let run_cmd = |program: &str, args: &[&str]| { - println!("{prefix} Running command: {} {} ...", program, args[0]); - let mut cmd = Command::new(program); - cmd.args(args); - cmd.current_dir(&package_dir); - let output = cmd.output().unwrap(); - println!("{prefix} Finished!"); - println!("{prefix} stdout:"); - println!("{}", std::str::from_utf8(&output.stdout).unwrap()); - println!("{prefix} stderr:"); - println!("{}", std::str::from_utf8(&output.stderr).unwrap()); - }; - run_cmd("cargo", &["install", "--path", ".", "--force"]); + run_cmd("cargo", &["install", "--path", ".", "--force"])?; let temp_exe = temp_dir.path().join("example.vmexe"); let temp_pk = temp_dir.path().join("example.pk"); let temp_vk = temp_dir.path().join("example.vk"); @@ -39,7 +25,7 @@ fn test_cli_app_e2e() -> Result<()> { "--transpile-to", temp_exe.to_str().unwrap(), ], - ); + )?; run_cmd( "cargo", @@ -53,7 +39,7 @@ fn test_cli_app_e2e() -> Result<()> { "--vk-output", temp_vk.to_str().unwrap(), ], - ); + )?; run_cmd( "cargo", @@ -65,7 +51,7 @@ fn test_cli_app_e2e() -> Result<()> { "--config", "example/app_config.toml", ], - ); + )?; run_cmd( "cargo", @@ -80,7 +66,7 @@ fn test_cli_app_e2e() -> Result<()> { "--output", temp_proof.to_str().unwrap(), ], - ); + )?; run_cmd( "cargo", @@ -93,7 +79,83 @@ fn test_cli_app_e2e() -> Result<()> { "--proof", temp_proof.to_str().unwrap(), ], - ); + )?; + + Ok(()) +} + +#[test] +fn test_cli_app_e2e_default_paths() -> Result<()> { + let temp_dir = tempdir()?; + run_cmd("cargo", &["install", "--path", ".", "--force"])?; + let temp_exe = temp_dir.path().join("example.vmexe"); + + run_cmd( + "cargo", + &[ + "openvm", + "build", + "--manifest-dir", + "../sdk/example", + "--transpile", + "--transpiler-config", + "example/app_config.toml", + "--transpile-to", + temp_exe.to_str().unwrap(), + ], + )?; + run_cmd( + "cargo", + &["openvm", "keygen", "--config", "example/app_config.toml"], + )?; + + run_cmd( + "cargo", + &[ + "openvm", + "run", + "--exe", + temp_exe.to_str().unwrap(), + "--config", + "example/app_config.toml", + ], + )?; + + run_cmd( + "cargo", + &[ + "openvm", + "prove", + "app", + "--exe", + temp_exe.to_str().unwrap(), + ], + )?; + + run_cmd("cargo", &["openvm", "verify", "app"])?; + + Ok(()) +} + +fn run_cmd(program: &str, args: &[&str]) -> Result<()> { + let package_dir = env::current_dir()?; + let prefix = "[test cli e2e]"; + println!( + "{prefix} Running command: {} {} {} ...", + program, args[0], args[1] + ); + let mut cmd = Command::new(program); + cmd.args(args); + cmd.current_dir(package_dir); + let output = cmd.output()?; + println!("{prefix} Finished!"); + println!("{prefix} stdout:"); + println!("{}", std::str::from_utf8(&output.stdout).unwrap()); + println!("{prefix} stderr:"); + println!("{}", std::str::from_utf8(&output.stderr).unwrap()); + if !output.status.success() { + return Err(eyre::eyre!("Command failed with status: {}", output.status)); + } Ok(()) } diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml index aa42495e34..e3c903e959 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -29,9 +29,8 @@ openvm-stark-sdk = { workspace = true } openvm-circuit = { workspace = true } openvm = { workspace = true } -bincode = { workspace = true, features = ["serde", "std"] } +bitcode = { workspace = true } bon = { workspace = true } -bson = { workspace = true } derivative = { workspace = true } derive_more = { workspace = true } serde = { workspace = true } diff --git a/crates/sdk/src/fs.rs b/crates/sdk/src/fs.rs index 8b21ae8edb..1359adb13c 100644 --- a/crates/sdk/src/fs.rs +++ b/crates/sdk/src/fs.rs @@ -1,6 +1,5 @@ use std::{ - fs::{create_dir_all, write, File}, - io::Write, + fs::{create_dir_all, read, write}, path::Path, }; @@ -16,83 +15,96 @@ use crate::{ }; pub fn read_exe_from_file>(path: P) -> Result> { - let data = std::fs::read(path)?; - let exe = bincode::serde::decode_from_slice(&data, bincode::config::standard())?.0; - Ok(exe) + read_from_file_bitcode(path) } pub fn write_exe_to_file>(exe: VmExe, path: P) -> Result<()> { - let data = bincode::serde::encode_to_vec(&exe, bincode::config::standard())?; - File::create(path)?.write_all(&data)?; - Ok(()) + write_to_file_bitcode(path, exe) } pub fn read_app_pk_from_file, P: AsRef>( path: P, ) -> Result> { - read_from_file_bson(path) + read_from_file_bitcode(path) } pub fn write_app_pk_to_file, P: AsRef>( app_pk: AppProvingKey, path: P, ) -> Result<()> { - write_to_file_bson(path, app_pk) + write_to_file_bitcode(path, app_pk) } pub fn read_app_vk_from_file>(path: P) -> Result { - read_from_file_bson(path) + read_from_file_bitcode(path) } pub fn write_app_vk_to_file>(app_vk: AppVerifyingKey, path: P) -> Result<()> { - write_to_file_bson(path, app_vk) + write_to_file_bitcode(path, app_vk) } pub fn read_app_proof_from_file>(path: P) -> Result> { - read_from_file_bson(path) + read_from_file_bitcode(path) } pub fn write_app_proof_to_file>( proof: ContinuationVmProof, path: P, ) -> Result<()> { - write_to_file_bson(path, proof) + write_to_file_bitcode(path, proof) } pub fn read_agg_pk_from_file>(path: P) -> Result { - read_from_file_bson(path) + read_from_file_bitcode(path) } pub fn write_agg_pk_to_file>(agg_pk: AggProvingKey, path: P) -> Result<()> { - write_to_file_bson(path, agg_pk) + write_to_file_bitcode(path, agg_pk) } pub fn read_evm_proof_from_file>(path: P) -> Result { - read_from_file_bson(path) + read_from_file_bitcode(path) } pub fn write_evm_proof_to_file>(proof: EvmProof, path: P) -> Result<()> { - write_to_file_bson(path, proof) + write_to_file_bitcode(path, proof) } pub fn read_evm_verifier_from_file>(path: P) -> Result { - read_from_file_bson(path) + read_from_file_bytes(path) } pub fn write_evm_verifier_to_file>(verifier: EvmVerifier, path: P) -> Result<()> { - write_to_file_bson(path, verifier) + write_to_file_bytes(path, verifier) } -pub(crate) fn read_from_file_bson>(path: P) -> Result { - let ret = bson::from_reader(File::open(path)?)?; +pub(crate) fn read_from_file_bitcode>(path: P) -> Result { + let data = std::fs::read(path)?; + let ret = bitcode::deserialize(&data)?; Ok(ret) } -pub(crate) fn write_to_file_bson>(path: P, data: T) -> Result<()> { - let bytes = bson::to_vec(&data)?; +pub(crate) fn write_to_file_bitcode>(path: P, data: T) -> Result<()> { + let bytes = bitcode::serialize(&data)?; if let Some(parent) = path.as_ref().parent() { create_dir_all(parent)?; } write(path, bytes)?; Ok(()) } + +pub(crate) fn read_from_file_bytes>, P: AsRef>(path: P) -> Result { + let bytes = read(path)?; + Ok(T::from(bytes)) +} + +pub(crate) fn write_to_file_bytes>, P: AsRef>( + path: P, + data: T, +) -> Result<()> { + if let Some(parent) = path.as_ref().parent() { + create_dir_all(parent)?; + } + write(path, data.into())?; + Ok(()) +} diff --git a/extensions/native/recursion/Cargo.toml b/extensions/native/recursion/Cargo.toml index a497522fd6..c834d647a0 100644 --- a/extensions/native/recursion/Cargo.toml +++ b/extensions/native/recursion/Cargo.toml @@ -32,7 +32,7 @@ cfg-if = { workspace = true } [dev-dependencies] openvm-native-recursion = { workspace = true, features = ["test-utils"] } tempfile = "3.14.0" -bson.workspace = true +bitcode = { workspace = true } [features] default = ["parallel", "mimalloc"] diff --git a/extensions/native/recursion/src/halo2/mod.rs b/extensions/native/recursion/src/halo2/mod.rs index b1d7740fb6..9fb9fce0e2 100644 --- a/extensions/native/recursion/src/halo2/mod.rs +++ b/extensions/native/recursion/src/halo2/mod.rs @@ -6,7 +6,7 @@ pub mod testing_utils; mod tests; pub mod wrapper; -use std::{fmt, fmt::Debug}; +use std::fmt::Debug; use itertools::Itertools; use openvm_native_compiler::{ @@ -15,12 +15,7 @@ use openvm_native_compiler::{ }; use openvm_stark_backend::p3_field::extension::BinomialExtensionField; use openvm_stark_sdk::{p3_baby_bear::BabyBear, p3_bn254_fr::Bn254Fr}; -use serde::{ - de, - de::{MapAccess, Visitor}, - ser::SerializeStruct, - Deserialize, Deserializer, Serialize, Serializer, -}; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use snark_verifier_sdk::{ halo2::{gen_dummy_snark_from_vk, gen_snark_shplonk}, snark_verifier::halo2_base::{ @@ -229,18 +224,22 @@ impl Halo2Prover { } } +#[derive(Serialize, Deserialize)] +struct SerializedHalo2ProvingPinning { + pk_bytes: Vec, + metadata: Halo2ProvingMetadata, +} + impl Serialize for Halo2ProvingPinning { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let pk_bytes = self.pk.to_bytes(SerdeFormat::RawBytes); - - // Start serializing as a struct with 2 fields: "pk" and "metadata" - let mut state = serializer.serialize_struct("Halo2ProvingPinning", 2)?; - state.serialize_field("pk", &pk_bytes)?; - state.serialize_field("metadata", &self.metadata)?; - state.end() + let serialized = SerializedHalo2ProvingPinning { + pk_bytes: self.pk.to_bytes(SerdeFormat::RawBytes), + metadata: self.metadata.clone(), + }; + serialized.serialize(serializer) } } @@ -249,57 +248,16 @@ impl<'de> Deserialize<'de> for Halo2ProvingPinning { where D: Deserializer<'de>, { - #[derive(Deserialize)] - #[serde(field_identifier, rename_all = "lowercase")] - enum Field { - Pk, - Metadata, - } - - struct Halo2ProvingPinningVisitor; - - impl<'de> Visitor<'de> for Halo2ProvingPinningVisitor { - type Value = Halo2ProvingPinning; + let SerializedHalo2ProvingPinning { pk_bytes, metadata } = + SerializedHalo2ProvingPinning::deserialize(deserializer)?; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a struct named Halo2ProvingPinning") - } - - fn visit_map(self, mut map: V) -> Result - where - V: MapAccess<'de>, - { - let mut pk_bytes: Option> = None; - let mut metadata: Option = None; - - while let Some(key) = map.next_key()? { - match key { - Field::Pk => { - pk_bytes = Some(map.next_value()?); - } - Field::Metadata => { - metadata = Some(map.next_value()?); - } - } - } - - let pk_bytes = pk_bytes.ok_or_else(|| de::Error::missing_field("pk"))?; - let metadata = metadata.ok_or_else(|| de::Error::missing_field("metadata"))?; - let pk = ProvingKey::::from_bytes::>( - &pk_bytes, - SerdeFormat::RawBytes, - metadata.config_params.clone(), - ) - .map_err(|e| de::Error::custom(format!("invalid bytes for proving key: {}", e)))?; - - Ok(Halo2ProvingPinning { pk, metadata }) - } - } - - deserializer.deserialize_struct( - "Halo2ProvingPinning", - &["pk", "metadata"], - Halo2ProvingPinningVisitor, + let pk = ProvingKey::::from_bytes::>( + &pk_bytes, + SerdeFormat::RawBytes, + metadata.config_params.clone(), ) + .map_err(|e| de::Error::custom(format!("invalid bytes for proving key: {}", e)))?; + + Ok(Halo2ProvingPinning { pk, metadata }) } } diff --git a/extensions/native/recursion/src/halo2/tests/mod.rs b/extensions/native/recursion/src/halo2/tests/mod.rs index 87072b16a9..92ec273a15 100644 --- a/extensions/native/recursion/src/halo2/tests/mod.rs +++ b/extensions/native/recursion/src/halo2/tests/mod.rs @@ -180,8 +180,9 @@ fn test_pinning_serde() { }; let mut f = tempfile::NamedTempFile::new().unwrap(); - f.write_all(&bson::to_vec(&wrapper).unwrap()).unwrap(); - let new_wrapper: Halo2WrapperProvingKey = bson::from_reader(f.reopen().unwrap()).unwrap(); + f.write_all(&bitcode::serialize(&wrapper).unwrap()).unwrap(); + let new_wrapper: Halo2WrapperProvingKey = + bitcode::deserialize(&std::fs::read(f.path()).unwrap()).unwrap(); let new_pinning = new_wrapper.pinning; let params = gen_kzg_params(DUMMY_K as u32); let mut builder = BaseCircuitBuilder::from_stage(Prover) diff --git a/extensions/native/recursion/src/halo2/wrapper.rs b/extensions/native/recursion/src/halo2/wrapper.rs index 4b606f2a3f..25138b97cd 100644 --- a/extensions/native/recursion/src/halo2/wrapper.rs +++ b/extensions/native/recursion/src/halo2/wrapper.rs @@ -22,6 +22,18 @@ use crate::halo2::{ #[derive(Debug, Clone, Serialize, Deserialize)] pub struct EvmVerifier(pub Vec); +impl From> for EvmVerifier { + fn from(bytes: Vec) -> Self { + Self(bytes) + } +} + +impl From for Vec { + fn from(verifier: EvmVerifier) -> Self { + verifier.0 + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Halo2WrapperProvingKey { pub pinning: Halo2ProvingPinning, From 75674cddbb20e68afe32908211c682c876a3b15a Mon Sep 17 00:00:00 2001 From: Xinding Wei Date: Fri, 13 Dec 2024 22:22:17 -0800 Subject: [PATCH 02/69] [chore] Clean up legacy access adapter code (#1032) * Clean up legacy access adapter codes * fix: trusted setup dl * fix: e2e prover with profiling * chore: fix summarize.py labels --------- Co-authored-by: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> --- .github/workflows/benchmark-call.yml | 6 ++-- benchmarks/src/bin/fib_e2e.rs | 14 ++++----- benchmarks/src/utils.rs | 7 ++--- ci/scripts/metric_unify/summarize.py | 8 ++--- crates/sdk/src/prover/mod.rs | 5 ++++ crates/vm/src/system/memory/adapter/mod.rs | 3 ++ crates/vm/src/system/memory/manager/mod.rs | 30 +++---------------- .../native/recursion/trusted_setup_s3.sh | 3 +- 8 files changed, 28 insertions(+), 48 deletions(-) diff --git a/.github/workflows/benchmark-call.yml b/.github/workflows/benchmark-call.yml index f62efa7373..436b00bdec 100644 --- a/.github/workflows/benchmark-call.yml +++ b/.github/workflows/benchmark-call.yml @@ -159,7 +159,6 @@ jobs: echo "FEATURE_FLAGS=${FEATURE_FLAGS},aggregation" >> $GITHUB_ENV - name: Setup e2e (halo2 and arguments) - working-directory: extensions/native/recursion # We only ever run halo2 for recursion run: | E2E_BENCH=${{ inputs.e2e_bench }} echo "E2E_BENCH=${E2E_BENCH}" >> $GITHUB_ENV @@ -169,6 +168,7 @@ jobs: INTERNAL_ARG="--internal_log_blowup ${{ inputs.internal_log_blowup }}" echo "INPUT_ARGS=${ROOT_ARG} ${INTERNAL_ARG} ${INPUT_ARGS}" >> $GITHUB_ENV bash ./extensions/native/recursion/trusted_setup_s3.sh + export PARAMS_DIR=$(pwd)/params fi - name: Set BIN_NAME and CMD_ARGS @@ -185,8 +185,8 @@ jobs: select(.name == $name) | .working_directory ' ./ci/benchmark-config.json) - RELATIVE_PATH=$(python3 -c "import os.path; print(os.path.relpath('.', '$WORKSPACE_DIR'))") - echo "working_dir=$WORKSPACE_DIR" >> $GITHUB_OUTPUT + RELATIVE_PATH=$(python3 -c "import os.path; print(os.path.relpath('.', '$WORKING_DIR'))") + echo "working_dir=$WORKING_DIR" >> $GITHUB_OUTPUT echo "relative_path=$RELATIVE_PATH" >> $GITHUB_OUTPUT # Metric name is unique within a run (matrix) diff --git a/benchmarks/src/bin/fib_e2e.rs b/benchmarks/src/bin/fib_e2e.rs index 463ad4bd69..188119eae1 100644 --- a/benchmarks/src/bin/fib_e2e.rs +++ b/benchmarks/src/bin/fib_e2e.rs @@ -13,6 +13,7 @@ use openvm_rv32im_transpiler::{ use openvm_sdk::{ commit::commit_app_exe, config::{AggConfig, AggStarkConfig, AppConfig, Halo2Config}, + prover::ContinuationProver, Sdk, StdIn, }; use openvm_stark_sdk::{ @@ -87,14 +88,11 @@ async fn main() -> Result<()> { let mut stdin = StdIn::default(); stdin.write(&n); run_with_metric_collection("OUTPUT_PATH", || { - Sdk.generate_evm_proof( - &halo2_params_reader, - app_pk, - app_committed_exe, - full_agg_pk, - stdin, - ) - .unwrap(); + let mut e2e_prover = + ContinuationProver::new(&halo2_params_reader, app_pk, app_committed_exe, full_agg_pk) + .with_profiling(); + e2e_prover.set_program_name("fib_e2e"); + let _proof = e2e_prover.generate_proof_for_evm(stdin); }); Ok(()) diff --git a/benchmarks/src/utils.rs b/benchmarks/src/utils.rs index c2c6ba2646..2751323916 100644 --- a/benchmarks/src/utils.rs +++ b/benchmarks/src/utils.rs @@ -100,11 +100,8 @@ where let committed_exe = time(gauge!("commit_exe_time_ms"), || { commit_app_exe(app_config.app_fri_params, exe) }); - // 3. Executes runtime again without metric collection and generate trace. - time(gauge!("execute_and_trace_gen_time_ms"), || { - vm.execute_and_generate_with_cached_program(committed_exe.clone(), input_stream.clone()) - })?; - // 4. Executes runtime once with full metric collection for flamegraphs (slow). + // 3. Executes runtime once with full metric collection for flamegraphs (slow). + // 4. Executes runtime again without metric collection and generate trace. // 5. Generate STARK proofs for each segment (segmentation is determined by `config`), with timer. // generate_app_proof will emit metrics for proof time of each let vk = app_pk.app_vm_pk.vm_pk.get_vk(); diff --git a/ci/scripts/metric_unify/summarize.py b/ci/scripts/metric_unify/summarize.py index 2f3c5202ac..39f7def71e 100644 --- a/ci/scripts/metric_unify/summarize.py +++ b/ci/scripts/metric_unify/summarize.py @@ -121,7 +121,7 @@ def generate_row(md_file, sections, aggregation_groups, gh_pages_link): group_row = section_by_group.get(section) if group_row is None: res += [format_cell("-", None, None)] * COLS_PER_SECTION - else: + else: res.append(str(group_row.log_blowup)) for cell in group_row.cells: res.append(str(cell)) @@ -154,16 +154,16 @@ def main(): md_files = args.metrics_md_files.split(',') outputs = [] for md_file in md_files: - outputs.append(generate_row(md_file, ["leaf_verifier"], {}, args.gh_pages_link)) + outputs.append(generate_row(md_file, ["leaf"], {}, args.gh_pages_link)) write_md_table(outputs, "Benchmarks", headers, rewrite=True) if args.e2e_md_files and args.e2e_md_files.strip(): outputs = [] md_files = args.e2e_md_files.split(',') for md_file in md_files: - outputs.append(generate_row(md_file, ["root_verifier", "leaf_verifier", "internal_verifier"], {"internal.*": "internal_verifier"}, args.gh_pages_link)) + outputs.append(generate_row(md_file, ["root", "leaf", "internal"], {"internal.*": "internal"}, args.gh_pages_link)) if outputs: write_md_table(outputs, "E2E Benchmarks", e2e_headers) if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/crates/sdk/src/prover/mod.rs b/crates/sdk/src/prover/mod.rs index 1a7c47738f..72b6f1921c 100644 --- a/crates/sdk/src/prover/mod.rs +++ b/crates/sdk/src/prover/mod.rs @@ -61,6 +61,11 @@ impl ContinuationProver { self } + pub fn with_profiling(mut self) -> Self { + self.set_profile(true); + self + } + pub fn set_program_name(&mut self, program_name: impl AsRef) -> &mut Self { self.stark_prover.set_program_name(program_name); self diff --git a/crates/vm/src/system/memory/adapter/mod.rs b/crates/vm/src/system/memory/adapter/mod.rs index 71dcdb4309..652d918cbc 100644 --- a/crates/vm/src/system/memory/adapter/mod.rs +++ b/crates/vm/src/system/memory/adapter/mod.rs @@ -80,6 +80,9 @@ impl AccessAdapterInventory { .map(|chip| chip.current_trace_height()) .collect() } + pub fn get_widths(&self) -> Vec { + self.chips.iter().map(|chip| chip.trace_width()).collect() + } pub fn airs(&self) -> Vec>> where F: PrimeField32, diff --git a/crates/vm/src/system/memory/manager/mod.rs b/crates/vm/src/system/memory/manager/mod.rs index b7bbae0312..33cb989b49 100644 --- a/crates/vm/src/system/memory/manager/mod.rs +++ b/crates/vm/src/system/memory/manager/mod.rs @@ -13,7 +13,6 @@ use itertools::{izip, zip_eq}; pub use memory::{MemoryReadRecord, MemoryWriteRecord}; use openvm_circuit_primitives::{ assert_less_than::{AssertLtSubAir, LessThanAuxCols}, - is_less_than::IsLtSubAir, is_zero::IsZeroSubAir, utils::next_power_of_two_or_zero, var_range::{VariableRangeCheckerBus, VariableRangeCheckerChip}, @@ -36,12 +35,9 @@ use self::interface::MemoryInterface; use super::{merkle::DirectCompressionBus, volatile::VolatileBoundaryChip}; use crate::{ arch::{hasher::HasherChip, MemoryConfig}, - system::memory::{ - adapter::AccessAdapterAir, - offline_checker::{ - MemoryBridge, MemoryBus, MemoryReadAuxCols, MemoryReadOrImmediateAuxCols, - MemoryWriteAuxCols, AUX_LEN, - }, + system::memory::offline_checker::{ + MemoryBridge, MemoryBus, MemoryReadAuxCols, MemoryReadOrImmediateAuxCols, + MemoryWriteAuxCols, AUX_LEN, }, }; @@ -450,14 +446,6 @@ impl MemoryController { self.memory.timestamp() } - fn access_adapter_air(&self) -> AccessAdapterAir { - let lt_air = IsLtSubAir::new(self.range_checker.bus(), self.mem_config.clk_max_bits); - AccessAdapterAir:: { - memory_bus: self.memory_bus, - lt_air, - } - } - /// Returns the final memory state if persistent. pub fn finalize( &mut self, @@ -681,19 +669,9 @@ impl MemoryController { widths.push(BaseAir::::width(&merkle_chip.air)); } }; - self.add_access_adapter_width::<2>(&mut widths); - self.add_access_adapter_width::<4>(&mut widths); - self.add_access_adapter_width::<8>(&mut widths); - self.add_access_adapter_width::<16>(&mut widths); - self.add_access_adapter_width::<32>(&mut widths); - self.add_access_adapter_width::<64>(&mut widths); + widths.extend(self.access_adapters.get_widths()); widths } - fn add_access_adapter_width(&self, widths: &mut Vec) { - if self.mem_config.max_access_adapter_n >= N { - widths.push(BaseAir::::width(&self.access_adapter_air::())); - } - } pub fn current_trace_cells(&self) -> Vec { zip_eq(self.current_trace_heights(), self.trace_widths()) diff --git a/extensions/native/recursion/trusted_setup_s3.sh b/extensions/native/recursion/trusted_setup_s3.sh index c2291b6a7b..a249217b0b 100644 --- a/extensions/native/recursion/trusted_setup_s3.sh +++ b/extensions/native/recursion/trusted_setup_s3.sh @@ -7,7 +7,6 @@ else fi echo "maxk=$maxk" -bash scripts/install_s5cmd.sh mkdir -p params/ cd params for k in $(seq 10 $maxk) @@ -20,4 +19,4 @@ do s5cmd --no-sign-request cp --concurrency 10 "s3://axiom-crypto/challenge_0085/${pkey_file}" . fi done -cd .. \ No newline at end of file +cd .. From 6b877149655eab106264021d25adc61aa7f43f5b Mon Sep 17 00:00:00 2001 From: Lun-Kai Hsu Date: Fri, 13 Dec 2024 22:22:37 -0800 Subject: [PATCH 03/69] [docs] update ISA with setup opcodes (#1036) * update isa for setup opcodes * fix * Update docs/specs/vm/ISA.md Co-authored-by: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> * Update docs/specs/vm/RISCV.md --------- Co-authored-by: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> --- docs/specs/vm/ISA.md | 7 +++++++ docs/specs/vm/RISCV.md | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/specs/vm/ISA.md b/docs/specs/vm/ISA.md index 4464fc4687..ad8337ed1f 100644 --- a/docs/specs/vm/ISA.md +++ b/docs/specs/vm/ISA.md @@ -332,8 +332,10 @@ Each instruction performs block accesses with block size `4` in address space `1 | ---------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ADDMOD_RV32\ | `a,b,c,1,2` | `[r32{0}(a): N::NUM_LIMBS]_2 = [r32{0}(b): N::NUM_LIMBS]_2 + [r32{0}(c): N::NUM_LIMBS]_2 (mod N)` | | SUBMOD_RV32\ | `a,b,c,1,2` | `[r32{0}(a): N::NUM_LIMBS]_2 = [r32{0}(b): N::NUM_LIMBS]_2 - [r32{0}(c): N::NUM_LIMBS]_2 (mod N)` | +| SETUP_ADDSUBMOD_RV32\ | `a,b,c,1,2` | `assert([r32{0}(b): N::NUM_LIMBS]_2 == N)` for the chip that handles add and sub. For the sake of implementation convenience it also writes something (can be anything) into `[r32{0}(a): N::NUM_LIMBS]_2` | | MULMOD_RV32\ | `a,b,c,1,2` | `[r32{0}(a): N::NUM_LIMBS]_2 = [r32{0}(b): N::NUM_LIMBS]_2 * [r32{0}(c): N::NUM_LIMBS]_2 (mod N)` | | DIVMOD_RV32\ | `a,b,c,1,2` | `[r32{0}(a): N::NUM_LIMBS]_2 = [r32{0}(b): N::NUM_LIMBS]_2 / [r32{0}(c): N::NUM_LIMBS]_2 (mod N)`. Undefined behavior if `gcd([r32{0}(c): N::NUM_LIMBS]_2, N) != 1`. | +| SETUP_MULDIVMOD_RV32\ | `a,b,c,1,2` | `assert([r32{0}(b): N::NUM_LIMBS]_2 == N)` for the chip that handles mul and div. For the sake of implementation convenience it also writes something (can be anything) into `[r32{0}(a): N::NUM_LIMBS]_2` | ### Modular Branching @@ -342,6 +344,7 @@ The configuration of `N` is the same as above. For each instruction, the input e | Name | Operands | Description | | ----------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ISEQMOD_RV32\ | `a,b,c,1,2` | `[a:4]_1 = [r32{0}(b): N::NUM_LIMBS]_2 == [r32{0}(c): N::NUM_LIMBS]_2 (mod N) ? 1 : 0`. Enforces that `[r32{0}(b): N::NUM_LIMBS]_2, [r32{0}(c): N::NUM_LIMBS]_2` are less than `N` and then sets the register value of `[a:4]_1` to `1` or `0` depending on whether the two big integers are equal. | +| SETUP_ISEQMOD_RV32\ | `a,b,c,1,2` | `assert([r32{0}(b): N::NUM_LIMBS]_2 == N)` in the chip that handles modular equality. For the sake of implementation convenience it also writes something (can be anything) into register value of `[a:4]_1` ### Short Weierstrass Elliptic Curve Arithmetic @@ -362,7 +365,9 @@ r32_ec_point(a) -> EcPoint { | Name | Operands | Description | | -------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | SW_ADD_NE\ | `a,b,c,1,2` | Set `r32_ec_point(a) = r32_ec_point(b) + r32_ec_point(c)` (curve addition). Assumes that `r32_ec_point(b), r32_ec_point(c)` both lie on the curve and are not the identity point. Further assumes that `r32_ec_point(b).x, r32_ec_point(c).x` are not equal in the coordinate field. | +| SETUP_SW_ADD_NE\ | `a,b,c,1,2` | `assert(r32_ec_point(b).x == C::MODULUS)` in the chip for EC ADD. For the sake of implementation convenience it also writes something (can be anything) into `[r32{0}(a): 2*C::COORD_SIZE]_2`. It is required for proper functionality that `assert(r32_ec_point(b).x != r32_ec_point(c).x)` | | SW_DOUBLE\ | `a,b,_,1,2` | Set `r32_ec_point(a) = 2 * r32_ec_point(b)`. This doubles the input point. Assumes that `r32_ec_point(b)` lies on the curve and is not the identity point. | +| SETUP_SW_DOUBLE\ | `a,b,_,1,2` | `assert(r32_ec_point(b).x == C::MODULUS)` in the chip for EC DOUBLE. For the sake of implementation convenience it also writes something (can be anything) into `[r32{0}(a): 2*C::COORD_SIZE]_2`. It is required for proper functionality that `assert(r32_ec_point(b).y != 0 mod C::MODULUS)` | ### Complex Extension Field @@ -386,8 +391,10 @@ r32_fp2(a) -> Fp2 { | ---------- | ----------- | ------------------------------------------ | | ADD\ | `a,b,c,1,2` | Set `r32_fp2(a) = r32_fp2(b) + r32_fp2(c)` | | SUB\ | `a,b,c,1,2` | Set `r32_fp2(a) = r32_fp2(b) - r32_fp2(c)` | +| SETUP_ADDSUB\ | `a,b,c,1,2` | `assert([r32_fp2(b).c0 == N)` for the chip that handles add and sub. For the sake of implementation convenience it also writes something (can be anything) into `r32_fp2(a)` | | MUL\ | `a,b,c,1,2` | Set `r32_fp2(a) = r32_fp2(b) * r32_fp2(c)` | | DIV\ | `a,b,c,1,2` | Set `r32_fp2(a) = r32_fp2(b) / r32_fp2(c)` | +| SETUP_MULDIV\ | `a,b,c,1,2` | `assert([r32_fp2(b).c0 == N)` for the chip that handles mul and div. For the sake of implementation convenience it also writes something (can be anything) into `r32_fp2(a)` | ### Optimal Ate Pairing diff --git a/docs/specs/vm/RISCV.md b/docs/specs/vm/RISCV.md index d083fcdf28..a07902885e 100644 --- a/docs/specs/vm/RISCV.md +++ b/docs/specs/vm/RISCV.md @@ -91,7 +91,7 @@ Short Weierstrass elliptic curve arithmetic depends on elliptic curve `C`. The i | --------------- | --- | ----------- | ------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | sw_add_ne\ | R | 0101011 | 001 | `idx*8` | `EcPoint([rd:2*C::COORD_SIZE]_2) = EcPoint([rs1:2*C::COORD_SIZE]_2) + EcPoint([rs2:2*C::COORD_SIZE]_2)`. Assumes that input affine points are not identity and do not have same x-coordinate. | | sw_double\ | R | 0101011 | 001 | `idx*8+1` | `EcPoint([rd:2*C::COORD_SIZE]_2) = 2 * EcPoint([rs1:2*C::COORD_SIZE]_2)`. Assumes that input affine point is not identity. `rs2` is unused and must be set to `x0`. | -| setup\ | R | 0101011 | 001 | `idx*8+2` | `assert([rs1: C::COORD_SIZE]_2 == C::MODULUS)` in the chip defined by the register index of `rs2`. For the sake of implementation convenience it also writes something (can be anything) into `[rd: 2*C::COORD_SIZE]_2`. It is required for proper functionality that `[rs1 + C::COORD_SIZE: C::COORD_SIZE]_2 != C::Fp::ZERO`. If `ind(rs2) != 0`, then this instruction is setup for `sw_add_ne`. Otherwise it is setup for `sw_double`. When `ind(rs2) != 0`, it is required that `[rs2: C::COORD_SIZE]_2 != C::MODULUS` and `[rs2 + C::COORD_SIZE: C::COORD_SIZE]_2 != C::Fp::ZERO`. | +| setup\ | R | 0101011 | 001 | `idx*8+2` | `assert([rs1: C::COORD_SIZE]_2 == C::MODULUS)` in the chip defined by the register index of `rs2`. For the sake of implementation convenience it also writes something (can be anything) into `[rd: 2*C::COORD_SIZE]_2`. If `ind(rs2) != 0`, then this instruction is setup for `sw_add_ne`. Otherwise it is setup for `sw_double`. When `ind(rs2) != 0` (add_ne), it is required for proper functionality that `[rs2: C::COORD_SIZE]_2 != [rs1: C::COORD_SIZE]_2`; otherwise (double), it is required that `[rs1 + C::COORD_SIZE: C::COORD_SIZE]_2 != C::Fp::ZERO` | | hint_decompress | R | 0101011 | 001 | `idx*8+3` | Read `x: C::Fp` from `[rs1: C::COORD_SIZE]_2` and `rec_id: u8` from `[rs2]_2`. Reset the hint stream to equal the unique `y: C::Fp` such that `(x, y)` is a point on `C` and `y` has the same parity as `rec_id`, if it exists. Otherwise reset hint stream to arbitrary `C::Fp`. `rd` should be `x0`. | Since `funct7` is 7-bits, up to 16 curves can be supported simultaneously. We use `idx*8` to leave some room for future expansion. From 7646803de4827963b8e748c58da558a88c6140b0 Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Sat, 14 Dec 2024 14:12:11 -0500 Subject: [PATCH 04/69] chore: switch benchmark results branch (#1041) * chore: switch benchmark results branch `gh-pages` -> `benchmark-results` * fix: more renamings --- .github/workflows/benchmark-call.yml | 30 ++++++++++++++-------------- .github/workflows/benchmarks.yml | 22 ++++++++++---------- README.md | 2 +- ci/scripts/metric_unify/summarize.py | 10 +++++----- ci/scripts/utils.sh | 8 ++++---- 5 files changed, 36 insertions(+), 36 deletions(-) diff --git a/.github/workflows/benchmark-call.yml b/.github/workflows/benchmark-call.yml index 436b00bdec..7d5fc12cb0 100644 --- a/.github/workflows/benchmark-call.yml +++ b/.github/workflows/benchmark-call.yml @@ -261,11 +261,11 @@ jobs: fi ########################################################################## - # Update gh-pages with individual results # + # Update benchmark-results with individual results # ########################################################################## - uses: actions/checkout@v4 with: - ref: gh-pages + ref: benchmark-results - name: Set up git run: | @@ -274,28 +274,28 @@ jobs: - name: Set github pages path for dispatch run: | - GH_PAGES_PATH="benchmarks-dispatch/${{ github.head_ref || github.ref }}" - echo "GH_PAGES_PATH=${GH_PAGES_PATH}" >> $GITHUB_ENV + BENCHMARK_RESULTS_PATH="benchmarks-dispatch/${{ github.head_ref || github.ref }}" + echo "BENCHMARK_RESULTS_PATH=${BENCHMARK_RESULTS_PATH}" >> $GITHUB_ENV - name: Set github pages path for PR if: github.event_name == 'pull_request' run: | - GH_PAGES_PATH="benchmarks-pr/${{ github.event.pull_request.number }}/individual" - echo "GH_PAGES_PATH=${GH_PAGES_PATH}" >> $GITHUB_ENV + BENCHMARK_RESULTS_PATH="benchmarks-pr/${{ github.event.pull_request.number }}/individual" + echo "BENCHMARK_RESULTS_PATH=${BENCHMARK_RESULTS_PATH}" >> $GITHUB_ENV - name: Set github pages path for push if: github.event_name == 'push' && github.ref == 'refs/heads/main' run: | - GH_PAGES_PATH="benchmarks/individual" - echo "GH_PAGES_PATH=${GH_PAGES_PATH}" >> $GITHUB_ENV + BENCHMARK_RESULTS_PATH="benchmarks/individual" + echo "BENCHMARK_RESULTS_PATH=${BENCHMARK_RESULTS_PATH}" >> $GITHUB_ENV - name: Update PR github pages with new bench results run: | - mkdir -p ${GH_PAGES_PATH} + mkdir -p ${BENCHMARK_RESULTS_PATH} s3_md_file="${METRIC_NAME}-${current_sha}.md" - s5cmd cp "${{ env.S3_PATH }}/${s3_md_file}" ${GH_PAGES_PATH}/${s3_md_file} - git add ${GH_PAGES_PATH}/${s3_md_file} - git commit --allow-empty -m "Update benchmark result at ${GH_PAGES_PATH}/${s3_md_file}" + s5cmd cp "${{ env.S3_PATH }}/${s3_md_file}" ${BENCHMARK_RESULTS_PATH}/${s3_md_file} + git add ${BENCHMARK_RESULTS_PATH}/${s3_md_file} + git commit --allow-empty -m "Update benchmark result at ${BENCHMARK_RESULTS_PATH}/${s3_md_file}" MAX_RETRIES=10 RETRY_DELAY=5 @@ -304,9 +304,9 @@ jobs: while [ $ATTEMPT -lt $MAX_RETRIES ]; do echo "Attempt $((ATTEMPT + 1)) to push of $MAX_RETRIES..." - git fetch origin gh-pages - git merge origin/gh-pages --no-edit - if git push origin gh-pages; then + git fetch origin benchmark-results + git merge origin/benchmark-results --no-edit + if git push origin benchmark-results; then SUCCESS=true break else diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 547f793f11..e88ff179d6 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -138,14 +138,14 @@ jobs: - name: Set github pages path for PR if: github.event_name == 'pull_request' run: | - GH_PAGES_PATH="benchmarks-pr/${{ github.event.pull_request.number }}" - echo "GH_PAGES_PATH=${GH_PAGES_PATH}" >> $GITHUB_ENV + BENCHMARK_RESULTS_PATH="benchmarks-pr/${{ github.event.pull_request.number }}" + echo "BENCHMARK_RESULTS_PATH=${BENCHMARK_RESULTS_PATH}" >> $GITHUB_ENV - name: Set github pages path for push if: github.event_name == 'push' && github.ref == 'refs/heads/main' run: | - GH_PAGES_PATH="benchmarks" - echo "GH_PAGES_PATH=${GH_PAGES_PATH}" >> $GITHUB_ENV + BENCHMARK_RESULTS_PATH="benchmarks" + echo "BENCHMARK_RESULTS_PATH=${BENCHMARK_RESULTS_PATH}" >> $GITHUB_ENV - name: Load all metadata files from S3 run: | @@ -189,7 +189,7 @@ jobs: python3 ci/scripts/metric_unify/summarize.py "${md_file_list}" \ --e2e-md-files "${E2E_FILE_LIST}" \ --aggregation-json ci/scripts/metric_unify/aggregation.json \ - --gh-pages-link "https://github.com/${{ github.repository }}/blob/gh-pages/${GH_PAGES_PATH}" + --benchmark-results-link "https://github.com/${{ github.repository }}/blob/benchmark-results/${BENCHMARK_RESULTS_PATH}" echo "" >> summary.md echo "Commit: https://github.com/${{ github.repository }}/commit/${CURRENT_SHA}" >> summary.md @@ -199,12 +199,12 @@ jobs: cp summary.md /tmp/benchmark-results/ ########################################################################## - # Update gh-pages with summary upon a PR event # + # Update benchmark-results with summary upon a PR event # ########################################################################## - uses: actions/checkout@v4 if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/main') with: - ref: gh-pages + ref: benchmark-results - name: Set up git if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/main') @@ -215,14 +215,14 @@ jobs: - name: Update github pages with new bench results if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/main') run: | - mkdir -p ${GH_PAGES_PATH} - cp /tmp/benchmark-results/summary.md ${GH_PAGES_PATH}/summary.md - git add ${GH_PAGES_PATH}/summary.md + mkdir -p ${BENCHMARK_RESULTS_PATH} + cp /tmp/benchmark-results/summary.md ${BENCHMARK_RESULTS_PATH}/summary.md + git add ${BENCHMARK_RESULTS_PATH}/summary.md if [[ "${{ github.event_name }}" == "push" ]]; then cp /tmp/benchmark-results/summary.md index.md git add index.md fi - git commit --allow-empty -m "Update summarized benchmark result at ${GH_PAGES_PATH}/summary.md" + git commit --allow-empty -m "Update summarized benchmark result at ${BENCHMARK_RESULTS_PATH}/summary.md" git push --force ########################################################################## diff --git a/README.md b/README.md index 02d5f3433e..815ba4898a 100644 --- a/README.md +++ b/README.md @@ -45,5 +45,5 @@ The flamegraphs will be written to `*.svg` files in `.bench_metrics/flamegraphs` ### Latest Benchmark Results -Latest benchmark results can be found [here](https://github.com/axiom-crypto/afs-prototype/blob/gh-pages/index.md). +Latest benchmark results can be found [here](https://github.com/axiom-crypto/afs-prototype/blob/benchmark-results/index.md). These are run via [github workflows](./.github/workflows/benchmark-call.yml) and should always be up to date with the latest `main` branch. diff --git a/ci/scripts/metric_unify/summarize.py b/ci/scripts/metric_unify/summarize.py index 39f7def71e..c0040c36fd 100644 --- a/ci/scripts/metric_unify/summarize.py +++ b/ci/scripts/metric_unify/summarize.py @@ -100,7 +100,7 @@ def read_first_markdown_table(md_file): return table, other_info # group stats (ex. app, agg, root) are either added to the row as is, or aggregated together -def generate_row(md_file, sections, aggregation_groups, gh_pages_link): +def generate_row(md_file, sections, aggregation_groups, benchmark_results_link): table, other_info = read_first_markdown_table(md_file) sections = [table[0].group] + sections section_by_group = {}; @@ -116,7 +116,7 @@ def generate_row(md_file, sections, aggregation_groups, gh_pages_link): section_by_group[group_name] = row break - res = [f"[ {table[0].group} ]({gh_pages_link}/individual/{md_file})"] + res = [f"[ {table[0].group} ]({benchmark_results_link}/individual/{md_file})"] for section in sections: group_row = section_by_group.get(section) if group_row is None: @@ -142,7 +142,7 @@ def main(): argparser.add_argument('metrics_md_files', type=str, help="Comma separated list of metrics markdown file names") argparser.add_argument('--e2e-md-files', type=str, required=False, help="Comma separated list of e2e metrics markdown file names") argparser.add_argument('--aggregation-json', type=str, required=True, help="Path to a JSON file with metrics to aggregate") - argparser.add_argument('--gh-pages-link', type=str, required=True, help="Link to this PR's gh-pages directory") + argparser.add_argument('--benchmark-results-link', type=str, required=True, help="Link to this PR's benchmark-results directory") args = argparser.parse_args() aggregations = read_aggregations(args.aggregation_json) @@ -154,14 +154,14 @@ def main(): md_files = args.metrics_md_files.split(',') outputs = [] for md_file in md_files: - outputs.append(generate_row(md_file, ["leaf"], {}, args.gh_pages_link)) + outputs.append(generate_row(md_file, ["leaf"], {}, args.benchmark_results_link)) write_md_table(outputs, "Benchmarks", headers, rewrite=True) if args.e2e_md_files and args.e2e_md_files.strip(): outputs = [] md_files = args.e2e_md_files.split(',') for md_file in md_files: - outputs.append(generate_row(md_file, ["root", "leaf", "internal"], {"internal.*": "internal"}, args.gh_pages_link)) + outputs.append(generate_row(md_file, ["root", "leaf", "internal"], {"internal.*": "internal"}, args.benchmark_results_link)) if outputs: write_md_table(outputs, "E2E Benchmarks", e2e_headers) diff --git a/ci/scripts/utils.sh b/ci/scripts/utils.sh index 29bd3d60d4..7c45813bb8 100644 --- a/ci/scripts/utils.sh +++ b/ci/scripts/utils.sh @@ -54,7 +54,7 @@ add_metadata() { echo "[Benchmark Workflow](https://github.com/${repo}/actions/runs/${run_id})" >> $result_path } -commit_and_push_gh_pages() { +commit_and_push_benchmark_results() { local files=$1 local commit_message=$2 git add ${files} @@ -67,9 +67,9 @@ commit_and_push_gh_pages() { while [ $ATTEMPT -lt $MAX_RETRIES ]; do echo "Attempt $((ATTEMPT + 1)) to push of $MAX_RETRIES..." - git fetch origin gh-pages - git merge origin/gh-pages --no-edit - if git push origin gh-pages; then + git fetch origin benchmark-results + git merge origin/benchmark-results --no-edit + if git push origin benchmark-results; then SUCCESS=true break else From b1755e541e2c3bda514d39f051da27e57dfefe0e Mon Sep 17 00:00:00 2001 From: Yi Sun Date: Sat, 14 Dec 2024 20:19:54 -0500 Subject: [PATCH 05/69] feat: add initial rustdocs workflow (#1042) * feat: add initial rustdocs workflow * chore: add testing trigger * chore: change to fetch with CLI * chore: configure git * chore: replace github config * fix: Vercel env variable names * chore: remove no-deps * chore: fix index page * fix * cargo check for halo2 table * chore: switch to serving on s3 * chore: fix index page * typo --------- Co-authored-by: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> --- .github/workflows/docs.yml | 64 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 .github/workflows/docs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000000..d1feed19de --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,64 @@ +name: Build and Serve Crate Docs + +on: + push: + branches: [main] + tags: + - v*.*.* + pull_request: + branches: [main] + paths: + - ".github/workflows/docs.yml" + +env: + CARGO_NET_GIT_FETCH_WITH_CLI: true + +jobs: + docs: + permissions: + contents: write + packages: read + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + runs-on: + - runs-on=${{ github.run_id }} + - runner=8cpu-linux-arm64 + steps: + - uses: actions/checkout@v4 + - name: Set up Rust toolchain + uses: dtolnay/rust-toolchain@nightly + + # TEMPORARY + - name: Give GitHub Actions access to private repositories + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: | + ${{ secrets.GH_ACTIONS_DEPLOY_PRIVATE_KEY }} + + - name: Cargo check + run: cargo check + + - name: Build documentation + run: cargo doc --workspace + + # We only want the index page to display workspace crates, so we build + # separately and copy over the index as a hack + - name: Build index page + run: | + cargo doc --workspace --no-deps --exclude "openvm-benchmarks" --target-dir target/doc-nodeps + cp target/doc-nodeps/doc/index.html target/doc/ + env: + RUSTDOCFLAGS: --enable-index-page -Zunstable-options + + - name: Install s5cmd + run: | + source ci/scripts/utils.sh + install_s5cmd + + # TODO: store versioned copy of docs when new v*.*.* tag is pushed + - name: Sync static S3 bucket + env: + S3_BUCKET: ${{ vars.CRATE_DOCS_S3_BUCKET }} + run: | + cd target/doc + s5cmd sync . s3://${S3_BUCKET%/}/static/ From 0a3905c3dc6bcbf888b60750aa7eb5d878589188 Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Sat, 14 Dec 2024 23:09:04 -0500 Subject: [PATCH 06/69] chore(docs): show only workspace crates in sidebar (#1044) --- .github/workflows/docs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index d1feed19de..140fa57f28 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -47,6 +47,7 @@ jobs: run: | cargo doc --workspace --no-deps --exclude "openvm-benchmarks" --target-dir target/doc-nodeps cp target/doc-nodeps/doc/index.html target/doc/ + cp target/doc-nodeps/doc/crates.js target/doc/ env: RUSTDOCFLAGS: --enable-index-page -Zunstable-options From f8a9af6862ce58f8f55d907d3d2c1f13d8c5b4f3 Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Sat, 14 Dec 2024 23:35:07 -0500 Subject: [PATCH 07/69] fix: libraries should use `complex_declare!` with unique names (#1043) * fix: libraries should use `complex_declare!` with unique names Because the extern function name will depend on the struct name used. * chore: renames * chore: more rename --- book/src/using-extensions/customizable-extensions.md | 10 +++++----- .../tests/programs/examples/final_exp_hint.rs | 4 ---- crates/toolchain/tests/programs/examples/fp12_mul.rs | 8 -------- .../tests/programs/examples/pairing_check.rs | 4 ++-- .../tests/programs/examples/pairing_line.rs | 8 -------- .../tests/programs/examples/pairing_miller_loop.rs | 12 ++---------- .../tests/programs/examples/pairing_miller_step.rs | 12 ++---------- extensions/pairing/guest/src/bls12_381/fp2.rs | 8 ++++++-- extensions/pairing/guest/src/bn254/fp2.rs | 8 ++++++-- 9 files changed, 23 insertions(+), 51 deletions(-) diff --git a/book/src/using-extensions/customizable-extensions.md b/book/src/using-extensions/customizable-extensions.md index 9bf789f23e..1d69997c2a 100644 --- a/book/src/using-extensions/customizable-extensions.md +++ b/book/src/using-extensions/customizable-extensions.md @@ -10,8 +10,8 @@ To declare a modular arithmetic struct, one needs to use the `moduli_declare!` m ```rust moduli_declare! { - Bls12381_Fp { modulus = "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab" }, - Bn254_Fp { modulus = "21888242871839275222246405745257275088696311157297823662689037894645226208583" }, + Bls12_381Fp { modulus = "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab" }, + Bn254Fp { modulus = "21888242871839275222246405745257275088696311157297823662689037894645226208583" }, } ``` @@ -47,8 +47,8 @@ To declare an elliptic curve struct, one needs to use the `sw_declare!` macro. A ```rust sw_declare! { - Bls12381 { mod_type = Bls12381_Fp, b = BLS12381_B }, - Bn254 { mod_type = Bn254_Fp, b = BN254_B }, + Bls12_381G1Affine { mod_type = Bls12_381Fp, b = BLS12_381_B }, + Bn254G1Affine { mod_type = Bn254Fp, b = BN254_B }, } ``` @@ -58,7 +58,7 @@ The arithmetic operations for these classes, when compiling for the `zkvm` targe ```rust sw_init! { - Bls12381, Bn254, + Bls12_381Fp, Bn254Fp, } ``` diff --git a/crates/toolchain/tests/programs/examples/final_exp_hint.rs b/crates/toolchain/tests/programs/examples/final_exp_hint.rs index 3dd8c2ce22..c5e0e2100a 100644 --- a/crates/toolchain/tests/programs/examples/final_exp_hint.rs +++ b/crates/toolchain/tests/programs/examples/final_exp_hint.rs @@ -19,10 +19,6 @@ openvm_algebra_moduli_setup::moduli_init! { "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" } -openvm_ecc_sw_setup::sw_init! { - Fp, -} - pub fn main() { let (p, q, expected): (Vec>, Vec>, (Fp12, Fp12)) = read(); let actual = Bls12_381::pairing_check_hint(&p, &q); diff --git a/crates/toolchain/tests/programs/examples/fp12_mul.rs b/crates/toolchain/tests/programs/examples/fp12_mul.rs index 84ac3c4007..f7ef344801 100644 --- a/crates/toolchain/tests/programs/examples/fp12_mul.rs +++ b/crates/toolchain/tests/programs/examples/fp12_mul.rs @@ -18,10 +18,6 @@ mod bn254 { "0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001" } - openvm_ecc_sw_setup::sw_init! { - Fp, - } - pub fn test_fp12_mul(io: &[u8]) { setup_0(); assert_eq!(io.len(), 32 * 36); @@ -56,10 +52,6 @@ mod bls12_381 { "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" } - openvm_ecc_sw_setup::sw_init! { - Fp, - } - pub fn test_fp12_mul(io: &[u8]) { setup_0(); assert_eq!(io.len(), 48 * 36); diff --git a/crates/toolchain/tests/programs/examples/pairing_check.rs b/crates/toolchain/tests/programs/examples/pairing_check.rs index e77cac55d0..b1fce22614 100644 --- a/crates/toolchain/tests/programs/examples/pairing_check.rs +++ b/crates/toolchain/tests/programs/examples/pairing_check.rs @@ -25,7 +25,7 @@ mod bn254 { } openvm_algebra_complex_macros::complex_init! { - Fp2 { mod_idx = 0 }, + Bn254Fp2 { mod_idx = 0 }, } pub fn test_pairing_check(io: &[u8]) { @@ -65,7 +65,7 @@ mod bls12_381 { } openvm_algebra_complex_macros::complex_init! { - Fp2 { mod_idx = 0 }, + Bls12_381Fp2 { mod_idx = 0 }, } pub fn test_pairing_check(io: &[u8]) { diff --git a/crates/toolchain/tests/programs/examples/pairing_line.rs b/crates/toolchain/tests/programs/examples/pairing_line.rs index f04b364227..922f59992b 100644 --- a/crates/toolchain/tests/programs/examples/pairing_line.rs +++ b/crates/toolchain/tests/programs/examples/pairing_line.rs @@ -20,10 +20,6 @@ mod bn254 { "0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001" } - openvm_ecc_sw_setup::sw_init! { - Fp, - } - pub fn test_mul_013_by_013(io: &[u8]) { assert_eq!(io.len(), 32 * 18); let l0 = &io[..32 * 4]; @@ -76,10 +72,6 @@ mod bls12_381 { "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" } - openvm_ecc_sw_setup::sw_init! { - Fp, - } - pub fn test_mul_023_by_023(io: &[u8]) { assert_eq!(io.len(), 48 * 18); let l0 = &io[..48 * 4]; diff --git a/crates/toolchain/tests/programs/examples/pairing_miller_loop.rs b/crates/toolchain/tests/programs/examples/pairing_miller_loop.rs index fa6eea1817..cb7e670e1f 100644 --- a/crates/toolchain/tests/programs/examples/pairing_miller_loop.rs +++ b/crates/toolchain/tests/programs/examples/pairing_miller_loop.rs @@ -24,11 +24,7 @@ mod bn254 { } openvm_algebra_complex_macros::complex_init! { - Fp2 { mod_idx = 0 }, - } - - openvm_ecc_sw_setup::sw_init! { - Fp, + Bn254Fp2 { mod_idx = 0 }, } pub fn test_miller_loop(io: &[u8]) { @@ -72,11 +68,7 @@ mod bls12_381 { } openvm_algebra_complex_macros::complex_init! { - Fp2 { mod_idx = 0 }, - } - - openvm_ecc_sw_setup::sw_init! { - Fp, + Bls12_381Fp2 { mod_idx = 0 }, } pub fn test_miller_loop(io: &[u8]) { diff --git a/crates/toolchain/tests/programs/examples/pairing_miller_step.rs b/crates/toolchain/tests/programs/examples/pairing_miller_step.rs index 1ea0460f15..02e9d60baf 100644 --- a/crates/toolchain/tests/programs/examples/pairing_miller_step.rs +++ b/crates/toolchain/tests/programs/examples/pairing_miller_step.rs @@ -22,11 +22,7 @@ mod bn254 { } openvm_algebra_complex_macros::complex_init! { - Fp2 { mod_idx = 0 }, - } - - openvm_ecc_sw_setup::sw_init! { - Fp, + Bn254Fp2 { mod_idx = 0 }, } pub fn test_miller_step(io: &[u8]) { @@ -104,11 +100,7 @@ mod bls12_381 { } openvm_algebra_complex_macros::complex_init! { - Fp2 { mod_idx = 0 }, - } - - openvm_ecc_sw_setup::sw_init! { - Fp, + Bls12_381Fp2 { mod_idx = 0 }, } pub fn test_miller_step(io: &[u8]) { diff --git a/extensions/pairing/guest/src/bls12_381/fp2.rs b/extensions/pairing/guest/src/bls12_381/fp2.rs index 7af9f2f62e..746efbbbd2 100644 --- a/extensions/pairing/guest/src/bls12_381/fp2.rs +++ b/extensions/pairing/guest/src/bls12_381/fp2.rs @@ -6,14 +6,18 @@ use openvm_algebra_guest::{field::FieldExtension, DivUnsafe, Field, IntMod}; use super::Fp; +// The struct name needs to be globally unique for linking purposes. +// The mod_type is a path used only in the struct definition. complex_declare! { - Fp2 { mod_type = Fp } + Bls12_381Fp2 { mod_type = Fp } } complex_impl_field! { - Fp2, + Bls12_381Fp2, } +pub type Fp2 = Bls12_381Fp2; + impl FieldExtension for Fp2 { const D: usize = 2; type Coeffs = [Fp; 2]; diff --git a/extensions/pairing/guest/src/bn254/fp2.rs b/extensions/pairing/guest/src/bn254/fp2.rs index 5f10b713c2..6f3ba86430 100644 --- a/extensions/pairing/guest/src/bn254/fp2.rs +++ b/extensions/pairing/guest/src/bn254/fp2.rs @@ -6,14 +6,18 @@ use openvm_algebra_guest::{field::FieldExtension, DivUnsafe, Field, IntMod}; use super::Fp; +// The struct name needs to be globally unique for linking purposes. +// The mod_type is a path used only in the struct definition. complex_declare! { - Fp2 { mod_type = Fp } + Bn254Fp2 { mod_type = Fp } } complex_impl_field! { - Fp2, + Bn254Fp2, } +pub type Fp2 = Bn254Fp2; + impl FieldExtension for Fp2 { const D: usize = 2; type Coeffs = [Fp; 2]; From 2088ad490cb78a8df8e334571731c10bcd6fdb18 Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Sun, 15 Dec 2024 00:43:19 -0500 Subject: [PATCH 08/69] perf: use windowed method for bn254 small msm (#1046) --- extensions/ecc/guest/src/k256/mod.rs | 2 +- extensions/pairing/guest/src/bn254/mod.rs | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/extensions/ecc/guest/src/k256/mod.rs b/extensions/ecc/guest/src/k256/mod.rs index b917547afd..8ce61eb1f3 100644 --- a/extensions/ecc/guest/src/k256/mod.rs +++ b/extensions/ecc/guest/src/k256/mod.rs @@ -68,7 +68,7 @@ impl IntrinsicCurve for k256::Secp256k1 { { // heuristic if coeffs.len() < 25 { - let table = CachedMulTable::::new_with_prime_order(bases, 4); + let table = CachedMulTable::::new_with_prime_order(bases, 4); table.windowed_mul(coeffs) } else { crate::msm(coeffs, bases) diff --git a/extensions/pairing/guest/src/bn254/mod.rs b/extensions/pairing/guest/src/bn254/mod.rs index 9f1854f2c6..185af4f4e2 100644 --- a/extensions/pairing/guest/src/bn254/mod.rs +++ b/extensions/pairing/guest/src/bn254/mod.rs @@ -2,7 +2,10 @@ use core::ops::{Add, AddAssign, Neg}; use openvm_algebra_guest::{Field, IntMod}; use openvm_algebra_moduli_setup::moduli_declare; -use openvm_ecc_guest::{weierstrass::IntrinsicCurve, CyclicGroup, Group}; +use openvm_ecc_guest::{ + weierstrass::{CachedMulTable, IntrinsicCurve}, + CyclicGroup, Group, +}; mod fp12; mod fp2; @@ -146,7 +149,20 @@ impl IntrinsicCurve for Bn254 { type Scalar = Scalar; type Point = G1Affine; - // TODO: msm optimization + fn msm(coeffs: &[Self::Scalar], bases: &[Self::Point]) -> Self::Point + where + for<'a> &'a Self::Point: Add<&'a Self::Point, Output = Self::Point>, + { + // heuristic + if coeffs.len() < 25 { + // BN254(Fp) is of prime order by Weil conjecture: + // + let table = CachedMulTable::::new_with_prime_order(bases, 4); + table.windowed_mul(coeffs) + } else { + openvm_ecc_guest::msm(coeffs, bases) + } + } } impl PairingIntrinsics for Bn254 { From 705abe7f612130a49cbfb1094f897205b3d33297 Mon Sep 17 00:00:00 2001 From: Lun-Kai Hsu Date: Sun, 15 Dec 2024 00:38:31 -0800 Subject: [PATCH 09/69] [docs] user book: writing program (#1039) * wip * update * Update book/src/getting-started/quickstart.md * Update book/src/getting-started/quickstart.md * update * move to write program * add partial quickstart * address comments * address some comments * wip --- book/src/advanced-usage/testing-program.md | 80 ++++++++++++++++++++++ book/src/getting-started/install.md | 6 ++ book/src/getting-started/quickstart.md | 45 ++++++++++++ book/src/writing-apps/write-program.md | 44 ++++++++++++ 4 files changed, 175 insertions(+) create mode 100644 book/src/advanced-usage/testing-program.md diff --git a/book/src/advanced-usage/testing-program.md b/book/src/advanced-usage/testing-program.md new file mode 100644 index 0000000000..36839e91f5 --- /dev/null +++ b/book/src/advanced-usage/testing-program.md @@ -0,0 +1,80 @@ + +## Testing the program + +### Running on the host machine + +To test the program on the host machine, one can use the `std` feature: `cargo run --features std`. So for example to run the [fibonacci program](https://github.com/openvm-org/openvm/tree/main/benchmarks/programs/fibonacci): + +```bash +printf '\xA0\x86\x01\x00\x00\x00\x00\x00' | cargo run --features std +``` + +### Running with the OpenVM runtime + +*TODO*: point to how to install CLI + +First to build the guest program: +``` +cargo axiom build +``` + +This compiles the guest program into an [ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) that can be found at `target/riscv32im-risc0-zkvm-elf` directory. +Next, a host program is needed to run the ELF with openvm runtime. This is where one can configure the openvm with different parameters. There are a few steps: + +```rust +use openvm::transpiler::{openvm_platform::memory::MEM_SIZE, elf::Elf}; +use openvm_circuit::arch::instructions::exe::OpenVmExe +use openvm_circuit::arch::VmExecutor; +use openvm_sdk::{config::SdkVmConfig, Sdk, StdIn}; + +let sdk = Sdk; +// 1. Build the vm config with the extensions needed. +// TODO: link to extension +let vm_config = SdkVmConfig::builder() + .system(Default::default()) + .rv32i(Default::default()) + .io(Default::default()) + .build(); + +// 2. Load the ELF +let elf = Elf::decode("your_path_to_elf", MEM_SIZE as u32)?; +let exe = OpenVmExe::from_elf(elf, vm_config.transpiler()).unwrap(); + +// 3. Prepare the input data +let my_input = SomeStruct; // anything that can be serialized +let mut stdin = StdIn::default(); +stdin.write(StdIn::from_bytes(my_input.as_bytes())); + +// 4. Run the program +let executor = VmExecutor::<_, _>::new(vm_config); +executor.execute(exe, stdin)?; +``` +Some example host programs can be found [here](https://github.com/openvm-org/openvm/tree/main/benchmarks/src/bin). + +### Generating to prove + +To generate a proof besides executing the program, instead of using `executor` above (step 4), do the following: +```rust +// Some additional configuration. +let app_log_blowup = 2; +let app_fri_params = FriParameters::standard_with_100_bits_conjectured_security(app_log_blowup); +let app_config = AppConfig { ... }; + +// Keygen and prove +let app_pk = sdk.app_keygen(app_config)?; +let app_committed_exe = sdk.commit_app_exe(app_fri_params, exe)?; +let mut app_prover = + AppProver::new(app_pk.app_vm_pk.clone(), app_committed_exe) + .with_program_name(program_name); +let proof = app_prover.generate_app_proof(stdin); +let app_vk = app_pk.get_vk(); +sdk.verify_app_proof(&app_vk, &proof)?; +``` + +## Troubleshooting + +todo + +## FAQ + +todo \ No newline at end of file diff --git a/book/src/getting-started/install.md b/book/src/getting-started/install.md index 264f59e21c..bb470b6b03 100644 --- a/book/src/getting-started/install.md +++ b/book/src/getting-started/install.md @@ -5,4 +5,10 @@ TODO: how to install `cargo-openvm`. ## Build from source +```bash +git clone https://github.com/openvm-org/openvm.git +cd openvm +cargo install --force --path crates/cli +``` + ## Toolchain diff --git a/book/src/getting-started/quickstart.md b/book/src/getting-started/quickstart.md index acb98436ef..03825f536f 100644 --- a/book/src/getting-started/quickstart.md +++ b/book/src/getting-started/quickstart.md @@ -1 +1,46 @@ # Quickstart + +In this section we will build and run a fibonacci program. + +## Setup + +First, create a new Rust project. + +```bash +cargo init fibonacci +``` + +Since we are using some nightly features, we need to specify the Rust version. Create a `rust-toolchain.toml` file with the following content: + +```toml +[toolchain] +channel = "nightly-2024-10-30" # "1.82.0" +components = ["clippy", "rustfmt"] +``` + +In `Cargo.toml`, add the following dependency: + +```toml +openvm = { git = "https://github.com/openvm-org/openvm.git", features = ["std"] } +``` + +Note that `std` is not enabled by default, so explicitly enabling it is required. + +## The fibonacci program + +The `read` function takes input from the stdin, and it also works with OpenVM runtime. +```rust +use openvm::io::read; + +fn main() { + let n: u64 = read(); + let mut a: u64 = 0; + let mut b: u64 = 1; + for _ in 0..n { + let c: u64 = a.wrapping_add(b); + a = b; + b = c; + } + println!("{}", a); +} +``` diff --git a/book/src/writing-apps/write-program.md b/book/src/writing-apps/write-program.md index 70f295889f..66836a4ade 100644 --- a/book/src/writing-apps/write-program.md +++ b/book/src/writing-apps/write-program.md @@ -1 +1,45 @@ # Writing a Program + +## Writing a guest program + +See the example [fibonacci program](https://github.com/openvm-org/openvm-example-fibonacci). + +The guest program should be a `no_std` Rust crate. As long as it is `no_std`, you can import any other +`no_std` crates and write Rust as you normally would. Import the `openvm` library crate to use `openvm` intrinsic functions (for example `openvm::io::*`). + +The guest program also needs `#![no_main]` because `no_std` does not have certain default handlers. These are provided by the `openvm::entry!` macro. You should still create a `main` function, and then add `openvm::entry!(main)` for the macro to set up the function to run as a normal `main` function. While the function can be named anything when `target_os = "zkvm"`, for compatibility with std you should still name the function `main`. + +To support both `std` and `no_std` execution, the top of your guest program should have: + +```rust +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] +``` + +More examples of guest programs can be found in the [benchmarks/programs](https://github.com/openvm-org/openvm/tree/main/benchmarks/programs) directory. + +### no-std + +Although it's usually ok to use std (like in quickstart), not all std functionalities are supported (e.g., randomness). There might be unexpected runtime errors if one uses std, so it is recommended you develop no_std libraries if possible to reduce surprises. +Even without std, `assert!` and `panic!` can work as normal. To use `std` features, one should add the following to `Cargo.toml` feature sections: +```toml +[features] +std = ["openvm/std"] +``` + +### Building and running + +*TODO*: point to CLI installation instructions + +## Handling I/O + +`openvm::io` provides a few functions to read and write data. + +`read` takes from stdin the next vec and deserialize it into a generic type `T`, so one should specify the type when calling it: +```rust +let n: u64 = read(); +``` + +`read_vec` will just read a vector and return `Vec`. + +`reveal` From c19c9ac60b135bb0f38fc997df5eb149db8144b4 Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Sun, 15 Dec 2024 04:28:49 -0500 Subject: [PATCH 10/69] chore: remove extraneous folder in specs (#1048) * chore: remove extraneous folder in specs * chore: remove internal doc * fix: memory adapters * chore: comment out unused * chore: move benchmark readme * chore: update diagram --- Cargo.lock | 74 ++++++++++++------------- Cargo.toml | 2 +- README.md | 49 +--------------- assets/modularity.png | Bin 0 -> 56887 bytes assets/modularity.svg | 1 - docs/README.md | 2 +- docs/crates/benchmarks.md | 42 ++++++++++++++ docs/specs/{vm => }/ISA.md | 0 docs/specs/README.md | 23 ++++---- docs/specs/{vm => }/RISCV.md | 0 docs/specs/{vm/stark.md => circuit.md} | 22 +++----- docs/specs/{vm => }/continuations.md | 0 docs/specs/{vm => }/memory.md | 23 ++++---- docs/specs/vm/README.md | 8 --- 14 files changed, 115 insertions(+), 131 deletions(-) create mode 100644 assets/modularity.png delete mode 100644 assets/modularity.svg create mode 100644 docs/crates/benchmarks.md rename docs/specs/{vm => }/ISA.md (100%) rename docs/specs/{vm => }/RISCV.md (100%) rename docs/specs/{vm/stark.md => circuit.md} (90%) rename docs/specs/{vm => }/continuations.md (100%) rename docs/specs/{vm => }/memory.md (68%) delete mode 100644 docs/specs/vm/README.md diff --git a/Cargo.lock b/Cargo.lock index 7dadd1ec53..ff542fd2b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1069,7 +1069,7 @@ dependencies = [ [[package]] name = "cargo-openvm" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "anstyle", "aws-config", @@ -3339,7 +3339,7 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openvm" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "bytemuck", "chrono", @@ -3390,7 +3390,7 @@ dependencies = [ [[package]] name = "openvm-algebra-guest" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "halo2curves-axiom 0.7.0", "num-bigint-dig", @@ -3404,7 +3404,7 @@ dependencies = [ [[package]] name = "openvm-algebra-moduli-setup" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "openvm-macros-common", "quote", @@ -3413,7 +3413,7 @@ dependencies = [ [[package]] name = "openvm-algebra-transpiler" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "openvm-algebra-guest", "openvm-instructions", @@ -3427,7 +3427,7 @@ dependencies = [ [[package]] name = "openvm-benchmarks" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "bincode 2.0.0-rc.3", "clap", @@ -3469,7 +3469,7 @@ dependencies = [ [[package]] name = "openvm-bigint-circuit" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "derive-new", "derive_more 1.0.0", @@ -3494,7 +3494,7 @@ dependencies = [ [[package]] name = "openvm-bigint-guest" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "num-bigint-dig", "num-traits", @@ -3506,7 +3506,7 @@ dependencies = [ [[package]] name = "openvm-bigint-transpiler" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "openvm-bigint-guest", "openvm-instructions", @@ -3520,7 +3520,7 @@ dependencies = [ [[package]] name = "openvm-build" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "cargo_metadata", "dirs", @@ -3534,7 +3534,7 @@ dependencies = [ [[package]] name = "openvm-circuit" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "ark-ff 0.4.2", "async-trait", @@ -3593,7 +3593,7 @@ dependencies = [ [[package]] name = "openvm-circuit-derive" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "itertools 0.13.0", "proc-macro2", @@ -3603,7 +3603,7 @@ dependencies = [ [[package]] name = "openvm-circuit-primitives" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "derive-new", "itertools 0.13.0", @@ -3625,7 +3625,7 @@ dependencies = [ [[package]] name = "openvm-circuit-primitives-derive" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "itertools 0.13.0", "proc-macro2", @@ -3671,7 +3671,7 @@ dependencies = [ [[package]] name = "openvm-ecc-guest" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "ecdsa 0.16.9", "elliptic-curve 0.13.8", @@ -3697,7 +3697,7 @@ dependencies = [ [[package]] name = "openvm-ecc-sw-setup" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "openvm-macros-common", "quote", @@ -3706,7 +3706,7 @@ dependencies = [ [[package]] name = "openvm-ecc-transpiler" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "openvm-ecc-guest", "openvm-instructions", @@ -3719,7 +3719,7 @@ dependencies = [ [[package]] name = "openvm-instructions" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "backtrace", "derive-new", @@ -3736,7 +3736,7 @@ dependencies = [ [[package]] name = "openvm-instructions-derive" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "openvm-instructions", "proc-macro2", @@ -3748,7 +3748,7 @@ dependencies = [ [[package]] name = "openvm-keccak256-circuit" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "derive-new", "derive_more 1.0.0", @@ -3778,7 +3778,7 @@ dependencies = [ [[package]] name = "openvm-keccak256-guest" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "openvm-platform", "serde", @@ -3787,7 +3787,7 @@ dependencies = [ [[package]] name = "openvm-keccak256-transpiler" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "openvm-instructions", "openvm-instructions-derive", @@ -3800,7 +3800,7 @@ dependencies = [ [[package]] name = "openvm-macros-common" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "syn 2.0.90", ] @@ -3828,7 +3828,7 @@ dependencies = [ [[package]] name = "openvm-native-circuit" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "derive-new", "derive_more 1.0.0", @@ -3895,7 +3895,7 @@ dependencies = [ [[package]] name = "openvm-native-compiler-derive" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "openvm-native-compiler", "openvm-native-recursion", @@ -3907,7 +3907,7 @@ dependencies = [ [[package]] name = "openvm-native-recursion" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "bitcode", "cfg-if", @@ -3967,7 +3967,7 @@ dependencies = [ [[package]] name = "openvm-pairing-guest" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "group 0.13.0", "halo2curves-axiom 0.7.0", @@ -3993,7 +3993,7 @@ dependencies = [ [[package]] name = "openvm-pairing-transpiler" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "openvm-instructions", "openvm-instructions-derive", @@ -4006,7 +4006,7 @@ dependencies = [ [[package]] name = "openvm-platform" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "bytemuck", "critical-section", @@ -4019,7 +4019,7 @@ dependencies = [ [[package]] name = "openvm-poseidon2-air" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "ark-ff 0.4.2", "itertools 0.13.0", @@ -4037,7 +4037,7 @@ dependencies = [ [[package]] name = "openvm-rv32-adapters" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "derive-new", "hex", @@ -4058,7 +4058,7 @@ dependencies = [ [[package]] name = "openvm-rv32im-circuit" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "derive-new", "derive_more 1.0.0", @@ -4086,7 +4086,7 @@ dependencies = [ [[package]] name = "openvm-rv32im-guest" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "openvm-platform", "strum_macros", @@ -4094,7 +4094,7 @@ dependencies = [ [[package]] name = "openvm-rv32im-transpiler" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "openvm-instructions", "openvm-instructions-derive", @@ -4108,7 +4108,7 @@ dependencies = [ [[package]] name = "openvm-sdk" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "async-trait", "bitcode", @@ -4214,7 +4214,7 @@ dependencies = [ [[package]] name = "openvm-toolchain-tests" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "derive_more 1.0.0", "eyre", @@ -4250,7 +4250,7 @@ dependencies = [ [[package]] name = "openvm-transpiler" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "derive_more 1.0.0", "elf", diff --git a/Cargo.toml b/Cargo.toml index 00f57edc13..c7b0865c92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "0.1.0" +version = "0.1.0-alpha" edition = "2021" rust-version = "1.82" authors = ["Intrinsic Technologies"] diff --git a/README.md b/README.md index 815ba4898a..b87d1d6fb1 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,6 @@ # OpenVM -**Install** -| User Book +[**Install**](https://book.openvm.dev/getting-started/install.html) +| [User Book](https://book.openvm.dev) | [Contributor Docs](./docs) -| Crate Docs - -## Benchmarks - -To run benchmarks, install python3 and run: - -```bash -python ci/scripts/bench.py -``` - -where `` is a benchmark implemented as a rust binary (located in `src/bin` in a crate). Current benchmark options are: - -- `verify_fibair` -- `fibonacci` -- `regex` - in the `benchmarks` crate. - The benchmark outputs a JSON of metrics. You can process this into markdown with: - -```bash -python ci/scripts/metric_unify/main.py -``` - -Currently the processing is done automatically at the end of `bench.py`. The script automatically detects if you have a previously saved metric file for the same benchmark and includes the diff report in the output. - -### Flamegraphs - -Flamegraphs to visualize the metrics collected by the VM cycle tracker can be generated if you have [inferno-flamegraph](https://crates.io/crates/inferno) installed. Install via - -```bash -cargo install inferno -``` - -Then run - -```bash -python ci/scripts/metric_unify/flamegraph.py -``` - -The flamegraphs will be written to `*.svg` files in `.bench_metrics/flamegraphs` with respect to the repo root. - -### Latest Benchmark Results - -Latest benchmark results can be found [here](https://github.com/axiom-crypto/afs-prototype/blob/benchmark-results/index.md). -These are run via [github workflows](./.github/workflows/benchmark-call.yml) and should always be up to date with the latest `main` branch. +| [Crate Docs](https://docs.openvm.dev/openvm) diff --git a/assets/modularity.png b/assets/modularity.png new file mode 100644 index 0000000000000000000000000000000000000000..338d58cd9815e438b6d5db91a780b4e74735338e GIT binary patch literal 56887 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 \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 9b456ec0b6..4673492376 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,4 +6,4 @@ This directory contains documentation for contributors. - [Design and Specification](./specs) - [Crates](./crates) -![](../assets/modularity.svg) +![](../assets/modularity.png) diff --git a/docs/crates/benchmarks.md b/docs/crates/benchmarks.md new file mode 100644 index 0000000000..8c1a4d8a8a --- /dev/null +++ b/docs/crates/benchmarks.md @@ -0,0 +1,42 @@ +# Benchmarks + +To run benchmarks, install python3 and run (from root of repo): + +```bash +python ci/scripts/bench.py +``` + +where `` is a benchmark implemented as a rust binary (located in `src/bin` in a crate). Current benchmark options are: + +- `verify_fibair` +- `fibonacci` +- `regex` + in the `benchmarks` crate. + The benchmark outputs a JSON of metrics. You can process this into markdown with: + +```bash +python ci/scripts/metric_unify/main.py +``` + +Currently the processing is done automatically at the end of `bench.py`. The script automatically detects if you have a previously saved metric file for the same benchmark and includes the diff report in the output. + +### Flamegraphs + +Flamegraphs to visualize the metrics collected by the VM cycle tracker can be generated if you have [inferno-flamegraph](https://crates.io/crates/inferno) installed. Install via + +```bash +cargo install inferno +``` + +Then run + +```bash +python ci/scripts/metric_unify/flamegraph.py +``` + +The flamegraphs will be written to `*.svg` files in `.bench_metrics/flamegraphs` with respect to the repo root. + +### Latest Benchmark Results + +Latest benchmark results can be found [here](https://github.com/openvm-org/openvm/blob/benchmark-results/index.md). +These are run via [github workflows](./.github/workflows/benchmark-call.yml) and should always be up to date with the latest `main` branch. diff --git a/docs/specs/vm/ISA.md b/docs/specs/ISA.md similarity index 100% rename from docs/specs/vm/ISA.md rename to docs/specs/ISA.md diff --git a/docs/specs/README.md b/docs/specs/README.md index 220693e521..79b84a9e25 100644 --- a/docs/specs/README.md +++ b/docs/specs/README.md @@ -1,26 +1,25 @@ # Design and Specifications -- [Virtual Machines](./vm/README.md) -- [Aggregation](./aggregation.md) - design for VM-based proof aggregation. - ## Design OpenVM provides the modular framework to co-design and build a custom zkVM, ISA, and supporting programming language frontend simultaneously. -- The [STARK architecture](./vm/stark.md) explains the VM architecture, which focuses on maximizing modularity and composability. The architecture supports adding arbitrary chips to handle custom instructions, as long as they fall within our ISA framework. +- The [Circuit Architecture](./circuit.md) explains the VM circuit architecture, which focuses on maximizing modularity and composability. The architecture supports adding arbitrary chips to handle custom instructions, as long as they fall within our ISA framework. - There are a few required system chips: Program, Connector, Range Checker, Memory (which can be several chips depending on the configuration) -- The OpenVM [Instruction Set Architecture](./vm/ISA.md) specifies our ISA framework and also lists the currently supported instructions in different VM extensions. Our ISA is specialized for zkVMs and provide additional flexibility over traditional machine architectures: +- The [Instruction Set Architecture](./ISA.md) specifies our ISA framework and also lists the currently supported instructions in different VM extensions. Our ISA is specialized for zkVMs and provide additional flexibility over traditional machine architectures: - Support for multiple traditional machine architectures _simultaneously_ with multiple memory address spaces. These address spaces also allow support for both register and stack based architectures. - Variable word size, which allows flexible support for different register sizes and also higher bandwidth memory accesses. -- Programming language support is provided using Rust as the language frontend. We compile Rust to 32-bit RISC-V ELF binary via LLVM. To provide intrinsic support for custom instructions within Rust, we use Rust to insert custom RISC-V directives to the LLVM assembler, which are assembled into the ELF. We use an extendable [transpiler](./vm/RISCV.md) to convert the RISC-V ELF into OpenVM assembly. While intrinsic instructions are custom, they still respect the RISC-V architecture. Our framework supports the addition of additional frontends to generate OpenVM assembly, which can be included within Rust itself via procedural macros or as a standalone frontend to generate OpenVM assembly. - - [RISC-V custom instructions and transpiler](./vm/RISCV.md) +- Programming language support is provided using Rust as the language frontend. We compile Rust to 32-bit RISC-V ELF binary via LLVM. To provide intrinsic support for custom instructions within Rust, we use Rust to insert custom RISC-V directives to the LLVM assembler, which are assembled into the ELF. We use an extendable [transpiler](./RISCV.md) to convert the RISC-V ELF into OpenVM assembly. While intrinsic instructions are custom, they still respect the RISC-V architecture. Our framework supports the addition of additional frontends to generate OpenVM assembly, which can be included within Rust itself via procedural macros or as a standalone frontend to generate OpenVM assembly. + - [RISC-V custom instructions and transpiler](./RISCV.md) - We provide a general recursion library written in a standalone Rust eDSL for OpenVM native kernel instructions. The library supports inner aggregation of arbitrary STARK proofs, outer aggregation using Halo2, and on-chain verification of the aggregated SNARK proof. -- All VMs within our framework support proving of programs with unbounded cycle count using continuations. Our [continuations design](./vm/continuations.md) maximizes proving parallelism and does not rely on any interactive communication between continuation segments. +- All VMs within our framework support proving of programs with unbounded cycle count using continuations. Our [continuations design](./continuations.md) maximizes proving parallelism and does not rely on any interactive communication between continuation segments. ### Extensions -The framework is designed to be extendable via external crates _without forking_. A new extension of the overall architecture involves adding 3 new crates: +The framework is designed to be extendable via external crates _without forking_. +VM extensions provide a way to simultaneously extend the VM with new chips, opcodes, and toolchain support for these opcodes. +A new extension of the overall architecture consists of three components: -- Library: the no_std guest library that should be importable from guest program with target_os=zkvm -- Transpiler extension: implement `TranspilerExtension` trait to specify how newly introduced custom RISC-V instructions should be transpiled into custom OpenVM instructions. -- VM extension: implement `VmExtension` trait and define new chips and assign them to handle the new opcodes. +- Library: the guest library that compiles program code (usually in Rust) into RISC-V assembly with custom instructions. +- Transpiler extension: extend the transpiler to specify how newly introduced custom RISC-V instructions should be transpiled into custom OpenVM instructions. +- VM extension: define new chips and assign them to handle the new opcodes. diff --git a/docs/specs/vm/RISCV.md b/docs/specs/RISCV.md similarity index 100% rename from docs/specs/vm/RISCV.md rename to docs/specs/RISCV.md diff --git a/docs/specs/vm/stark.md b/docs/specs/circuit.md similarity index 90% rename from docs/specs/vm/stark.md rename to docs/specs/circuit.md index 2c12e62577..d5747c5da5 100644 --- a/docs/specs/vm/stark.md +++ b/docs/specs/circuit.md @@ -1,4 +1,4 @@ -# STARK Architecture +# Circuit Architecture We build our virtual machines in a STARK proving system with a multi-matrix commitment scheme and shared verifier randomness between AIR matrices to enable permutation arguments such as log-up. @@ -85,9 +85,6 @@ instructions similarly to most of the other opcodes. Also, instead of having the and duplicate the instructions with the chips that actually execute them, we have each step of execution only generating one new row in the machine chip (and maybe more lines in other primitive chips that it uses for execution). -See [internal doc](https://docs.google.com/document/d/1-UkvxiW5tvYH5qw7O4t2WjMIY8v2Gso9kt_MrWW5hPg/edit?usp=sharing) for -discussion of alternatives and the trace cell cost analysis. - ### Offline Memory In the no-CPU design, each chip receives the opcode instruction directly, and memory access (read or write) is @@ -130,6 +127,7 @@ Read) sets. Any memory access in a chip must add one entry into each set and con To balance the Read and Write sets, an additional chip must ensure that every accessed address has an initial $(a, v_ {init}, 0)$ added to the Write set, and $(a, v_{final}, t_{last})$ added to Read set. + The initial and final memory accesses are constrained different when the VM has continuations. See [Continuations](./continuations.md) for full details. In summary, because the initial and final memory states are @@ -160,19 +159,14 @@ The main idea is that in the offline checking memory argument [above](#offline-m t)$ where the length of $v$ is variable. The difference in word sizes only needs to be resolved when there is a sequence of read+write or write+read involving different word sizes. -We introduce chips MemoryConverterChip[$w$] and MemoryPackChip[$w$] where +We introduce chips `AccessAdapterChip` that can: -- MemoryConverterChip: read $(a, v_0 || ... || v_{B-1}, t)$ and write $(a + B \cdot i, v_i, t + 1)$ for $i \in [0,B)$ - where $B$ is `WORD_BLOCK_SIZE`, or -- MemoryConverterChip: read $(a + B \cdot i, v_i, t - 1)$ for $i \in [0,B)$ and write $(a, v_0 || ... || v_{w-1}, t)$ to - the memory. -- MemoryPackChip: read $(a + B \cdot i, v_i, t_{prev,i})$ for $i \in [0,B)$ and write $(a, v_0 || ... || v_{w-1}, t)$ to - the memory. It must constrain $t_{prev,i} < t$. The difference between this and MemoryConverterChip is that it allows - packing smaller words that may have been modified at different previous times. This packing is more expensive. +- read $(a, v_0 || ... || v_{N-1}, t)$ and write $(a, v_0 || ... || v_{N/2 - 1}, t)$ and $(a + N/2, v_{N/2} || ... || v_{N-1}, t)$ +- read $(a, v_0 || ... || v_{N/2 - 1}, t_0)$ and $(a + N / 2, v_{N/2} || ... || v_{N-1}, t_1)$ and write $(a, v_0 || .. || v_{N-1}, max(t_0, t_1))$ -Here the length of $v_i$ is $w$. +where we allow `N` to be different powers of two. -The values of $a, v_i$ that appear in the trace of the converter chip is generated on-demand based on the needs of the +The values of $a, v_i$ that appear in the trace of the access adapter chip are generated on-demand based on the needs of the runtime memory access. In other words, the converter inserts additional writes into the MEMORY_BUS when needed in order to link up accesses of different word sizes. diff --git a/docs/specs/vm/continuations.md b/docs/specs/continuations.md similarity index 100% rename from docs/specs/vm/continuations.md rename to docs/specs/continuations.md diff --git a/docs/specs/vm/memory.md b/docs/specs/memory.md similarity index 68% rename from docs/specs/vm/memory.md rename to docs/specs/memory.md index 072ed8e39e..22170d1d62 100644 --- a/docs/specs/vm/memory.md +++ b/docs/specs/memory.md @@ -1,33 +1,34 @@ # Overview + Chips in the VM need to perform memory read and write operations. The goal of the memory offline checking is to ensure that memory consistency across all chips. Every memory operation consists of operation type (Read or Write), address (address_space and pointer), data, and timestamp. All memory operations across all chips should happen at distinct timestamps between 1 and 2^29. We assume that memory is initialized at timestamp 0. For simplicity, we assume that all memory operations are enabled (there is a way to disable them in the implementation). We call an address accessed when it's initialized, finalized, read from, or written to. An address is initialized at timestamp 0 and is finalized at the same timestamp it was last read from or written to (or 0 if there were no operations involving it). To verify memory consistency, we use MEMORY_BUS to post information about all memory accesses, done through interactions. + - To verify Read (`address`, `data`, `new_timestamp`) operations, we need to know `prev_timestamp`, the previous timestamp the address was accessed. We enforce that `prev_timestamp < new_timestamp`, and do the following interactions on MEMORY_BUS: - - Send (`address`, `data`, `prev_timestamp`) - - Receive (`address`, `data`, `new_timestamp`) + - Send (`address`, `data`, `prev_timestamp`) + - Receive (`address`, `data`, `new_timestamp`) - To verify Write (`address, new_data, new_timestamp`) operations, we need to know `prev_timestamp` and `prev_data`, the previous timestamp the address was accessed and the data stored at the address at that time. We enforce that `prev_timestamp` < `new_timestamp`, and do the following interactions on MEMORY_BUS: - - Send (`address`, `prev_data`, `prev_timestamp`) - - Receive (`address`, `new_data`, `new_timestamp`) + - Send (`address`, `prev_data`, `prev_timestamp`) + - Receive (`address`, `new_data`, `new_timestamp`) -To initialize and finalize memory, we need a Memory Interface chip. For every `address` used in the segment, suppose it's initialized with `initial_data`, `final_data` is stored at the address at the end of the segment, and `final_timestamp` is the timestamp of the last operation involving it in the segment. Then, the interface chip does the following interactions on MEMORY_BUS: - - Send (`address`, `initial_data`, 0) - - Receive (`address`, `final_data`, `final_timestamp`) +To initialize and finalize memory, we need a Memory Interface chip. For every `address` used in the segment, suppose it's initialized with `initial_data`, `final_data` is stored at the address at the end of the segment, and `final_timestamp` is the timestamp of the last operation involving it in the segment. Then, the interface chip does the following interactions on MEMORY_BUS: - Send (`address`, `initial_data`, 0) - Receive (`address`, `final_data`, `final_timestamp`) Note that all interactions use multiplicity 1. Crucially, the Memory Interface does exactly one such Send and Receive for every `address` used in the segment. In particular, the AIR enforces that all addresses those interactions are done on are distinct. # Soundness proof + Assume that the MEMORY_BUS interactions and the constraints mentioned above are satisfied. Fix any address `address` that is used in the segment. To prove memory consistency, it's enough to prove all memory operations on `address` are consistent. Let's look at all interactions done on MEMORY_BUS involving `address`. -Suppose the list of operations involving `address` *sorted* by `timestamp` is `ops_i` for `0 <= i < k`. As discussed above, for every operation `i`, we do one Receive, `r_i`, and one Send, `s_i`, on the MEMORY_BUS. Since the constraint `r_i.timestamp < s_i.timestamp` is enforced, the only way for the MEMORY_BUS interactions to balance out is through the interactions involving `address` done by the Memory Interface. This can be seen by noticing that none of the Receive interactions `r_i` can match `s_{k-1}` as it has the highest timestamp. In fact this implies that the Memory Interface has to do exactly one Receive involving `address` with the final timestamp and data, and, similarly, one Send with the initial timestamp (0) and data. Note that only one such Send and Receive are made as we enforce all addresses in Memory Interface are distinct. +Suppose the list of operations involving `address` _sorted_ by `timestamp` is `ops_i` for `0 <= i < k`. As discussed above, for every operation `i`, we do one Receive, `r_i`, and one Send, `s_i`, on the MEMORY*BUS. Since the constraint `r_i.timestamp < s_i.timestamp` is enforced, the only way for the MEMORY_BUS interactions to balance out is through the interactions involving `address` done by the Memory Interface. This can be seen by noticing that none of the Receive interactions `r_i` can match `s*{k-1}`as it has the highest timestamp. In fact this implies that the Memory Interface has to do exactly one Receive involving`address` with the final timestamp and data, and, similarly, one Send with the initial timestamp (0) and data. Note that only one such Send and Receive are made as we enforce all addresses in Memory Interface are distinct. Using a similar technique, by induction, we can show that `s_i.timestamp = r_{i+1}.timestamp` and `s_i.data = r_{i+1}.data` for all `0 < i < k - 1`. Since `(s_i.address, s_i.data, s_i.timestamp) = (ops_i.address, ops_i.data, ops_i.timestamp)` for all operations and `s_i.data = r_i.data` for Read operations, this proves memory consistency for `address`. - # Implementation details -In this model, there is no central memory/offline checker AIR. Every chip is responsible for doing the necessary interactions discussed above for its memory operations. To do this, every chip's AIR to have some auxiliary columns for every memory operation. The auxiliary columns include `prev_timestamp` and, for Write operations, `prev_data`. -When we use Volatile Memory as the Memory Interface (MemoryAuditChip in the implementation), we do not, on the AIR level, constrain the initial memory. This means that if the first operation on an address is a Read, the corresponding data can be anything -- it's on the program to read from addresses that have been written to. Separately, the MemoryAuditAIR enforces that all addresses are distinct by enforcing sorting, but there are other more efficient ways to do this. +In this model, there is no central memory/offline checker AIR. Every chip is responsible for doing the necessary interactions discussed above for its memory operations. To do this, every chip's AIR to have some auxiliary columns for every memory operation. The auxiliary columns include `prev_timestamp` and, for Write operations, `prev_data`. + +When we use Volatile Memory as the Memory Interface (PersistentBoundaryAir in the implementation), we do not, on the AIR level, constrain the initial memory. This means that if the first operation on an address is a Read, the corresponding data can be anything -- it's on the program to read from addresses that have been written to. Separately, the PeristentBoundaryAir enforces that all addresses are distinct by enforcing sorting, but there are other more efficient ways to do this. diff --git a/docs/specs/vm/README.md b/docs/specs/vm/README.md deleted file mode 100644 index 364c044ec1..0000000000 --- a/docs/specs/vm/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Modular Virtual Machines - -This section describes our modular framework that allows to build STARK virtual machines with customizable opcodes. - -- [ISA](./ISA.md) discusses the instruction set architecture that all virtual machines in this framework must follow, and the memory model we use. This spec is purely at the computer architecture level and does not discuss the STARK implementations (although the design choices are influenced by these considerations). -- [RISC-V](./RISCV.md) defines custom RISC-V instructions for OpenVM and the transpilation spec from RV32IM and these custom RISC-V instructions to OpenVM instructions. -- [STARK](./stark.md) discusses the design of how components of the virtual machine are implemented as STARKs. -- [Continuations](./continuations.md) discusses how continuations can be enabled for any virtual machine. From 2cc6fdf1264352130787a357bfcabc9e667b895d Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Sun, 15 Dec 2024 12:49:19 -0500 Subject: [PATCH 11/69] book: introduction (#1050) * feat: first draft of book intro * chore: wording --- book/src/introduction.md | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/book/src/introduction.md b/book/src/introduction.md index 65a5d72df6..08c1bde13e 100644 --- a/book/src/introduction.md +++ b/book/src/introduction.md @@ -1,17 +1,24 @@ -# Introduction +# OpenVM -OpenVM is ... +_A modular toolkit for extensible zkVMs_ -... is _modular_, which means that its functionality is provided by several independent components. In particular, one can expand the functionality of OpenVM by adding new components. +OpenVM is an open-source zero-knowledge virtual machine (zkVM) framework focused on modularity at every level of the stack. OpenVM is designed for customization and extensibility without sacrificing performance or maintainability. -An _extension_ (we could also call it a _module_ but we prefer not to in order to avoid confusion with the concept of a _module_ in the Rust language) is a component that provides a specific functionality. It consists of the following parts: +## Key Features -- one -- two -- three +- **Modular no-CPU Architecture**: Unlike traditional machine architectures, the OpenVM architecture has no central processing unit. This design choice allows for seamless integration of custom chips, **without forking or modifying the core architecture**. -... +- **Extensible Instruction Set**: The instruction set architecture (ISA) is designed to be extended with new custom instructions that integrate directly with the virtual machine. -The next chapters are supposed to serve as a manual for using this modularity. +- **Rust Frontend**: ISA extensions are directly accessible through a Rust frontend via [intrinsic functions](https://en.wikipedia.org/wiki/Intrinsic_function), providing a smooth developer experience. -# In particular, Chapter 2 is for this, Chapter 3 is for that, et cetera. +- **On-chain Verification**: Every VM made using the framework comes with out-of-the-box support for unbounded program proving with verification on Ethereum. + +## Using This Book + +The following chapters will guide you through: + +- [Getting started](./getting-started/install.md) +- [Writing applications](./writing-apps/overview.md) in Rust targeting OpenVM and generating proofs. +- [Using existing extensions](./using-extensions/) to optimize your Rust programs. +- How to add custom VM extensions From 65494db55992a4d3fe8be19f3f7c59b94083a212 Mon Sep 17 00:00:00 2001 From: Yi Sun Date: Sun, 15 Dec 2024 14:05:59 -0600 Subject: [PATCH 12/69] chore: add MIT license (#1047) * chore: add MIT license * chore: update authors --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..49fac70f94 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 The OpenVM Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 8eb4543608743d1398182e7461bdec76b8ab1bbc Mon Sep 17 00:00:00 2001 From: Xinding Wei Date: Sun, 15 Dec 2024 12:26:12 -0800 Subject: [PATCH 13/69] [refactor] Implement Chip for All Memory Chips (#1038) * Implement Chip for all memory chips * Parallize Map transformation --- crates/circuits/primitives/derive/src/lib.rs | 36 ++- crates/vm/src/arch/testing/mod.rs | 26 +- crates/vm/src/system/memory/adapter/mod.rs | 88 +++---- .../vm/src/system/memory/manager/interface.rs | 1 + crates/vm/src/system/memory/manager/mod.rs | 208 +++++++--------- crates/vm/src/system/memory/merkle/mod.rs | 21 +- .../vm/src/system/memory/merkle/tests/mod.rs | 84 +++---- .../vm/src/system/memory/merkle/tests/util.rs | 17 +- crates/vm/src/system/memory/merkle/trace.rs | 67 ++++- crates/vm/src/system/memory/persistent.rs | 230 +++++++++++++----- crates/vm/src/system/memory/volatile/mod.rs | 110 +++++---- crates/vm/src/system/memory/volatile/tests.rs | 52 ++-- 12 files changed, 548 insertions(+), 392 deletions(-) diff --git a/crates/circuits/primitives/derive/src/lib.rs b/crates/circuits/primitives/derive/src/lib.rs index 1e1ba1aa52..18db7d2970 100644 --- a/crates/circuits/primitives/derive/src/lib.rs +++ b/crates/circuits/primitives/derive/src/lib.rs @@ -5,7 +5,7 @@ extern crate proc_macro; use itertools::multiunzip; use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, Data, DeriveInput, Fields, GenericParam}; +use syn::{parse_macro_input, Data, DeriveInput, Fields, GenericParam, LitStr, Meta}; #[proc_macro_derive(AlignedBorrow)] pub fn aligned_borrow_derive(input: TokenStream) -> TokenStream { @@ -72,8 +72,9 @@ pub fn aligned_borrow_derive(input: TokenStream) -> TokenStream { TokenStream::from(methods) } -#[proc_macro_derive(Chip)] +#[proc_macro_derive(Chip, attributes(chip))] pub fn chip_derive(input: TokenStream) -> TokenStream { + // Parse the attributes from the struct or enum let ast: syn::DeriveInput = syn::parse(input).unwrap(); let name = &ast.ident; @@ -160,6 +161,37 @@ pub fn chip_derive(input: TokenStream) -> TokenStream { let where_clause = new_generics.make_where_clause(); where_clause.predicates.push(syn::parse_quote! { openvm_stark_backend::config::Domain: openvm_stark_backend::p3_commit::PolynomialSpace }); + let attributes = ast.attrs.iter().find(|&attr| attr.path().is_ident("chip")); + if let Some(attr) = attributes { + let mut fail_flag = false; + + match &attr.meta { + Meta::List(meta_list) => { + meta_list + .parse_nested_meta(|meta| { + if meta.path.is_ident("where") { + let value = meta.value()?; // this parses the `=` + let s: LitStr = value.parse()?; + let where_value = s.value(); + where_clause.predicates.push(syn::parse_str(&where_value)?); + } else { + fail_flag = true; + } + Ok(()) + }) + .unwrap(); + } + _ => fail_flag = true, + } + if fail_flag { + return syn::Error::new( + name.span(), + "Only `#[chip(where = ...)]` format is supported", + ) + .to_compile_error() + .into(); + } + } quote! { impl #impl_generics openvm_stark_backend::Chip for #name #ty_generics #where_clause { diff --git a/crates/vm/src/arch/testing/mod.rs b/crates/vm/src/arch/testing/mod.rs index c960d5ab18..4ad33640ba 100644 --- a/crates/vm/src/arch/testing/mod.rs +++ b/crates/vm/src/arch/testing/mod.rs @@ -1,16 +1,12 @@ use std::{cell::RefCell, rc::Rc, sync::Arc}; -use itertools::izip; use openvm_circuit_primitives::var_range::{VariableRangeCheckerBus, VariableRangeCheckerChip}; use openvm_instructions::instruction::Instruction; use openvm_stark_backend::{ config::{StarkGenericConfig, Val}, engine::VerificationData, p3_field::PrimeField32, - p3_matrix::{ - dense::{DenseMatrix, RowMajorMatrix}, - Matrix, - }, + p3_matrix::dense::{DenseMatrix, RowMajorMatrix}, prover::types::AirProofInput, verifier::VerificationError, Chip, @@ -267,21 +263,15 @@ where let range_checker = memory_controller.borrow().range_checker.clone(); self = self.load(memory_tester); // dummy memory interactions { - let memory = memory_controller.borrow(); - let public_values = memory.generate_public_values_per_air(); - let airs = memory.airs(); - drop(memory); - let traces = Rc::try_unwrap(memory_controller) + let air_proof_inputs = Rc::try_unwrap(memory_controller) .unwrap() .into_inner() - .generate_traces(); - - for (pvs, air, trace) in izip!(public_values, airs, traces) { - if trace.height() > 0 { - self.air_proof_inputs - .push(AirProofInput::simple(air, trace, pvs)); - } - } + .generate_air_proof_inputs(); + self.air_proof_inputs.extend( + air_proof_inputs + .into_iter() + .filter(|api| api.main_trace_height() > 0), + ); } self = self.load(range_checker); // this must be last because other trace generation mutates its state } diff --git a/crates/vm/src/system/memory/adapter/mod.rs b/crates/vm/src/system/memory/adapter/mod.rs index 652d918cbc..ff015a19c8 100644 --- a/crates/vm/src/system/memory/adapter/mod.rs +++ b/crates/vm/src/system/memory/adapter/mod.rs @@ -7,7 +7,7 @@ use openvm_circuit_primitives::{ is_less_than::IsLtSubAir, utils::next_power_of_two_or_zero, var_range::VariableRangeCheckerChip, TraceSubRowGenerator, }; -use openvm_circuit_primitives_derive::ChipUsageGetter; +use openvm_circuit_primitives_derive::{Chip, ChipUsageGetter}; use openvm_stark_backend::{ config::{Domain, StarkGenericConfig, Val}, p3_air::BaseAir, @@ -31,6 +31,7 @@ mod tests; #[derive(Debug, Clone)] pub struct AccessAdapterInventory { chips: Vec>, + air_names: Vec, } impl AccessAdapterInventory { @@ -44,19 +45,19 @@ impl AccessAdapterInventory { let mb = memory_bus; let cmb = clk_max_bits; let maan = max_access_adapter_n; - Self { - chips: [ - Self::create_access_adapter_chip::<2>(rc.clone(), mb, cmb, maan), - Self::create_access_adapter_chip::<4>(rc.clone(), mb, cmb, maan), - Self::create_access_adapter_chip::<8>(rc.clone(), mb, cmb, maan), - Self::create_access_adapter_chip::<16>(rc.clone(), mb, cmb, maan), - Self::create_access_adapter_chip::<32>(rc.clone(), mb, cmb, maan), - Self::create_access_adapter_chip::<64>(rc.clone(), mb, cmb, maan), - ] - .into_iter() - .flatten() - .collect(), - } + let chips: Vec<_> = [ + Self::create_access_adapter_chip::<2>(rc.clone(), mb, cmb, maan), + Self::create_access_adapter_chip::<4>(rc.clone(), mb, cmb, maan), + Self::create_access_adapter_chip::<8>(rc.clone(), mb, cmb, maan), + Self::create_access_adapter_chip::<16>(rc.clone(), mb, cmb, maan), + Self::create_access_adapter_chip::<32>(rc.clone(), mb, cmb, maan), + Self::create_access_adapter_chip::<64>(rc.clone(), mb, cmb, maan), + ] + .into_iter() + .flatten() + .collect(); + let air_names = (0..chips.len()).map(|i| air_name(1 << (i + 1))).collect(); + Self { chips, air_names } } pub fn num_access_adapters(&self) -> usize { self.chips.len() @@ -80,9 +81,16 @@ impl AccessAdapterInventory { .map(|chip| chip.current_trace_height()) .collect() } + #[allow(dead_code)] pub fn get_widths(&self) -> Vec { self.chips.iter().map(|chip| chip.trace_width()).collect() } + pub fn get_cells(&self) -> Vec { + self.chips + .iter() + .map(|chip| chip.current_trace_cells()) + .collect() + } pub fn airs(&self) -> Vec>> where F: PrimeField32, @@ -90,23 +98,16 @@ impl AccessAdapterInventory { { self.chips.iter().map(|chip| chip.air()).collect() } - pub fn generate_traces(self) -> Vec> - where - F: PrimeField32, - { - self.chips - .into_par_iter() - .map(|chip| chip.generate_trace()) - .collect() + pub fn air_names(&self) -> Vec { + self.air_names.clone() } - #[allow(dead_code)] - pub fn generate_air_proof_input(self) -> Vec> + pub fn generate_air_proof_inputs(self) -> Vec> where F: PrimeField32, Domain: PolynomialSpace, { self.chips - .into_par_iter() + .into_iter() .map(|chip| chip.generate_air_proof_input()) .collect() } @@ -157,8 +158,9 @@ pub trait GenericAccessAdapterChipTrait { F: PrimeField32; } -#[derive(Debug, Clone, ChipUsageGetter)] +#[derive(Debug, Clone, Chip, ChipUsageGetter)] #[enum_dispatch(GenericAccessAdapterChipTrait)] +#[chip(where = "F: PrimeField32")] enum GenericAccessAdapterChip { N2(AccessAdapterChip), N4(AccessAdapterChip), @@ -168,33 +170,6 @@ enum GenericAccessAdapterChip { N64(AccessAdapterChip), } -impl Chip for GenericAccessAdapterChip> -where - Val: PrimeField32, -{ - fn air(&self) -> Arc> { - match self { - GenericAccessAdapterChip::N2(chip) => chip.air(), - GenericAccessAdapterChip::N4(chip) => chip.air(), - GenericAccessAdapterChip::N8(chip) => chip.air(), - GenericAccessAdapterChip::N16(chip) => chip.air(), - GenericAccessAdapterChip::N32(chip) => chip.air(), - GenericAccessAdapterChip::N64(chip) => chip.air(), - } - } - - fn generate_air_proof_input(self) -> AirProofInput { - match self { - GenericAccessAdapterChip::N2(chip) => chip.generate_air_proof_input(), - GenericAccessAdapterChip::N4(chip) => chip.generate_air_proof_input(), - GenericAccessAdapterChip::N8(chip) => chip.generate_air_proof_input(), - GenericAccessAdapterChip::N16(chip) => chip.generate_air_proof_input(), - GenericAccessAdapterChip::N32(chip) => chip.generate_air_proof_input(), - GenericAccessAdapterChip::N64(chip) => chip.generate_air_proof_input(), - } - } -} - impl GenericAccessAdapterChip { fn new( range_checker: Arc, @@ -313,7 +288,7 @@ where impl ChipUsageGetter for AccessAdapterChip { fn air_name(&self) -> String { - format!("AccessAdapter<{}>", N) + air_name(N) } fn current_trace_height(&self) -> usize { @@ -324,3 +299,8 @@ impl ChipUsageGetter for AccessAdapterChip { BaseAir::::width(&self.air) } } + +#[inline] +fn air_name(n: usize) -> String { + format!("AccessAdapter<{}>", n) +} diff --git a/crates/vm/src/system/memory/manager/interface.rs b/crates/vm/src/system/memory/manager/interface.rs index 1ef03726bb..a3a69d8b1a 100644 --- a/crates/vm/src/system/memory/manager/interface.rs +++ b/crates/vm/src/system/memory/manager/interface.rs @@ -7,6 +7,7 @@ use crate::system::memory::{ Equipartition, CHUNK, }; +#[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum MemoryInterface { Volatile { diff --git a/crates/vm/src/system/memory/manager/mod.rs b/crates/vm/src/system/memory/manager/mod.rs index 33cb989b49..595257a3c4 100644 --- a/crates/vm/src/system/memory/manager/mod.rs +++ b/crates/vm/src/system/memory/manager/mod.rs @@ -9,7 +9,6 @@ use std::{ }; use getset::Getters; -use itertools::{izip, zip_eq}; pub use memory::{MemoryReadRecord, MemoryWriteRecord}; use openvm_circuit_primitives::{ assert_less_than::{AssertLtSubAir, LessThanAuxCols}, @@ -21,13 +20,13 @@ use openvm_circuit_primitives::{ use openvm_instructions::exe::MemoryImage; use openvm_stark_backend::{ config::{Domain, StarkGenericConfig}, - p3_air::BaseAir, p3_commit::PolynomialSpace, p3_field::PrimeField32, - p3_matrix::dense::RowMajorMatrix, + p3_maybe_rayon::prelude::{IntoParallelIterator, ParallelIterator}, p3_util::log2_strict_usize, prover::types::AirProofInput, rap::AnyRap, + Chip, ChipUsageGetter, }; use serde::{Deserialize, Serialize}; @@ -66,12 +65,6 @@ pub struct TimestampedValues { pub values: [T; N], } -#[derive(Clone, Debug)] -pub struct MemoryControllerResult { - traces: Vec>, - public_values: Vec>, -} - pub type MemoryControllerRef = Rc>>; /// A equipartition of memory, with timestamps and values. @@ -106,11 +99,26 @@ pub struct MemoryController { memory: Memory, access_adapters: AccessAdapterInventory, - /// If set, the height of the traces will be overridden. - overridden_heights: Option, // Filled during finalization. - result: Option>, + final_state: Option>, +} + +#[allow(clippy::large_enum_variant)] +#[derive(Debug)] +enum FinalState { + Volatile(VolatileFinalState), + #[allow(dead_code)] + Persistent(PersistentFinalState), +} +#[derive(Debug, Default)] +struct VolatileFinalState { + _marker: PhantomData, +} +#[allow(dead_code)] +#[derive(Debug)] +struct PersistentFinalState { + final_memory: Equipartition, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] @@ -236,8 +244,7 @@ impl MemoryController { ), range_checker, range_checker_bus, - result: None, - overridden_heights: None, + final_state: None, } } @@ -279,29 +286,34 @@ impl MemoryController { ), range_checker, range_checker_bus, - result: None, - overridden_heights: None, + final_state: None, } } pub fn set_override_trace_heights(&mut self, overridden_heights: MemoryTraceHeights) { - match &self.interface_chip { - MemoryInterface::Volatile { .. } => match &overridden_heights { + match &mut self.interface_chip { + MemoryInterface::Volatile { boundary_chip } => match overridden_heights { MemoryTraceHeights::Volatile(oh) => { + boundary_chip.set_overridden_height(oh.boundary); self.access_adapters - .set_override_trace_heights(oh.access_adapters.clone()); + .set_override_trace_heights(oh.access_adapters); } _ => panic!("Expect overridden_heights to be MemoryTraceHeights::Volatile"), }, - MemoryInterface::Persistent { .. } => match &overridden_heights { + MemoryInterface::Persistent { + boundary_chip, + merkle_chip, + .. + } => match overridden_heights { MemoryTraceHeights::Persistent(oh) => { + boundary_chip.set_overridden_height(oh.boundary); + merkle_chip.set_overridden_height(oh.merkle); self.access_adapters - .set_override_trace_heights(oh.access_adapters.clone()); + .set_override_trace_heights(oh.access_adapters); } _ => panic!("Expect overridden_heights to be MemoryTraceHeights::Persistent"), }, } - self.overridden_heights = Some(overridden_heights); } pub fn set_initial_memory(&mut self, memory: Equipartition) { @@ -451,27 +463,15 @@ impl MemoryController { &mut self, hasher: Option<&mut impl HasherChip>, ) -> Option> { - if self.result.is_some() { + if self.final_state.is_some() { panic!("Cannot finalize more than once"); } - let mut traces = vec![]; - let mut pvs = vec![]; let (records, final_memory) = match &mut self.interface_chip { MemoryInterface::Volatile { boundary_chip } => { - let overridden_heights = self.overridden_heights.as_ref().map(|oh| match oh { - MemoryTraceHeights::Volatile(oh) => oh, - _ => unreachable!(), - }); let (final_memory, records) = self.memory.finalize::<1>(); - debug_assert_eq!(traces.len(), BOUNDARY_AIR_OFFSET); - traces.push( - boundary_chip - .generate_trace(&final_memory, overridden_heights.map(|oh| oh.boundary)), - ); - debug_assert_eq!(pvs.len(), BOUNDARY_AIR_OFFSET); - pvs.push(vec![]); - + boundary_chip.finalize(final_memory); + self.final_state = Some(FinalState::Volatile(VolatileFinalState::default())); (records, None) } MemoryInterface::Persistent { @@ -479,45 +479,24 @@ impl MemoryController { boundary_chip, initial_memory, } => { - let overridden_heights = self.overridden_heights.as_ref().map(|oh| match oh { - MemoryTraceHeights::Persistent(oh) => oh, - _ => unreachable!(), - }); let hasher = hasher.unwrap(); - let (final_partition, records) = self.memory.finalize::<8>(); - traces.push(boundary_chip.generate_trace( - initial_memory, - &final_partition, - hasher, - overridden_heights.map(|oh| oh.boundary), - )); - pvs.push(vec![]); - + let (final_partition, records) = self.memory.finalize::(); + boundary_chip.finalize(initial_memory, &final_partition, hasher); let final_memory_values = final_partition - .iter() - .map(|(key, value)| (*key, value.values)) + .into_par_iter() + .map(|(key, value)| (key, value.values)) .collect(); - let initial_node = MemoryNode::tree_from_memory( merkle_chip.air.memory_dimensions, initial_memory, hasher, ); - let (expand_trace, final_node) = merkle_chip.generate_trace_and_final_tree( - &initial_node, - &final_memory_values, - hasher, - overridden_heights.map(|oh| oh.merkle), - ); - - debug_assert_eq!(traces.len(), MERKLE_AIR_OFFSET); - traces.push(expand_trace); - let mut expand_pvs = vec![]; - expand_pvs.extend(initial_node.hash()); - expand_pvs.extend(final_node.hash()); - debug_assert_eq!(pvs.len(), MERKLE_AIR_OFFSET); - pvs.push(expand_pvs); + merkle_chip.finalize(&initial_node, &final_memory_values, hasher); + self.final_state = Some(FinalState::Persistent(PersistentFinalState { + final_memory: final_memory_values.clone(), + })); + // FIXME: avoid clone here. (records, Some(final_memory_values)) } }; @@ -525,17 +504,6 @@ impl MemoryController { self.access_adapters.add_record(record); } - // FIXME: avoid clone. - let aa_traces = self.access_adapters.clone().generate_traces(); - let aa_pvs = vec![vec![]; aa_traces.len()]; - traces.extend(aa_traces); - pvs.extend(aa_pvs); - - self.result = Some(MemoryControllerResult { - traces, - public_values: pvs, - }); - final_memory } @@ -543,18 +511,30 @@ impl MemoryController { where Domain: PolynomialSpace, { - let airs = self.airs(); - let MemoryControllerResult { - traces, - public_values, - } = self.result.unwrap(); - izip!(airs, traces, public_values) - .map(|(air, trace, pvs)| AirProofInput::simple(air, trace, pvs)) - .collect() - } + let mut ret = Vec::new(); - pub fn generate_traces(self) -> Vec> { - self.result.unwrap().traces + let Self { + interface_chip, + access_adapters, + .. + } = self; + match interface_chip { + MemoryInterface::Volatile { boundary_chip } => { + ret.push(boundary_chip.generate_air_proof_input()); + } + MemoryInterface::Persistent { + merkle_chip, + boundary_chip, + .. + } => { + debug_assert_eq!(ret.len(), BOUNDARY_AIR_OFFSET); + ret.push(boundary_chip.generate_air_proof_input()); + debug_assert_eq!(ret.len(), MERKLE_AIR_OFFSET); + ret.push(merkle_chip.generate_air_proof_input()); + } + } + ret.extend(access_adapters.generate_air_proof_inputs()); + ret } pub fn airs(&self) -> Vec>> @@ -566,7 +546,7 @@ impl MemoryController { match &self.interface_chip { MemoryInterface::Volatile { boundary_chip } => { debug_assert_eq!(airs.len(), BOUNDARY_AIR_OFFSET); - airs.push(Arc::new(boundary_chip.air.clone())) + airs.push(boundary_chip.air()) } MemoryInterface::Persistent { boundary_chip, @@ -574,9 +554,9 @@ impl MemoryController { .. } => { debug_assert_eq!(airs.len(), BOUNDARY_AIR_OFFSET); - airs.push(Arc::new(boundary_chip.air.clone())); + airs.push(boundary_chip.air()); debug_assert_eq!(airs.len(), MERKLE_AIR_OFFSET); - airs.push(Arc::new(merkle_chip.air.clone())); + airs.push(merkle_chip.air()); } } airs.extend(self.access_adapters.airs()); @@ -590,11 +570,7 @@ impl MemoryController { if self.continuation_enabled() { num_airs += 1; } - for n in [2, 4, 8, 16, 32, 64] { - if self.mem_config.max_access_adapter_n >= n { - num_airs += 1; - } - } + num_airs += self.access_adapters.num_access_adapters(); num_airs } @@ -603,11 +579,7 @@ impl MemoryController { if self.continuation_enabled() { air_names.push("Merkle".to_string()); } - for n in [2, 4, 8, 16, 32, 64] { - if self.mem_config.max_access_adapter_n >= n { - air_names.push(format!("AccessAdapter<{}>", n)); - } - } + air_names.extend(self.access_adapters.air_names()); air_names } @@ -620,7 +592,7 @@ impl MemoryController { match &self.interface_chip { MemoryInterface::Volatile { boundary_chip } => { MemoryTraceHeights::Volatile(VolatileMemoryTraceHeights { - boundary: boundary_chip.current_height(), + boundary: boundary_chip.current_trace_height(), access_adapters, }) } @@ -629,8 +601,8 @@ impl MemoryController { merkle_chip, .. } => MemoryTraceHeights::Persistent(PersistentMemoryTraceHeights { - boundary: boundary_chip.current_height(), - merkle: merkle_chip.current_height(), + boundary: boundary_chip.current_trace_height(), + merkle: merkle_chip.current_trace_height(), access_adapters, }), } @@ -654,33 +626,23 @@ impl MemoryController { } } - fn trace_widths(&self) -> Vec { - let mut widths = vec![]; + pub fn current_trace_cells(&self) -> Vec { + let mut ret = Vec::new(); match &self.interface_chip { MemoryInterface::Volatile { boundary_chip } => { - widths.push(BaseAir::::width(&boundary_chip.air)); + ret.push(boundary_chip.current_trace_cells()) } MemoryInterface::Persistent { boundary_chip, merkle_chip, .. } => { - widths.push(BaseAir::::width(&boundary_chip.air)); - widths.push(BaseAir::::width(&merkle_chip.air)); + ret.push(boundary_chip.current_trace_cells()); + ret.push(merkle_chip.current_trace_cells()); } - }; - widths.extend(self.access_adapters.get_widths()); - widths - } - - pub fn current_trace_cells(&self) -> Vec { - zip_eq(self.current_trace_heights(), self.trace_widths()) - .map(|(h, w)| h * w) - .collect() - } - - pub fn generate_public_values_per_air(&self) -> Vec> { - self.result.as_ref().unwrap().public_values.clone() + } + ret.extend(self.access_adapters.get_cells()); + ret } } diff --git a/crates/vm/src/system/memory/merkle/mod.rs b/crates/vm/src/system/memory/merkle/mod.rs index 654c86b03b..ad01e25c8b 100644 --- a/crates/vm/src/system/memory/merkle/mod.rs +++ b/crates/vm/src/system/memory/merkle/mod.rs @@ -1,5 +1,3 @@ -use std::marker::PhantomData; - use openvm_stark_backend::p3_field::PrimeField32; use rustc_hash::FxHashSet; @@ -21,7 +19,14 @@ pub struct MemoryMerkleChip { pub air: MemoryMerkleAir, touched_nodes: FxHashSet<(usize, usize, usize)>, num_touched_nonleaves: usize, - _marker: PhantomData, + final_state: Option>, + overridden_height: Option, +} +#[derive(Debug)] +struct FinalState { + rows: Vec>, + init_root: [F; CHUNK], + final_root: [F; CHUNK], } impl MemoryMerkleChip { @@ -43,9 +48,13 @@ impl MemoryMerkleChip { }, touched_nodes, num_touched_nonleaves: 1, - _marker: PhantomData, + final_state: None, + overridden_height: None, } } + pub fn set_overridden_height(&mut self, override_height: usize) { + self.overridden_height = Some(override_height); + } fn touch_node(&mut self, height: usize, as_label: usize, address_label: usize) { if self.touched_nodes.insert((height, as_label, address_label)) { @@ -68,8 +77,4 @@ impl MemoryMerkleChip { (address.as_canonical_u32() as usize) / CHUNK, ); } - - pub fn current_height(&self) -> usize { - 2 * self.num_touched_nonleaves - } } diff --git a/crates/vm/src/system/memory/merkle/tests/mod.rs b/crates/vm/src/system/memory/merkle/tests/mod.rs index fc63937a3f..fb800c2d13 100644 --- a/crates/vm/src/system/memory/merkle/tests/mod.rs +++ b/crates/vm/src/system/memory/merkle/tests/mod.rs @@ -2,15 +2,18 @@ use std::{ array, borrow::BorrowMut, collections::{BTreeMap, BTreeSet, HashSet}, + sync::Arc, }; use openvm_stark_backend::{ interaction::InteractionType, p3_field::{AbstractField, PrimeField32}, p3_matrix::dense::RowMajorMatrix, + prover::types::AirProofInput, + Chip, ChipUsageGetter, }; use openvm_stark_sdk::{ - any_rap_arc_vec, config::baby_bear_poseidon2::BabyBearPoseidon2Engine, + config::baby_bear_poseidon2::BabyBearPoseidon2Engine, dummy_airs::interaction::dummy_interaction_air::DummyInteractionAir, engine::StarkFriEngine, p3_baby_bear::BabyBear, utils::create_seeded_rng, }; @@ -80,11 +83,13 @@ fn test( } } - println!("trace height = {}", chip.current_height()); - let (trace, final_tree) = - chip.generate_trace_and_final_tree(&initial_tree, final_memory, &mut hash_test_chip, None); - - assert_eq!(final_tree, final_tree_check); + println!("trace height = {}", chip.current_trace_height()); + chip.finalize(&initial_tree, final_memory, &mut hash_test_chip); + assert_eq!( + chip.final_state.as_ref().unwrap().final_root, + final_tree_check.hash() + ); + let chip_api = chip.generate_air_proof_input(); let dummy_interaction_air = DummyInteractionAir::new(4 + CHUNK, true, merkle_bus.0); let mut dummy_interaction_trace_rows = vec![]; @@ -145,17 +150,14 @@ fn test( dummy_interaction_trace_rows, dummy_interaction_air.field_width() + 1, ); - - let mut public_values = vec![vec![]; 3]; - public_values[0].extend(initial_tree.hash()); - public_values[0].extend(final_tree_check.hash()); - - let hash_test_chip_air = hash_test_chip.air(); - BabyBearPoseidon2Engine::run_simple_test_fast( - any_rap_arc_vec![chip.air, dummy_interaction_air, hash_test_chip_air], - vec![trace, dummy_interaction_trace, hash_test_chip.trace()], - public_values, - ) + let dummy_interaction_api = + AirProofInput::simple_no_pis(Arc::new(dummy_interaction_air), dummy_interaction_trace); + + BabyBearPoseidon2Engine::run_test_fast(vec![ + chip_api, + dummy_interaction_api, + hash_test_chip.generate_air_proof_input(), + ]) .expect("Verification failed"); } @@ -251,18 +253,11 @@ fn expand_test_no_accesses() { COMPRESSION_BUS, ); - let (trace, _) = chip.generate_trace_and_final_tree(&tree, &memory, &mut hash_test_chip, None); - - let mut public_values = vec![vec![]; 2]; - public_values[0].extend(tree.hash()); - public_values[0].extend(tree.hash()); - - let hash_test_chip_air = hash_test_chip.air(); - BabyBearPoseidon2Engine::run_simple_test_fast( - any_rap_arc_vec![chip.air, hash_test_chip_air], - vec![trace, hash_test_chip.trace()], - public_values, - ) + chip.finalize(&tree, &memory, &mut hash_test_chip); + BabyBearPoseidon2Engine::run_test_fast(vec![ + chip.generate_air_proof_input(), + hash_test_chip.generate_air_proof_input(), + ]) .expect("This should occur"); } @@ -290,25 +285,22 @@ fn expand_test_negative() { COMPRESSION_BUS, ); - let (mut trace, _) = - chip.generate_trace_and_final_tree(&tree, &memory, &mut hash_test_chip, None); - for row in trace.rows_mut() { - let row: &mut MemoryMerkleCols<_, DEFAULT_CHUNK> = row.borrow_mut(); - if row.expand_direction == BabyBear::NEG_ONE { - row.left_direction_different = BabyBear::ZERO; - row.right_direction_different = BabyBear::ZERO; + chip.finalize(&tree, &memory, &mut hash_test_chip); + let mut chip_api = chip.generate_air_proof_input(); + { + let trace = chip_api.raw.common_main.as_mut().unwrap(); + for row in trace.rows_mut() { + let row: &mut MemoryMerkleCols<_, DEFAULT_CHUNK> = row.borrow_mut(); + if row.expand_direction == BabyBear::NEG_ONE { + row.left_direction_different = BabyBear::ZERO; + row.right_direction_different = BabyBear::ZERO; + } } } - let mut public_values = vec![vec![]; 2]; - public_values[0].extend(tree.hash()); - public_values[0].extend(tree.hash()); - - let hash_test_chip_air = hash_test_chip.air(); - BabyBearPoseidon2Engine::run_simple_test_fast( - any_rap_arc_vec![chip.air, hash_test_chip_air], - vec![trace, hash_test_chip.trace()], - public_values, - ) + BabyBearPoseidon2Engine::run_test_fast(vec![ + chip_api, + hash_test_chip.generate_air_proof_input(), + ]) .expect("This should occur"); } diff --git a/crates/vm/src/system/memory/merkle/tests/util.rs b/crates/vm/src/system/memory/merkle/tests/util.rs index f5104fda98..3bd7a500e3 100644 --- a/crates/vm/src/system/memory/merkle/tests/util.rs +++ b/crates/vm/src/system/memory/merkle/tests/util.rs @@ -1,6 +1,13 @@ -use std::array::from_fn; +use std::{array::from_fn, sync::Arc}; -use openvm_stark_backend::{p3_air::BaseAir, p3_field::Field, p3_matrix::dense::RowMajorMatrix}; +use openvm_stark_backend::{ + config::{Domain, StarkGenericConfig}, + p3_air::BaseAir, + p3_commit::PolynomialSpace, + p3_field::Field, + p3_matrix::dense::RowMajorMatrix, + prover::types::AirProofInput, +}; use openvm_stark_sdk::dummy_airs::interaction::dummy_interaction_air::DummyInteractionAir; use crate::arch::{ @@ -40,6 +47,12 @@ impl HashTestChip { } RowMajorMatrix::new(rows, width) } + pub fn generate_air_proof_input(&self) -> AirProofInput + where + Domain: PolynomialSpace, + { + AirProofInput::simple_no_pis(Arc::new(self.air()), self.trace()) + } } impl Hasher for HashTestChip { diff --git a/crates/vm/src/system/memory/merkle/trace.rs b/crates/vm/src/system/memory/merkle/trace.rs index 6c9f21fa8d..8045e7fc92 100644 --- a/crates/vm/src/system/memory/merkle/trace.rs +++ b/crates/vm/src/system/memory/merkle/trace.rs @@ -1,26 +1,33 @@ use std::{borrow::BorrowMut, cmp::Reverse, sync::Arc}; -use openvm_stark_backend::{p3_field::PrimeField32, p3_matrix::dense::RowMajorMatrix}; +use openvm_stark_backend::{ + config::{StarkGenericConfig, Val}, + p3_field::{AbstractField, PrimeField32}, + p3_matrix::dense::RowMajorMatrix, + prover::types::AirProofInput, + rap::AnyRap, + Chip, ChipUsageGetter, +}; use rustc_hash::FxHashSet; use crate::{ arch::hasher::HasherChip, system::memory::{ manager::dimensions::MemoryDimensions, - merkle::{MemoryMerkleChip, MemoryMerkleCols}, + merkle::{FinalState, MemoryMerkleChip, MemoryMerkleCols}, tree::MemoryNode::{self, NonLeaf}, Equipartition, }, }; impl MemoryMerkleChip { - pub fn generate_trace_and_final_tree( + pub fn finalize( &mut self, initial_tree: &MemoryNode, final_memory: &Equipartition, hasher: &mut impl HasherChip, - overridden_height: Option, - ) -> (RowMajorMatrix, MemoryNode) { + ) { + assert!(self.final_state.is_none(), "Merkle chip already finalized"); // there needs to be a touched node with `height_section` = 0 // shouldn't be a leaf because // trace generation will expect an interaction from MemoryInterfaceChip in that case @@ -42,13 +49,41 @@ impl MemoryMerkleChip { 0, hasher, ); + self.final_state = Some(FinalState { + rows, + init_root: initial_tree.hash(), + final_root: final_tree.hash(), + }); + } +} + +impl Chip for MemoryMerkleChip> +where + Val: PrimeField32, +{ + fn air(&self) -> Arc> { + Arc::new(self.air.clone()) + } + + fn generate_air_proof_input(self) -> AirProofInput { + let air = Arc::new(self.air); + assert!( + self.final_state.is_some(), + "Merkle chip must finalize before trace generation" + ); + let FinalState { + mut rows, + init_root, + final_root, + } = self.final_state.unwrap(); // important that this sort be stable, // because we need the initial root to be first and the final root to be second + // TODO: do we only need find all height == 0 instead of sorting? rows.sort_by_key(|row| Reverse(row.parent_height)); - let width = MemoryMerkleCols::::width(); + let width = MemoryMerkleCols::, CHUNK>::width(); let mut height = rows.len().next_power_of_two(); - if let Some(mut oh) = overridden_height { + if let Some(mut oh) = self.overridden_height { oh = oh.next_power_of_two(); assert!( oh >= height, @@ -56,14 +91,28 @@ impl MemoryMerkleChip { ); height = oh; } - let mut trace = F::zero_vec(width * height); + let mut trace = Val::::zero_vec(width * height); for (trace_row, row) in trace.chunks_exact_mut(width).zip(rows) { *trace_row.borrow_mut() = row; } let trace = RowMajorMatrix::new(trace, width); - (trace, final_tree) + let pvs = init_root.into_iter().chain(final_root).collect(); + AirProofInput::simple(air, trace, pvs) + } +} +impl ChipUsageGetter for MemoryMerkleChip { + fn air_name(&self) -> String { + "Merkle".to_string() + } + + fn current_trace_height(&self) -> usize { + 2 * self.num_touched_nonleaves + } + + fn trace_width(&self) -> usize { + MemoryMerkleCols::::width() } } diff --git a/crates/vm/src/system/memory/persistent.rs b/crates/vm/src/system/memory/persistent.rs index e854e591af..57eb71d633 100644 --- a/crates/vm/src/system/memory/persistent.rs +++ b/crates/vm/src/system/memory/persistent.rs @@ -1,15 +1,22 @@ use std::{ borrow::{Borrow, BorrowMut}, iter, + sync::Arc, }; use openvm_circuit_primitives_derive::AlignedBorrow; +#[allow(unused_imports)] +use openvm_stark_backend::p3_maybe_rayon::prelude::IndexedParallelIterator; use openvm_stark_backend::{ + config::{StarkGenericConfig, Val}, interaction::InteractionBuilder, p3_air::{Air, BaseAir}, p3_field::{AbstractField, PrimeField32}, p3_matrix::{dense::RowMajorMatrix, Matrix}, - rap::{BaseAirWithPublicValues, PartitionedBaseAir}, + p3_maybe_rayon::prelude::{IntoParallelIterator, ParallelIterator, ParallelSliceMut}, + prover::types::AirProofInput, + rap::{AnyRap, BaseAirWithPublicValues, PartitionedBaseAir}, + Chip, ChipUsageGetter, }; use rustc_hash::FxHashSet; @@ -115,7 +122,49 @@ impl Air for PersistentBoundaryA #[derive(Debug)] pub struct PersistentBoundaryChip { pub air: PersistentBoundaryAir, - touched_labels: FxHashSet<(F, usize)>, + touched_labels: TouchedLabels, + overridden_height: Option, +} + +#[derive(Debug)] +enum TouchedLabels { + Running(FxHashSet<(F, usize)>), + Final(Vec>), +} + +#[derive(Debug)] +struct FinalTouchedLabel { + address_space: F, + label: usize, + init_values: [F; CHUNK], + final_values: [F; CHUNK], + init_exists: bool, + init_hash: [F; CHUNK], + final_hash: [F; CHUNK], + final_timestamp: u32, +} + +impl Default for TouchedLabels { + fn default() -> Self { + Self::Running(FxHashSet::default()) + } +} + +impl TouchedLabels { + fn touch(&mut self, address_space: F, label: usize) { + match self { + TouchedLabels::Running(touched_labels) => { + touched_labels.insert((address_space, label)); + } + _ => panic!("Cannot touch after finalization"), + } + } + fn len(&self) -> usize { + match self { + TouchedLabels::Running(touched_labels) => touched_labels.len(), + TouchedLabels::Final(touched_labels) => touched_labels.len(), + } + } } impl PersistentBoundaryChip { @@ -132,78 +181,133 @@ impl PersistentBoundaryChip { merkle_bus, compression_bus, }, - touched_labels: FxHashSet::default(), + touched_labels: Default::default(), + overridden_height: None, } } - pub fn touch_address(&mut self, address_space: F, pointer: F) { - let label = pointer.as_canonical_u32() as usize / CHUNK; - self.touched_labels.insert((address_space, label)); + pub fn set_overridden_height(&mut self, overridden_height: usize) { + self.overridden_height = Some(overridden_height); } - pub fn current_height(&self) -> usize { - 2 * self.touched_labels.len() + pub fn touch_address(&mut self, address_space: F, pointer: F) { + let label = pointer.as_canonical_u32() as usize / CHUNK; + self.touched_labels.touch(address_space, label); } - pub fn generate_trace( - &self, + pub fn finalize( + &mut self, initial_memory: &Equipartition, final_memory: &TimestampedEquipartition, hasher: &mut impl HasherChip, - overridden_height: Option, - ) -> RowMajorMatrix { - let width = PersistentBoundaryCols::::width(); - // Boundary AIR should always present in order to fix the AIR ID of merkle AIR. - let mut height = (2 * self.touched_labels.len()).next_power_of_two(); - if let Some(mut oh) = overridden_height { - oh = oh.next_power_of_two(); - assert!( - oh >= height, - "Overridden height is less than the required height" - ); - height = oh; + ) { + match &mut self.touched_labels { + TouchedLabels::Running(touched_labels) => { + // TODO: parallelize this. + let final_touched_labels = touched_labels + .iter() + .map(|touched_label| { + let (init_exists, initial_hash, init_values) = + match initial_memory.get(touched_label) { + Some(values) => (true, hasher.hash_and_record(values), *values), + None => ( + true, + hasher.hash_and_record(&[F::ZERO; CHUNK]), + [F::ZERO; CHUNK], + ), + }; + let timestamped_values = final_memory.get(touched_label).unwrap(); + let final_hash = hasher.hash_and_record(×tamped_values.values); + FinalTouchedLabel { + address_space: touched_label.0, + label: touched_label.1, + init_values, + final_values: timestamped_values.values, + init_exists, + init_hash: initial_hash, + final_hash, + final_timestamp: timestamped_values.timestamp, + } + }) + .collect(); + self.touched_labels = TouchedLabels::Final(final_touched_labels); + } + _ => panic!("Cannot finalize after finalization"), } - let mut rows = F::zero_vec(height * width); - - for (row, &(address_space, label)) in - rows.chunks_mut(2 * width).zip(self.touched_labels.iter()) - { - let (initial_row, final_row) = row.split_at_mut(width); - *initial_row.borrow_mut() = match initial_memory.get(&(address_space, label)) { - Some(values) => { - let initial_hash = hasher.hash_and_record(values); - PersistentBoundaryCols { - expand_direction: F::ONE, - address_space, - leaf_label: F::from_canonical_usize(label), - values: *values, - hash: initial_hash, - timestamp: F::from_canonical_u32(INITIAL_TIMESTAMP), - } - } - None => { - let initial_hash = hasher.hash_and_record(&[F::ZERO; CHUNK]); - PersistentBoundaryCols { - expand_direction: F::ONE, - address_space, - leaf_label: F::from_canonical_usize(label), - values: [F::ZERO; CHUNK], - hash: initial_hash, - timestamp: F::ZERO, - } - } - }; - let timestamped_values = final_memory.get(&(address_space, label)).unwrap(); - let final_hash = hasher.hash_and_record(×tamped_values.values); - *final_row.borrow_mut() = PersistentBoundaryCols { - expand_direction: F::NEG_ONE, - address_space, - leaf_label: F::from_canonical_usize(label), - values: timestamped_values.values, - hash: final_hash, - timestamp: F::from_canonical_u32(timestamped_values.timestamp), + } +} + +impl Chip for PersistentBoundaryChip, CHUNK> +where + Val: PrimeField32, +{ + fn air(&self) -> Arc> { + Arc::new(self.air.clone()) + } + + fn generate_air_proof_input(self) -> AirProofInput { + let air = Arc::new(self.air); + let trace = { + let width = PersistentBoundaryCols::, CHUNK>::width(); + // Boundary AIR should always present in order to fix the AIR ID of merkle AIR. + let mut height = (2 * self.touched_labels.len()).next_power_of_two(); + if let Some(mut oh) = self.overridden_height { + oh = oh.next_power_of_two(); + assert!( + oh >= height, + "Overridden height is less than the required height" + ); + height = oh; + } + let mut rows = Val::::zero_vec(height * width); + + let touched_labels = match self.touched_labels { + TouchedLabels::Final(touched_labels) => touched_labels, + _ => panic!("Cannot generate trace before finalization"), }; - } - RowMajorMatrix::new(rows, width) + + rows.par_chunks_mut(2 * width) + .zip(touched_labels.into_par_iter()) + .for_each(|(row, touched_label)| { + let (initial_row, final_row) = row.split_at_mut(width); + *initial_row.borrow_mut() = PersistentBoundaryCols { + expand_direction: Val::::ONE, + address_space: touched_label.address_space, + leaf_label: Val::::from_canonical_usize(touched_label.label), + values: touched_label.init_values, + hash: touched_label.init_hash, + timestamp: if touched_label.init_exists { + Val::::from_canonical_u32(INITIAL_TIMESTAMP) + } else { + Val::::ZERO + }, + }; + + *final_row.borrow_mut() = PersistentBoundaryCols { + expand_direction: Val::::NEG_ONE, + address_space: touched_label.address_space, + leaf_label: Val::::from_canonical_usize(touched_label.label), + values: touched_label.final_values, + hash: touched_label.final_hash, + timestamp: Val::::from_canonical_u32(touched_label.final_timestamp), + }; + }); + RowMajorMatrix::new(rows, width) + }; + AirProofInput::simple_no_pis(air, trace) + } +} + +impl ChipUsageGetter for PersistentBoundaryChip { + fn air_name(&self) -> String { + "Boundary".to_string() + } + + fn current_trace_height(&self) -> usize { + 2 * self.touched_labels.len() + } + + fn trace_width(&self) -> usize { + PersistentBoundaryCols::::width() } } diff --git a/crates/vm/src/system/memory/volatile/mod.rs b/crates/vm/src/system/memory/volatile/mod.rs index b29532d975..04e0c68579 100644 --- a/crates/vm/src/system/memory/volatile/mod.rs +++ b/crates/vm/src/system/memory/volatile/mod.rs @@ -14,12 +14,15 @@ use openvm_circuit_primitives::{ }; use openvm_circuit_primitives_derive::AlignedBorrow; use openvm_stark_backend::{ + config::{StarkGenericConfig, Val}, interaction::InteractionBuilder, p3_air::{Air, AirBuilder, BaseAir}, p3_field::{AbstractField, Field, PrimeField32}, p3_matrix::{dense::RowMajorMatrix, Matrix}, p3_maybe_rayon::prelude::*, - rap::{BaseAirWithPublicValues, PartitionedBaseAir}, + prover::types::AirProofInput, + rap::{AnyRap, BaseAirWithPublicValues, PartitionedBaseAir}, + Chip, ChipUsageGetter, }; use super::TimestampedEquipartition; @@ -132,6 +135,8 @@ pub struct VolatileBoundaryChip { pub air: VolatileBoundaryAir, touched_addresses: HashSet<(F, F)>, range_checker: Arc, + overridden_height: Option, + final_memory: Option>, } impl VolatileBoundaryChip { @@ -151,6 +156,8 @@ impl VolatileBoundaryChip { ), touched_addresses: HashSet::new(), range_checker, + overridden_height: None, + final_memory: None, } } @@ -161,21 +168,36 @@ impl VolatileBoundaryChip { pub fn all_addresses(&self) -> Vec<(F, F)> { self.touched_addresses.iter().cloned().collect() } - - pub fn current_height(&self) -> usize { - self.touched_addresses.len() - } } impl VolatileBoundaryChip { + pub fn set_overridden_height(&mut self, overridden_height: usize) { + self.overridden_height = Some(overridden_height); + } /// Volatile memory requires the starting and final memory to be in equipartition with block size `1`. /// When block size is `1`, then the `label` is the same as the address pointer. - pub fn generate_trace( - &self, - final_memory: &TimestampedEquipartition, - overridden_height: Option, - ) -> RowMajorMatrix { - let trace_height = if let Some(height) = overridden_height { + pub fn finalize(&mut self, final_memory: TimestampedEquipartition) { + self.final_memory = Some(final_memory); + } +} + +impl Chip for VolatileBoundaryChip> +where + Val: PrimeField32, +{ + fn air(&self) -> Arc> { + Arc::new(self.air.clone()) + } + + fn generate_air_proof_input(self) -> AirProofInput { + // Volatile memory requires the starting and final memory to be in equipartition with block size `1`. + // When block size is `1`, then the `label` is the same as the address pointer. + let width = self.trace_width(); + let air = Arc::new(self.air); + let final_memory = self + .final_memory + .expect("Trace generation should be called after finalize"); + let trace_height = if let Some(height) = self.overridden_height { assert!( height >= final_memory.len(), "Overridden height is less than the required height" @@ -184,65 +206,71 @@ impl VolatileBoundaryChip { } else { final_memory.len() }; - self.generate_trace_with_height(final_memory, trace_height.next_power_of_two()) - } - - fn generate_trace_with_height( - &self, - final_memory: &TimestampedEquipartition, - trace_height: usize, - ) -> RowMajorMatrix { - assert!(trace_height.is_power_of_two()); - let width = BaseAir::::width(&self.air); + let trace_height = trace_height.next_power_of_two(); // Collect into Vec to sort from BTreeMap and also so we can look at adjacent entries - let sorted_final_memory: Vec<_> = final_memory.iter().collect(); - assert!(sorted_final_memory.len() <= trace_height); + let sorted_final_memory: Vec<_> = final_memory.into_par_iter().collect(); + let memory_len = sorted_final_memory.len(); - let mut rows = F::zero_vec(trace_height * width); + let mut rows = Val::::zero_vec(trace_height * width); rows.par_chunks_mut(width) - .zip(&sorted_final_memory) + .zip(sorted_final_memory.par_iter()) .enumerate() .for_each(|(i, (row, ((addr_space, ptr), timestamped_values)))| { // `pointer` is the same as `label` since the equipartition has block size 1 let [data] = timestamped_values.values; let row: &mut VolatileBoundaryCols<_> = row.borrow_mut(); row.addr_space = *addr_space; - row.pointer = F::from_canonical_usize(*ptr); - row.initial_data = F::ZERO; + row.pointer = Val::::from_canonical_usize(*ptr); + row.initial_data = Val::::ZERO; row.final_data = data; - row.final_timestamp = F::from_canonical_u32(timestamped_values.timestamp); - row.is_valid = F::ONE; + row.final_timestamp = Val::::from_canonical_u32(timestamped_values.timestamp); + row.is_valid = Val::::ONE; // If next.is_valid == 1: - if i != sorted_final_memory.len() - 1 { - let (next_addr_space, next_ptr) = *sorted_final_memory[i + 1].0; - let mut out = F::ZERO; - self.air.addr_lt_air.0.generate_subrow( + if i != memory_len - 1 { + let (next_addr_space, next_ptr) = sorted_final_memory[i + 1].0; + let mut out = Val::::ZERO; + air.addr_lt_air.0.generate_subrow( ( &self.range_checker, &[row.addr_space, row.pointer], - &[next_addr_space, F::from_canonical_usize(next_ptr)], + &[next_addr_space, Val::::from_canonical_usize(next_ptr)], ), ((&mut row.addr_lt_aux).into(), &mut out), ); - debug_assert_eq!(out, F::ONE, "Addresses are not sorted"); + debug_assert_eq!(out, Val::::ONE, "Addresses are not sorted"); } }); // Always do a dummy range check on the last row due to wraparound - if !sorted_final_memory.is_empty() { - let mut out = F::ZERO; + if memory_len > 0 { + let mut out = Val::::ZERO; let row: &mut VolatileBoundaryCols<_> = rows[width * (trace_height - 1)..].borrow_mut(); - self.air.addr_lt_air.0.generate_subrow( + air.addr_lt_air.0.generate_subrow( ( &self.range_checker, - &[F::ZERO, F::ZERO], - &[F::ZERO, F::ZERO], + &[Val::::ZERO, Val::::ZERO], + &[Val::::ZERO, Val::::ZERO], ), ((&mut row.addr_lt_aux).into(), &mut out), ); } - RowMajorMatrix::new(rows, width) + let trace = RowMajorMatrix::new(rows, width); + AirProofInput::simple_no_pis(air, trace) + } +} + +impl ChipUsageGetter for VolatileBoundaryChip { + fn air_name(&self) -> String { + "Boundary".to_string() + } + + fn current_trace_height(&self) -> usize { + self.touched_addresses.len() + } + + fn trace_width(&self) -> usize { + VolatileBoundaryCols::::width() } } diff --git a/crates/vm/src/system/memory/volatile/tests.rs b/crates/vm/src/system/memory/volatile/tests.rs index 6b00e01a9b..eaaf3bb674 100644 --- a/crates/vm/src/system/memory/volatile/tests.rs +++ b/crates/vm/src/system/memory/volatile/tests.rs @@ -3,12 +3,16 @@ use std::{collections::HashSet, iter, sync::Arc}; use openvm_circuit_primitives::var_range::{VariableRangeCheckerBus, VariableRangeCheckerChip}; use openvm_stark_backend::{ p3_field::{AbstractField, PrimeField32}, - p3_matrix::{dense::RowMajorMatrix, Matrix}, + p3_matrix::dense::RowMajorMatrix, + prover::types::AirProofInput, + Chip, }; use openvm_stark_sdk::{ - any_rap_arc_vec, config::baby_bear_poseidon2::BabyBearPoseidon2Engine, - dummy_airs::interaction::dummy_interaction_air::DummyInteractionAir, engine::StarkFriEngine, - p3_baby_bear::BabyBear, utils::create_seeded_rng, + config::baby_bear_poseidon2::{BabyBearPoseidon2Config, BabyBearPoseidon2Engine}, + dummy_airs::interaction::dummy_interaction_air::DummyInteractionAir, + engine::StarkFriEngine, + p3_baby_bear::BabyBear, + utils::create_seeded_rng, }; use rand::Rng; use test_log::test; @@ -42,7 +46,8 @@ fn boundary_air_test() { let range_bus = VariableRangeCheckerBus::new(RANGE_CHECKER_BUS, DECOMP); let range_checker = Arc::new(VariableRangeCheckerChip::new(range_bus)); - let boundary_chip = VolatileBoundaryChip::new(memory_bus, 2, LIMB_BITS, range_checker.clone()); + let mut boundary_chip = + VolatileBoundaryChip::new(memory_bus, 2, LIMB_BITS, range_checker.clone()); let mut final_memory = TimestampedEquipartition::new(); @@ -104,35 +109,30 @@ fn boundary_air_test() { 6, ); - let boundary_trace = boundary_chip.generate_trace(&final_memory, None); + boundary_chip.finalize(final_memory.clone()); + let boundary_api: AirProofInput = + boundary_chip.generate_air_proof_input(); // test trace height override { - let overridden_height = boundary_trace.height() * 2; + let overridden_height = boundary_api.main_trace_height() * 2; let range_checker = Arc::new(VariableRangeCheckerChip::new(range_bus)); - let boundary_chip = + let mut boundary_chip = VolatileBoundaryChip::new(memory_bus, 2, LIMB_BITS, range_checker.clone()); - let boundary_trace = boundary_chip.generate_trace(&final_memory, Some(overridden_height)); + boundary_chip.set_overridden_height(overridden_height); + boundary_chip.finalize(final_memory.clone()); + let boundary_api: AirProofInput = + boundary_chip.generate_air_proof_input(); assert_eq!( - boundary_trace.height(), + boundary_api.main_trace_height(), overridden_height.next_power_of_two() ); } - let range_checker_trace = range_checker.generate_trace(); - - BabyBearPoseidon2Engine::run_simple_test_no_pis_fast( - any_rap_arc_vec![ - boundary_chip.air, - range_checker.air, - init_memory_dummy_air, - final_memory_dummy_air - ], - vec![ - boundary_trace, - range_checker_trace, - init_memory_trace, - final_memory_trace, - ], - ) + BabyBearPoseidon2Engine::run_test_fast(vec![ + boundary_api, + range_checker.generate_air_proof_input(), + AirProofInput::simple_no_pis(Arc::new(init_memory_dummy_air), init_memory_trace), + AirProofInput::simple_no_pis(Arc::new(final_memory_dummy_air), final_memory_trace), + ]) .expect("Verification failed"); } From 47c6499725c375ea30390232be6d7f5a615ec652 Mon Sep 17 00:00:00 2001 From: Golovanov399 Date: Sun, 15 Dec 2024 23:37:54 +0300 Subject: [PATCH 14/69] [feat] axvm-algebra-guest usage (#1053) * Update doctoc and add the complex extension macros part * Update algebra part * Remove all doctoc stuff * Rewrite * Restructure folders etc * MathJax * Remove todo about exp_bytes, we don't use it anyway * Part of the comments --- book/book.toml | 3 + book/src/SUMMARY.md | 2 +- book/src/advanced-usage/testing-program.md | 1 - book/src/custom-extensions/algebra.md | 143 ++++++++++++++++++ book/src/custom-extensions/bigint.md | 1 + book/src/custom-extensions/ecc.md | 30 ++++ book/src/custom-extensions/keccak.md | 1 + book/src/custom-extensions/overview.md | 19 +++ book/src/custom-extensions/pairing.md | 1 + book/src/introduction.md | 2 +- .../customizable-extensions.md | 76 ---------- 11 files changed, 200 insertions(+), 79 deletions(-) create mode 100644 book/src/custom-extensions/algebra.md create mode 100644 book/src/custom-extensions/bigint.md create mode 100644 book/src/custom-extensions/ecc.md create mode 100644 book/src/custom-extensions/keccak.md create mode 100644 book/src/custom-extensions/overview.md create mode 100644 book/src/custom-extensions/pairing.md delete mode 100644 book/src/using-extensions/customizable-extensions.md diff --git a/book/book.toml b/book/book.toml index 44d812cc99..2e20f98145 100644 --- a/book/book.toml +++ b/book/book.toml @@ -7,3 +7,6 @@ title = "OpenVM Book" [output.html] site-url = "https://book.openvm.dev/" +additional-head = [ + "" +] \ No newline at end of file diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 3bf8b614ab..b40ab41b87 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -19,7 +19,7 @@ # Using Extensions -- [Customizable Extensions](./using-extensions/customizable-extensions.md) +- [Customizable Extensions](./custom-extensions/overview.md) # Advanced Usage diff --git a/book/src/advanced-usage/testing-program.md b/book/src/advanced-usage/testing-program.md index 36839e91f5..1db3d71521 100644 --- a/book/src/advanced-usage/testing-program.md +++ b/book/src/advanced-usage/testing-program.md @@ -1,4 +1,3 @@ - ## Testing the program ### Running on the host machine diff --git a/book/src/custom-extensions/algebra.md b/book/src/custom-extensions/algebra.md new file mode 100644 index 0000000000..c9a6b32f95 --- /dev/null +++ b/book/src/custom-extensions/algebra.md @@ -0,0 +1,143 @@ +# OpenVM Algebra + +The OpenVM Algebra extension provides tools to create and manipulate modular arithmetic structures and their complex extensions. For example, if $p$ is prime, OpenVM Algebra can handle modular arithmetic in $\mathbb{F}_p$​ and its quadratic extension fields $\mathbb{F}_p[x]/(x^2 + 1)$. + +The functional part is provided by the `openvm-algebra-guest` crate, which is a guest library that can be used in any OpenVM program. The macros for creating corresponding structs are in the `openvm-algebra-moduli-setup` and `openvm-algebra-complex-macros` crates. + +## Available traits and methods + +- `IntMod` trait: + Defines the type `Repr` and constants `MODULUS`, `NUM_LIMBS`, `ZERO`, and `ONE`. It also provides basic methods for constructing a modular arithmetic object and performing arithmetic operations. + - `Repr` typically is `[u8; NUM_LIMBS]`, representing the number’s underlying storage. + - `MODULUS` is the compile-time known modulus. + - `ZERO` and `ONE` represent the additive and multiplicative identities, respectively. + - Constructors include `from_repr`, `from_le_bytes`, `from_be_bytes`, `from_u8`, `from_u32`, and `from_u64`. + +- `Field` trait: + Provides constants `ZERO` and `ONE` and methods for basic arithmetic operations within a field. + +## Modular arithmetic + +To [leverage](./overview.md) compile-time known moduli for performance, you declare, initialize, and then set up the arithmetic structures: + +1. **Declare**: Use the `moduli_declare!` macro to define a modular arithmetic struct. This can be done multiple times in various crates or modules: + +```rust +moduli_declare! { + Bls12_381Fp { modulus = "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab" }, + Bn254Fp { modulus = "21888242871839275222246405745257275088696311157297823662689037894645226208583" }, +} +``` + +This creates `Bls12_381Fp` and `Bn254Fp` structs, each implementing the `IntMod` trait. The modulus parameter must be a string literal in decimal or hexadecimal format. + +2. **Init**: Use the `moduli_init!` macro exactly once in the final binary: + +```rust +moduli_init! { + "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", + "21888242871839275222246405745257275088696311157297823662689037894645226208583" +} +``` + +This step enumerates the declared moduli (e.g., `0` for the first one, `1` for the second one) and sets up internal linkage so the compiler can generate the appropriate RISC-V instructions associated with each modulus. + +3. **Setup**: At runtime, before performing arithmetic, a setup instruction must be sent to ensure security and correctness. For the $i$-th modulus, you call `setup_()` (e.g., `setup_0()` or `setup_1()`). Alternatively, `setup_all_moduli()` can be used to handle all declared moduli. + +**Summary**: +- `moduli_declare!`: Declares modular arithmetic structures and can be done multiple times. +- `moduli_init!`: Called once in the final binary to assign and lock in the moduli. +- `setup_()`/`setup_all_moduli()`: Ensures at runtime that the correct modulus is in use, providing a security check and finalizing the environment for safe arithmetic operations. + +## Complex field extension + +Complex extensions, such as $\mathbb{F}_p[x]/(x^2 + 1)$, are defined similarly using `complex_declare!` and `complex_init!`: + +1. **Declare**: + +```rust +complex_declare! { + Bn254Fp2 { mod_type = Bn254Fp } +} +``` + +This creates a `Bn254Fp2` struct, representing a complex extension field. The `mod_type` must implement `IntMod`. + +2. **Init**: Called once, after `moduli_init!`, to enumerate these extensions and generate corresponding instructions: + +```rust +complex_init! { + Bn254Fp2 { mod_idx = 0 }, +} +``` + +Note that you need to use the same type name in `complex_declare!` and `complex_init!`. For example, the following code will **fail** to compile: + +```rust +// moduli related macros... + +complex_declare! { + Bn254Fp2 { mod_type = Bn254Fp }, +} + +pub type Fp2 = Bn254Fp2; + +complex_init! { + Fp2 { mod_idx = 0 }, +} +``` + +Here, `mod_idx` refers to the index of the underlying modulus as initialized by `moduli_init!` + +3. **Setup**: Similar to moduli, call `setup_complex_()` or `setup_all_complex_extensions()` at runtime to secure the environment. + +### Example program + +Here is a toy example using both the modular arithmetic and complex field extension capabilities: +```rust +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +use openvm_algebra_guest::IntMod; + +openvm::entry!(main); + +// This macro will create two structs, `Mod1` and `Mod2`, +// one for arithmetic modulo 998244353, and the other for arithmetic modulo 1000000007. +openvm_algebra_moduli_setup::moduli_declare! { + Mod1 { modulus = "998244353" }, + Mod2 { modulus = "1000000007" } +} + +// This macro will initialize the moduli. +// Now, `Mod1` is the "zeroth" modular struct, and `Mod2` is the "first" one. +openvm_algebra_moduli_setup::moduli_init! { + "998244353", "1000000007" +} + +// This macro will create two structs, `Complex1` and `Complex2`, +// one for arithmetic in the field $\mathbb{F}_{998244353}[x]/(x^2 + 1)$, +// and the other for arithmetic in the field $\mathbb{F}_{1000000007}[x]/(x^2 + 1)$. +openvm_algebra_complex_macros::complex_declare! { + Complex1 { mod_type = Mod1 }, + Complex2 { mod_type = Mod2 }, +} + +// The order of these structs does not matter, +// given that we specify the `mod_idx` parameters properly. +openvm_algebra_complex_macros::complex_init! { + Complex2 { mod_idx = 1 }, Complex1 { mod_idx = 0 }, +} + +pub fn main() { + // Since we only use an arithmetic operation with `Mod1` and not `Mod2`, + // we only need to call `setup_0()` here. + setup_0(); + setup_all_complex_extensions(); + let a = Complex1::new(Mod1::ZERO, Mod1::from_u32(0x3b8) * Mod1::from_u32(0x100000)); // a = -i in the corresponding field + let b = Complex2::new(Mod2::ZERO, Mod2::from_u32(1000000006)); // b = -i in the corresponding field + assert_eq!(a.clone() * &a * &a * &a * &a, a); // a^5 = a + assert_eq!(b.clone() * &b * &b * &b * &b, b); // b^5 = b + // Note that these assertions would fail, have we provided the `mod_idx` parameters wrongly. +} +``` diff --git a/book/src/custom-extensions/bigint.md b/book/src/custom-extensions/bigint.md new file mode 100644 index 0000000000..61c459249e --- /dev/null +++ b/book/src/custom-extensions/bigint.md @@ -0,0 +1 @@ +# OpenVM BigInt diff --git a/book/src/custom-extensions/ecc.md b/book/src/custom-extensions/ecc.md new file mode 100644 index 0000000000..9dc1e3453b --- /dev/null +++ b/book/src/custom-extensions/ecc.md @@ -0,0 +1,30 @@ +# OpenVM ECC + +For elliptic curve cryptography, the `openvm-ecc` crate provides macros similar to those in [`openvm-algebra`](./algebra.md): + +1. **Declare**: Use `sw_declare!` to define elliptic curves over the previously declared moduli. For example: + +```rust +sw_declare! { + Bls12_381G1Affine { mod_type = Bls12_381Fp, b = BLS12_381_B }, + Bn254G1Affine { mod_type = Bn254Fp, b = BN254_B }, +} +``` + +Each declared curve must specify the `mod_type` (implementing `IntMod`) and a constant `b` for the Weierstrass curve equation $y^2 = x^3 + b$. + +2. **Init**: Called once, it enumerates these curves and allows the compiler to produce optimized instructions: + +```rust +sw_init! { + Bls12_381Fp, Bn254Fp, +} +``` + +3. **Setup**: Similar to the moduli and complex extensions, runtime setup instructions ensure that the correct curve parameters are being used, guaranteeing secure operation. + +**Summary**: + +- `sw_declare!`: Declares elliptic curve structures. +- `sw_init!`: Initializes them once, linking them to the underlying moduli. +- `setup_sw_()`/`setup_all_curves()`: Secures runtime correctness. diff --git a/book/src/custom-extensions/keccak.md b/book/src/custom-extensions/keccak.md new file mode 100644 index 0000000000..7e83568f07 --- /dev/null +++ b/book/src/custom-extensions/keccak.md @@ -0,0 +1 @@ +# OpenVM Keccak \ No newline at end of file diff --git a/book/src/custom-extensions/overview.md b/book/src/custom-extensions/overview.md new file mode 100644 index 0000000000..a23da253f6 --- /dev/null +++ b/book/src/custom-extensions/overview.md @@ -0,0 +1,19 @@ +# Using Existing Extensions + +You can seamlessly integrate certain performance-optimized extensions maintained by the OpenVM team to enhance your arithmetic operations and cryptographic computations. + +Certain arithmetic operations, particularly modular arithmetic, can be optimized significantly when the modulus is known at compile time. This approach requires a framework to inform the compiler about all the moduli and associated arithmetic structures we intend to use. To achieve this, three steps are involved: + +1. **Declare**: Introduce a modular arithmetic or related structure, along with its modulus and functionality. This can be done in any library or binary file. +2. **Init**: Performed exactly once in the final binary. It aggregates all previously declared structures, assigns them stable indices, and sets up linkage so that they can be referenced in generated code. +3. **Setup**: A one-time runtime procedure for security. This ensures that the compiled code matches the virtual machine’s expectations and that each instruction set is tied to the correct modulus or extension. + +These steps ensure both performance and security: performance because the modulus is known at compile time, and security because runtime checks confirm that the correct structures have been initialized. + +The list of existing extensions: + +- [`openvm-algebra`](./algebra.md) +- [`openvm-bigint`](./bigint.md) +- [`openvm-keccak`](./keccak.md) +- [`openvm-pairing`](./pairing.md) +- [`openvm-ecc`](./ecc.md) diff --git a/book/src/custom-extensions/pairing.md b/book/src/custom-extensions/pairing.md new file mode 100644 index 0000000000..ebb943f14c --- /dev/null +++ b/book/src/custom-extensions/pairing.md @@ -0,0 +1 @@ +# OpenVM Pairing \ No newline at end of file diff --git a/book/src/introduction.md b/book/src/introduction.md index 08c1bde13e..80d66d608f 100644 --- a/book/src/introduction.md +++ b/book/src/introduction.md @@ -20,5 +20,5 @@ The following chapters will guide you through: - [Getting started](./getting-started/install.md) - [Writing applications](./writing-apps/overview.md) in Rust targeting OpenVM and generating proofs. -- [Using existing extensions](./using-extensions/) to optimize your Rust programs. +- [Using existing extensions](./custom-extensions/overview.md) to optimize your Rust programs. - How to add custom VM extensions diff --git a/book/src/using-extensions/customizable-extensions.md b/book/src/using-extensions/customizable-extensions.md deleted file mode 100644 index 1d69997c2a..0000000000 --- a/book/src/using-extensions/customizable-extensions.md +++ /dev/null @@ -1,76 +0,0 @@ -# Using already existing extensions - -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. - -## `openvm-algebra` - -This crate allows one to create and use structs for convenient modular arithmetic operations, and also for their complex extensions (for example, if $p$ is a prime number, `openvm-algebra` provides methods for modular arithmetic in the field $\mathbb{F}_p[x]/(x^2 + 1)$). - -To declare a modular arithmetic struct, one needs to use the `moduli_declare!` macro. A usage example is given below: - -```rust -moduli_declare! { - Bls12_381Fp { modulus = "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab" }, - Bn254Fp { modulus = "21888242871839275222246405745257275088696311157297823662689037894645226208583" }, -} -``` - -This creates two structs, `Bls12381_Fp` and `Bn254_Fp`, each representing the modular arithmetic class. These classes implement `Add`, `Sub` and other basic arithmetic operations; the underlying functions used for this are a part of the `IntMod` trait. The modulus for each struct is specified in the `modulus` parameter of the macro. It should be a string literal in either decimal or hexadecimal format (in the latter case, it must start with `0x`). - -The arithmetic operations for these classes, when compiling for the `zkvm` target, are converted into RISC-V asm instructions which are distinguished by the `funct7` field. The corresponding "distinguishers assignment" is happening when another macro is called: - -```rust -moduli_init! { - "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", - "21888242871839275222246405745257275088696311157297823662689037894645226208583" -} -``` - -This macro **must be called exactly once** in the final executable program, and it must contain all the moduli that have ever been declared in the `moduli_declare!` macros across all the compilation units. It is possible to `declare` a number in decimal and `init` it in hexadecimal, and vice versa. - -When `moduli_init!` is called, the moduli in it are enumerated from `0`. For each chip that is used, the first instruction that this chip receives must be a `setup` instruction -- this adds a record to the trace that guarantees that the modulus this chip uses is exactly the one we `init`ed. - -To send a setup instruction for the $i$-th struct, one needs to call the `setup_()` function (for instance, `setup_1()`). There is also a function `setup_all_moduli()` that calls all the available `setup` functions. - -To summarize: - -- `moduli_declare!` declares a struct for a modular arithmetic class. It can be called multiple times across the compilation units. -- `moduli_init!` initializes the data required for transpiling the program into the RISC-V assembly. **Every modulus ever `declare`d in the program must be among the arguments of `moduli_init!`**. -- `setup_()` sends a setup instruction for the $i$-th struct. Here, **$i$-th struct is the one that corresponds to the $i$-th modulus in `moduli_init!`**. The order of `moduli_declare!` invocations or the arguments in them does not matter. -- `setup_all_moduli()` sends setup instructions for all the structs. - -## `openvm-ecc` - -This crate allows one to create and use structs for elliptic curve cryptography. More specifically, it only supports curves where the defining equation is in short [Weierstrass curves](https://en.wikipedia.org/wiki/Weierstrass_form) (that is, `a = 0`). - -To declare an elliptic curve struct, one needs to use the `sw_declare!` macro. A usage example is given below: - -```rust -sw_declare! { - Bls12_381G1Affine { mod_type = Bls12_381Fp, b = BLS12_381_B }, - Bn254G1Affine { mod_type = Bn254Fp, b = BN254_B }, -} -``` - -Similar to the `moduli_declare!` macro, the `sw_declare!` macro creates a struct for an elliptic curve. The `mod_type` parameter specifies the type of the modulus for this curve, and the `b` parameter specifies the free coefficient of the curve equation; both of these parameters are required. The `mod_type` parameter must be a struct that implements the `IntMod` trait. The `b` parameter must be a constant. - -The arithmetic operations for these classes, when compiling for the `zkvm` target, are converted into RISC-V asm instructions which are distinguished by the `funct7` field. The corresponding "distinguishers assignment" is happening when another macro is called: - -```rust -sw_init! { - Bls12_381Fp, Bn254Fp, -} -``` - -Again, this macro **must be called exactly once** in the final executable program, and it must contain all the curves that have ever been declared in the `sw_declare!` macros across all the compilation units. - -When `sw_init!` is called, the curves in it are enumerated from `0`. For each chip that is used, the first instruction that this chip receives must be a `setup` instruction -- this adds a record to the trace that guarantees that the curve this chip uses is exactly the one we `init`ed. - -To send a setup instruction for the $i$-th struct, one needs to call the `setup_sw_()` function (for instance, `setup_sw_1()`). There is also a function `setup_all_curves()` that calls all the available `setup` functions. - -To summarize: - -- `sw_declare!` declares a struct for an elliptic curve. It can be called multiple times across the compilation units. -- `sw_init!` initializes the data required for transpiling the program into the RISC-V assembly. **Every curve ever `declare`d in the program must be among the arguments of `sw_init!`**. -- `setup_sw_()` sends a setup instruction for the $i$-th struct. Here, **$i$-th struct is the one that corresponds to the $i$-th curve in `sw_init!`**. The order of `sw_declare!` invocations or the arguments in them does not matter. -- `setup_all_curves()` sends setup instructions for all the structs. From a26c3ded06f2ea3f478538c8d88abcd588050613 Mon Sep 17 00:00:00 2001 From: Lun-Kai Hsu Date: Sun, 15 Dec 2024 12:55:31 -0800 Subject: [PATCH 15/69] [docs] update to writing program (#1049) * update to writing program * address comments * add compile * address comments --- book/src/SUMMARY.md | 1 + book/src/getting-started/quickstart.md | 2 +- book/src/writing-apps/compile.md | 8 +++++ book/src/writing-apps/write-program.md | 44 +++++++++++++++++++++++--- 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index b40ab41b87..f250675b66 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -24,3 +24,4 @@ # Advanced Usage - [Overview](./advanced-usage/overview.md) +- [Testing the program](./advanced-usage/testing-program.md) diff --git a/book/src/getting-started/quickstart.md b/book/src/getting-started/quickstart.md index 03825f536f..c40f274cd1 100644 --- a/book/src/getting-started/quickstart.md +++ b/book/src/getting-started/quickstart.md @@ -10,7 +10,7 @@ First, create a new Rust project. cargo init fibonacci ``` -Since we are using some nightly features, we need to specify the Rust version. Create a `rust-toolchain.toml` file with the following content: +Since we are using some nightly features, we need to specify the Rust version. Run `rustup component add rust-src --toolchain nightly-2024-10-30` and create a `rust-toolchain.toml` file with the following content: ```toml [toolchain] diff --git a/book/src/writing-apps/compile.md b/book/src/writing-apps/compile.md index 9067cfda0f..f3b18f2570 100644 --- a/book/src/writing-apps/compile.md +++ b/book/src/writing-apps/compile.md @@ -1 +1,9 @@ # Cross-Compilation + +First let's define some key terms used in cross-compilation: +- **host** - the machine you're compiling and/or proving on. Note that one can compile and prove on different machines, but they are both called *host* as they are traditional machine architectures. +- **guest** - the executable to be run in a different VM architecture (e.g. the OpenVM runtime, or Android app). + +There are multiple things happening in the `cargo openvm build` command as in the section [here](./write-program.md). In short, this command compiles on host to an executable for guest target. +It first compiles the program normally on your *host* platform with RISC-V and then transpiles it to a different target. See here for some explanation of [cross-compilation](https://rust-lang.github.io/rustup/cross-compilation.html). +Right now we use `riscv32im-risc0-zkvm-elf` target which is available in the [Rust toolchain](https://doc.rust-lang.org/rustc/platform-support/riscv32im-risc0-zkvm-elf.html), but we will contribute an OpenVM target to Rust in the future. diff --git a/book/src/writing-apps/write-program.md b/book/src/writing-apps/write-program.md index 66836a4ade..dd151de2c8 100644 --- a/book/src/writing-apps/write-program.md +++ b/book/src/writing-apps/write-program.md @@ -31,15 +31,51 @@ std = ["openvm/std"] *TODO*: point to CLI installation instructions +First we need to build the program targeting the OpenVM runtime, and that requires some configuration. Put the following in `openvm.toml`: +```toml +[app_fri_params] +log_blowup = 2 +num_queries = 42 +proof_of_work_bits = 16 + +[app_vm_config.io] +[app_vm_config.rv32i] +[app_vm_config.rv32m] +range_tuple_checker_sizes = [256, 2048] +``` + +And run the following command to build the program: + +```bash +cargo openvm build --transpile --transpiler-config openvm.toml --transpile-to outputs/fibonacci.vmexe +``` + +Next we can keygen the generate the proving and verifying keys: + +```bash +cargo openvm keygen --config app_config.toml --output outputs/pk --vk-output outputs/vk +``` + +Now, to prove the program some input is needed. The input parameter is either a hex string or a file path. So for example if we want to compute the 10th fibonacci number, we can run: + +```bash +cargo openvm prove app --app-pk outputs/pk --exe outputs/fibonacci.vmexe --input "0x000000000000000A" --output outputs/proof +cargo openvm verify app --app-vk outputs/vk --proof outputs/proof +``` + +No errors should be returned, and the proof should be correctly verified. + ## Handling I/O -`openvm::io` provides a few functions to read and write data. +The program can take input from stdin, with some functions provided by `openvm::io`. -`read` takes from stdin the next vec and deserialize it into a generic type `T`, so one should specify the type when calling it: +`openvm::io::read` takes from stdin and deserializes it into a generic type `T`, so one should specify the type when calling it: ```rust let n: u64 = read(); ``` -`read_vec` will just read a vector and return `Vec`. +`openvm::io::read_vec` will just read a vector and return `Vec`. + +`openvm::io::reveal` sends public values to the final proof (to be read by the smart contract). -`reveal` +For debugging purposes, `openvm::io::print` and `openvm::io::println` can be used normally, but `println!` will only work if `std` is enabled. From 6c0c0e25ef70dba8a8505880e99e31b58b094626 Mon Sep 17 00:00:00 2001 From: Xinding Wei Date: Sun, 15 Dec 2024 12:55:54 -0800 Subject: [PATCH 16/69] [chore] Parallelize Poseidon trace generation (#1045) * Parallelize Poseidon trace generation * chore: par_extend --------- Co-authored-by: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> --- Cargo.lock | 1 + crates/vm/Cargo.toml | 3 ++- crates/vm/src/system/poseidon2/trace.rs | 20 ++++++++++++++++---- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff542fd2b6..c09ebf212b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3580,6 +3580,7 @@ dependencies = [ "parking_lot", "rand", "rand_xoshiro", + "rayon", "rustc-hash 2.1.0", "serde", "static_assertions", diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index 1b0510e1a3..1287a34d82 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -38,6 +38,7 @@ derivative.workspace = true static_assertions.workspace = true async-trait.workspace = true getset.workspace = true +rayon = { workspace = true, optional = true } [dev-dependencies] p3-dft = { workspace = true } @@ -70,7 +71,7 @@ hex.workspace = true [features] default = ["parallel", "mimalloc"] -parallel = ["openvm-stark-backend/parallel"] +parallel = ["openvm-stark-backend/parallel", "dep:rayon"] test-utils = ["openvm-ecc-guest/halo2curves", "dep:openvm-stark-sdk"] bench-metrics = [ "dep:metrics", diff --git a/crates/vm/src/system/poseidon2/trace.rs b/crates/vm/src/system/poseidon2/trace.rs index b866ac95f9..0e6ad93ccd 100644 --- a/crates/vm/src/system/poseidon2/trace.rs +++ b/crates/vm/src/system/poseidon2/trace.rs @@ -6,10 +6,13 @@ use openvm_stark_backend::{ p3_air::BaseAir, p3_field::PrimeField32, p3_matrix::dense::RowMajorMatrix, + p3_maybe_rayon::prelude::*, prover::types::AirProofInput, rap::{get_air_name, AnyRap}, Chip, ChipUsageGetter, }; +#[cfg(feature = "parallel")] +use rayon::iter::ParallelExtend; use super::{columns::*, Poseidon2Chip}; @@ -35,12 +38,21 @@ where let aux_cols_factory = memory_controller.borrow().aux_cols_factory(); let mut flat_rows: Vec<_> = records - .into_iter() + .into_par_iter() .flat_map(|record| Self::record_to_cols(&aux_cols_factory, record).flatten()) .collect(); - for _ in 0..diff { - flat_rows.extend(Poseidon2VmCols::>::blank_row(&air).flatten()); - } + #[cfg(feature = "parallel")] + flat_rows.par_extend( + vec![Poseidon2VmCols::>::blank_row(&air).flatten(); diff] + .into_par_iter() + .flatten(), + ); + #[cfg(not(feature = "parallel"))] + flat_rows.extend( + vec![Poseidon2VmCols::>::blank_row(&air).flatten(); diff] + .into_iter() + .flatten(), + ); AirProofInput::simple_no_pis( Arc::new(air.clone()), From 689e1715befe7ab30165452e0e59f2fcaa6803e4 Mon Sep 17 00:00:00 2001 From: stephenh-axiom-xyz Date: Sun, 15 Dec 2024 19:53:46 -0500 Subject: [PATCH 17/69] chore: default exe and app_config path in CLI (#1056) --- .github/workflows/ecc.yml | 2 +- benchmarks/src/bin/base64_json.rs | 3 +- benchmarks/src/bin/bincode.rs | 2 +- benchmarks/src/bin/ecrecover.rs | 3 +- benchmarks/src/bin/fib_e2e.rs | 2 +- benchmarks/src/bin/fibonacci.rs | 2 +- benchmarks/src/bin/regex.rs | 3 +- benchmarks/src/bin/revm_transfer.rs | 3 +- benchmarks/src/bin/rkyv.rs | 2 +- benchmarks/src/bin/verify_fibair.rs | 2 +- benchmarks/src/utils.rs | 6 +- .../example/{app_config.toml => openvm.toml} | 5 -- crates/cli/src/commands/bench.rs | 6 +- crates/cli/src/commands/build.rs | 55 ++++++---------- crates/cli/src/commands/keygen.rs | 9 ++- crates/cli/src/commands/prove.rs | 8 +-- crates/cli/src/commands/run.rs | 17 +++-- crates/cli/src/default.rs | 19 ++++++ crates/cli/src/util.rs | 23 ++++++- crates/cli/tests/app_e2e.rs | 64 +++---------------- crates/sdk/src/config/mod.rs | 33 ++++++++-- crates/sdk/src/keygen/mod.rs | 11 ++-- crates/sdk/tests/integration_test.rs | 3 +- extensions/bigint/circuit/src/extension.rs | 7 +- extensions/rv32im/circuit/src/extension.rs | 7 +- 25 files changed, 153 insertions(+), 144 deletions(-) rename crates/cli/example/{app_config.toml => openvm.toml} (53%) diff --git a/.github/workflows/ecc.yml b/.github/workflows/ecc.yml index 25e5b59867..5269998bd3 100644 --- a/.github/workflows/ecc.yml +++ b/.github/workflows/ecc.yml @@ -66,4 +66,4 @@ jobs: - name: Build openvm-ecc-guest crate for openvm working-directory: extensions/ecc/guest run: | - cargo openvm build + cargo openvm build --no-transpile diff --git a/benchmarks/src/bin/base64_json.rs b/benchmarks/src/bin/base64_json.rs index 2fcc6e8ba4..a8b5e93528 100644 --- a/benchmarks/src/bin/base64_json.rs +++ b/benchmarks/src/bin/base64_json.rs @@ -39,7 +39,8 @@ fn main() -> Result<()> { .with_extension(Keccak256TranspilerExtension), )?; let app_config = AppConfig { - app_fri_params: FriParameters::standard_with_100_bits_conjectured_security(app_log_blowup), + app_fri_params: FriParameters::standard_with_100_bits_conjectured_security(app_log_blowup) + .into(), app_vm_config: Keccak256Rv32Config::default(), leaf_fri_params: FriParameters::standard_with_100_bits_conjectured_security(agg_log_blowup) .into(), diff --git a/benchmarks/src/bin/bincode.rs b/benchmarks/src/bin/bincode.rs index 65ca4b318b..3db752b6e6 100644 --- a/benchmarks/src/bin/bincode.rs +++ b/benchmarks/src/bin/bincode.rs @@ -30,7 +30,7 @@ fn main() -> Result<()> { }; let app_config = AppConfig { - app_fri_params, + app_fri_params: app_fri_params.into(), app_vm_config: Rv32ImConfig::default(), leaf_fri_params: leaf_fri_params.into(), compiler_options, diff --git a/benchmarks/src/bin/ecrecover.rs b/benchmarks/src/bin/ecrecover.rs index 1780882201..46c9301a38 100644 --- a/benchmarks/src/bin/ecrecover.rs +++ b/benchmarks/src/bin/ecrecover.rs @@ -121,7 +121,8 @@ fn main() -> Result<()> { )?; // TODO: update sw_setup macros and read it from elf. let vm_config = AppConfig { - app_fri_params: FriParameters::standard_with_100_bits_conjectured_security(app_log_blowup), + app_fri_params: FriParameters::standard_with_100_bits_conjectured_security(app_log_blowup) + .into(), app_vm_config: Rv32ImEcRecoverConfig::for_curves(vec![SECP256K1_CONFIG.clone()]), leaf_fri_params: FriParameters::standard_with_100_bits_conjectured_security(agg_log_blowup) .into(), diff --git a/benchmarks/src/bin/fib_e2e.rs b/benchmarks/src/bin/fib_e2e.rs index 188119eae1..3a33d93b04 100644 --- a/benchmarks/src/bin/fib_e2e.rs +++ b/benchmarks/src/bin/fib_e2e.rs @@ -49,7 +49,7 @@ async fn main() -> Result<()> { let max_segment_length = cli_args.max_segment_length.unwrap_or(1_000_000); let app_config = AppConfig { - app_fri_params, + app_fri_params: app_fri_params.into(), app_vm_config: Rv32ImConfig::with_public_values_and_segment_len( NUM_PUBLIC_VALUES, max_segment_length, diff --git a/benchmarks/src/bin/fibonacci.rs b/benchmarks/src/bin/fibonacci.rs index 93ffa3e746..e894b18c5e 100644 --- a/benchmarks/src/bin/fibonacci.rs +++ b/benchmarks/src/bin/fibonacci.rs @@ -51,7 +51,7 @@ fn main() -> Result<()> { }; let app_config = AppConfig { - app_fri_params, + app_fri_params: app_fri_params.into(), app_vm_config: Rv32ImConfig::default(), leaf_fri_params: leaf_fri_params.into(), compiler_options, diff --git a/benchmarks/src/bin/regex.rs b/benchmarks/src/bin/regex.rs index 59eaf604c7..369a3c2a69 100644 --- a/benchmarks/src/bin/regex.rs +++ b/benchmarks/src/bin/regex.rs @@ -39,7 +39,8 @@ fn main() -> Result<()> { .with_extension(Keccak256TranspilerExtension), )?; let app_config = AppConfig { - app_fri_params: FriParameters::standard_with_100_bits_conjectured_security(app_log_blowup), + app_fri_params: FriParameters::standard_with_100_bits_conjectured_security(app_log_blowup) + .into(), app_vm_config: Keccak256Rv32Config::default(), leaf_fri_params: FriParameters::standard_with_100_bits_conjectured_security(agg_log_blowup) .into(), diff --git a/benchmarks/src/bin/revm_transfer.rs b/benchmarks/src/bin/revm_transfer.rs index 10e69ba2c7..7c603bde3f 100644 --- a/benchmarks/src/bin/revm_transfer.rs +++ b/benchmarks/src/bin/revm_transfer.rs @@ -37,7 +37,8 @@ fn main() -> Result<()> { .with_extension(Rv32IoTranspilerExtension), )?; let app_config = AppConfig { - app_fri_params: FriParameters::standard_with_100_bits_conjectured_security(app_log_blowup), + app_fri_params: FriParameters::standard_with_100_bits_conjectured_security(app_log_blowup) + .into(), app_vm_config: Keccak256Rv32Config::default(), leaf_fri_params: FriParameters::standard_with_100_bits_conjectured_security(1).into(), compiler_options: CompilerOptions::default().with_cycle_tracker(), diff --git a/benchmarks/src/bin/rkyv.rs b/benchmarks/src/bin/rkyv.rs index ff54f894a1..ab4dae2e0e 100644 --- a/benchmarks/src/bin/rkyv.rs +++ b/benchmarks/src/bin/rkyv.rs @@ -30,7 +30,7 @@ fn main() -> Result<()> { }; let app_config = AppConfig { - app_fri_params, + app_fri_params: app_fri_params.into(), app_vm_config: Rv32ImConfig::default(), leaf_fri_params: leaf_fri_params.into(), compiler_options, diff --git a/benchmarks/src/bin/verify_fibair.rs b/benchmarks/src/bin/verify_fibair.rs index 2b53ee4e22..3bc0ba335f 100644 --- a/benchmarks/src/bin/verify_fibair.rs +++ b/benchmarks/src/bin/verify_fibair.rs @@ -48,7 +48,7 @@ fn main() -> Result<()> { ..Default::default() }; let app_config = AppConfig { - app_fri_params: leaf_fri_params, + app_fri_params: leaf_fri_params.into(), app_vm_config, leaf_fri_params: leaf_fri_params.into(), compiler_options, diff --git a/benchmarks/src/utils.rs b/benchmarks/src/utils.rs index 2751323916..aad24b8af8 100644 --- a/benchmarks/src/utils.rs +++ b/benchmarks/src/utils.rs @@ -89,8 +89,8 @@ where VC::Executor: Chip, VC::Periphery: Chip, { - counter!("fri.log_blowup").absolute(app_config.app_fri_params.log_blowup as u64); - let engine = BabyBearPoseidon2Engine::new(app_config.app_fri_params); + counter!("fri.log_blowup").absolute(app_config.app_fri_params.fri_params.log_blowup as u64); + let engine = BabyBearPoseidon2Engine::new(app_config.app_fri_params.fri_params); let vm = VirtualMachine::new(engine, app_config.app_vm_config.clone()); // 1. Generate proving key from config. let app_pk = time(gauge!("keygen_time_ms"), || { @@ -98,7 +98,7 @@ where }); // 2. Commit to the exe by generating cached trace for program. let committed_exe = time(gauge!("commit_exe_time_ms"), || { - commit_app_exe(app_config.app_fri_params, exe) + commit_app_exe(app_config.app_fri_params.fri_params, exe) }); // 3. Executes runtime once with full metric collection for flamegraphs (slow). // 4. Executes runtime again without metric collection and generate trace. diff --git a/crates/cli/example/app_config.toml b/crates/cli/example/openvm.toml similarity index 53% rename from crates/cli/example/app_config.toml rename to crates/cli/example/openvm.toml index 1c74a3cd70..8ac6a25d95 100644 --- a/crates/cli/example/app_config.toml +++ b/crates/cli/example/openvm.toml @@ -1,8 +1,3 @@ -[app_fri_params] -log_blowup = 2 -num_queries = 42 -proof_of_work_bits = 16 - [app_vm_config.rv32i] [app_vm_config.rv32m] range_tuple_checker_sizes = [256, 2048] diff --git a/crates/cli/src/commands/bench.rs b/crates/cli/src/commands/bench.rs index e7c7270383..5e4c4bd808 100644 --- a/crates/cli/src/commands/bench.rs +++ b/crates/cli/src/commands/bench.rs @@ -17,7 +17,7 @@ use openvm_stark_sdk::{ }; use super::build::{build, BuildArgs}; -use crate::util::{write_status, Input}; +use crate::util::{classical_exe_path, write_status, Input}; #[derive(Clone, Parser)] #[command(name = "bench", about = "(default) Build and prove a program")] @@ -43,10 +43,8 @@ impl BenchCmd { if self.profile { setup_tracing(); } - let mut build_args = self.build_args.clone(); - build_args.transpile = true; let elf_path = build(&self.build_args)?.unwrap(); - let exe_path = build_args.exe_path(&elf_path); + let exe_path = classical_exe_path(&elf_path); let exe = read_exe_from_file(&exe_path)?; // TODO: read from openvm.toml diff --git a/crates/cli/src/commands/build.rs b/crates/cli/src/commands/build.rs index 30bce3f11f..95841b646f 100644 --- a/crates/cli/src/commands/build.rs +++ b/crates/cli/src/commands/build.rs @@ -1,22 +1,17 @@ -use std::{ - fs::read, - path::{Path, PathBuf}, -}; +use std::{fs::read, path::PathBuf}; use clap::Parser; use eyre::Result; use openvm_build::{ build_guest_package, find_unique_executable, get_package, GuestOptions, TargetFilter, }; -use openvm_rv32im_transpiler::{Rv32ITranspilerExtension, Rv32MTranspilerExtension}; -use openvm_sdk::{ - config::{AppConfig, SdkVmConfig}, - fs::write_exe_to_file, - Sdk, -}; -use openvm_transpiler::{elf::Elf, openvm_platform::memory::MEM_SIZE, transpiler::Transpiler}; +use openvm_sdk::{fs::write_exe_to_file, Sdk}; +use openvm_transpiler::{elf::Elf, openvm_platform::memory::MEM_SIZE}; -use crate::{default::DEFAULT_MANIFEST_DIR, util::read_to_struct_toml}; +use crate::{ + default::{DEFAULT_APP_CONFIG_PATH, DEFAULT_APP_EXE_PATH, DEFAULT_MANIFEST_DIR}, + util::read_config_toml_or_default, +}; #[derive(Parser)] #[command(name = "build", about = "Compile an OpenVM program")] @@ -53,34 +48,28 @@ pub struct BuildArgs { #[arg( long, default_value = "false", - help = "Transpiles the program after building when set" + help = "Skips transpilation into exe when set" )] - pub transpile: bool, + pub no_transpile: bool, #[arg( long, + default_value = DEFAULT_APP_CONFIG_PATH, help = "Path to the SDK config .toml file that specifies the transpiler extensions" )] - pub transpiler_config: Option, + pub config: PathBuf, #[arg( long, - help = "Output path for the transpiled program (default: .vmexe)" + default_value = DEFAULT_APP_EXE_PATH, + help = "Output path for the transpiled program" )] - pub transpile_to: Option, + pub exe_output: PathBuf, #[arg(long, default_value = "release", help = "Build profile")] pub profile: String, } -impl BuildArgs { - pub fn exe_path(&self, elf_path: &Path) -> PathBuf { - self.transpile_to - .clone() - .unwrap_or_else(|| elf_path.with_extension("vmexe")) - } -} - #[derive(Clone, clap::Args)] #[group(required = false, multiple = false)] pub struct BinTypeFilter { @@ -129,23 +118,17 @@ pub(crate) fn build(build_args: &BuildArgs) -> Result> { } }; - if build_args.transpile { + if !build_args.no_transpile { let elf_path = elf_path?; println!("[openvm] Transpiling the package..."); - let output_path = build_args.exe_path(&elf_path); - let transpiler = if let Some(transpiler_config) = build_args.transpiler_config.clone() { - let app_config: AppConfig = read_to_struct_toml(&transpiler_config)?; - app_config.app_vm_config.transpiler() - } else { - Transpiler::default() - .with_extension(Rv32ITranspilerExtension) - .with_extension(Rv32MTranspilerExtension) - }; + let output_path = &build_args.exe_output; + let app_config = read_config_toml_or_default(&build_args.config)?; + let transpiler = app_config.app_vm_config.transpiler(); let data = read(elf_path.clone())?; let elf = Elf::decode(&data, MEM_SIZE as u32)?; let exe = Sdk.transpile(elf, transpiler)?; - write_exe_to_file(exe, &output_path)?; + write_exe_to_file(exe, output_path)?; println!( "[openvm] Successfully transpiled to {}", diff --git a/crates/cli/src/commands/keygen.rs b/crates/cli/src/commands/keygen.rs index 2d1b2f67de..678ed8d5ae 100644 --- a/crates/cli/src/commands/keygen.rs +++ b/crates/cli/src/commands/keygen.rs @@ -3,20 +3,19 @@ use std::path::PathBuf; use clap::Parser; use eyre::Result; use openvm_sdk::{ - config::{AppConfig, SdkVmConfig}, fs::{write_app_pk_to_file, write_app_vk_to_file}, Sdk, }; use crate::{ - default::{DEFAULT_APP_PK_PATH, DEFAULT_APP_VK_PATH}, - util::read_to_struct_toml, + default::{DEFAULT_APP_CONFIG_PATH, DEFAULT_APP_PK_PATH, DEFAULT_APP_VK_PATH}, + util::read_config_toml_or_default, }; #[derive(Parser)] #[command(name = "keygen", about = "Generate an application proving key")] pub struct KeygenCmd { - #[clap(long, action, help = "Path to app config TOML file")] + #[clap(long, action, help = "Path to app config TOML file", default_value = DEFAULT_APP_CONFIG_PATH)] config: PathBuf, #[clap( @@ -38,7 +37,7 @@ pub struct KeygenCmd { impl KeygenCmd { pub fn run(&self) -> Result<()> { - let app_config: AppConfig = read_to_struct_toml(&self.config)?; + let app_config = read_config_toml_or_default(&self.config)?; let app_pk = Sdk.app_keygen(app_config)?; write_app_vk_to_file(app_pk.get_vk(), &self.vk_output)?; write_app_pk_to_file(app_pk, &self.output)?; diff --git a/crates/cli/src/commands/prove.rs b/crates/cli/src/commands/prove.rs index 4ade86d73e..bf263d2c1a 100644 --- a/crates/cli/src/commands/prove.rs +++ b/crates/cli/src/commands/prove.rs @@ -16,8 +16,8 @@ use openvm_sdk::{ use crate::{ default::{ - DEFAULT_AGG_PK_PATH, DEFAULT_APP_PK_PATH, DEFAULT_APP_PROOF_PATH, DEFAULT_EVM_PROOF_PATH, - DEFAULT_PARAMS_DIR, + DEFAULT_AGG_PK_PATH, DEFAULT_APP_EXE_PATH, DEFAULT_APP_PK_PATH, DEFAULT_APP_PROOF_PATH, + DEFAULT_EVM_PROOF_PATH, DEFAULT_PARAMS_DIR, }, util::{read_to_stdin, Input}, }; @@ -35,7 +35,7 @@ enum ProveSubCommand { #[clap(long, action, help = "Path to app proving key", default_value = DEFAULT_APP_PK_PATH)] app_pk: PathBuf, - #[clap(long, action, help = "Path to OpenVM executable")] + #[clap(long, action, help = "Path to OpenVM executable", default_value = DEFAULT_APP_EXE_PATH)] exe: PathBuf, #[clap(long, value_parser, help = "Input to OpenVM program")] @@ -48,7 +48,7 @@ enum ProveSubCommand { #[clap(long, action, help = "Path to app proving key", default_value = DEFAULT_APP_PK_PATH)] app_pk: PathBuf, - #[clap(long, action, help = "Path to OpenVM executable")] + #[clap(long, action, help = "Path to OpenVM executable", default_value = DEFAULT_APP_EXE_PATH)] exe: PathBuf, #[clap(long, value_parser, help = "Input to OpenVM program")] diff --git a/crates/cli/src/commands/run.rs b/crates/cli/src/commands/run.rs index 0bd51f4b8a..e498e14714 100644 --- a/crates/cli/src/commands/run.rs +++ b/crates/cli/src/commands/run.rs @@ -2,21 +2,20 @@ use std::path::PathBuf; use clap::Parser; use eyre::Result; -use openvm_sdk::{ - config::{AppConfig, SdkVmConfig}, - fs::read_exe_from_file, - Sdk, -}; +use openvm_sdk::{fs::read_exe_from_file, Sdk}; -use crate::util::{read_to_stdin, read_to_struct_toml, Input}; +use crate::{ + default::{DEFAULT_APP_CONFIG_PATH, DEFAULT_APP_EXE_PATH}, + util::{read_config_toml_or_default, read_to_stdin, Input}, +}; #[derive(Parser)] #[command(name = "run", about = "Run an OpenVM program")] pub struct RunCmd { - #[clap(long, action, help = "Path to OpenVM executable")] + #[clap(long, action, help = "Path to OpenVM executable", default_value = DEFAULT_APP_EXE_PATH)] exe: PathBuf, - #[clap(long, action, help = "Path to app config TOML file")] + #[clap(long, action, help = "Path to app config TOML file", default_value = DEFAULT_APP_CONFIG_PATH)] config: PathBuf, #[clap(long, value_parser, help = "Input to OpenVM program")] @@ -26,7 +25,7 @@ pub struct RunCmd { impl RunCmd { pub fn run(&self) -> Result<()> { let exe = read_exe_from_file(&self.exe)?; - let app_config: AppConfig = read_to_struct_toml(&self.config)?; + let app_config = read_config_toml_or_default(&self.config)?; let output = Sdk.execute(exe, app_config.app_vm_config, read_to_stdin(&self.input)?)?; println!("Execution output: {:?}", output); Ok(()) diff --git a/crates/cli/src/default.rs b/crates/cli/src/default.rs index 539152e4b8..d24bd0d043 100644 --- a/crates/cli/src/default.rs +++ b/crates/cli/src/default.rs @@ -1,10 +1,29 @@ +use openvm_sdk::config::{AppConfig, SdkVmConfig}; +use openvm_stark_sdk::config::FriParameters; + pub const DEFAULT_MANIFEST_DIR: &str = "."; pub const DEFAULT_AGG_PK_PATH: &str = concat!(env!("HOME"), "/.openvm/agg.pk"); pub const DEFAULT_VERIFIER_PATH: &str = concat!(env!("HOME"), "/.openvm/verifier.sol"); pub const DEFAULT_PARAMS_DIR: &str = concat!(env!("HOME"), "/.openvm/params/"); +pub const DEFAULT_APP_CONFIG_PATH: &str = "./openvm.toml"; +pub const DEFAULT_APP_EXE_PATH: &str = "./openvm/app.vmexe"; pub const DEFAULT_APP_PK_PATH: &str = "./openvm/app.pk"; pub const DEFAULT_APP_VK_PATH: &str = "./openvm/app.vk"; pub const DEFAULT_APP_PROOF_PATH: &str = "./openvm/app.proof"; pub const DEFAULT_EVM_PROOF_PATH: &str = "./openvm/evm.proof"; + +pub fn default_app_config() -> AppConfig { + AppConfig { + app_fri_params: FriParameters::standard_with_100_bits_conjectured_security(2).into(), + app_vm_config: SdkVmConfig::builder() + .system(Default::default()) + .rv32i(Default::default()) + .rv32m(Default::default()) + .io(Default::default()) + .build(), + leaf_fri_params: FriParameters::standard_with_100_bits_conjectured_security(2).into(), + compiler_options: Default::default(), + } +} diff --git a/crates/cli/src/util.rs b/crates/cli/src/util.rs index 4e80152123..a80e9043e3 100644 --- a/crates/cli/src/util.rs +++ b/crates/cli/src/util.rs @@ -6,9 +6,14 @@ use std::{ }; use eyre::Result; -use openvm_sdk::StdIn; +use openvm_sdk::{ + config::{AppConfig, SdkVmConfig}, + StdIn, +}; use serde::de::DeserializeOwned; +use crate::default::default_app_config; + #[allow(dead_code)] #[derive(Debug, Clone)] pub(crate) enum Input { @@ -56,6 +61,10 @@ pub(crate) fn write_status(style: &dyn Display, status: &str, msg: &str) { println!("{style}{status:>12}{style:#} {msg}"); } +pub(crate) fn classical_exe_path(elf_path: &Path) -> PathBuf { + elf_path.with_extension("vmexe") +} + pub(crate) fn read_to_struct_toml(path: &PathBuf) -> Result { let toml = read_to_string(path.as_ref() as &Path)?; let ret = toml::from_str(&toml)?; @@ -72,3 +81,15 @@ pub(crate) fn read_to_stdin(input: &Option) -> Result { None => Ok(StdIn::default()), } } + +pub(crate) fn read_config_toml_or_default(config: &PathBuf) -> Result> { + let mut app_config: Result> = read_to_struct_toml(config); + if app_config.is_err() { + println!( + "{:?} not found, using default application configuration", + config + ); + app_config = Ok(default_app_config()); + } + app_config +} diff --git a/crates/cli/tests/app_e2e.rs b/crates/cli/tests/app_e2e.rs index c68a1b661b..a2af9cb08f 100644 --- a/crates/cli/tests/app_e2e.rs +++ b/crates/cli/tests/app_e2e.rs @@ -18,11 +18,10 @@ fn test_cli_app_e2e() -> Result<()> { "openvm", "build", "--manifest-dir", - "../sdk/example", - "--transpile", - "--transpiler-config", - "example/app_config.toml", - "--transpile-to", + "example", + "--config", + "example/openvm.toml", + "--exe-output", temp_exe.to_str().unwrap(), ], )?; @@ -33,7 +32,7 @@ fn test_cli_app_e2e() -> Result<()> { "openvm", "keygen", "--config", - "example/app_config.toml", + "example/openvm.toml", "--output", temp_pk.to_str().unwrap(), "--vk-output", @@ -49,7 +48,7 @@ fn test_cli_app_e2e() -> Result<()> { "--exe", temp_exe.to_str().unwrap(), "--config", - "example/app_config.toml", + "example/openvm.toml", ], )?; @@ -86,55 +85,12 @@ fn test_cli_app_e2e() -> Result<()> { #[test] fn test_cli_app_e2e_default_paths() -> Result<()> { - let temp_dir = tempdir()?; run_cmd("cargo", &["install", "--path", ".", "--force"])?; - let temp_exe = temp_dir.path().join("example.vmexe"); - - run_cmd( - "cargo", - &[ - "openvm", - "build", - "--manifest-dir", - "../sdk/example", - "--transpile", - "--transpiler-config", - "example/app_config.toml", - "--transpile-to", - temp_exe.to_str().unwrap(), - ], - )?; - - run_cmd( - "cargo", - &["openvm", "keygen", "--config", "example/app_config.toml"], - )?; - - run_cmd( - "cargo", - &[ - "openvm", - "run", - "--exe", - temp_exe.to_str().unwrap(), - "--config", - "example/app_config.toml", - ], - )?; - - run_cmd( - "cargo", - &[ - "openvm", - "prove", - "app", - "--exe", - temp_exe.to_str().unwrap(), - ], - )?; - + run_cmd("cargo", &["openvm", "build", "--manifest-dir", "example"])?; + run_cmd("cargo", &["openvm", "keygen"])?; + run_cmd("cargo", &["openvm", "run"])?; + run_cmd("cargo", &["openvm", "prove", "app"])?; run_cmd("cargo", &["openvm", "verify", "app"])?; - Ok(()) } diff --git a/crates/sdk/src/config/mod.rs b/crates/sdk/src/config/mod.rs index aa71384605..76012d66d8 100644 --- a/crates/sdk/src/config/mod.rs +++ b/crates/sdk/src/config/mod.rs @@ -6,13 +6,15 @@ use serde::{Deserialize, Serialize}; mod global; pub use global::*; +const DEFAULT_APP_BLOWUP: usize = 2; const DEFAULT_LEAF_BLOWUP: usize = 2; const DEFAULT_INTERNAL_BLOWUP: usize = 2; const DEFAULT_ROOT_BLOWUP: usize = 3; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct AppConfig { - pub app_fri_params: FriParameters, + #[serde(default)] + pub app_fri_params: AppFriParams, pub app_vm_config: VC, #[serde(default)] pub leaf_fri_params: LeafFriParams, @@ -50,7 +52,7 @@ pub struct Halo2Config { impl AppConfig { pub fn new(app_fri_params: FriParameters, app_vm_config: VC) -> Self { Self { - app_fri_params, + app_fri_params: AppFriParams::from(app_fri_params), app_vm_config, leaf_fri_params: Default::default(), compiler_options: Default::default(), @@ -63,11 +65,9 @@ impl AppConfig { leaf_fri_params: FriParameters, ) -> Self { Self { - app_fri_params, + app_fri_params: AppFriParams::from(app_fri_params), app_vm_config, - leaf_fri_params: LeafFriParams { - fri_params: leaf_fri_params, - }, + leaf_fri_params: LeafFriParams::from(leaf_fri_params), compiler_options: Default::default(), } } @@ -103,6 +103,27 @@ impl Default for AggConfig { } } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AppFriParams { + pub fri_params: FriParameters, +} + +impl Default for AppFriParams { + fn default() -> Self { + Self { + fri_params: FriParameters::standard_with_100_bits_conjectured_security( + DEFAULT_APP_BLOWUP, + ), + } + } +} + +impl From for AppFriParams { + fn from(fri_params: FriParameters) -> Self { + Self { fri_params } + } +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct LeafFriParams { pub fri_params: FriParameters, diff --git a/crates/sdk/src/keygen/mod.rs b/crates/sdk/src/keygen/mod.rs index 62bb8ac168..3bdc8567d7 100644 --- a/crates/sdk/src/keygen/mod.rs +++ b/crates/sdk/src/keygen/mod.rs @@ -83,14 +83,17 @@ where VC::Periphery: Chip, { pub fn keygen(config: AppConfig) -> Self { - let app_engine = BabyBearPoseidon2Engine::new(config.app_fri_params); + let app_engine = BabyBearPoseidon2Engine::new(config.app_fri_params.fri_params); let app_vm_pk = { let vm = VirtualMachine::new(app_engine, config.app_vm_config.clone()); let vm_pk = vm.keygen(); - assert!(vm_pk.max_constraint_degree <= config.app_fri_params.max_constraint_degree()); + assert!( + vm_pk.max_constraint_degree + <= config.app_fri_params.fri_params.max_constraint_degree() + ); assert!(config.app_vm_config.system().continuation_enabled); VmProvingKey { - fri_params: config.app_fri_params, + fri_params: config.app_fri_params.fri_params, vm_config: config.app_vm_config.clone(), vm_pk, } @@ -98,7 +101,7 @@ where let leaf_committed_exe = { let leaf_engine = BabyBearPoseidon2Engine::new(config.leaf_fri_params.fri_params); let leaf_program = LeafVmVerifierConfig { - app_fri_params: config.app_fri_params, + app_fri_params: config.app_fri_params.fri_params, app_system_config: config.app_vm_config.system().clone(), compiler_options: config.compiler_options, } diff --git a/crates/sdk/tests/integration_test.rs b/crates/sdk/tests/integration_test.rs index 7a57596fcf..c162a61517 100644 --- a/crates/sdk/tests/integration_test.rs +++ b/crates/sdk/tests/integration_test.rs @@ -112,7 +112,8 @@ fn agg_stark_config_for_test() -> AggStarkConfig { fn small_test_app_config(app_log_blowup: usize) -> AppConfig { AppConfig { - app_fri_params: standard_fri_params_with_100_bits_conjectured_security(app_log_blowup), + app_fri_params: standard_fri_params_with_100_bits_conjectured_security(app_log_blowup) + .into(), app_vm_config: NativeConfig::new( SystemConfig::default() .with_max_segment_len(200) diff --git a/extensions/bigint/circuit/src/extension.rs b/extensions/bigint/circuit/src/extension.rs index b4d3faf304..be112e7ac3 100644 --- a/extensions/bigint/circuit/src/extension.rs +++ b/extensions/bigint/circuit/src/extension.rs @@ -56,17 +56,22 @@ impl Default for Int256Rv32Config { #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct Int256 { + #[serde(default = "default_range_tuple_checker_sizes")] pub range_tuple_checker_sizes: [u32; 2], } impl Default for Int256 { fn default() -> Self { Self { - range_tuple_checker_sizes: [1 << 8, 32 * (1 << 8)], + range_tuple_checker_sizes: default_range_tuple_checker_sizes(), } } } +fn default_range_tuple_checker_sizes() -> [u32; 2] { + [1 << 8, 32 * (1 << 8)] +} + #[derive(ChipUsageGetter, Chip, InstructionExecutor, From, AnyEnum)] pub enum Int256Executor { BaseAlu256(Rv32BaseAlu256Chip), diff --git a/extensions/rv32im/circuit/src/extension.rs b/extensions/rv32im/circuit/src/extension.rs index 499b61579c..f3e24f7f82 100644 --- a/extensions/rv32im/circuit/src/extension.rs +++ b/extensions/rv32im/circuit/src/extension.rs @@ -133,17 +133,22 @@ pub struct Rv32Io; /// RISC-V 32-bit Multiplication Extension (RV32M) Extension #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct Rv32M { + #[serde(default = "default_range_tuple_checker_sizes")] pub range_tuple_checker_sizes: [u32; 2], } impl Default for Rv32M { fn default() -> Self { Self { - range_tuple_checker_sizes: [1 << 8, 8 * (1 << 8)], + range_tuple_checker_sizes: default_range_tuple_checker_sizes(), } } } +fn default_range_tuple_checker_sizes() -> [u32; 2] { + [1 << 8, 8 * (1 << 8)] +} + // ============ Executor and Periphery Enums for Extension ============ /// RISC-V 32-bit Base (RV32I) Instruction Executors From c9f6c33c80462ea0aea0935ba5413f66aaccebf7 Mon Sep 17 00:00:00 2001 From: Lun-Kai Hsu Date: Sun, 15 Dec 2024 17:01:38 -0800 Subject: [PATCH 18/69] [book] chapter on ecc guest (#1057) * book section on ecc guest * update * use from xy * Apply suggestions from code review * feat: add more explanations --------- Co-authored-by: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> --- book/src/SUMMARY.md | 7 ++- book/src/custom-extensions/ecc.md | 87 +++++++++++++++++++++++++- book/src/custom-extensions/overview.md | 20 +++--- 3 files changed, 104 insertions(+), 10 deletions(-) diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index f250675b66..939a8948eb 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -19,7 +19,12 @@ # Using Extensions -- [Customizable Extensions](./custom-extensions/overview.md) +- [Overview](./custom-extensions/overview.md) +- [Keccak](./custom-extensions/keccak.md) +- [Big Integer](./custom-extensions/bigint.md) +- [Algebra](./custom-extensions/algebra.md) +- [Elliptic Curve Cryptography](./custom-extensions/ecc.md) +- [Elliptic Curve Pairing](./custom-extensions/pairing.md) # Advanced Usage diff --git a/book/src/custom-extensions/ecc.md b/book/src/custom-extensions/ecc.md index 9dc1e3453b..6e37c13fc1 100644 --- a/book/src/custom-extensions/ecc.md +++ b/book/src/custom-extensions/ecc.md @@ -1,6 +1,33 @@ # OpenVM ECC -For elliptic curve cryptography, the `openvm-ecc` crate provides macros similar to those in [`openvm-algebra`](./algebra.md): +The OpenVM Elliptic Curve Cryptography Extension provides support for elliptic curve operations through the `openvm-ecc-guest` crate. + +## Available traits and methods + +- `Group` trait: + This represents an element of a [group]() where the operation is addition. Therefore the trait includes functions for `add`, `sub`, and `double`. + + - `IDENTITY` is the identity element of the group. + +- `CyclicGroup` trait: + It's a group that has a generator, so it defines `GENERATOR` and `NEG_GENERATOR`. + +- `WeierstrassPoint` trait: + It represents an affine point on a Weierstrass elliptic curve and it extends `Group`. + + - `Coordinate` type is the type of the coordinates of the point, and it implements `IntMod`. + - `x()`, `y()` are used to get the affine coordinates + - `from_xy` is a constructor for the point, which checks if the point is either identity or on the affine curve. + - The point supports elliptic curve operations through intrinsic functions `add_ne_nonidentity` and `double_nonidentity`. + - `decompress`: Sometimes an elliptic curve point is compressed and represented by its `x` coordinate and the odd/even parity of the `y` coordinate. `decompress` is used to decompress the point back to `(x, y)`. + +- `msm`: for multi-scalar multiplication. + +- `ecdsa`: for doing ECDSA signature verification and public key recovery from signature. + +## Macros + +For elliptic curve cryptography, the `openvm-ecc-guest` crate provides macros similar to those in [`openvm-algebra-guest`](./algebra.md): 1. **Declare**: Use `sw_declare!` to define elliptic curves over the previously declared moduli. For example: @@ -12,6 +39,7 @@ sw_declare! { ``` Each declared curve must specify the `mod_type` (implementing `IntMod`) and a constant `b` for the Weierstrass curve equation $y^2 = x^3 + b$. +This creates `Bls12_381G1Affine` and `Bn254G1Affine` structs which implement the `Group` and `WeierstrassPoint` traits. The underlying memory layout of the structs uses the memory layout of the `Bls12_381Fp` and `Bn254Fp` structs, respectively. 2. **Init**: Called once, it enumerates these curves and allows the compiler to produce optimized instructions: @@ -28,3 +56,60 @@ sw_init! { - `sw_declare!`: Declares elliptic curve structures. - `sw_init!`: Initializes them once, linking them to the underlying moduli. - `setup_sw_()`/`setup_all_curves()`: Secures runtime correctness. + +To use elliptic curve operations on a struct defined with `sw_declare!`, it is expected that the struct for the curve's coordinate field was defined using `moduli_declare!`. In particular, the coordinate field needs to be initialized and set up as described in the [algebra extension](./algebra.md) chapter. + +For the basic operations provided by the `WeierstrassPoint` trait, the scalar field is not needed. For the ECDSA functions in the `ecdsa` module, the scalar field must also be declared, initialized, and set up. + +## Example program + +See a working example [here](https://github.com/openvm-org/openvm/blob/main/crates/toolchain/tests/programs/examples/ec.rs). + +To use the ECC extension, add the following dependencies to `Cargo.toml`: + +```toml +openvm-algebra-guest = { git = "https://github.com/openvm-org/openvm.git" } +openvm-ecc-guest = { git = "https://github.com/openvm-org/openvm.git", features = ["k256"] } +``` + +One can define their own ECC structs but we will use the Secp256k1 struct from `openvm-ecc-guest` and thus the `k256` feature should be enabled. + +```rust +use openvm_ecc_guest::{ + k256::{Secp256k1Coord, Secp256k1Point, Secp256k1Scalar}, + Group, weierstrass::WeierstrassPoint, +}; + +openvm_algebra_guest::moduli_setup::moduli_init! { + "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F", + "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141" +} + +openvm_ecc_guest::sw_setup::sw_init! { + Secp256k1Coord, +} +``` + +We `moduli_init!` both the coordinate and scalar field because they were declared in the `k256` module, although we will not be using the scalar field below. + +With the above we can start doing elliptic curve operations like adding points: + +```rust +pub fn main() { + setup_all_moduli(); + setup_all_curves(); + let x1 = Secp256k1Coord::from_u32(1); + let y1 = Secp256k1Coord::from_le_bytes(&hex!( + "EEA7767E580D75BC6FDD7F58D2A84C2614FB22586068DB63B346C6E60AF21842" + )); + let p1 = Secp256k1Point::from_xy_nonidentity(x1, y1).unwrap(); + + let x2 = Secp256k1Coord::from_u32(2); + let y2 = Secp256k1Coord::from_le_bytes(&hex!( + "D1A847A8F879E0AEE32544DA5BA0B3BD1703A1F52867A5601FF6454DD8180499" + )); + let p2 = Secp256k1Point::from_xy_nonidentity(x2, y2).unwrap(); + + let p3 = &p1 + &p2; +} +``` diff --git a/book/src/custom-extensions/overview.md b/book/src/custom-extensions/overview.md index a23da253f6..2b5c427d0c 100644 --- a/book/src/custom-extensions/overview.md +++ b/book/src/custom-extensions/overview.md @@ -2,7 +2,17 @@ You can seamlessly integrate certain performance-optimized extensions maintained by the OpenVM team to enhance your arithmetic operations and cryptographic computations. -Certain arithmetic operations, particularly modular arithmetic, can be optimized significantly when the modulus is known at compile time. This approach requires a framework to inform the compiler about all the moduli and associated arithmetic structures we intend to use. To achieve this, three steps are involved: +In this chapter, we will explain how to use the following existing extensions: + +- [`openvm-keccak-guest`](./keccak.md) - Keccak256 hash function. +- [`openvm-bigint-guest`](./bigint.md) - Big integer arithmetic for 256-bit signed and unsigned integers. +- [`openvm-algebra-guest`](./algebra.md) - Modular arithmetic and complex field extensions. +- [`openvm-ecc-guest`](./ecc.md) - Elliptic curve cryptography. +- [`openvm-pairing-guest`](./pairing.md) - Elliptic curve optimal Ate pairings. + +Some extensions such as `openvm-keccak-guest` and `openvm-bigint-guest` can be enabled without specifying any additional configuration. + +On the other hand certain arithmetic operations, particularly modular arithmetic, can be optimized significantly when the modulus is known at compile time. This approach requires a framework to inform the compiler about all the moduli and associated arithmetic structures we intend to use. To achieve this, three steps are involved: 1. **Declare**: Introduce a modular arithmetic or related structure, along with its modulus and functionality. This can be done in any library or binary file. 2. **Init**: Performed exactly once in the final binary. It aggregates all previously declared structures, assigns them stable indices, and sets up linkage so that they can be referenced in generated code. @@ -10,10 +20,4 @@ Certain arithmetic operations, particularly modular arithmetic, can be optimized These steps ensure both performance and security: performance because the modulus is known at compile time, and security because runtime checks confirm that the correct structures have been initialized. -The list of existing extensions: - -- [`openvm-algebra`](./algebra.md) -- [`openvm-bigint`](./bigint.md) -- [`openvm-keccak`](./keccak.md) -- [`openvm-pairing`](./pairing.md) -- [`openvm-ecc`](./ecc.md) +Our design for the configuration procedure above was inspired by the [EVMMAX proposal](https://github.com/jwasinger/EIPs/blob/evmmax-2/EIPS/eip-6601.md). From 93e489081af7e91d23c1a50d49063e898857c807 Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Sun, 15 Dec 2024 20:12:04 -0500 Subject: [PATCH 19/69] chore: fix filename in command (#1064) --- book/src/writing-apps/write-program.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/book/src/writing-apps/write-program.md b/book/src/writing-apps/write-program.md index dd151de2c8..e6e9027ead 100644 --- a/book/src/writing-apps/write-program.md +++ b/book/src/writing-apps/write-program.md @@ -22,16 +22,18 @@ More examples of guest programs can be found in the [benchmarks/programs](https: Although it's usually ok to use std (like in quickstart), not all std functionalities are supported (e.g., randomness). There might be unexpected runtime errors if one uses std, so it is recommended you develop no_std libraries if possible to reduce surprises. Even without std, `assert!` and `panic!` can work as normal. To use `std` features, one should add the following to `Cargo.toml` feature sections: + ```toml [features] std = ["openvm/std"] -``` +``` ### Building and running -*TODO*: point to CLI installation instructions +_TODO_: point to CLI installation instructions First we need to build the program targeting the OpenVM runtime, and that requires some configuration. Put the following in `openvm.toml`: + ```toml [app_fri_params] log_blowup = 2 @@ -53,7 +55,7 @@ cargo openvm build --transpile --transpiler-config openvm.toml --transpile-to ou Next we can keygen the generate the proving and verifying keys: ```bash -cargo openvm keygen --config app_config.toml --output outputs/pk --vk-output outputs/vk +cargo openvm keygen --config openvm.toml --output outputs/pk --vk-output outputs/vk ``` Now, to prove the program some input is needed. The input parameter is either a hex string or a file path. So for example if we want to compute the 10th fibonacci number, we can run: @@ -70,6 +72,7 @@ No errors should be returned, and the proof should be correctly verified. The program can take input from stdin, with some functions provided by `openvm::io`. `openvm::io::read` takes from stdin and deserializes it into a generic type `T`, so one should specify the type when calling it: + ```rust let n: u64 = read(); ``` From 2c216cd6efc3013ff3bf78b92630a34ab689b0cd Mon Sep 17 00:00:00 2001 From: Golovanov399 Date: Mon, 16 Dec 2024 04:56:51 +0300 Subject: [PATCH 20/69] [book] add the toml related stuff to the algebra chapter (#1063) * Add the toml part to the algebra book chapter, also add the BigUint deserializer * Add a "no-hexadecimal" part --- Cargo.lock | 1 + book/src/custom-extensions/algebra.md | 14 ++++++++++++++ crates/cli/Cargo.toml | 1 + extensions/algebra/circuit/src/fp2_extension.rs | 6 +++++- extensions/algebra/circuit/src/lib.rs | 2 ++ .../algebra/circuit/src/modular_extension.rs | 10 +++++++--- extensions/algebra/circuit/src/util.rs | 16 ++++++++++++++++ 7 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 extensions/algebra/circuit/src/util.rs diff --git a/Cargo.lock b/Cargo.lock index c09ebf212b..8e7a4c094c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1082,6 +1082,7 @@ dependencies = [ "eyre", "goblin", "hex", + "num-bigint-dig", "openvm-build", "openvm-circuit", "openvm-cli-example-test", diff --git a/book/src/custom-extensions/algebra.md b/book/src/custom-extensions/algebra.md index c9a6b32f95..b5b00aea3e 100644 --- a/book/src/custom-extensions/algebra.md +++ b/book/src/custom-extensions/algebra.md @@ -141,3 +141,17 @@ pub fn main() { // Note that these assertions would fail, have we provided the `mod_idx` parameters wrongly. } ``` + +### Config parameters + +For the guest program to build successfully, all used moduli must be declared in the `.toml` config file in the following format: + +```toml +[app_vm_config.modular] +supported_modulus = ["115792089237316195423570985008687907853269984665640564039457584007908834671663"] + +[app_vm_config.fp2] +supported_modulus = ["115792089237316195423570985008687907853269984665640564039457584007908834671663"] +``` + +The `supported_modulus` parameter is a list of moduli that the guest program will use. They must be provided in decimal format in the `.toml` file. \ No newline at end of file diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 759bb9624d..b780ff724f 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -47,6 +47,7 @@ prettytable-rs = "0.10" textwrap = "0.16.0" ctrlc = "3.4.2" toml = { workspace = true } +num-bigint-dig = { workspace = true, features = ["serde"] } [dev-dependencies] openvm-cli-example-test = { path = "example" } diff --git a/extensions/algebra/circuit/src/fp2_extension.rs b/extensions/algebra/circuit/src/fp2_extension.rs index f6276f0c4f..8942c47867 100644 --- a/extensions/algebra/circuit/src/fp2_extension.rs +++ b/extensions/algebra/circuit/src/fp2_extension.rs @@ -19,10 +19,14 @@ use openvm_stark_backend::p3_field::PrimeField32; use serde::{Deserialize, Serialize}; use strum::EnumCount; -use crate::fp2_chip::{Fp2AddSubChip, Fp2MulDivChip}; +use crate::{ + fp2_chip::{Fp2AddSubChip, Fp2MulDivChip}, + util::deserialize_vec_biguint_from_str, +}; #[derive(Clone, Debug, derive_new::new, Serialize, Deserialize)] pub struct Fp2Extension { + #[serde(deserialize_with = "deserialize_vec_biguint_from_str")] pub supported_modulus: Vec, } diff --git a/extensions/algebra/circuit/src/lib.rs b/extensions/algebra/circuit/src/lib.rs index ffddacc61a..7018513dcf 100644 --- a/extensions/algebra/circuit/src/lib.rs +++ b/extensions/algebra/circuit/src/lib.rs @@ -1,6 +1,8 @@ pub mod fp2_chip; pub mod modular_chip; +mod util; + mod fp2; pub use fp2::*; mod modular_extension; diff --git a/extensions/algebra/circuit/src/modular_extension.rs b/extensions/algebra/circuit/src/modular_extension.rs index 604a1ea8e2..ccce43ada0 100644 --- a/extensions/algebra/circuit/src/modular_extension.rs +++ b/extensions/algebra/circuit/src/modular_extension.rs @@ -20,13 +20,17 @@ use openvm_stark_backend::p3_field::PrimeField32; use serde::{Deserialize, Serialize}; use strum::EnumCount; -use crate::modular_chip::{ - ModularAddSubChip, ModularAddSubCoreChip, ModularIsEqualChip, ModularIsEqualCoreChip, - ModularMulDivChip, ModularMulDivCoreChip, +use crate::{ + modular_chip::{ + ModularAddSubChip, ModularAddSubCoreChip, ModularIsEqualChip, ModularIsEqualCoreChip, + ModularMulDivChip, ModularMulDivCoreChip, + }, + util::deserialize_vec_biguint_from_str, }; #[derive(Clone, Debug, derive_new::new, Serialize, Deserialize)] pub struct ModularExtension { + #[serde(deserialize_with = "deserialize_vec_biguint_from_str")] pub supported_modulus: Vec, } diff --git a/extensions/algebra/circuit/src/util.rs b/extensions/algebra/circuit/src/util.rs new file mode 100644 index 0000000000..54bdba4348 --- /dev/null +++ b/extensions/algebra/circuit/src/util.rs @@ -0,0 +1,16 @@ +use num_bigint_dig::BigUint; +use serde::Deserialize; + +pub(crate) fn deserialize_vec_biguint_from_str<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + let v: Vec = Deserialize::deserialize(deserializer)?; + let res = v.into_iter().map(|s| s.parse()).collect::>(); + if res.iter().any(|x| x.is_err()) { + return Err(serde::de::Error::custom("Failed to parse BigUint")); + } + Ok(res.into_iter().map(|x| x.unwrap()).collect()) +} From 5b987912f07f2a2b9a76ea0bd42cc26373f7c69b Mon Sep 17 00:00:00 2001 From: Arayi Khalatyan <127004086+arayikhalatyan@users.noreply.github.com> Date: Sun, 15 Dec 2024 21:23:35 -0500 Subject: [PATCH 21/69] docs: keccak + bigint guest libraries (#1058) * feat: keccak + bigint guest libraries * fix: don't use hinting * address review comments --- book/src/custom-extensions/bigint.md | 196 +++++++++++++++++++++++++++ book/src/custom-extensions/keccak.md | 69 +++++++++- 2 files changed, 264 insertions(+), 1 deletion(-) diff --git a/book/src/custom-extensions/bigint.md b/book/src/custom-extensions/bigint.md index 61c459249e..8b0cfd53a5 100644 --- a/book/src/custom-extensions/bigint.md +++ b/book/src/custom-extensions/bigint.md @@ -1 +1,197 @@ # OpenVM BigInt + +The OpenVM BigInt extension (aka `Int256`) provides two structs: `U256` and `I256`. These structs can be used to perform 256 bit arithmetic operations. The functional part is provided by the `openvm-bigint-guest` crate, which is a guest library that can be used in any OpenVM program. + +## `U256` + +The `U256` struct is a 256-bit unsigned integer type. + +### Constants + +The `U256` struct has the following constants: + +- `MAX`: The maximum value of a `U256`. +- `MIN`: The minimum value of a `U256`. +- `ZERO`: The zero constant. + +### Constructors + +The `U256` struct implements the following constructors: `from_u8`, `from_u32`, and `from_u64`. + +### Binary Operations + +The `U256` struct implements the following binary operations: `addition`, `subtraction`, `multiplication`, `bitwise and`, `bitwise or`, `bitwise xor`, `bitwise shift right`, and `bitwise shift left`. All operations will wrap the result when the result is outside the range of the `U256` type. + +All of the operations can be used in 6 different ways: +`U256 op U256` or `U256 op &U256` or `&U256 op U256` or `&U256 op &U256` or `U256 op= U256` or `&U256 op= U256`. + +### Other + +When using the `U256` struct with `target_os = "zkvm"`, the struct utilizes efficient implementations of comparison operators as well as the `clone` method. + +### Example matrix multiplication using `U256` + +See the full example [here](https://github.com/openvm-org/openvm/blob/main/crates/toolchain/tests/programs/examples/matrix-power.rs). + +```rust +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +openvm::entry!(main); +use core::array; +use openvm_bigint_guest::U256; + +const N: usize = 16; +type Matrix = [[U256; N]; N]; + +pub fn get_matrix(val: u8) -> Matrix { + array::from_fn(|_| array::from_fn(|_| U256::from_u8(val))) +} + +pub fn mult(a: &Matrix, b: &Matrix) -> Matrix { + let mut c = get_matrix(0); + for i in 0..N { + for j in 0..N { + for k in 0..N { + c[i][j] += &a[i][k] * &b[k][j]; + } + } + } + c +} + +pub fn get_identity_matrix() -> Matrix { + let mut res = get_matrix(0); + for i in 0..N { + res[i][i] = U256::from_u8(1); + } + res +} + +pub fn main() { + let a: Matrix = get_identity_matrix(); + let b: Matrix = get_matrix(28); + let c: Matrix = mult(&a, &b); + assert_eq!(c, b); +} +``` + +## `I256` + +The `I256` struct is a 256-bit signed integer type. The `I256` struct is very similar to the `U256` struct. + +### Constants + +The `I256` struct has the following constants: + +- `MAX`: The maximum value of a `I256`. +- `MIN`: The minimum value of a `I256`. +- `ZERO`: The zero constant. + +### Binary Operations + +The `I256` struct implements the following binary operations: `addition`, `subtraction`, `multiplication`, `bitwise and`, `bitwise or`, `bitwise xor`, `bitwise shift right`, and `bitwise shift left`. All operations will wrap the result when the result is outside the range of the `I256` type. Note that unlike the `U256`, when performing the shift right operation `I256` will perform an arithmetic shift right (i.e. sign extends the result). + +All of the operations can be used in 6 different ways: +`I256 op I256` or `I256 op &I256` or `&I256 op I256` or `&I256 op &I256` or `I256 op= I256` or `&I256 op= I256`. + +### Constructors + +The `I256` struct implements the following constructors: `from_i8`, `from_i32`, and `from_i64`. + +### Other + +When using the `I256` struct with `target_os = "zkvm"`, the struct utilizes efficient implementations of comparison operators as well as the `clone` method. + +### Example matrix multiplication using `I256` + +See the full example [here](https://github.com/openvm-org/openvm/blob/main/crates/toolchain/tests/programs/examples/signed-matrix-power.rs). + +```rust +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +openvm::entry!(main); +use core::array; +use openvm_bigint_guest::I256; + +const N: usize = 16; +type Matrix = [[I256; N]; N]; + +pub fn get_matrix(val: i32) -> Matrix { + array::from_fn(|_| array::from_fn(|_| I256::from_i32(val))) +} + +pub fn mult(a: &Matrix, b: &Matrix) -> Matrix { + let mut c = get_matrix(0); + for i in 0..N { + for j in 0..N { + for k in 0..N { + c[i][j] += &a[i][k] * &b[k][j]; + } + } + } + c +} + +pub fn get_identity_matrix() -> Matrix { + let mut res = get_matrix(0); + for i in 0..N { + res[i][i] = I256::from_i32(1); + } + res +} + +pub fn main() { + let a: Matrix = get_identity_matrix(); + let b: Matrix = get_matrix(-28); + let c: Matrix = mult(&a, &b); + assert_eq!(c, b); +} +``` + +## External Functions + +The Bigint Guest extension provides another way to use the native implementation. It provides external functions that are meant to be linked to other external libraries. The external libraries can use these functions as a hook for the 256 bit integer native implementations. Enabled only when the `target_os = "zkvm"`. All of the functions are defined as `unsafe extern "C" fn`. Also, note that you must enable the feature `export-intrinsics` to make them globally linkable. + +- `zkvm_u256_wrapping_add_impl(result: *mut u8, a: *const u8, b: *const u8)`: takes in a pointer to the result, and two pointers to the inputs. `result = a + b`. +- `zkvm_u256_wrapping_sub_impl(result: *mut u8, a: *const u8, b: *const u8)`: takes in a pointer to the result, and two pointers to the inputs. `result = a - b`. +- `zkvm_u256_wrapping_mul_impl(result: *mut u8, a: *const u8, b: *const u8)`: takes in a pointer to the result, and two pointers to the inputs. `result = a * b`. +- `zkvm_u256_bitxor_impl(result: *mut u8, a: *const u8, b: *const u8)`: takes in a pointer to the result, and two pointers to the inputs. `result = a ^ b`. +- `zkvm_u256_bitand_impl(result: *mut u8, a: *const u8, b: *const u8)`: takes in a pointer to the result, and two pointers to the inputs. `result = a & b`. +- `zkvm_u256_bitor_impl(result: *mut u8, a: *const u8, b: *const u8)`: takes in a pointer to the result, and two pointers to the inputs. `result = a | b`. +- `zkvm_u256_wrapping_shl_impl(result: *mut u8, a: *const u8, b: *const u8)`: takes in a pointer to the result, and two pointers to the inputs. `result = a << b`. +- `zkvm_u256_wrapping_shr_impl(result: *mut u8, a: *const u8, b: *const u8)`: takes in a pointer to the result, and two pointers to the inputs. `result = a >> b`. +- `zkvm_u256_arithmetic_shr_impl(result: *mut u8, a: *const u8, b: *const u8)`: takes in a pointer to the result, and two pointers to the inputs. `result = a.arithmetic_shr(b)`. +- `zkvm_u256_eq_impl(a: *const u8, b: *const u8) -> bool`: takes in two pointers to the inputs. Returns `true` if `a == b`, otherwise `false`. +- `zkvm_u256_cmp_impl(a: *const u8, b: *const u8) -> Ordering`: takes in two pointers to the inputs. Returns the ordering of `a` and `b`. +- `zkvm_u256_clone_impl(result: *mut u8, a: *const u8)`: takes in a pointer to the result buffer, and a pointer to the input. `result = a`. + +And in the external library, you can do the following: + +```rust +extern "C" { + fn zkvm_u256_wrapping_add_impl(result: *mut u8, a: *const u8, b: *const u8); +} + +fn wrapping_add(a: &Custom_U256, b: &Custom_U256) -> Custom_U256 { + #[cfg(target_os = "zkvm")] { + let mut result: MaybeUninit = MaybeUninit::uninit(); + unsafe { + zkvm_u256_wrapping_add_impl(result.as_mut_ptr() as *mut u8, a as *const u8, b as *const u8); + } + unsafe { result.assume_init() } + } + #[cfg(not(target_os = "zkvm"))] { + // Regular wrapping add implementation + } +} +``` + +### Config parameters + +For the guest program to build successfully add the following to your `.toml` file: + +```toml +[app_vm_config.bigint] +``` \ No newline at end of file diff --git a/book/src/custom-extensions/keccak.md b/book/src/custom-extensions/keccak.md index 7e83568f07..527ea6c25b 100644 --- a/book/src/custom-extensions/keccak.md +++ b/book/src/custom-extensions/keccak.md @@ -1 +1,68 @@ -# OpenVM Keccak \ No newline at end of file +# OpenVM Keccak256 + +The OpenVm Keccak256 extension provides tools for using the Keccak-256 hash function. +The functional part is provided by the `openvm-keccak-guest` crate, which is a guest library that can be used in any OpenVM program. + +## Functions for guest code + +The OpenVM Keccak256 Guest extension provides two functions for using in your guest code: + +- `keccak256(input: &[u8]) -> [u8; 32]`: Computes the Keccak-256 hash of the input data and returns it as an array of 32 bytes. +- `set_keccak256(input: &[u8], output: &mut [u8; 32])`: Sets the output to the Keccak-256 hash of the input data into the provided output buffer. + +See the full example [here](https://github.com/openvm-org/openvm/blob/main/crates/toolchain/tests/programs/examples/keccak.rs). + +### Example: +```rust +use openvm_keccak256_guest::keccak256; + +pub fn main() { + let test_vectors = [ + ("", "C5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470"), + ("CC", "EEAD6DBFC7340A56CAEDC044696A168870549A6A7F6F56961E84A54BD9970B8A"), + ]; + for (input, expected_output) in test_vectors.iter() { + let input = Vec::from_hex(input).unwrap(); + let expected_output = Vec::from_hex(expected_output).unwrap(); + let output = keccak256(&black_box(input)); + if output != *expected_output { + panic!(); + } + } +} +``` + +## Native Keccak256 + +Keccak guest extension also provides another way to use the native Keccak-256 implementation. It provides a function that is meant to be linked to other external libraries. The external libraries can use this function as a hook for the Keccak-256 native implementation. Enabled only when the target is `zkvm`. + +- `native_keccak256(input: *const u8, len: usize, output: *mut u8)`: This function has `C` ABI. It takes in a pointer to the input, the length of the input, and a pointer to the output buffer. + +In the external library, you can do the following: + +```rust +extern "C" { + fn native_keccak256(input: *const u8, len: usize, output: *mut u8); +} + +fn keccak256(input: &[u8]) -> [u8; 32] { + #[cfg(target_os = "zkvm")] { + let mut output = [0u8; 32]; + unsafe { + native_keccak256(input.as_ptr(), input.len(), output.as_mut_ptr() as *mut u8); + } + output + } + #[cfg(not(target_os = "zkvm"))] { + // Regular Keccak-256 implementation + } +} +``` + +### Config parameters + +For the guest program to build successfully add the following to your `.toml` file: + +```toml +[app_vm_config.keccak256] +``` \ No newline at end of file From 930dd54aa6cb8857e25836c4125e8cd56c133332 Mon Sep 17 00:00:00 2001 From: Zach Langley Date: Sun, 15 Dec 2024 21:26:42 -0500 Subject: [PATCH 22/69] docs/crates cleanup (#1062) * docs/crates cleanup * Apply suggestions from code review * chore: another warning * chore: spacing * chore: mv * chore: link benchmarks * chore: update --------- Co-authored-by: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> --- docs/crates/README.md | 3 +- docs/crates/benchmarks.md | 14 ++- docs/crates/stark.md | 145 ------------------------------ docs/crates/vm-extensions.md | 17 +--- docs/crates/vm.md | 167 +++++++++++++++++------------------ 5 files changed, 90 insertions(+), 256 deletions(-) delete mode 100644 docs/crates/stark.md diff --git a/docs/crates/README.md b/docs/crates/README.md index 5306cb62b6..1ea30bfdb1 100644 --- a/docs/crates/README.md +++ b/docs/crates/README.md @@ -2,7 +2,8 @@ Code-level guides to the crates in the repository. -- [`openvm-stark-backend`](./stark.md): Proof system backend - `openvm-circuit` - [VM Architecture and Chips](./vm.md) - [VM Extensions](./vm-extensions.md) +- `openvm-benchmarks` + - [Running Benchmarks](./benchmarks.md) diff --git a/docs/crates/benchmarks.md b/docs/crates/benchmarks.md index 8c1a4d8a8a..150bf00002 100644 --- a/docs/crates/benchmarks.md +++ b/docs/crates/benchmarks.md @@ -3,19 +3,15 @@ To run benchmarks, install python3 and run (from root of repo): ```bash -python ci/scripts/bench.py +python ci/scripts/bench.py --instance_type --memory_allocator ``` -where `` is a benchmark implemented as a rust binary (located in `src/bin` in a crate). Current benchmark options are: - -- `verify_fibair` -- `fibonacci` -- `regex` - in the `benchmarks` crate. - The benchmark outputs a JSON of metrics. You can process this into markdown with: +where `` is a benchmark implemented as a rust binary (located in `src/bin` in the `openvm-benchmarks` crate). +For local benchmarking, the `--instance_type` flag can take an arbitrary string. +The benchmark outputs a JSON of metrics. You can process this into markdown with: ```bash -python ci/scripts/metric_unify/main.py +python ci/scripts/metric_unify/main.py --aggregation-json ci/scripts/metric_unify/aggregation.json ``` Currently the processing is done automatically at the end of `bench.py`. The script automatically detects if you have a previously saved metric file for the same benchmark and includes the diff report in the output. diff --git a/docs/crates/stark.md b/docs/crates/stark.md deleted file mode 100644 index 20287a0956..0000000000 --- a/docs/crates/stark.md +++ /dev/null @@ -1,145 +0,0 @@ -# STARK Backend - -### Traits for Constraints - -An AIR in our system represents the set of constraints and metadata necessary to generate and verify a STARK proof. This is implemened through the following set of traits, which are split between core plonky3 and our `stark-backend` crate, which provides: - -- the ability to handle logUp / interactions -- the ability to handle separate cached traces - -#### From plonky3 - -```rust -pub trait BaseAir { - fn width(&self) -> usize; -} - -pub trait Air: BaseAir { - fn eval(&self, builder: &mut AB); -} - -pub trait AirBuilder { - type F: Field; // use for constants - type Var: Into + Copy + // .. concrete type of row values - type Expr: AbstractField + // .. most general expression for a constraint -} -``` - -The way `Air` works is that you always implement `Air` with respect to "**some** `AirBuilder` with some properties (additional trait bounds)". However in practice we implement `Air` for "**all** `AirBuilder`s with some properties". - -The struct implementing `Air` should be **stateless**. The struct should only contain configuration parameters necessary to determine the AIR constraints. - -```rust -pub trait BaseAirWithPublicValues: BaseAir { - fn num_public_values(&self) -> usize { - 0 - } -} - -// to use default impl: -impl BaseAirWithPublicValues for MyAir {} -``` - -#### From `openvm-stark-backend` - -For cached trace support: - -```rust -/// An AIR with 1 or more main trace partitions. -pub trait PartitionedBaseAir: BaseAir { - /// By default, an AIR has no cached main trace. - fn cached_main_widths(&self) -> Vec { - vec![] - } - /// By default, an AIR has only one private main trace. - fn common_main_width(&self) -> usize { - self.width() - } -} - -// to use default impl: -impl PartitionedBaseAir for MyAir {} -``` - -The common main trace is the "usual" main trace. All common main trace across all AIRs are committed into one commitment. Cached main are additional sections of main trace that are committed individually. Cached trace is not used in VM **except** by ProgramAir, where the OpenVM `Program` is committed into a dedicated commitment. - -```rust -pub trait Rap: Sync -where - AB: PermutationAirBuilder, -{ - fn eval(&self, builder: &mut AB); -} -``` - -We auto-implement `Rap` for any `Air where AB: InteractionBuilder`. The `Rap` adds in the extension field columns specified by interactions; note that these columns are not specified explicitly in plonky3 trace generation. - -![image](../../assets/rap.png) - -So when you implement `Air` you automatically implement `Rap` **for some** AirBuilder. - -The stark-backend uses three different concrete `AirBuilder` implementations: - -- `SymbolicRapBuilder>` -- `ProverConstraintFolder<'a, SC>` -- `DebugConstraintBuilder<'a, SC>` - -that depend on a `SC: StarkGenericConfig`. The `SC` specifies FRI proof system configuration parameters. - -```rust -pub trait AnyRap: - Rap>> // for keygen to extract fixed data about the RAP - + for<'a> Rap> // for prover quotient polynomial calculation - + for<'a> Rap> // for debugging - + BaseAirWithPublicValues> - + PartitionedBaseAir> { - // .. -} -``` - -This is an **auto-implemented** trait on any struct that implements `Air` for all AirBuilders the backend cares about above, for a **specific** `SC`. - -The backend wants to be able to prove multiple different AIRs together. So it must take a bunch of different `dyn AnyRap`. For some sizing reasons, instead it must take `Arc>` where `Arc` is a smart pointer to get around lifetimes and cloning issues. It is best to always use `Arc`, don't mix `Arc, Rc, Box` for the above purpose. - -### Traits for Trace Generation - -To generate a proof, we pair an AIR (represented by `Arc>`) with a set of methods to generate input traces in the `Chip` trait: - -```rust -pub trait Chip { - fn air(&self) -> Arc>; - - /// Generate all necessary input for proving a single AIR. - fn generate_air_proof_input(self) -> AirProofInput; - fn generate_air_proof_input_with_id(self, air_id: usize) -> (usize, AirProofInput) { - (air_id, self.generate_air_proof_input()) - } -} -``` - -The struct implementing `Chip` is stateful and stores **records**, which are the minimal amount of data necessary to generate the values in the trace matrix. A chip owns exactly one AIR. - -- We must have `Chip` generic in `SC` to avoid many issues with returning `Arc>`. -- If you have an enum of `Chip`s, you can derive `Chip` on the enum using proc-macro `#[derive(Chip)]` from `afs_derive`. The macro expects the enum to be generic in ``. - -#### `StarkGenericConfig` - -`StarkGenericConfig` is a complicated trait with deeply nested associated types. There are various typedefs to get associated types out of it. The most important is `Val`; this is the field `F` you want. Import `Val` from `afs_stark_backend::config::Val`, which is a re-export of `p3_uni_stark::Val`. - -Usual way to implement: - -```rust -impl Chip for MyChip> -where Val: PrimeField32 { - // .. -} -``` - -If you need `F` for some reason and the above doesn't work, another way is: - -```rust -impl Chip for MyChip -where Domain: PolynomialSpace { - // .. -} -``` diff --git a/docs/crates/vm-extensions.md b/docs/crates/vm-extensions.md index 5ce4ce54a1..ea15304ba7 100644 --- a/docs/crates/vm-extensions.md +++ b/docs/crates/vm-extensions.md @@ -12,7 +12,8 @@ pub trait VmExtension { } ``` -The `VmExtensionTrait` is a way to specify how to construct a collection of chips and all assign opcodes to be handled by them. This data is collected into a `VmInventory` struct, which is returned. +The `VmExtension` trait is a way to specify how to construct a collection of chips and all assign opcodes to be handled +by them. This data is collected into a `VmInventory` struct, which is returned. To handle previous chip dependencies necessary for chip construction and also automatic bus index management, we provide a `VmInventoryBuilder` api. @@ -123,7 +124,7 @@ The macro will also make two big enums: one that is an enum of the `Ext*::Execut The macro will then generate a `create_chip_complex` function. -For that we need to understand what `VmChipComplex` is: it replaces the role of the previous `VmChipSet` and consists of: +For that we need to understand what `VmChipComplex` consists of: - System chips - `VmInventory` @@ -152,19 +153,9 @@ function. What this does in words: For each extension's inventory generation, the `VmInventoryBuilder` is provided with a view of all current chips already inside the running chip complex. This means the inventory generation process is sequential in the order the extensions are specified, and each extension has borrow access to all chips constructed by any extension before it. -### `VirtualMachine` - -The top level structs of `VirtualMachine`, `VmExecutor`, `SegmentExecutor` remain almost entirely the same, but now has `VmConfig` as a generic: - -```rust -pub struct VirtualMachine; -``` - -TODO: discuss usage - ## Examples -The `extensions/` folder contains extensions implementing all non-system functionality via several extensions. For example, the `Rv32I`, `Rv32M`, and `Rv32Io` extensions implement `VmExtension` in [`openvm-rv32im-circuit`](../../extensions/rv32im/circuit/) and correspond to the RISC-V 32-bit base and multiplication instruction sets and an extension for IO, respectively. +The [`extensions/`](../../extensions/) folder contains extensions implementing all non-system functionality via custom extensions. For example, the `Rv32I`, `Rv32M`, and `Rv32Io` extensions implement `VmExtension` in [`openvm-rv32im-circuit`](../../extensions/rv32im/circuit/) and correspond to the RISC-V 32-bit base and multiplication instruction sets and an extension for IO, respectively. # Design Choices diff --git a/docs/crates/vm.md b/docs/crates/vm.md index d60049d8ea..ce873f37bf 100644 --- a/docs/crates/vm.md +++ b/docs/crates/vm.md @@ -2,36 +2,40 @@ ### `InstructionExecutor` Trait -We define an **instruction** to be a VM **opcode** combined with the **operands** to the opcode. Running the instrumented runtime for an opcode is encapsulated in the following trait: +We define an **instruction** to be an **opcode** combined with the **operands** for the opcode. Running the instrumented +runtime for an opcode is encapsulated in the following trait: ```rust pub trait InstructionExecutor { - /// Runtime execution of the instruction, if the instruction is - /// owned by the current instance. May internally store records of - /// this call for later trace generation. + /// Runtime execution of the instruction, if the instruction is owned by the + /// current instance. May internally store records of this call for later trace generation. fn execute( &mut self, instruction: Instruction, - from_state: ExecutionState, - ) -> Result, ExecutionError>; + from_state: ExecutionState, + ) -> Result>; } ``` +There is a `struct VmOpcode(usize)` to protect the global opcode `usize`, which must be globally unique for each opcode +supported in a given VM. + ### Chips for Opcode Groups -We divide all opcodes in the VM into groups, each of which is handled by a single **chip**. A chip should be a struct of type `C` and associated Air of type `A` which satisfy the following trait bounds: +Opcodes are partitioned into groups, each of which is handled by a single **chip**. A chip should be a struct of +type `C` and associated Air of type `A` which satisfy the following trait bounds: ```rust C: Chip + InstructionExecutor A: Air + BaseAir + BaseAirWithPublicValues ``` -Together, these perform the following functionalities: - -- **Keygen:** This is done via the `.eval()` function from `Air` -- **Trace Generation:** This is done by calling `.execute()` from `InstructionExecutor` which stores execution records and then `generate_air_proof_input()` from `Chip` which generates the trace using the corresponding records. +Together, these provide the following functionalities: -There is a `struct VmOpcode(usize)` to protect the global opcode usize. +- **Keygen:** Performed via the `Air::::eval()` function. +- **Trace Generation:** This is done by calling `InstructionExecutor::::execute()` which computes and stores + execution records and then `Chip::::generate_air_proof_input()` which generates the trace using the corresponding + records. ### Phantom Sub-Instructions @@ -53,103 +57,80 @@ pub trait PhantomSubExecutor { pub struct PhantomDiscriminant(pub u16); ``` -The `PhantomChip` maintains a map `FxHashMap>>` to handle different phantom sub-instructions. +The `PhantomChip` internally maintains a mapping from `PhantomDiscriminant` to `Box>>` to +handle different phantom sub-instructions. ### VM Configuration -**This section needs to be updated for extensions.** - -Each specific instantiation of a modular VM is defined in the following structs which handle VMs with/without continuations: +Each specific instantiation of a modular VM is defined by the following struct: ```rust -pub struct VirtualMachine { - pub config: VC, - /// Streams are shared between `ExecutionSegment`s and within each - /// segment shared with any chip(s) that handle hint opcodes - streams: Arc>>, - initial_memory: Option>, -} - -pub struct SingleSegmentVM { - pub config: VC, - _marker: PhantomData, +pub struct VirtualMachine { + pub engine: E, + pub executor: VmExecutor, VC>, } ``` -The `Streams` holds an `input_stream` and `hint_stream`: +The engine type `E` should be `openvm_stark_backend::engine::StarkEngine `and the VM config type `VC` is +`openvm_circuit::arch::config::VmConfig>`, shown below. ```rust -pub struct Streams { - pub input_stream: VecDeque>, - pub hint_stream: VecDeque, -} -``` - -Configuration of opcodes and memory is handled by: +pub trait VmConfig: Clone + Serialize + DeserializeOwned { + type Executor: InstructionExecutor + AnyEnum + ChipUsageGetter; + type Periphery: AnyEnum + ChipUsageGetter; -```rust -pub struct VC { - /// List of all executors except modular executors. - pub executors: Vec, - /// List of all supported modulus - pub supported_modulus: Vec, - - pub poseidon2_max_constraint_degree: usize, - pub memory_config: MemoryConfig, - pub num_public_values: usize, - pub max_segment_len: usize, - pub collect_metrics: bool, -} + /// Must contain system config + fn system(&self) -> &SystemConfig; + fn system_mut(&mut self) -> &mut SystemConfig; -pub struct MemoryConfig { - pub addr_space_max_bits: usize, - pub pointer_max_bits: usize, - pub clk_max_bits: usize, - pub decomp: usize, - pub persistence_type: PersistenceType, + fn create_chip_complex( + &self, + ) -> Result, VmInventoryError>; } ``` +A `VmConfig` has two associated types: `Executor` and `Periphery`. The `Executor` is typically an enum over chips that +are instruction executors, while `Periphery` is an enum for the chips that are not. +See [VM Extensions](./vm-extensions.md) for more details. + ### ZK Operations for the VM #### Keygen -TODO: Update for `VmChipComplex`. +Key generation is computed from the `VmConfig` describing the VM. The `VmConfig` is used to create the `VmChipComplex`, +which in turn provides the list of AIRs that are used in the proving and verification process. #### Trace Generation Trace generation proceeds from: -> `VirtualMachine.execute_and_generate_with_cached_program()` +> `VirtualMachine::execute_and_generate_with_cached_program()` -with subsets of functionality offered by `.execute()` and `execute_and_generate()`. The following struct tracks each continuation segment: +with subsets of functionality offered by `VirtualMachine::execute()` and `VirtualMachine::execute_and_generate()`. The +following struct tracks each continuation segment: ```rust -pub struct ExecutionSegment { - pub config: VC, - pub chip_set: VmChipSet, - - // The streams should be mutated in serial without thread-safety, - // but the `VmCoreChip` trait requires thread-safety. - pub streams: Arc>>, - - pub final_memory: Option>, - - pub cycle_tracker: CycleTracker, - /// Collected metrics for this segment alone. - /// Only collected when `config.collect_metrics` is true. - pub(crate) collected_metrics: VmMetrics, +pub struct ExecutionSegment> { + pub chip_complex: VmChipComplex, + pub final_memory: Option>, + pub air_names: Vec, + pub since_last_segment_check: usize, } ``` This will: -- Split the execution into `ExecutionSegment`s using `ExecutionSegment.execute_from_pc()`, which calls `ExecutionSegment.should_segment()` to segment online. Note that this creates a `VmChipSet` for each segment from `VmConfig.create_chip_set()`, where **each segment contains each chip**. It also passes all streams to all segments and runs the generation in serial. -- Generate traces for each segment by calling `VmChipSet.generate_proof_input()`, which iterates through all chips in order and calls `generate_proof_input()`. +- Split the execution into `ExecutionSegment`s using `ExecutionSegment.execute_from_pc()`, which calls + `ExecutionSegment.should_segment()` to segment online. Note that this creates a `VmChipComplex` for each segment from + `VmConfig.create_chip_set()`, where **each segment contains each chip**. It also passes all streams to all segments + and runs the generation in serial. +- Generate traces for each segment by calling `VmChipSet.generate_proof_input()`, which iterates through all chips in + order and calls `generate_proof_input()`. #### Proof Generation -This is done by calling `StarkEngine.prove()` on `ProofInput` created from each segment in `generate_proof_input()`. There is no SDK-level API for this in `VirtualMachine` at present. +Prove generation is performed by calling `StarkEngine.prove()` on `ProofInput` created from each segment in +`generate_proof_input()`. There is no SDK-level API for this in `VirtualMachine` at present. ## VM Integration API @@ -168,14 +149,21 @@ Most chips in the VM satisfy this, with notable exceptions being Keccak and Pose - `VmCoreChip>` - `VmCoreAir>` -[!WARNING] -The word **core** will be banned from usage outside of this context. +> [!WARNING] +> The word **core** will be banned from usage outside of this context. -Main idea: each VM chip will be created from an AdapterChip and a CoreChip. Analogously, the VM AIR is created from an AdapterAir and CoreAir so that the columns of the VM AIR are formed by concatenating the columns from the AdapterAir followed by the CoreAir. +Main idea: each VM chip is created from an `AdapterChip` and a `CoreChip`. Analogously, the VM AIR is created from an +`AdapterAir` and `CoreAir` so that the columns of the VM AIR are formed by concatenating the columns from the +`AdapterAir` followed by the `CoreAir`. -The AdapterChip is responsible for all interactions with the VM system: it owns interactions with the memory bus, program bus, execution bus. It will read data from memory and expose the data (but not intermediate pointers, address spaces, etc.) to the CoreChip and then write data provided by the CoreChip back to memory. +The `AdapterChip` is responsible for all interactions with the VM system: it owns interactions with the memory bus, +program bus, execution bus. It will read data from memory and expose the data (but not intermediate pointers, address +spaces, etc.) to the CoreChip and then write data provided by the CoreChip back to memory. -The AdapterAir does not see the CoreAir, but the CoreAir is able to see the AdapterAir, meaning that the same AdapterAir can be used with several CoreAir's. The AdapterInterface provides a way for CoreAir to provide expressions to be included in AdapterAir constraints -- in particular AdapterAir interactions can still involve CoreAir expressions. +The `AdapterAir` does not see the `CoreAir`, but the `CoreAir` is able to see the `AdapterAir`, meaning that the same +`AdapterAir` +can be used with several `CoreAir`'s. The AdapterInterface provides a way for `CoreAir` to provide expressions to be +included in `AdapterAir` constraints -- in particular `AdapterAir` interactions can still involve `CoreAir` expressions. Traits with their associated types and functions: @@ -192,24 +180,24 @@ pub trait VmAdapterChip { type ReadRecord: Send; /// Records generated by adapter after main instruction execution type WriteRecord: Send; - /// AdapterAir should not have public values + /// `AdapterAir` should not have public values type Air: BaseAir + Clone; - type Interface: VmAdapterInterface; + type Interface: VmAdapterInterface; fn preprocess( &mut self, memory: &mut MemoryChip, instruction: &Instruction, - ) -> Result<(Reads>, Self::ReadRecord)>; + ) -> Result<(>::Reads, Self::ReadRecord)>; fn postprocess( &mut self, memory: &mut MemoryChip, instruction: &Instruction, - from_state: ExecutionState, + from_state: ExecutionState, ctx: AdapterRuntimeContext>, read_record: &Self::ReadRecord, - ) -> Result<(ExecutionState, Self::WriteRecord)>; + ) -> Result<(ExecutionState, Self::WriteRecord)>; /// Populates `row_slice` with values corresponding to `record`. /// The provided `row_slice` will have length equal to `self.air().width()`. @@ -220,7 +208,10 @@ pub trait VmAdapterChip { row_slice: &mut [F], read_record: Self::ReadRecord, write_record: Self::WriteRecord, + aux_cols_factory: &MemoryAuxColsFactory, ); + + fn air(&self) -> &Self::Air; } pub trait VmAdapterAir: BaseAir { @@ -272,7 +263,7 @@ pub struct AdapterRuntimeContext> { pub writes: I::Writes, } -// For passing from CoreAir to AdapterAir with T = AB::Expr +// For passing from `CoreAir` to `AdapterAir` with T = AB::Expr pub struct AdapterAirContext> { /// Leave as `None` to allow the adapter to decide the `to_pc` automatically. pub to_pc: Option, @@ -282,8 +273,8 @@ pub struct AdapterAirContext> { } ``` -[!WARNING] -You do not need to implement `Air` on the struct you implement `VmAdapterAir` or `VmCoreAir` on. +> [!WARNING] +> You do not need to implement `Air` on the struct you implement `VmAdapterAir` or `VmCoreAir` on. ### Creating a Chip from Adapter and Core @@ -366,4 +357,4 @@ pub struct ImmInstruction { pub opcode: T, pub imm: T } -``` \ No newline at end of file +``` From c9fc62368d498486a7ead66a444e6bd3e9aaa3e2 Mon Sep 17 00:00:00 2001 From: Golovanov399 Date: Mon, 16 Dec 2024 05:38:41 +0300 Subject: [PATCH 23/69] Turn MathJax on, replace $...$ with \\(...\\) (#1067) --- book/book.toml | 4 +--- book/src/custom-extensions/algebra.md | 11 +++++------ book/src/custom-extensions/ecc.md | 2 +- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/book/book.toml b/book/book.toml index 2e20f98145..c1c5a8953f 100644 --- a/book/book.toml +++ b/book/book.toml @@ -7,6 +7,4 @@ title = "OpenVM Book" [output.html] site-url = "https://book.openvm.dev/" -additional-head = [ - "" -] \ No newline at end of file +mathjax-support = true \ No newline at end of file diff --git a/book/src/custom-extensions/algebra.md b/book/src/custom-extensions/algebra.md index b5b00aea3e..47585c97be 100644 --- a/book/src/custom-extensions/algebra.md +++ b/book/src/custom-extensions/algebra.md @@ -1,6 +1,6 @@ # OpenVM Algebra -The OpenVM Algebra extension provides tools to create and manipulate modular arithmetic structures and their complex extensions. For example, if $p$ is prime, OpenVM Algebra can handle modular arithmetic in $\mathbb{F}_p$​ and its quadratic extension fields $\mathbb{F}_p[x]/(x^2 + 1)$. +The OpenVM Algebra extension provides tools to create and manipulate modular arithmetic structures and their complex extensions. For example, if \\(p\\) is prime, OpenVM Algebra can handle modular arithmetic in \\(\mathbb{F}_p\\)​ and its quadratic extension fields \\(\mathbb{F}_p[x]/(x^2 + 1)\\). The functional part is provided by the `openvm-algebra-guest` crate, which is a guest library that can be used in any OpenVM program. The macros for creating corresponding structs are in the `openvm-algebra-moduli-setup` and `openvm-algebra-complex-macros` crates. @@ -8,7 +8,7 @@ The functional part is provided by the `openvm-algebra-guest` crate, which is a - `IntMod` trait: Defines the type `Repr` and constants `MODULUS`, `NUM_LIMBS`, `ZERO`, and `ONE`. It also provides basic methods for constructing a modular arithmetic object and performing arithmetic operations. - - `Repr` typically is `[u8; NUM_LIMBS]`, representing the number’s underlying storage. + - `Repr` typically is `[u8; NUM_LIMBS]`, representing the number's underlying storage. - `MODULUS` is the compile-time known modulus. - `ZERO` and `ONE` represent the additive and multiplicative identities, respectively. - Constructors include `from_repr`, `from_le_bytes`, `from_be_bytes`, `from_u8`, `from_u32`, and `from_u64`. @@ -21,7 +21,6 @@ The functional part is provided by the `openvm-algebra-guest` crate, which is a To [leverage](./overview.md) compile-time known moduli for performance, you declare, initialize, and then set up the arithmetic structures: 1. **Declare**: Use the `moduli_declare!` macro to define a modular arithmetic struct. This can be done multiple times in various crates or modules: - ```rust moduli_declare! { Bls12_381Fp { modulus = "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab" }, @@ -42,7 +41,7 @@ moduli_init! { This step enumerates the declared moduli (e.g., `0` for the first one, `1` for the second one) and sets up internal linkage so the compiler can generate the appropriate RISC-V instructions associated with each modulus. -3. **Setup**: At runtime, before performing arithmetic, a setup instruction must be sent to ensure security and correctness. For the $i$-th modulus, you call `setup_()` (e.g., `setup_0()` or `setup_1()`). Alternatively, `setup_all_moduli()` can be used to handle all declared moduli. +3. **Setup**: At runtime, before performing arithmetic, a setup instruction must be sent to ensure security and correctness. For the \\(i\\)-th modulus, you call `setup_()` (e.g., `setup_0()` or `setup_1()`). Alternatively, `setup_all_moduli()` can be used to handle all declared moduli. **Summary**: - `moduli_declare!`: Declares modular arithmetic structures and can be done multiple times. @@ -51,7 +50,7 @@ This step enumerates the declared moduli (e.g., `0` for the first one, `1` for t ## Complex field extension -Complex extensions, such as $\mathbb{F}_p[x]/(x^2 + 1)$, are defined similarly using `complex_declare!` and `complex_init!`: +Complex extensions, such as \\(\mathbb{F}_p[x]/(x^2 + 1)\\), are defined similarly using `complex_declare!` and `complex_init!`: 1. **Declare**: @@ -154,4 +153,4 @@ supported_modulus = ["1157920892373161954235709850086879078532699846656405640394 supported_modulus = ["115792089237316195423570985008687907853269984665640564039457584007908834671663"] ``` -The `supported_modulus` parameter is a list of moduli that the guest program will use. They must be provided in decimal format in the `.toml` file. \ No newline at end of file +The `supported_modulus` parameter is a list of moduli that the guest program will use. They must be provided in decimal format in the `.toml` file. diff --git a/book/src/custom-extensions/ecc.md b/book/src/custom-extensions/ecc.md index 6e37c13fc1..9436d6b4a8 100644 --- a/book/src/custom-extensions/ecc.md +++ b/book/src/custom-extensions/ecc.md @@ -38,7 +38,7 @@ sw_declare! { } ``` -Each declared curve must specify the `mod_type` (implementing `IntMod`) and a constant `b` for the Weierstrass curve equation $y^2 = x^3 + b$. +Each declared curve must specify the `mod_type` (implementing `IntMod`) and a constant `b` for the Weierstrass curve equation \\(y^2 = x^3 + b\\). This creates `Bls12_381G1Affine` and `Bn254G1Affine` structs which implement the `Group` and `WeierstrassPoint` traits. The underlying memory layout of the structs uses the memory layout of the `Bls12_381Fp` and `Bn254Fp` structs, respectively. 2. **Init**: Called once, it enumerates these curves and allows the compiler to produce optimized instructions: From 979a4087cff579c5425517d70ea8fd93227feef8 Mon Sep 17 00:00:00 2001 From: Zach Langley Date: Sun, 15 Dec 2024 21:39:55 -0500 Subject: [PATCH 24/69] docs: Add Continuation VM doc to continuations.md (#1055) * docs: Add Continuation VM doc to continuations.md * Update language * comments * fix agg-2 image --- assets/agg-2.png | Bin 0 -> 88297 bytes assets/agg.png | Bin 0 -> 91907 bytes docs/specs/aggregation.md | 103 ------------------ docs/specs/continuations.md | 205 ++++++++++++++++++++++++++++++++++-- 4 files changed, 199 insertions(+), 109 deletions(-) create mode 100644 assets/agg-2.png create mode 100644 assets/agg.png delete mode 100644 docs/specs/aggregation.md diff --git a/assets/agg-2.png b/assets/agg-2.png new file mode 100644 index 0000000000000000000000000000000000000000..11fcc8de813fa0d824192b83a52f51783baa56f0 GIT binary patch literal 88297 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 z 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

cWMY99%wvNbo!VQVgQKrJS9ejle2U`I7<1 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 G`~LuDY1+^L literal 0 HcmV?d00001 diff --git a/assets/agg.png b/assets/agg.png new file mode 100644 index 0000000000000000000000000000000000000000..2e03723445cb7652f7ad02e7a32e07c90fbfbbf0 GIT binary patch literal 91907 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 - -## Static Aggregation - -Assume that we have a static (i.e., known ahead of time) list `allowed_vks` of STARK verifying keys (unique identifiers for STARK circuits). - -Suppose we have a variable-length list of proofs `proofs` where `proofs.len()` is independent of `allowed_vks.len()`. The goal is to produce a single STARK proof that asserts that `proofs[i]` verifies with respect verifying key `vk[i]` where `allowed_vks` contains `vk[i]`, for all `i`. Additionally, there should be the optionality to store a commitment to the ordered list of `(hash(vk[i]), public_values[i])` where `public_values[i]` are the public values of proof `i`. - -We aggregate `proofs` using a tree-structure. The arity of the tree can be adjusted for performance; -by default it is 2. The height of the tree is variable and equal to $\lceil \log{n} \rceil$ where $n$ is the number of proofs and the base of logarithm is the arity. - -We distinguish between three types of nodes in the tree: - -- Leaf -- Internal -- Root - -Each node of the tree will be a STARK VM circuit, _without continuations_, proving a VM program that runs STARK verification on an `arity` number of proofs. We make the distinction that each type of node in the tree may be a **different** VM circuit, meaning with different chip configurations. All VM circuits must support the opcodes necessary to do STARK verification. - -For each node type, a different program is run in the VM circuit: - -- Leaf: the program verifies `<=leaf_arity` proofs, where each proof is verified with respect to one of the verification keys in `allowed_vks`. The leaf program will have the proof, public values, and verifying keys of each proof in program memory, and the program can be augmented with additional checks (for example, state transitions checks are necessary for continuations). -- Internal: the program verifies `<= internal_arity` proofs, where all proofs are verified with respect to the same verifying key. This verifying key is either that of a leaf circuit or that of an internal circuit (the present circuit itself). The circuit cannot know the verifying key of itself, so to avoid a circular dependency, the hash of the verifying key is made a public value. -- Root: this program _may_ just be the same as the Internal program, but for the purposes of optimizing [on-chain aggregation](#on-chain-aggregation), there is the possiblity for it to be different. The root program verifies `<= root_arity` proofs, where all proofs are of the internal circuit. Note that `root_arity` may be `1`. - -### STARK Configurations - -Before proceeding, we must discuss the topic of STARK configurations: any STARK proof depends on at least three configuration parameters: - -- `F` the base field of the AIRs -- `EF` the extension field of the AIRs used for challenge values -- the hash function used for the FRI PCS. This hash function must be able to hash `F` and `EF` elements, where elements can be packed before hashing. - -For all Leaf and Internal circuits [above](#static-aggregation), we use an **Inner Config**. Example Inner Configs are: - -- `F` is BabyBear, `EF` is quartic extension of BabyBear, hash is BabyBearPoseidon2 -- `F` is BabyBear, `EF` is quartic extension of BabyBear, hash is SHA256 -- `F` is Mersenne31, `EF` is quartic extension of Mersenne31, hash is Mersenne31Poseidon2 -- `F` is Mersenne31, `EF` is quartic extension of Mersenne31, hash is SHA256 - -We discuss considerations for choice of hash below. - -On the other hand, the Root circuit will use an **Outer Config**. Example Outer Configs are: - -- `F` is BabyBear, `EF` is quartic extension of BabyBear, hash is BN254FrPoseidon2 (or BN254FrPoseidon1) -- ~~`F` is BabyBear, `EF` is quartic extension of BabyBear, hash is SHA256~~ -- `F` is BN254Fr, `EF` is BN254Fr, hash is BN254FrPoseidon2 (or BN254FrPoseidon1) -- ~~`F` is BN254Fr, `EF` is BN254Fr, hash is SHA256~~ -- Analogous configurations with BabyBear replaced with Mersenne31. - -To explain, since `31 * 8 < 254`, eight BabyBear field elements can be packed together and embedded (non-algebraically) into a BN254Fr field element. In this way BN254FrPoseidon2 can be used to hash BabyBear elements. - -The choice of hash function in the Outer Config only affects what hash must be verified in the Halo2 circuit for on-chain aggregation (see [below](#on-chain-aggregation)). For performance, it is therefore always better to use BN254FrPoseidon2 for the Outer Config. - -### On-chain Aggregation - -The Root circuit above is the last STARK circuit, whose single proof will in turn verify all initial `proofs`. Due to the size of STARK proofs, for on-chain verification we must wrap this proof inside an elliptic curve based SNARK proof so that the final SNARK proof can be verified on-chain (where on-chain currently means within an Ethereum Virtual Machine). - -We create a Halo2 circuit that verifies any proof of the Root STARK circuit. This is a non-universal circuit whose verifying key depends on the specific STARK circuit to be verified. The majority of the verification logic can be code-generated into the `halo2-lib` eDSL which uses a special vertical custom gate specialized for cheap on-chain verification cost. There are two main performance considerations: - -#### 1. Hash - -To perform FRI verification in the Halo2 circuit, the circuit must constrain calculations of STARK Outer Config hashes. As mentioned above, this hash will be BN254FrPoseidon2. The constraints for this hash can either be implemented directly using the `halo2-base` vertical gate, or with a custom gate. The custom gate will be faster but with higher verification cost. There are two approaches to consider: - -Approach A - -- Use a single Halo2 circuit with only thinnest `halo2-base` vertical gate to verify the Root STARK circuit proof. - -Approach B - -- Use a first Halo2 circuit with custom gate for BN254FrPoseidon2 to verify the Root STARK circuit proof. -- Use a second Halo2 circuit with only the thinnest `halo2-base` vertical gate to verify the previous Halo2 circuit. - -Approach B is likely better, provided that the time to generate both proofs is faster than the time to generate the single proof in Approach A. - -#### 2. Outer Config Base Field - -The Outer Config base field `F` can be either a 31-bit field or BN254Fr. - -When `F` is 31-bit field: - -- For FRI folding and other arithmetic in STARK verification, the Halo2 circuit must perform BabyBear prime field arithmetic and extension field arithmetic inside the halo2 circuit. These are non-native arithmetic operations. - -When `F` is BN254Fr and `EF` is BN254Fr: - -- Halo2 circuit only needs to perform native field arithmetic inside the halo2 circuit. -- The Root STARK circuit must now perform non-native BabyBear field arithmetic and extension field arithmetic inside the STARK to support the verification of the STARKs with the Inner Config. This non-native arithmetic is still expected to be much faster in the STARK than in Halo2, but the added chip complexity may also increase verifier cost in the Halo2 circuit. -- If the Inner Config hash is BabyBearPoseidon2, now the Root STARK circuit must constrain BabyBearPoseidon2 inside a circuit with base field BN254Fr. This is definitely not efficient. **Therefore it is not possible for the Outer Config base field to be BN254Fr if the Inner Config hash is BabyBearPoseidon2.** -- This Outer Config is only possible if the Inner Config hash is a hash that does not depend on the native field (e.g., SHA256 or Blake2b or Blake3). - - **Observation:** even if the hash used for the Internal circuit is SHA256, the Leaf circuit can still be proven using BabyBearPoseidon2. Likewise, it is even possible to have the Internal circuits use BabyBearPoseidon2 at higher depths in the tree (away from the root). The only requirement is that the last Internal circuit proof, which will be verified by the Root circuit, needs to be proven with SHA256 as the hash. - -TODO: to determine which Outer Config is best, we will: - -- Instrument the cost of non-native small field arithmetic in the Halo2 circuit. -- Benchmark an aggregation VM with Inner Config hash BabyBearPoseidon2 proven over BabyBearPoseidon2 versus one with Inner Config hash SHA256 proven over SHA256. - -## Dynamic Aggregation - -TODO diff --git a/docs/specs/continuations.md b/docs/specs/continuations.md index f45c847eaf..65f518fe65 100644 --- a/docs/specs/continuations.md +++ b/docs/specs/continuations.md @@ -1,3 +1,201 @@ +# Aggregation + +Given the execution segments of a program, each segment will be proven in parallel within a **Application VM** (App VM). +These proofs are subsequently aggregated into an aggregation tree by a **leaf aggregation +program**. This segment aggregation program runs inside _a different VM_, referred to as the **Aggregation VM** (Agg +VM), which operates without continuations enabled. + +The aggregation program takes a variable number of consecutive segment proofs and consolidates them into a single proof +that captures the entire range of segments. + +![Aggregation example](../../assets/agg.png) + +The following figure shows that the shape of the aggregation tree is not fixed. + +![Another aggregation example](../../assets/agg-2.png) + +We will now give an overview of the steps of the overall aggregation, starting from the final smart contract verifier +and going down to the application proof. + +## Smart Contract + +A smart contract is deployed by on-chain, which provides a function to verify a Halo2 proof. + +## Static Verifier Wrapper + +The **Static Verifier Wrapper** is a Halo2 SNARK verifier circuit generated by OpenVM. The static verifier +wrapper is determined by the following parameters: + +* Number of public values +* The Aggregation VM chip constraints (but **not** the App VM chips) + +## Continuation Verifier + +The continuation verifier is a Halo2 circuit (static verifier) together with some single segment VM circuits (Agg VM). +The continuation verifier depends on the specific circuit design of the static verifier and Aggregation VM, as well as +the number of user public values, but it does not depend on the App VM's circuit. + +The continuation verifier ensures that a set of ordered App VM segment proofs collectively validates the execution of a +specific `VmExe` on a specific App VM, with given inputs. + +### Static Verifier + +The Static Verifier is a Halo2 verifier circuit that validates a Root VM Verifier proof and exposes its public values. + +Static Verifier Requirements: + +* The height of each trace is fixed. +* Trace heights are in a descending order. + +Public Values Exposed: + +* Exe commit encoded in Bn254 +* Leaf commit encoded in Bn254 +* User public values in BabyBear + +Parameters (which could result in a different circuit): + +* Number of public values (from upper stream) +* k in Halo2 +* Determines the number of columns of the circuit. + +* Number of public values (from upstream) +* k in Halo2 (determines the number of columns in the circuit) +* Root VM verifier + * VK (including the heights of all traces) + * Root verifier program commitment + +### Aggregation VM + +The Aggregation VM organizes proofs into an aggregation tree, where nodes include: + +* Root VM Verifier +* Internal VM Verifier +* Leaf VM Verifier + +Each node can have an arbitrary number of children, enabling flexible tree structures to optimize for cost reduction +(more children) or latency reduction (less children) during proving. + +### Root VM Verifier + +The Root VM Verifier is proven in RootConfig, using commitments via Bn254Poseidon2. All traces are padded to a constant +height for verification. + +The Root VM Verifier verifies 1 or more proofs of: + +- Leaf VM Verifier +- Internal VM Verifier + +In practice, Root VM verifier only verifies one proof to guarantee constant heights. + +Logical Input: + +* Root input + +Cached Trace Commit: + +* `ProgramAir`: commits the root verifier program + +Public values: + +* `RootVmVerifierPvs` + * Note: exe_commit is the commitment of the executable. The way to compute it can be found here. + +Parameters: + +* For circuit: + * Root VM Config +* For root verifier program: + * Root FRI parameters to compute its commitment + * Internal verifier circuit \+ program commitment + * Leaf verifier circuit \+ program commitment + +### Internal VM Verifier + +The Internal VM Verifier validates one or more proofs of: + +* Leaf VM Verifier +* Internal VM Verifier + +Logical Input: + +* `InternalVmVerifierInput` + +Cached Trace Commit: + +* `ProgramAir`: commits the internal verifier program. `agg_vm_pk` contains it. + +Public values: + +* `InternalVmVerifierPvs` + +Parameters: + +* For circuit: + * Internal VM Config +* For root verifier program: + * Internal FRI parameters to compute its commitment + * Internal verifier circuit \+ program commitment + * Leaf verifier circuit \+ program commitment + +### Leaf VM Verifier + +Verify 1 or more proofs of: + +* segment circuits + +Logical Input: + +* `LeafVmVerifierInput` + +Cached Trace Commit: + +* ProgramAir: commits the leaf verifier program. The leaf verifier program commits . + +Public values: + +* `VmVerifierPvs` + +Parameters: + +* For circuit: + * Leaf VM Config +* For leaf verifier program: + * It’s not a part of the Continuation Verifier because it depends on the VK of the App VM and it doesn’t affect the VK + of the static verifier. + +### App VM + +App VM executes an executable with inputs and returns a list of segment proofs. + +## Segment + +Logical Input: + +* App VM input stream + +Cached Trace Commit: + +* ProgramAir: commits the program the App VM executed. + +Public values: + +* `VmConnectorPvs` +* `MemoryMerklePvs` + +User Public Values: + +* Up to `num_public_values` public values in a dedicated memory space. These public values are not exposed as public + values of segment circuits, but will be exposed by the final proof. + +Parameters: + +* Number of public values (from upstream) +* For circuit: + * App VM Config +* For App program: + * App FRI parameters to compute its commitment. + # Continuations Our high-level continuations framework follows previous standard designs (Starkware, Risc0), but uses a novel persistent @@ -82,9 +280,4 @@ and has the following interactions on the MERKLE_BUS**(-1, 0, (as - AS_OFFSET) \* 2^L, node_label, hash_final)** It receives `values` from the `MEMORY_BUS` and constrains `hash = compress(values, 0)` via the `POSEIDON2_DIRECT_BUS`. - -## Aggregation - -Given the execution segments of a program, we will prove each segment in a VM segment circuit in parallel. These proofs will then be aggregated in an [aggregation tree](../aggregation.md) by a segment aggregation program. This segment aggregation program will be run inside **a different VM** which **does not** have continuations turned on. The latter VM is called an **Aggregation VM**. - -See [Aggregation](../aggregation.md) for more details. +The aggregation program takes a variable number of consecutive segment proofs and consolidates them into a single proof From a82377326a59314ed35926538002760754bb386f Mon Sep 17 00:00:00 2001 From: stephenh-axiom-xyz Date: Sun, 15 Dec 2024 22:11:42 -0500 Subject: [PATCH 25/69] docs: SDK and CLI usage for proving and verifying (#1051) * docs: SDK and CLI usage for proving and verifying * docs: overview * docs: more updates * Update book/src/advanced-usage/sdk.md * chore: delete unused * chore: remove link * docs: more SDK + CLI stuff * docs: stdin section --------- Co-authored-by: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> --- book/src/advanced-usage/sdk.md | 131 +++++++++++++++++++++ book/src/advanced-usage/testing-program.md | 60 +--------- book/src/writing-apps/compile.md | 15 +++ book/src/writing-apps/onchain-verify.md | 28 +++++ book/src/writing-apps/overview.md | 76 ++++++++++++ book/src/writing-apps/prove.md | 43 +++++++ book/src/writing-apps/testing.md | 1 - book/src/writing-apps/verify.md | 30 ++++- crates/toolchain/build/src/lib.rs | 14 +++ 9 files changed, 337 insertions(+), 61 deletions(-) create mode 100644 book/src/advanced-usage/sdk.md delete mode 100644 book/src/writing-apps/testing.md diff --git a/book/src/advanced-usage/sdk.md b/book/src/advanced-usage/sdk.md new file mode 100644 index 0000000000..51a0145de3 --- /dev/null +++ b/book/src/advanced-usage/sdk.md @@ -0,0 +1,131 @@ +# Using the SDK + +While the CLI provides a convenient way to build, prove, and verify programs, you may want more fine-grained control over the process. The OpenVM Rust SDK allows you to customize various aspects of the workflow programmatically. + +For more information on the basic CLI flow, see [Overview of Basic Usage](./overview.md). Writing a guest program is the same as in the CLI. + +## Imports and Setup + +If you have a guest program and would like to try running the **host program** specified below, you can do so by adding the following imports and setup at the top of the file. You may need to modify the imports and/or the `SomeStruct` struct to match your program. + +```rust +use openvm::{platform::memory::MEM_SIZE, transpiler::elf::Elf}; +use openvm_circuit::arch::instructions::exe::OpenVmExe +use openvm_circuit::arch::VmExecutor; +use openvm_sdk::{config::SdkVmConfig, Sdk, StdIn}; + +let sdk = Sdk; + +#[derive(Serialize, Deserialize)] +pub struct SomeStruct { + pub a: u64, + pub b: u64, +} +``` + +## Building and Transpiling a Program + +The SDK provides lower-level control over the building and transpiling process. + +```rust +// 1. Build the VmConfig with the extensions needed. +let vm_config = SdkVmConfig::builder() + .system(Default::default()) + .rv32i(Default::default()) + .io(Default::default()) + .build(); + +// 2a. Build the ELF with guest options and a target filter. +let guest_opts = GuestOptions::default().with_features(vec!["parallel"]); +let target_filter = TargetFilter::default().with_kind("bin".to_string()); +let elf = sdk.build(guest_opts, "your_path_project_root", &target_filter)?; +// 2b. Load the ELF from a file +let elf = Elf::decode("your_path_to_elf", MEM_SIZE as u32)?; + +// 3. Transpile the ELF into a VmExe +let exe = sdk.transpile(elf, vm_config.transpiler())?; +``` + +### Using `SdkVmConfig` + +The `SdkVmConfig` struct allows you to specify the extensions and system configuration your VM will use. To customize your own configuration, you can use the `SdkVmConfig::builder()` method and set the extensions and system configuration you want. + +## Running a Program +To run your program and see the public value output, you can do the following: + +```rust +// 4. Format your input into StdIn +let my_input = SomeStruct; // anything that can be serialized +let mut stdin = StdIn::default(); +stdin.write(&my_input); + +// 5. Run the program +let output = sdk.execute(exe, vm_config, input)?; +``` + +### Using `StdIn` + +The `StdIn` struct allows you to format any serializable type into a VM-readable format by passing in a reference to your struct into `StdIn::write` as above. You also have the option to pass in a `&[u8]` into `StdIn::write_bytes`, or a `&[F]` into `StdIn::write_field` where `F` is the `openvm_stark_sdk::p3_baby_bear::BabyBear` field type. + +> **Generating CLI Bytes** +> To get the VM byte representation of a serializable struct `data` (i.e. for use in the CLI), you can print out the result of `openvm::serde::to_vec(data).unwrap()` in a Rust host program. + +## Generating Proofs + +After building and transpiling a program, you can then generate a proof. To do so, you need to commit your `VmExe`, generate an `AppProvingKey`, format your input into `StdIn`, and then generate a proof. + +```rust +// 6. Set app configuration +let app_log_blowup = 2; +let app_fri_params = FriParameters::standard_with_100_bits_conjectured_security(app_log_blowup); +let app_config = AppConfig::new(app_fri_params, vm_config); + +// 7. Commit the exe +let app_committed_exe = sdk.commit_app_exe(app_fri_params, exe)?; + +// 8. Generate an AppProvingKey +let app_pk = sdk.app_keygen(app_config)?; + +// 9a. Generate a proof +let proof = sdk.generate_app_proof(app_pk, app_committed_exe, stdin)?; +// 9b. Generate a proof with an AppProver with custom fields +let mut app_prover = + AppProver::new(app_pk.app_vm_pk.clone(), app_committed_exe) + .with_program_name(program_name); +let proof = app_prover.generate_app_proof(stdin); +``` + +## Verifying Proofs +After generating a proof, you can verify it. To do so, you need your verifying key (which you can get from your `AppProvingKey`) and the output of your `generate_app_proof` call. + +```rust +// 10. Verify your program +let app_vk = app_pk.get_vk(); +sdk.verify_app_proof(&app_vk, &proof)?; +``` + +## End-to-end EVM Proof Generation and Verification + +Generating and verifying an EVM proof is an extension of the above process. + +```rust +// 11. Generate the aggregation proving key +const DEFAULT_PARAMS_DIR: &str = concat!(env!("HOME"), "/.openvm/params/"); +let halo2_params_reader = Halo2ParamsReader::new(DEFAULT_PARAMS_DIR); +let agg_config = AggConfig::default(); +let agg_pk = sdk.agg_keygen(agg_config, &halo2_params_reader)?; + +// 12. Generate an EVM proof +let proof = sdk.generate_evm_proof(&halo2_params_reader, app_pk, app_committed_exe, agg_pk, stdin)?; + +// 13. Generate the SNARK verifier contract +let verifier = sdk.generate_snark_verifier_contract(&halo2_params_reader, &agg_pk)?; + +// 14. Verify the EVM proof +sdk.verify_evm_proof(&verifier, &proof)?; +``` + +Note that `DEFAULT_PARAMS_DIR` is the directory where Halo2 parameters are stored by the `cargo openvm setup` CLI command. For more information on the setup process, see the [onchain verify](../writing-apps/onchain-verify.md) doc. + +> ⚠️ **WARNING** +> `cargo openvm setup` requires very large amounts of computation and memory (~200 GB). diff --git a/book/src/advanced-usage/testing-program.md b/book/src/advanced-usage/testing-program.md index 1db3d71521..afd89ad955 100644 --- a/book/src/advanced-usage/testing-program.md +++ b/book/src/advanced-usage/testing-program.md @@ -10,65 +10,7 @@ printf '\xA0\x86\x01\x00\x00\x00\x00\x00' | cargo run --features std ### Running with the OpenVM runtime -*TODO*: point to how to install CLI - -First to build the guest program: -``` -cargo axiom build -``` - -This compiles the guest program into an [ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) that can be found at `target/riscv32im-risc0-zkvm-elf` directory. -Next, a host program is needed to run the ELF with openvm runtime. This is where one can configure the openvm with different parameters. There are a few steps: - -```rust -use openvm::transpiler::{openvm_platform::memory::MEM_SIZE, elf::Elf}; -use openvm_circuit::arch::instructions::exe::OpenVmExe -use openvm_circuit::arch::VmExecutor; -use openvm_sdk::{config::SdkVmConfig, Sdk, StdIn}; - -let sdk = Sdk; -// 1. Build the vm config with the extensions needed. -// TODO: link to extension -let vm_config = SdkVmConfig::builder() - .system(Default::default()) - .rv32i(Default::default()) - .io(Default::default()) - .build(); - -// 2. Load the ELF -let elf = Elf::decode("your_path_to_elf", MEM_SIZE as u32)?; -let exe = OpenVmExe::from_elf(elf, vm_config.transpiler()).unwrap(); - -// 3. Prepare the input data -let my_input = SomeStruct; // anything that can be serialized -let mut stdin = StdIn::default(); -stdin.write(StdIn::from_bytes(my_input.as_bytes())); - -// 4. Run the program -let executor = VmExecutor::<_, _>::new(vm_config); -executor.execute(exe, stdin)?; -``` -Some example host programs can be found [here](https://github.com/openvm-org/openvm/tree/main/benchmarks/src/bin). - -### Generating to prove - -To generate a proof besides executing the program, instead of using `executor` above (step 4), do the following: -```rust -// Some additional configuration. -let app_log_blowup = 2; -let app_fri_params = FriParameters::standard_with_100_bits_conjectured_security(app_log_blowup); -let app_config = AppConfig { ... }; - -// Keygen and prove -let app_pk = sdk.app_keygen(app_config)?; -let app_committed_exe = sdk.commit_app_exe(app_fri_params, exe)?; -let mut app_prover = - AppProver::new(app_pk.app_vm_pk.clone(), app_committed_exe) - .with_program_name(program_name); -let proof = app_prover.generate_app_proof(stdin); -let app_vk = app_pk.get_vk(); -sdk.verify_app_proof(&app_vk, &proof)?; -``` +For more information on building, transpiling, running, generating proofs, and verifying proofs with the CLI, see the [CLI](../writing-apps/overview.md)section. To do the same with the SDK, see the [SDK](sdk.md) section. ## Troubleshooting diff --git a/book/src/writing-apps/compile.md b/book/src/writing-apps/compile.md index f3b18f2570..cdcfcb0c6b 100644 --- a/book/src/writing-apps/compile.md +++ b/book/src/writing-apps/compile.md @@ -7,3 +7,18 @@ First let's define some key terms used in cross-compilation: There are multiple things happening in the `cargo openvm build` command as in the section [here](./write-program.md). In short, this command compiles on host to an executable for guest target. It first compiles the program normally on your *host* platform with RISC-V and then transpiles it to a different target. See here for some explanation of [cross-compilation](https://rust-lang.github.io/rustup/cross-compilation.html). Right now we use `riscv32im-risc0-zkvm-elf` target which is available in the [Rust toolchain](https://doc.rust-lang.org/rustc/platform-support/riscv32im-risc0-zkvm-elf.html), but we will contribute an OpenVM target to Rust in the future. + +## Running a Program + +After building and transpiling a program, you can execute it using the `run` command. The `run` command has the following arguments: + +```bash +cargo openvm run + --exe + --config + --input +``` + +If `--exe` and/or `--config` are not provided, the command will search for these files in `./openvm/app.vmexe` and `./openvm.toml` respectively. If `./openvm.toml` is not present, a default configuration will be used. + +If your program doesn't require inputs, you can (and should) omit the `--input` flag. diff --git a/book/src/writing-apps/onchain-verify.md b/book/src/writing-apps/onchain-verify.md index ea369b8c26..60a0398552 100644 --- a/book/src/writing-apps/onchain-verify.md +++ b/book/src/writing-apps/onchain-verify.md @@ -1 +1,29 @@ # Onchain Verification + +## Generating the Aggregation Proving Key and EVM Verifier Contract + +The workflow for generating an end-to-end EVM proof requires first generating an aggregation proving key and EVM verifier contract. This can be done by running the following command: + +```bash +cargo openvm setup +``` +> ⚠️ **WARNING** +> This command requires very large amounts of computation and memory (~200 GB). + +Upon a successful run, the command will write `agg.pk` and `verifier.sol` to `~/.openvm/`, where `~` is the directory specified by environment variable `$HOME`. Every command that requires these files will look for them in this directory. + +> ⚠️ **WARNING** +> If the `$HOME` environment variable is not set, this command may fail. + +Note that `cargo openvm setup` may attempt to download other files (i.e. KZG parameters) from an AWS S3 bucket into `~/.openvm/`. + +## Generating and Verifying an EVM Proof + +To generate and verify an EVM proof, you need to run the following commands: + +```bash +cargo openvm prove evm --input +cargo openvm verify evm +``` + +These commands are very similar to their `app` subcommand counterparts. For more information on the `prove` and `verify` commands, see the [prove](./prove.md) and [verify](./verify.md) docs. diff --git a/book/src/writing-apps/overview.md b/book/src/writing-apps/overview.md index 6e66d0352f..0637d04452 100644 --- a/book/src/writing-apps/overview.md +++ b/book/src/writing-apps/overview.md @@ -1 +1,77 @@ # Overview of Basic Usage + +## Writing a Program + +The first step to using OpenVM is to write a Rust program that can be executed by an OpenVM virtual machine. Writing a program for OpenVM is very similar to writing a standard Rust program, with a few key differences necessary to support the OpenVM environment. For more detailed information about writing programs, see the [Writing Programs](./write-program.md) guide. + +## Building and Transpiling a Program + +At this point, you should have a guest program with a `Cargo.toml` file in the root of your project directory. What's next? + +The first thing you will want to do is build and transpile your program using the following command: + +```bash +cargo openvm build +``` + +By default this will build the project located in the current directory. To see if it runs correctly, you can try executing it with the following: + +```bash +cargo openvm run --input +``` + +Note if your program doesn't require inputs, you can omit the `--input` flag. + +For more information on both commands, see the [build](./build.md) docs. + +### Inputs + +The `--input` field needs to either be a hex string or a file path to a file that will be read as bytes. Note that if your hex string represents a single number, it should be written in little-endian format (as this is what the VM expects). To see how more complex inputs can be converted into a VM-readable format, see the **Using StdIn** section of the [SDK](../advanced-usage/sdk.md) doc. + +## Generating a Proof + +Given an app configuration TOML file, you first need to generate a proving and verifying key: + +```bash +cargo openvm keygen +``` + +After generating the keys, you can generate a proof by running: + +```bash +cargo openvm prove app --input +``` + +Again, if your program doesn't require inputs, you can omit the `--input` flag. + +For more information on the `keygen` and `prove` commands, see the [prove](./prove.md) doc. + +## Verifying a Proof + +To verify a proof using the CLI, you need to provide the verifying key and the proof. + +```bash +cargo openvm verify app +``` + +For more information on the `verify` command, see the [verify](./verify.md) doc. + +## End-to-end EVM Proof Generation and Verification + +The process above details the workflow necessary to build, prove, and verify a guest program at the application level. However, to generate the end-to-end EVM proof, you need to (a) setup the aggregation proving key and verifier contract and (b) generate/verify the proof at the EVM level. + +To do (a), you need to run the following command. If you've run it previously on your machine, there is no need to do so again. This will write files necessary for EVM proving in `~/.openvm/`. + +```bash +cargo openvm setup +``` + +> ⚠️ **WARNING** +> This command requires very large amounts of computation and memory (~200 GB). + +To do (b), you simply need to replace `app` in `cargo openvm prove` and `cargo openvm verify` as such: + +```bash +cargo openvm prove evm --input +cargo openvm verify evm +``` diff --git a/book/src/writing-apps/prove.md b/book/src/writing-apps/prove.md index e9d3a9bc66..39450a33de 100644 --- a/book/src/writing-apps/prove.md +++ b/book/src/writing-apps/prove.md @@ -1 +1,44 @@ # Generating Proofs + +Generating a proof using the CLI is simple - first generate a key, then generate your proof. Using command defaults, this looks like: + +```bash +cargo openvm keygen +cargo openvm prove [app | evm] +``` + +## Key Generation + +The `keygen` CLI command has the following optional arguments: + +```bash +cargo openvm keygen + --config + --output + --vk_output +``` + +If `--config` is not provided, the command will search for `./openvm.toml` and use that as the application configuration if present. If it is not present, a default configuration will be used. + +If `--output` and/or `--vk_output` are not provided, the keys will be written to default locations `./openvm/app.pk` and/or `./openvm/app.vk` respectively. + +## Proof Generation + +The `prove` CLI command has the following optional arguments: + +```bash +cargo openvm prove [app | evm] + --app_pk + --exe + --input + --output +``` + +If your program doesn't require inputs, you can (and should) omit the `--input` flag. + +If `--app_pk` and/or `--exe` are not provided, the command will search for these files in `./openvm/app.pk` and `./openvm/app.vmexe` respectively. Similarly, if `--output` is not provided then the command will write the proof to `./openvm/[app | evm].proof` by default. + +The `app` subcommand is used to generate an application-level proof, while the `evm` command generates an end-to-end EVM proof. + +> ⚠️ **WARNING** +> In order to run the `evm` subcommand, you must have previously called the costly `cargo openvm setup`, which requires very large amounts of computation and memory (~200 GB). diff --git a/book/src/writing-apps/testing.md b/book/src/writing-apps/testing.md deleted file mode 100644 index f00b526a98..0000000000 --- a/book/src/writing-apps/testing.md +++ /dev/null @@ -1 +0,0 @@ -# Testing diff --git a/book/src/writing-apps/verify.md b/book/src/writing-apps/verify.md index ea369b8c26..5c95f69488 100644 --- a/book/src/writing-apps/verify.md +++ b/book/src/writing-apps/verify.md @@ -1 +1,29 @@ -# Onchain Verification +# Verifying Proofs + +## Application Level + +Verifying a proof at the application level requires both the proof and application verifying key. + +```bash +cargo openvm verify app + --app_vk + --proof +``` + +If you omit `--app_vk` and/or `--proof`, the command will search for those files at `./openvm/app.vk` and `./openvm/app.proof` respectively. + +Once again, if you omitted `--output` and `--vk_output` in the `keygen` and `prove` commands, you can omit `--app_vk` and `--proof` in the `verify` command. + +## EVM Level + +Verifying a proof at the EVM level requires just the proof, as the command uses the verifier generated when `cargo openvm setup` was called. + +```bash +cargo openvm verify evm --proof +``` + +If `proof` is omitted, the command will search for the proof at `./openvm/evm.proof`. + +As with all other EVM-level commands, `cargo openvm setup` is a prerequisite for `verify`. +> ⚠️ **WARNING** +> `cargo openvm setup` requires very large amounts of computation and memory (~200 GB). diff --git a/crates/toolchain/build/src/lib.rs b/crates/toolchain/build/src/lib.rs index c55af96d75..7b95e1729f 100644 --- a/crates/toolchain/build/src/lib.rs +++ b/crates/toolchain/build/src/lib.rs @@ -333,6 +333,20 @@ pub struct TargetFilter { pub kind: Option, } +impl TargetFilter { + /// Set substring of target name to match. + pub fn with_name_substr(mut self, name_substr: String) -> Self { + self.name_substr = Some(name_substr); + self + } + + /// Set kind of target to match. + pub fn with_kind(mut self, kind: String) -> Self { + self.kind = Some(kind); + self + } +} + /// Finds the unique executable target in the given package and target directory, /// using the given target filter. pub fn find_unique_executable, Q: AsRef>( From 6b3e68a5048ddf19e743951838cbbaa557b4b1e0 Mon Sep 17 00:00:00 2001 From: Golovanov399 Date: Mon, 16 Dec 2024 06:21:45 +0300 Subject: [PATCH 26/69] [book] Write the manual on how to create a new extension (#1060) * Write the manual * Update book/src/new-extension/howto.md * Update book/src/new-extension/howto.md Co-authored-by: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> * Some comments * Other comments * Rename --------- Co-authored-by: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> --- book/src/advanced-usage/new-extension.md | 41 ++++++++++++++++++++++++ book/src/introduction.md | 4 +-- 2 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 book/src/advanced-usage/new-extension.md diff --git a/book/src/advanced-usage/new-extension.md b/book/src/advanced-usage/new-extension.md new file mode 100644 index 0000000000..ecbd7dd134 --- /dev/null +++ b/book/src/advanced-usage/new-extension.md @@ -0,0 +1,41 @@ +# Creating a New Extension + +Extensions in OpenVM let you introduce additional functionality without disrupting the existing system. Consider, for example, an extension that provides two new operations on `u32` values: one that squares a number, and another that multiplies it by three. With such an extension: + +1. You define functions like `square(x: u32) -> u32` and `mul3(x: u32) -> u32` for use in guest code. +2. When the compiler encounters these functions, it generates corresponding custom [RISC-V instructions](https://github.com/openvm-org/openvm/blob/main/docs/specs/RISCV.md). +3. During the transpilation phase, these custom instructions are translated into OpenVM instructions. +4. At runtime, the OpenVM program sends these new instructions to a specialized [chip](https://github.com/openvm-org/openvm/blob/main/docs/specs/circuit.md) that computes the results and ensures their correctness. + +This modular architecture means the extension cleanly adds new capabilities while leaving the rest of OpenVM untouched. The entire system, including the extension’s operations, can still be proven correct. + +Conceptually, a new extension consists of three parts: +- **Guest**: High-level Rust code that defines and uses the new operations. +- **Transpiler**: Logic that converts custom RISC-V instructions into corresponding OpenVM instructions. +- **Circuit**: The special chips that enforce correctness of instruction execution through polynomial constraints. + +## Guest + +In the guest component, your goal is to produce the instructions that represent the new operations. When you want to perform an operation (for example, “calculate _this_ new function of _these_ arguments and write the result _here_”), you generate a custom instruction. You can use the helper macros `custom_insn_r!` and `custom_insn_i!` from `openvm_platform` to ensure these instructions follow the [RISC-V specification](https://riscv.org/specifications/ratified/). For more details, see the RISC-V [contributor documentation](https://github.com/openvm-org/openvm/blob/main/docs/specs/RISCV.md). + +## Transpiler + +The transpiler maps the newly introduced RISC-V instructions to their OpenVM counterparts. To achieve this, implement a struct that provides the `TranspilerExtension` trait, which includes: + +```rust +fn process_custom(&self, instruction_stream: &[u32]) -> Option<(Instruction, usize)>; +``` + +This function checks if the given instruction stream starts with one of your custom instructions. If so, it returns the corresponding OpenVM instruction and how many 32-bit words were consumed. If not, it returns `None`. + +Note that almost always the valid instruction consists of a single 32-bit RISC-V word (so whenever `Some(_, sz)` is returned, `sz` is 1), but in general this may not be the case. + +## Circuit + +The circuit component is where the extension’s logic is enforced in a zero-knowledge proof context. Here, you create a chip that: + +- Implements the computing logic, so that the output always corresponds to the correct result of the new operation. The chip has access to the memory shared with the other chips from the VM via [our special architecture](https://github.com/openvm-org/openvm/blob/main/docs/specs/ISA.md). +- Properly constrains all the inputs, outputs and intermediate variables using polynomial equations in such a way that there is no way to fill these variables with values that correspond to an incorrect output while fitting the constraints. + + +For more technical details on writing circuits and constraints, consult the OpenVM [contributor documentation](https://github.com/openvm-org/openvm/blob/main/docs/specs/README.md), which provides specifications and guidelines for integrating your extension into the OpenVM framework. diff --git a/book/src/introduction.md b/book/src/introduction.md index 80d66d608f..2e77c1675d 100644 --- a/book/src/introduction.md +++ b/book/src/introduction.md @@ -18,7 +18,7 @@ OpenVM is an open-source zero-knowledge virtual machine (zkVM) framework focused The following chapters will guide you through: -- [Getting started](./getting-started/install.md) +- [Getting started](./getting-started/install.md). - [Writing applications](./writing-apps/overview.md) in Rust targeting OpenVM and generating proofs. - [Using existing extensions](./custom-extensions/overview.md) to optimize your Rust programs. -- How to add custom VM extensions +- [How to add custom VM extensions](./advanced-usage/new-extension.md). From 79451a74576068f883e8b869402f8bb3ed5261f4 Mon Sep 17 00:00:00 2001 From: Golovanov399 Date: Mon, 16 Dec 2024 06:25:07 +0300 Subject: [PATCH 27/69] [book] describe `build` flags in the book (#1066) * ChatGPT here is the `cargo openvm build -h` output please generate the manual chapter about it * Rename `compile.md` -> `build.md` * chore: summary * fixes --------- Co-authored-by: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> --- book/src/SUMMARY.md | 6 +- book/src/writing-apps/build.md | 141 +++++++++++++++++++++++++++++++ book/src/writing-apps/compile.md | 24 ------ 3 files changed, 144 insertions(+), 27 deletions(-) create mode 100644 book/src/writing-apps/build.md delete mode 100644 book/src/writing-apps/compile.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 939a8948eb..38f675db2d 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -12,8 +12,7 @@ - [Overview](./writing-apps/overview.md) - [Writing a Program](./writing-apps/write-program.md) -- [Cross-Compilation](./writing-apps/compile.md) -- [Testing](./writing-apps/testing.md) +- [Cross-Compilation](./writing-apps/build.md) - [Generating Proofs](./writing-apps/prove.md) - [Onchain Verification](./writing-apps/verify.md) @@ -29,4 +28,5 @@ # Advanced Usage - [Overview](./advanced-usage/overview.md) -- [Testing the program](./advanced-usage/testing-program.md) +- [SDK](./advanced-usage/sdk.md) +- [Testing](./advanced-usage/testing-program.md) diff --git a/book/src/writing-apps/build.md b/book/src/writing-apps/build.md new file mode 100644 index 0000000000..aadbb7b3f9 --- /dev/null +++ b/book/src/writing-apps/build.md @@ -0,0 +1,141 @@ +# Cross-Compilation + +First let's define some key terms used in cross-compilation: + +- **host** - the machine you're compiling and/or proving on. Note that one can compile and prove on different machines, but they are both called _host_ as they are traditional machine architectures. +- **guest** - the executable to be run in a different VM architecture (e.g. the OpenVM runtime, or Android app). + +There are multiple things happening in the `cargo openvm build` command as in the section [here](./write-program.md). In short, this command compiles on host to an executable for guest target. +It first compiles the program normally on your _host_ platform with RISC-V and then transpiles it to a different target. See here for some explanation of [cross-compilation](https://rust-lang.github.io/rustup/cross-compilation.html). +Right now we use `riscv32im-risc0-zkvm-elf` target which is available in the [Rust toolchain](https://doc.rust-lang.org/rustc/platform-support/riscv32im-risc0-zkvm-elf.html), but we will contribute an OpenVM target to Rust in the future. + +## Build flags + +The following flags are available for the `cargo openvm build` command: + +- `--manifest-dir ` + + **Description**: Specifies the directory containing the `Cargo.toml` file for the guest code. + + **Default**: The current directory (`.`). + + **Usage Example**: If your `Cargo.toml` is located in `my_project/`, you can run: + + ```bash + cargo openvm build --manifest-dir my_project + ``` + + This ensures the build command is executed in that directory. + +- `--features ` + + **Description**: Passes a list of feature flags to the Cargo build process. These flags enable or disable conditional compilation features defined in your `Cargo.toml`. + + **Usage Example**: To enable the `my_feature` feature: + + ```bash + cargo openvm build --features my_feature + ``` + +- `--bin` + + **Description**: Restricts the build to binary targets. If your project has multiple target types (binaries, libraries, examples, etc.), using `--bin` ensures only binary targets are considered. + + **Usage Example**: + + ```bash + cargo openvm build --bin + ``` + +- `--example` + + **Description**: Restricts the build to example targets. Projects often include code samples or demos under the examples directory, and this flag focuses on compiling those. + + **Usage Example**: + + ```bash + cargo openvm build --example + ``` + +- `--name ` + + **Description**: Filters targets by name. Only targets whose names contain the given substring will be built. + + **Usage Example**: To build only targets that have `client` in their name: + + ```bash + cargo openvm build --name client + ``` + +- `--no-transpile` + + **Description**: After building the guest code, doesn't transpile the target ELF into an OpenVM-compatible executable (by default it does). + + **Usage Example**: + + ```bash + cargo openvm build --no-transpile + ``` + +- `--config ` + + **Description**: Specifies the path to a .toml configuration file that defines which VM extensions to use. + + **Default**: `./openvm.toml` if `--config` flag is not provided. + + **Usage Example**: + + ```bash + cargo openvm build --config path/to/openvm.toml + ``` + + This allows you to customize the extensions. Currently the CLI only supports known extensions listed in the [Using Existing Extensions](../custom-extensions/overview.md) section. To use other extensions, use the [SDK](../advanced-usage/sdk.md). + +- `--exe-output ` + + **Description**: Sets the output path for the transpiled program. + + **Default**: `./openvm/app.vmexe` if `--exe-output` flag is not provided. + + **Usage Example**: To specify a custom output filename: + + ```bash + cargo openvm build --exe-output ./output/custom_name.vmexe + ``` + +- `--profile ` + + **Description**: Determines the build profile used by Cargo. Common profiles are dev (faster builds, less optimization) and release (slower builds, more optimization). + + **Default**: release + + **Usage Example**: + + ```bash + cargo openvm build --profile dev + ``` + +- `--help` + + **Description**: Prints a help message describing the available options and their usage. + + **Usage Example**: + + ```bash + cargo openvm build --help + ``` + +## Running a Program + +After building and transpiling a program, you can execute it using the `run` command. The `run` command has the following arguments: + +```bash +cargo openvm run + --exe + --config + --input +``` + +If `--exe` and/or `--config` are not provided, the command will search for these files in `./openvm/app.vmexe` and `./openvm.toml` respectively. If `./openvm.toml` is not present, a default configuration will be used. + +If your program doesn't require inputs, you can (and should) omit the `--input` flag. diff --git a/book/src/writing-apps/compile.md b/book/src/writing-apps/compile.md deleted file mode 100644 index cdcfcb0c6b..0000000000 --- a/book/src/writing-apps/compile.md +++ /dev/null @@ -1,24 +0,0 @@ -# Cross-Compilation - -First let's define some key terms used in cross-compilation: -- **host** - the machine you're compiling and/or proving on. Note that one can compile and prove on different machines, but they are both called *host* as they are traditional machine architectures. -- **guest** - the executable to be run in a different VM architecture (e.g. the OpenVM runtime, or Android app). - -There are multiple things happening in the `cargo openvm build` command as in the section [here](./write-program.md). In short, this command compiles on host to an executable for guest target. -It first compiles the program normally on your *host* platform with RISC-V and then transpiles it to a different target. See here for some explanation of [cross-compilation](https://rust-lang.github.io/rustup/cross-compilation.html). -Right now we use `riscv32im-risc0-zkvm-elf` target which is available in the [Rust toolchain](https://doc.rust-lang.org/rustc/platform-support/riscv32im-risc0-zkvm-elf.html), but we will contribute an OpenVM target to Rust in the future. - -## Running a Program - -After building and transpiling a program, you can execute it using the `run` command. The `run` command has the following arguments: - -```bash -cargo openvm run - --exe - --config - --input -``` - -If `--exe` and/or `--config` are not provided, the command will search for these files in `./openvm/app.vmexe` and `./openvm.toml` respectively. If `./openvm.toml` is not present, a default configuration will be used. - -If your program doesn't require inputs, you can (and should) omit the `--input` flag. From 2856a6c0ceeccb60e9ba1133837dddd40dbc4c1a Mon Sep 17 00:00:00 2001 From: stephenh-axiom-xyz Date: Sun, 15 Dec 2024 22:32:27 -0500 Subject: [PATCH 28/69] fix: output config TOML parse error when TOML exists but is incorrect (#1069) --- crates/cli/src/util.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/cli/src/util.rs b/crates/cli/src/util.rs index a80e9043e3..581f00acd9 100644 --- a/crates/cli/src/util.rs +++ b/crates/cli/src/util.rs @@ -83,13 +83,13 @@ pub(crate) fn read_to_stdin(input: &Option) -> Result { } pub(crate) fn read_config_toml_or_default(config: &PathBuf) -> Result> { - let mut app_config: Result> = read_to_struct_toml(config); - if app_config.is_err() { + if config.exists() { + read_to_struct_toml(config) + } else { println!( "{:?} not found, using default application configuration", config ); - app_config = Ok(default_app_config()); + Ok(default_app_config()) } - app_config } From e850856944402b6c57962f7f8c53264d71918a5b Mon Sep 17 00:00:00 2001 From: Yi Sun Date: Sun, 15 Dec 2024 21:37:36 -0600 Subject: [PATCH 29/69] chore: remove overview, testing from advanced, add new-extension (#1070) --- book/src/SUMMARY.md | 3 +-- book/src/advanced-usage/overview.md | 1 - book/src/advanced-usage/testing-program.md | 21 --------------------- 3 files changed, 1 insertion(+), 24 deletions(-) delete mode 100644 book/src/advanced-usage/overview.md delete mode 100644 book/src/advanced-usage/testing-program.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 38f675db2d..fb4852b228 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -27,6 +27,5 @@ # Advanced Usage -- [Overview](./advanced-usage/overview.md) - [SDK](./advanced-usage/sdk.md) -- [Testing](./advanced-usage/testing-program.md) +- [Creating a New Extension](./advanced-usage/new-extension.md) diff --git a/book/src/advanced-usage/overview.md b/book/src/advanced-usage/overview.md deleted file mode 100644 index 07dd0c5c77..0000000000 --- a/book/src/advanced-usage/overview.md +++ /dev/null @@ -1 +0,0 @@ -# Overview diff --git a/book/src/advanced-usage/testing-program.md b/book/src/advanced-usage/testing-program.md deleted file mode 100644 index afd89ad955..0000000000 --- a/book/src/advanced-usage/testing-program.md +++ /dev/null @@ -1,21 +0,0 @@ -## Testing the program - -### Running on the host machine - -To test the program on the host machine, one can use the `std` feature: `cargo run --features std`. So for example to run the [fibonacci program](https://github.com/openvm-org/openvm/tree/main/benchmarks/programs/fibonacci): - -```bash -printf '\xA0\x86\x01\x00\x00\x00\x00\x00' | cargo run --features std -``` - -### Running with the OpenVM runtime - -For more information on building, transpiling, running, generating proofs, and verifying proofs with the CLI, see the [CLI](../writing-apps/overview.md)section. To do the same with the SDK, see the [SDK](sdk.md) section. - -## Troubleshooting - -todo - -## FAQ - -todo \ No newline at end of file From b91081d108bb35f6bc2f4ba3e5121fed9a196c66 Mon Sep 17 00:00:00 2001 From: stephenh-axiom-xyz Date: Sun, 15 Dec 2024 22:42:14 -0500 Subject: [PATCH 30/69] docs: SDK code runs (#1071) --- book/src/advanced-usage/sdk.md | 64 +++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/book/src/advanced-usage/sdk.md b/book/src/advanced-usage/sdk.md index 51a0145de3..e4f2fba1f5 100644 --- a/book/src/advanced-usage/sdk.md +++ b/book/src/advanced-usage/sdk.md @@ -9,12 +9,19 @@ For more information on the basic CLI flow, see [Overview of Basic Usage](./over If you have a guest program and would like to try running the **host program** specified below, you can do so by adding the following imports and setup at the top of the file. You may need to modify the imports and/or the `SomeStruct` struct to match your program. ```rust -use openvm::{platform::memory::MEM_SIZE, transpiler::elf::Elf}; -use openvm_circuit::arch::instructions::exe::OpenVmExe -use openvm_circuit::arch::VmExecutor; -use openvm_sdk::{config::SdkVmConfig, Sdk, StdIn}; - -let sdk = Sdk; +use std::{fs, sync::Arc}; +use eyre::Result; +use openvm::platform::memory::MEM_SIZE; +use openvm_build::{GuestOptions, TargetFilter}; +use openvm_native_recursion::halo2::utils::CacheHalo2ParamsReader; +use openvm_sdk::{ + config::{AggConfig, AppConfig, SdkVmConfig}, + prover::AppProver, + Sdk, StdIn, +}; +use openvm_stark_sdk::config::FriParameters; +use openvm_transpiler::elf::Elf; +use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] pub struct SomeStruct { @@ -29,6 +36,7 @@ The SDK provides lower-level control over the building and transpiling process. ```rust // 1. Build the VmConfig with the extensions needed. +let sdk = Sdk; let vm_config = SdkVmConfig::builder() .system(Default::default()) .rv32i(Default::default()) @@ -40,7 +48,8 @@ let guest_opts = GuestOptions::default().with_features(vec!["parallel"]); let target_filter = TargetFilter::default().with_kind("bin".to_string()); let elf = sdk.build(guest_opts, "your_path_project_root", &target_filter)?; // 2b. Load the ELF from a file -let elf = Elf::decode("your_path_to_elf", MEM_SIZE as u32)?; +let elf_bytes = fs::read("your_path_to_elf")?; +let elf = Elf::decode(&elf_bytes, MEM_SIZE as u32)?; // 3. Transpile the ELF into a VmExe let exe = sdk.transpile(elf, vm_config.transpiler())?; @@ -55,12 +64,13 @@ To run your program and see the public value output, you can do the following: ```rust // 4. Format your input into StdIn -let my_input = SomeStruct; // anything that can be serialized +let my_input = SomeStruct { a: 1, b: 2 }; // anything that can be serialized let mut stdin = StdIn::default(); stdin.write(&my_input); // 5. Run the program -let output = sdk.execute(exe, vm_config, input)?; +let output = sdk.execute(exe.clone(), vm_config.clone(), stdin.clone())?; +println!("public values output: {:?}", output); ``` ### Using `StdIn` @@ -84,15 +94,14 @@ let app_config = AppConfig::new(app_fri_params, vm_config); let app_committed_exe = sdk.commit_app_exe(app_fri_params, exe)?; // 8. Generate an AppProvingKey -let app_pk = sdk.app_keygen(app_config)?; +let app_pk = Arc::new(sdk.app_keygen(app_config)?); // 9a. Generate a proof -let proof = sdk.generate_app_proof(app_pk, app_committed_exe, stdin)?; +let proof = sdk.generate_app_proof(app_pk.clone(), app_committed_exe.clone(), stdin.clone())?; // 9b. Generate a proof with an AppProver with custom fields -let mut app_prover = - AppProver::new(app_pk.app_vm_pk.clone(), app_committed_exe) - .with_program_name(program_name); -let proof = app_prover.generate_app_proof(stdin); +let app_prover = AppProver::new(app_pk.app_vm_pk.clone(), app_committed_exe.clone()) + .with_program_name("test_program"); +let proof = app_prover.generate_app_proof(stdin.clone()); ``` ## Verifying Proofs @@ -111,20 +120,33 @@ Generating and verifying an EVM proof is an extension of the above process. ```rust // 11. Generate the aggregation proving key const DEFAULT_PARAMS_DIR: &str = concat!(env!("HOME"), "/.openvm/params/"); -let halo2_params_reader = Halo2ParamsReader::new(DEFAULT_PARAMS_DIR); +let halo2_params_reader = CacheHalo2ParamsReader::new(DEFAULT_PARAMS_DIR); let agg_config = AggConfig::default(); let agg_pk = sdk.agg_keygen(agg_config, &halo2_params_reader)?; -// 12. Generate an EVM proof -let proof = sdk.generate_evm_proof(&halo2_params_reader, app_pk, app_committed_exe, agg_pk, stdin)?; - -// 13. Generate the SNARK verifier contract +// 12. Generate the SNARK verifier contract let verifier = sdk.generate_snark_verifier_contract(&halo2_params_reader, &agg_pk)?; +// 13. Generate an EVM proof +let proof = sdk.generate_evm_proof( + &halo2_params_reader, + app_pk, + app_committed_exe, + agg_pk, + stdin, +)?; + // 14. Verify the EVM proof -sdk.verify_evm_proof(&verifier, &proof)?; +let success = sdk.verify_evm_proof(&verifier, &proof); +assert!(success); ``` +> ⚠️ **WARNING** +> Generating an EVM proof will require a substantial amount of computation and memory. If you have run `cargo openvm setup` and don't need a specialized aggregation configuration, consider deserializing the proving key from the file `~/.openvm/agg.pk` instead of generating it. + +> ⚠️ **WARNING** +> The aggregation proving key `agg_pk` above is large. Avoidå cloning it if possible. + Note that `DEFAULT_PARAMS_DIR` is the directory where Halo2 parameters are stored by the `cargo openvm setup` CLI command. For more information on the setup process, see the [onchain verify](../writing-apps/onchain-verify.md) doc. > ⚠️ **WARNING** From b44367771171e68d33ed8d33e7667349148a3428 Mon Sep 17 00:00:00 2001 From: stephenh-axiom-xyz Date: Sun, 15 Dec 2024 22:57:32 -0500 Subject: [PATCH 31/69] fix: remove char in docs (#1072) --- book/src/advanced-usage/sdk.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/advanced-usage/sdk.md b/book/src/advanced-usage/sdk.md index e4f2fba1f5..39d6d1fc5a 100644 --- a/book/src/advanced-usage/sdk.md +++ b/book/src/advanced-usage/sdk.md @@ -145,7 +145,7 @@ assert!(success); > Generating an EVM proof will require a substantial amount of computation and memory. If you have run `cargo openvm setup` and don't need a specialized aggregation configuration, consider deserializing the proving key from the file `~/.openvm/agg.pk` instead of generating it. > ⚠️ **WARNING** -> The aggregation proving key `agg_pk` above is large. Avoidå cloning it if possible. +> The aggregation proving key `agg_pk` above is large. Avoid cloning it if possible. Note that `DEFAULT_PARAMS_DIR` is the directory where Halo2 parameters are stored by the `cargo openvm setup` CLI command. For more information on the setup process, see the [onchain verify](../writing-apps/onchain-verify.md) doc. From 410e4aae987abdf2ed08e7ef90859ff703d6e5d3 Mon Sep 17 00:00:00 2001 From: Yu Jiang Tham Date: Sun, 15 Dec 2024 19:57:52 -0800 Subject: [PATCH 32/69] [docs] `ecc-pairing` book page (#1054) * ecc-pairing initial draft * Fix links * Update book/src/SUMMARY.md Co-authored-by: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> * Update book/src/using-extensions/pairing.md Co-authored-by: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> * Update book/src/using-extensions/pairing.md Co-authored-by: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> * Update book/src/using-extensions/pairing.md Co-authored-by: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> * Address PR comments (without CLI yet) * WIP: openvm.toml section * Update pairing w/ hardcoded input code * chore: remove unneeded * chore: remove unnecessary sections --------- Co-authored-by: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> --- book/src/custom-extensions/pairing.md | 181 +++++++++++++++++++- crates/toolchain/tests/src/pairing_tests.rs | 1 - 2 files changed, 180 insertions(+), 2 deletions(-) diff --git a/book/src/custom-extensions/pairing.md b/book/src/custom-extensions/pairing.md index ebb943f14c..0f9b1fc333 100644 --- a/book/src/custom-extensions/pairing.md +++ b/book/src/custom-extensions/pairing.md @@ -1 +1,180 @@ -# OpenVM Pairing \ No newline at end of file +# Elliptic Curve Pairing + +The pairing extension enables usage of the optimal Ate pairing check on the BN254 and BLS12-381 elliptic curves. The following field extension tower for $\mathbb{F}_{p^{12}}$ is used for pairings in this crate: + +$$ +\mathbb{F_{p^2}} = \mathbb{F_{p}}[u]/(u^2 - \beta)\\ +\mathbb{F_{p^6}} = \mathbb{F_{p^2}}[v]/(v^3 - \xi)\\ +\mathbb{F_{p^{12}}} = \mathbb{F_{p^6}}[w]/(w^2 - v) +$$ + +A full guest program example is available here: [pairing_check.rs](https://github.com/openvm-org/openvm/blob/c19c9ac60b135bb0f38fc997df5eb149db8144b4/crates/toolchain/tests/programs/examples/pairing_check.rs) + +## Guest program setup + +We'll be working with an example using the BLS12-381 elliptic curve. This is in addition to the setup that needs to be done in the [Writing a Program](../writing-apps/write-program.md) section. + +In the guest program, we will import the `PairingCheck` and `IntMod` traits, along with the BLS12-381 curve structs (**IMPORTANT:** this requires the `bls12_381` feature enabled in Cargo.toml for the `openvm-pairing-guest` dependency), and a few other values that we will need: + +```rust title="guest program" +use openvm_pairing_guest::{ + pairing::PairingCheck, + bls12_381::{Bls12_381, Fp, Fp2}, +}; +use openvm_ecc_guest::AffinePoint; +use openvm_algebra_guest::IntMod; +use openvm::io::read; +``` + +Additionally, we'll need to initialize our moduli and `Fp2` struct via the following macros. For a more in-depth description of these macros, please see the [OpenVM Algebra](./algebra.md) section. + +```rust +// These correspond to the BLS12-381 coordinate and scalar moduli, respectively +openvm_algebra_moduli_setup::moduli_init! { + "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", + "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" +} + +openvm_algebra_complex_macros::complex_init! { + Bls12_381Fp2 { mod_idx = 0 }, +} +``` + +And we'll run the required setup functions at the top of the guest program's `main()` function: + +```rust +setup_0(); +setup_all_complex_extensions(); +``` + +There are two moduli defined internally in the Bls12_381 feature. The `moduli_init!` macro thus requires both of them to be initialized. However, we do not need the scalar field of BLS12-381 (which is at index 1), and thus we only initialize the modulus from index 0, thus we only use `setup_0()` (as opposed to `setup_all_moduli()`, which will save us some columns when generating the trace). + +## Input values + +The inputs to the pairing check are `AffinePoint`s in $\mathbb{F}_p$ and $\mathbb{F}_{p^2}$. They can be constructed via the `AffinePoint::new` function, with the inner `Fp` and `Fp2` values constructed via various `from_...` functions. + +We can create a new struct to hold these `AffinePoint`s for the purpose of this guide. You may instead put them into a custom struct to serve your use case. + +```rust +#[derive(Clone, serde::Serialize, serde::Deserialize)] +pub struct PairingCheckInput { + p0: AffinePoint, + p1: AffinePoint, + q0: AffinePoint, + q1: AffinePoint, +} +``` + +## Pairing check + +Most users that use the pairing extension will want to assert that a pairing is valid (the final exponentiation equals one). With the `PairingCheck` trait imported from the previous section, we have access to the `pairing_check` function on the `Bls12_381` struct. After reading in the input struct, we can use its values in the `pairing_check`: + +```rust +let res = Bls12_381::pairing_check( + &[p0, p1], + &[q0, q1], +); +assert!(res.is_ok()) +``` + +## Additional functionality + +We also have access to each of the specific functions that the pairing check utilizes for either the BN254 or BLS12-381 elliptic curves. + +### Multi-Miller loop + +The multi-Miller loop requires the MultiMillerLoop trait can also be ran separately via: + +```rust +let f = Bls12_381::multi_miller_loop( + &[p0, p1], + &[q0, q1], +); +``` + +## Running via CLI + +### Config parameters + +For the guest program to build successfully, we'll need to create an `openvm.toml` configuration file somewhere. It contains all of the necessary configuration information for enabling the OpenVM components that are used in the pairing check. + +```toml +# openvm.toml +[app_vm_config.pairing] +supported_curves = ["Bls12_381"] + +[app_vm_config.modular] +supported_modulus = [ + "1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", +] + +[app_vm_config.fp2] +supported_modulus = [ + "1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", +] +``` + +### Full example code + +This example code contains hardcoded values and no inputs as an example that can be run via the CLI. + +```rust +#![no_main] +#![no_std] + +use hex_literal::hex; +use openvm_algebra_guest::{field::FieldExtension, IntMod}; +use openvm_ecc_guest::AffinePoint; +use openvm_pairing_guest::{ + bls12_381::{Bls12_381, Fp, Fp2}, + pairing::PairingCheck, +}; + +openvm::entry!(main); + +openvm_algebra_moduli_setup::moduli_init! { + "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", + "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" +} + +openvm_algebra_complex_macros::complex_init! { + Bls12_381Fp2 { mod_idx = 0 }, +} + +pub fn main() { + setup_0(); + setup_all_complex_extensions(); + + let p0 = AffinePoint::new( + Fp::from_be_bytes(&hex!("17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb")), + Fp::from_be_bytes(&hex!("08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1")) + ); + let p1 = AffinePoint::new( + Fp2::from_coeffs([ + Fp::from_be_bytes(&hex!("1638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053")), + Fp::from_be_bytes(&hex!("0a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577")) + ]), + Fp2::from_coeffs([ + Fp::from_be_bytes(&hex!("0468fb440d82b0630aeb8dca2b5256789a66da69bf91009cbfe6bd221e47aa8ae88dece9764bf3bd999d95d71e4c9899")), + Fp::from_be_bytes(&hex!("0f6d4552fa65dd2638b361543f887136a43253d9c66c411697003f7a13c308f5422e1aa0a59c8967acdefd8b6e36ccf3")) + ]), + ); + let q0 = AffinePoint::new( + Fp::from_be_bytes(&hex!("0572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e")), + Fp::from_be_bytes(&hex!("166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28")) + ); + let q1 = AffinePoint::new( + Fp2::from_coeffs([ + Fp::from_be_bytes(&hex!("024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8")), + Fp::from_be_bytes(&hex!("13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e")) + ]), + Fp2::from_coeffs([ + Fp::from_be_bytes(&hex!("0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801")), + Fp::from_be_bytes(&hex!("0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be")) + ]), + ); + + let res = Bls12_381::pairing_check(&[p0, -q0], &[p1, q1]); + assert!(res.is_ok()); +} +``` diff --git a/crates/toolchain/tests/src/pairing_tests.rs b/crates/toolchain/tests/src/pairing_tests.rs index 1aea3af067..65912bfb86 100644 --- a/crates/toolchain/tests/src/pairing_tests.rs +++ b/crates/toolchain/tests/src/pairing_tests.rs @@ -515,7 +515,6 @@ mod bls12_381 { G2Affine::from(Q * Fr::from(2)), G2Affine::from(Q * Fr::from(1)), ]; - let s = S_mul.map(|s| AffinePoint::new(s.x, s.y)); let q = Q_mul.map(|p| AffinePoint::new(p.x, p.y)); From 83d9e99c4d7685da7864238338c98c4b9219c0a4 Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Sun, 15 Dec 2024 23:00:10 -0500 Subject: [PATCH 33/69] chore: rename sections (#1073) --- book/src/SUMMARY.md | 4 ++-- book/src/writing-apps/build.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index fb4852b228..0a0689e521 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -12,7 +12,7 @@ - [Overview](./writing-apps/overview.md) - [Writing a Program](./writing-apps/write-program.md) -- [Cross-Compilation](./writing-apps/build.md) +- [Compiling](./writing-apps/build.md) - [Generating Proofs](./writing-apps/prove.md) - [Onchain Verification](./writing-apps/verify.md) @@ -21,7 +21,7 @@ - [Overview](./custom-extensions/overview.md) - [Keccak](./custom-extensions/keccak.md) - [Big Integer](./custom-extensions/bigint.md) -- [Algebra](./custom-extensions/algebra.md) +- [Algebra (Modular Arithmetic)](./custom-extensions/algebra.md) - [Elliptic Curve Cryptography](./custom-extensions/ecc.md) - [Elliptic Curve Pairing](./custom-extensions/pairing.md) diff --git a/book/src/writing-apps/build.md b/book/src/writing-apps/build.md index aadbb7b3f9..b5b0a8fd56 100644 --- a/book/src/writing-apps/build.md +++ b/book/src/writing-apps/build.md @@ -1,4 +1,4 @@ -# Cross-Compilation +# Compiling a Program First let's define some key terms used in cross-compilation: From aea81bf6d0c21cd44ba5fb67cb2285fa9137aa56 Mon Sep 17 00:00:00 2001 From: HrikB <23041116+HrikB@users.noreply.github.com> Date: Sun, 15 Dec 2024 20:15:31 -0800 Subject: [PATCH 34/69] Completed installation and quickstart sections (#1065) * docs: completed installation and quickstart sections * Apply suggestions from code review * update cli --------- Co-authored-by: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> --- book/src/getting-started/install.md | 42 +++++++++++++-- book/src/getting-started/quickstart.md | 73 ++++++++++++++++++++++---- 2 files changed, 100 insertions(+), 15 deletions(-) diff --git a/book/src/getting-started/install.md b/book/src/getting-started/install.md index bb470b6b03..18ecdbac82 100644 --- a/book/src/getting-started/install.md +++ b/book/src/getting-started/install.md @@ -1,9 +1,30 @@ # Install -TODO: how to install `cargo-openvm`. -`cargo install --git <...>` +To use OpenVM for generating proofs, you must install the OpenVM command line tool `cargo-openvm`. -## Build from source +`cargo-openvm` can be installed in two different ways. You can either install via git URL or build from source. + +## Option 1: Install Via Git URL (Recommended) + +```bash +cargo install --git http://github.com/openvm-org/openvm.git cargo-openvm +``` + +This will globally install `cargo-openvm`. You can validate a successful installation with: + +```bash +cargo openvm --version +``` + +## Option 2: Build from source + +To build from source, you will need the nightly toolchain. You can install it with: + +```bash +rustup toolchain install nightly +``` + +Then, clone the repository and begin the installation. ```bash git clone https://github.com/openvm-org/openvm.git @@ -11,4 +32,17 @@ cd openvm cargo install --force --path crates/cli ``` -## Toolchain +This will globally install `cargo-openvm`. You can validate a successful installation with: + +```bash +cargo openvm --version +``` + +## Install Rust Toolchain + +In order for the `cargo-openvm` build command to work, you must install certain Rust nightly components: + +```bash +rustup install nightly-2024-10-30 +rustup component add rust-src --toolchain nightly-2024-10-30 +``` diff --git a/book/src/getting-started/quickstart.md b/book/src/getting-started/quickstart.md index c40f274cd1..335a9c2df4 100644 --- a/book/src/getting-started/quickstart.md +++ b/book/src/getting-started/quickstart.md @@ -10,17 +10,10 @@ First, create a new Rust project. cargo init fibonacci ``` -Since we are using some nightly features, we need to specify the Rust version. Run `rustup component add rust-src --toolchain nightly-2024-10-30` and create a `rust-toolchain.toml` file with the following content: - -```toml -[toolchain] -channel = "nightly-2024-10-30" # "1.82.0" -components = ["clippy", "rustfmt"] -``` - In `Cargo.toml`, add the following dependency: ```toml +[dependencies] openvm = { git = "https://github.com/openvm-org/openvm.git", features = ["std"] } ``` @@ -28,9 +21,13 @@ Note that `std` is not enabled by default, so explicitly enabling it is required ## The fibonacci program -The `read` function takes input from the stdin, and it also works with OpenVM runtime. +The `read` function takes input from the stdin (it also works with OpenVM runtime). + ```rust -use openvm::io::read; +// src/main.rs +use openvm::io::{read, reveal}; + +openvm::entry!(main); fn main() { let n: u64 = read(); @@ -41,6 +38,60 @@ fn main() { a = b; b = c; } - println!("{}", a); + reveal(a as u32, 0); + reveal((a >> 32) as u32, 1); } ``` + +## Build + +To build the program, run: + +```bash +cargo openvm build +``` + +This will output an OpenVM executable file to `./openvm/app.vmexe`. + +## Keygen + +Before generating any proofs, we will also need to generate the proving and verification keys. + +```bash +cargo openvm keygen +``` + +This will output a serialized proving key to `./openvm/app.pk` and a verification key to `./openvm/app.vk`. + +## Proof Generation + +Now we are ready to generate a proof! Simply run: + +```bash +OPENVM_FAST_TEST=1 cargo openvm prove app --input "0x0A00000000000000" +``` + +The `--input` field is passed to the program which receives it via the `io::read` function. +In our `main.rs` we called `read()` to get `n: u64`. The input here is `n = 100u64` _in little endian_. Note that this value must be padded to exactly 8 bytes (64 bits). + +The serialized proof will be output to `./openvm/app.proof`. + +The `OPENVM_FAST_TEST` environment variable is used to enable fast proving for testing purposes. To run with proof with secure parameters, remove the environmental variable. + +## Proof Verification + +Finally, the proof can be verified. + +```bash +cargo openvm verify app +``` + +The process should exit with no errors. + +## Runtime Execution + +If necessary, the executable can also be run _without_ proof generation. This can be useful for testing purposes. + +```bash +cargo openvm run --input "0x0A00000000000000" +``` From 08e821d99f84847906723403cb1bbab9ea193340 Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Sun, 15 Dec 2024 23:31:10 -0500 Subject: [PATCH 35/69] [book] update writing program (#1075) --- book/src/custom-extensions/overview.md | 14 +++++++++ book/src/writing-apps/write-program.md | 43 +++----------------------- 2 files changed, 18 insertions(+), 39 deletions(-) diff --git a/book/src/custom-extensions/overview.md b/book/src/custom-extensions/overview.md index 2b5c427d0c..5b2a1b62fe 100644 --- a/book/src/custom-extensions/overview.md +++ b/book/src/custom-extensions/overview.md @@ -21,3 +21,17 @@ On the other hand certain arithmetic operations, particularly modular arithmetic These steps ensure both performance and security: performance because the modulus is known at compile time, and security because runtime checks confirm that the correct structures have been initialized. Our design for the configuration procedure above was inspired by the [EVMMAX proposal](https://github.com/jwasinger/EIPs/blob/evmmax-2/EIPS/eip-6601.md). + +## Configuration + +To use these extensions, you must populate a `openvm.toml` in your package root directory (where the `Cargo.toml` file is located). +We will explain in each extension how to configure the `openvm.toml` file. + +The template `openvm.toml` file is as follows: + +```toml +[app_vm_config.rv32i] +[app_vm_config.rv32m] +[app_vm_config.io] +# ... +``` diff --git a/book/src/writing-apps/write-program.md b/book/src/writing-apps/write-program.md index e6e9027ead..417469e503 100644 --- a/book/src/writing-apps/write-program.md +++ b/book/src/writing-apps/write-program.md @@ -28,45 +28,6 @@ Even without std, `assert!` and `panic!` can work as normal. To use `std` featur std = ["openvm/std"] ``` -### Building and running - -_TODO_: point to CLI installation instructions - -First we need to build the program targeting the OpenVM runtime, and that requires some configuration. Put the following in `openvm.toml`: - -```toml -[app_fri_params] -log_blowup = 2 -num_queries = 42 -proof_of_work_bits = 16 - -[app_vm_config.io] -[app_vm_config.rv32i] -[app_vm_config.rv32m] -range_tuple_checker_sizes = [256, 2048] -``` - -And run the following command to build the program: - -```bash -cargo openvm build --transpile --transpiler-config openvm.toml --transpile-to outputs/fibonacci.vmexe -``` - -Next we can keygen the generate the proving and verifying keys: - -```bash -cargo openvm keygen --config openvm.toml --output outputs/pk --vk-output outputs/vk -``` - -Now, to prove the program some input is needed. The input parameter is either a hex string or a file path. So for example if we want to compute the 10th fibonacci number, we can run: - -```bash -cargo openvm prove app --app-pk outputs/pk --exe outputs/fibonacci.vmexe --input "0x000000000000000A" --output outputs/proof -cargo openvm verify app --app-vk outputs/vk --proof outputs/proof -``` - -No errors should be returned, and the proof should be correctly verified. - ## Handling I/O The program can take input from stdin, with some functions provided by `openvm::io`. @@ -82,3 +43,7 @@ let n: u64 = read(); `openvm::io::reveal` sends public values to the final proof (to be read by the smart contract). For debugging purposes, `openvm::io::print` and `openvm::io::println` can be used normally, but `println!` will only work if `std` is enabled. + +### Building and running + +See the [overview](./overview.md) on how to build and run the program. From 3ba2f8fa34fddf5a8bf082bee9c1ee56eef2234c Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Mon, 16 Dec 2024 00:13:25 -0500 Subject: [PATCH 36/69] chore: cleanup (#1076) * chore: cleanup * add num2bits FIXMEs --- crates/circuits/mod-builder/src/README.md | 6 +- crates/circuits/primitives/src/bigint/mod.rs | 1 - .../programs/examples/pairing_miller_step.rs | 4 +- crates/toolchain/transpiler/src/util.rs | 2 - crates/vm/src/arch/extensions.rs | 1 - crates/vm/src/system/memory/merkle/air.rs | 1 - docs/repo/README.md | 1 - docs/specs/continuations.md | 116 +++++++++--------- extensions/algebra/guest/src/field/mod.rs | 2 +- extensions/keccak256/circuit/src/air.rs | 1 - .../native/circuit/src/loadstore/tests.rs | 2 - .../native/compiler/src/conversion/mod.rs | 1 - extensions/native/compiler/src/ir/bits.rs | 8 +- extensions/rv32-adapters/src/eq_mod.rs | 1 - .../rv32-adapters/src/vec_heap_two_reads.rs | 1 - .../rv32im/circuit/src/branch_lt/core.rs | 1 - .../rv32im/circuit/src/branch_lt/tests.rs | 1 - 17 files changed, 66 insertions(+), 84 deletions(-) diff --git a/crates/circuits/mod-builder/src/README.md b/crates/circuits/mod-builder/src/README.md index 5d9ebc41d7..7ac8dbcb64 100644 --- a/crates/circuits/mod-builder/src/README.md +++ b/crates/circuits/mod-builder/src/README.md @@ -2,7 +2,7 @@ Ec Double chip: -``` +```rust let x1 = ExprBuilder::new_input(builder.clone()); let y1 = ExprBuilder::new_input(builder.clone()); let nom = (x1.clone() * x1.clone()).scalar_mul(3); @@ -13,7 +13,3 @@ x3.save(); let mut y3 = lambda * (x1 - x3) - y1; y3.save(); ``` - -### TODO -- [ ] auto save on add/sub/mul. Need to track the max_overflow_limbs. -- [ ] select op: `select(flag, a, b) -> a if flag else b`. \ No newline at end of file diff --git a/crates/circuits/primitives/src/bigint/mod.rs b/crates/circuits/primitives/src/bigint/mod.rs index 7a9773d5b8..f3680dc0d7 100644 --- a/crates/circuits/primitives/src/bigint/mod.rs +++ b/crates/circuits/primitives/src/bigint/mod.rs @@ -134,7 +134,6 @@ impl OverflowInt { } } -// TODO: this doesn't work for references automatically? impl Add for OverflowInt where T: Add + Clone + Default, diff --git a/crates/toolchain/tests/programs/examples/pairing_miller_step.rs b/crates/toolchain/tests/programs/examples/pairing_miller_step.rs index 02e9d60baf..b29cf36170 100644 --- a/crates/toolchain/tests/programs/examples/pairing_miller_step.rs +++ b/crates/toolchain/tests/programs/examples/pairing_miller_step.rs @@ -37,7 +37,7 @@ mod bn254 { let mut pt_bytes = [0u8; 32 * 4]; let mut l_bytes = [0u8; 32 * 4]; - // TODO: if we ever need to change this, we should switch to using `bincode` to serialize + // TODO: if we ever need to change this, we should switch to using `StdIn::write` to serialize // for us and use `read()` instead of `read_vec()` pt_bytes[0..32].copy_from_slice(pt_cmp.x.c0.as_le_bytes()); pt_bytes[32..2 * 32].copy_from_slice(pt_cmp.x.c1.as_le_bytes()); @@ -67,7 +67,7 @@ mod bn254 { let mut l0_bytes = [0u8; 32 * 4]; let mut l1_bytes = [0u8; 32 * 4]; - // TODO: if we ever need to change this, we should switch to using `bincode` to serialize + // TODO: if we ever need to change this, we should switch to using `StdIn::write` to serialize // for us and use `read()` instead of `read_vec()` pt_bytes[0..32].copy_from_slice(pt_cmp.x.c0.as_le_bytes()); pt_bytes[32..2 * 32].copy_from_slice(pt_cmp.x.c1.as_le_bytes()); diff --git a/crates/toolchain/transpiler/src/util.rs b/crates/toolchain/transpiler/src/util.rs index 1872bf32df..8e5cfcefa9 100644 --- a/crates/toolchain/transpiler/src/util.rs +++ b/crates/toolchain/transpiler/src/util.rs @@ -98,8 +98,6 @@ pub fn from_s_type(opcode: usize, dec_insn: &SType) -> Instruct ) } -// TODO: implement J and U, prove or disprove that the address spaces are currently correct - /// Create a new [`Instruction`] from a B-type instruction. pub fn from_b_type(opcode: usize, dec_insn: &BType) -> Instruction { Instruction::new( diff --git a/crates/vm/src/arch/extensions.rs b/crates/vm/src/arch/extensions.rs index 7f748fc5be..38b199a591 100644 --- a/crates/vm/src/arch/extensions.rs +++ b/crates/vm/src/arch/extensions.rs @@ -739,7 +739,6 @@ impl VmChipComplex { 3 + self.memory_controller().borrow().num_airs() + self.inventory.num_airs() } - // TODO[jpw]: find better way to handle public values chip. It is an executor but // we always need to special case it because we need to fix the air id. fn public_values_chip_idx(&self) -> Option { self.config diff --git a/crates/vm/src/system/memory/merkle/air.rs b/crates/vm/src/system/memory/merkle/air.rs index ef75160b6b..ac3f88e349 100644 --- a/crates/vm/src/system/memory/merkle/air.rs +++ b/crates/vm/src/system/memory/merkle/air.rs @@ -176,7 +176,6 @@ impl MemoryMerkleAir { .chain(local.left_child_hash) .chain(local.right_child_hash) .chain(local.parent_hash); - // TODO: do not hardcode the hash bus builder.push_send( self.compression_bus.0, compress_fields, diff --git a/docs/repo/README.md b/docs/repo/README.md index 8c09222a85..eda4aad495 100644 --- a/docs/repo/README.md +++ b/docs/repo/README.md @@ -3,4 +3,3 @@ ### Structure - [Layout](./layout.md): Describes each of the crates in the repository and their function. -- CI: An overview of the CI workflows [todo] diff --git a/docs/specs/continuations.md b/docs/specs/continuations.md index 65f518fe65..4609845a76 100644 --- a/docs/specs/continuations.md +++ b/docs/specs/continuations.md @@ -26,8 +26,8 @@ A smart contract is deployed by on-chain, which provides a function to verify a The **Static Verifier Wrapper** is a Halo2 SNARK verifier circuit generated by OpenVM. The static verifier wrapper is determined by the following parameters: -* Number of public values -* The Aggregation VM chip constraints (but **not** the App VM chips) +- Number of public values +- The Aggregation VM chip constraints (but **not** the App VM chips) ## Continuation Verifier @@ -44,34 +44,34 @@ The Static Verifier is a Halo2 verifier circuit that validates a Root VM Verifie Static Verifier Requirements: -* The height of each trace is fixed. -* Trace heights are in a descending order. +- The height of each trace is fixed. +- Trace heights are in a descending order. Public Values Exposed: -* Exe commit encoded in Bn254 -* Leaf commit encoded in Bn254 -* User public values in BabyBear +- Exe commit encoded in Bn254 +- Leaf commit encoded in Bn254 +- User public values in BabyBear Parameters (which could result in a different circuit): -* Number of public values (from upper stream) -* k in Halo2 -* Determines the number of columns of the circuit. +- Number of public values (from upper stream) +- k in Halo2 +- Determines the number of columns of the circuit. -* Number of public values (from upstream) -* k in Halo2 (determines the number of columns in the circuit) -* Root VM verifier - * VK (including the heights of all traces) - * Root verifier program commitment +- Number of public values (from upstream) +- k in Halo2 (determines the number of columns in the circuit) +- Root VM verifier + - VK (including the heights of all traces) + - Root verifier program commitment ### Aggregation VM The Aggregation VM organizes proofs into an aggregation tree, where nodes include: -* Root VM Verifier -* Internal VM Verifier -* Leaf VM Verifier +- Root VM Verifier +- Internal VM Verifier +- Leaf VM Verifier Each node can have an arbitrary number of children, enabling flexible tree structures to optimize for cost reduction (more children) or latency reduction (less children) during proving. @@ -90,78 +90,78 @@ In practice, Root VM verifier only verifies one proof to guarantee constant heig Logical Input: -* Root input +- Root input Cached Trace Commit: -* `ProgramAir`: commits the root verifier program +- `ProgramAir`: commits the root verifier program Public values: -* `RootVmVerifierPvs` - * Note: exe_commit is the commitment of the executable. The way to compute it can be found here. +- `RootVmVerifierPvs` + - Note: exe_commit is the commitment of the executable. The way to compute it can be found here. Parameters: -* For circuit: - * Root VM Config -* For root verifier program: - * Root FRI parameters to compute its commitment - * Internal verifier circuit \+ program commitment - * Leaf verifier circuit \+ program commitment +- For circuit: + - Root VM Config +- For root verifier program: + - Root FRI parameters to compute its commitment + - Internal verifier circuit \+ program commitment + - Leaf verifier circuit \+ program commitment ### Internal VM Verifier The Internal VM Verifier validates one or more proofs of: -* Leaf VM Verifier -* Internal VM Verifier +- Leaf VM Verifier +- Internal VM Verifier Logical Input: -* `InternalVmVerifierInput` +- `InternalVmVerifierInput` Cached Trace Commit: -* `ProgramAir`: commits the internal verifier program. `agg_vm_pk` contains it. +- `ProgramAir`: commits the internal verifier program. `agg_vm_pk` contains it. Public values: -* `InternalVmVerifierPvs` +- `InternalVmVerifierPvs` Parameters: -* For circuit: - * Internal VM Config -* For root verifier program: - * Internal FRI parameters to compute its commitment - * Internal verifier circuit \+ program commitment - * Leaf verifier circuit \+ program commitment +- For circuit: + - Internal VM Config +- For root verifier program: + - Internal FRI parameters to compute its commitment + - Internal verifier circuit \+ program commitment + - Leaf verifier circuit \+ program commitment ### Leaf VM Verifier Verify 1 or more proofs of: -* segment circuits +- segment circuits Logical Input: -* `LeafVmVerifierInput` +- `LeafVmVerifierInput` Cached Trace Commit: -* ProgramAir: commits the leaf verifier program. The leaf verifier program commits . +- ProgramAir: commits the leaf verifier program. The leaf verifier program commits . Public values: -* `VmVerifierPvs` +- `VmVerifierPvs` Parameters: -* For circuit: - * Leaf VM Config -* For leaf verifier program: - * It’s not a part of the Continuation Verifier because it depends on the VK of the App VM and it doesn’t affect the VK +- For circuit: + - Leaf VM Config +- For leaf verifier program: + - It’s not a part of the Continuation Verifier because it depends on the VK of the App VM and it doesn’t affect the VK of the static verifier. ### App VM @@ -172,29 +172,29 @@ App VM executes an executable with inputs and returns a list of segment proofs. Logical Input: -* App VM input stream +- App VM input stream Cached Trace Commit: -* ProgramAir: commits the program the App VM executed. +- ProgramAir: commits the program the App VM executed. Public values: -* `VmConnectorPvs` -* `MemoryMerklePvs` +- `VmConnectorPvs` +- `MemoryMerklePvs` User Public Values: -* Up to `num_public_values` public values in a dedicated memory space. These public values are not exposed as public +- Up to `num_public_values` public values in a dedicated memory space. These public values are not exposed as public values of segment circuits, but will be exposed by the final proof. Parameters: -* Number of public values (from upstream) -* For circuit: - * App VM Config -* For App program: - * App FRI parameters to compute its commitment. +- Number of public values (from upstream) +- For circuit: + - App VM Config +- For App program: + - App FRI parameters to compute its commitment. # Continuations @@ -220,7 +220,7 @@ Similarly, the chip verifies, with respect to the post-state commitment, the mem in the segment and reads them into the `MEMORY_BUS` at their final timestamps. Thus the primary goal is an efficient commitment and verification format for the memory state. We designed our -persistent memory commitment such that the cost of verification is almost-linear in the +persistent memory commitment such that the cost of verification is almost-linear in the number of accesses done within the segment and logarithmic in the total size of memory used across all segments. As far as we know, all known solutions that achieve this use Merkle trees in some form. diff --git a/extensions/algebra/guest/src/field/mod.rs b/extensions/algebra/guest/src/field/mod.rs index b7c6c309b9..eda026c5af 100644 --- a/extensions/algebra/guest/src/field/mod.rs +++ b/extensions/algebra/guest/src/field/mod.rs @@ -6,7 +6,7 @@ use core::{ use crate::{DivAssignUnsafe, DivUnsafe}; -// TODO: this should extend an IntegralDomain trait +// TODO: this can now extend IntMod trait /// This is a simplified trait for field elements. pub trait Field: Sized diff --git a/extensions/keccak256/circuit/src/air.rs b/extensions/keccak256/circuit/src/air.rs index fe1d5099e9..3d69498d1d 100644 --- a/extensions/keccak256/circuit/src/air.rs +++ b/extensions/keccak256/circuit/src/air.rs @@ -40,7 +40,6 @@ pub struct KeccakVmAir { pub bitwise_lookup_bus: BitwiseOperationLookupBus, /// Maximum number of bits allowed for an address pointer pub ptr_max_bits: usize, - // TODO: add configuration for enabling direct non-memory interactions pub(super) offset: usize, } diff --git a/extensions/native/circuit/src/loadstore/tests.rs b/extensions/native/circuit/src/loadstore/tests.rs index b74346f528..7b88d9bd96 100644 --- a/extensions/native/circuit/src/loadstore/tests.rs +++ b/extensions/native/circuit/src/loadstore/tests.rs @@ -226,5 +226,3 @@ fn rand_native_loadstore_test() { let tester = tester.build().load(chip).finalize(); tester.simple_test().expect("Verification failed"); } - -// TODO[yi]: Add negative tests after clarifying ISA spec diff --git a/extensions/native/compiler/src/conversion/mod.rs b/extensions/native/compiler/src/conversion/mod.rs index 4eb5ca0caf..482215c905 100644 --- a/extensions/native/compiler/src/conversion/mod.rs +++ b/extensions/native/compiler/src/conversion/mod.rs @@ -115,7 +115,6 @@ enum AS { } impl AS { - // TODO[INT-1698] fn to_field(self) -> F { match self { AS::Immediate => F::ZERO, diff --git a/extensions/native/compiler/src/ir/bits.rs b/extensions/native/compiler/src/ir/bits.rs index b311841074..ea5ecdb391 100644 --- a/extensions/native/compiler/src/ir/bits.rs +++ b/extensions/native/compiler/src/ir/bits.rs @@ -25,8 +25,8 @@ impl Builder { self.assign(&sum, sum + bit * C::N::from_canonical_u32(1 << i)); } - // TODO: There is an edge case where the witnessed bits may slightly overflow and cause - // the output to be incorrect. This is a known issue and will be fixed in the future. + // FIXME: There is an edge case where the witnessed bits may slightly overflow and cause + // the output to be incorrect. self.assert_var_eq(sum, num); output @@ -64,8 +64,8 @@ impl Builder { self.assign(&sum, sum + bit * C::F::from_canonical_u32(1 << i)); } - // TODO: There is an edge case where the witnessed bits may slightly overflow and cause - // the output to be incorrect. This is a known issue and will be fixed in the future. + // FIXME: There is an edge case where the witnessed bits may slightly overflow and cause + // the output to be incorrect. self.assert_felt_eq(sum, num); // Cast Array> to Array> diff --git a/extensions/rv32-adapters/src/eq_mod.rs b/extensions/rv32-adapters/src/eq_mod.rs index c08b2010e7..802a14b19f 100644 --- a/extensions/rv32-adapters/src/eq_mod.rs +++ b/extensions/rv32-adapters/src/eq_mod.rs @@ -140,7 +140,6 @@ impl< // Compose the u32 register value into single field element, with // a range check on the highest limb. let rs_val_f = cols.rs_val.map(|decomp| { - // TODO: range check decomp.iter().rev().fold(AB::Expr::ZERO, |acc, &limb| { acc * AB::Expr::from_canonical_usize(1 << RV32_CELL_BITS) + limb }) diff --git a/extensions/rv32-adapters/src/vec_heap_two_reads.rs b/extensions/rv32-adapters/src/vec_heap_two_reads.rs index d6fb95640e..94a61b45ff 100644 --- a/extensions/rv32-adapters/src/vec_heap_two_reads.rs +++ b/extensions/rv32-adapters/src/vec_heap_two_reads.rs @@ -424,7 +424,6 @@ impl< let (rs2_record, rs2_val) = read_rv32_register(memory, d, c); let (rd_record, rd_val) = read_rv32_register(memory, d, a); - // TODO: assert address has < 2^address_bits assert!(rs1_val as usize + READ_SIZE * BLOCKS_PER_READ1 - 1 < (1 << self.air.address_bits)); let read1_records = from_fn(|i| { memory.read::(e, F::from_canonical_u32(rs1_val + (i * READ_SIZE) as u32)) diff --git a/extensions/rv32im/circuit/src/branch_lt/core.rs b/extensions/rv32im/circuit/src/branch_lt/core.rs index 15634d7941..4d13f71fe2 100644 --- a/extensions/rv32im/circuit/src/branch_lt/core.rs +++ b/extensions/rv32im/circuit/src/branch_lt/core.rs @@ -155,7 +155,6 @@ where }) + AB::Expr::from_canonical_usize(self.offset); - // TODO: update the default increment (i.e. 4) when opcodes are updated let to_pc = from_pc + cols.cmp_result * cols.imm + not(cols.cmp_result) * AB::Expr::from_canonical_u8(4); diff --git a/extensions/rv32im/circuit/src/branch_lt/tests.rs b/extensions/rv32im/circuit/src/branch_lt/tests.rs index 654a202465..92a4ad5ee2 100644 --- a/extensions/rv32im/circuit/src/branch_lt/tests.rs +++ b/extensions/rv32im/circuit/src/branch_lt/tests.rs @@ -78,7 +78,6 @@ fn run_rv32_branch_lt_rand_execute>( let (cmp_result, _, _, _) = run_cmp::(opcode, &a, &b); let from_pc = tester.execution.last_from_pc().as_canonical_u32() as i32; let to_pc = tester.execution.last_to_pc().as_canonical_u32() as i32; - // TODO: update the default increment (i.e. 4) when opcodes are updated let pc_inc = if cmp_result { imm } else { 4 }; assert_eq!(to_pc, from_pc + pc_inc); From 9ab6f8790ae7dc16450c70eb054bc9d20e05f562 Mon Sep 17 00:00:00 2001 From: Yu Jiang Tham Date: Sun, 15 Dec 2024 21:15:30 -0800 Subject: [PATCH 37/69] Fix LaTeX rendering (#1080) --- book/src/custom-extensions/pairing.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/book/src/custom-extensions/pairing.md b/book/src/custom-extensions/pairing.md index 0f9b1fc333..7ccfa5aa9f 100644 --- a/book/src/custom-extensions/pairing.md +++ b/book/src/custom-extensions/pairing.md @@ -1,10 +1,10 @@ # Elliptic Curve Pairing -The pairing extension enables usage of the optimal Ate pairing check on the BN254 and BLS12-381 elliptic curves. The following field extension tower for $\mathbb{F}_{p^{12}}$ is used for pairings in this crate: +The pairing extension enables usage of the optimal Ate pairing check on the BN254 and BLS12-381 elliptic curves. The following field extension tower for \\(\mathbb{F}\_{p^{12}}\\) is used for pairings in this crate: $$ -\mathbb{F_{p^2}} = \mathbb{F_{p}}[u]/(u^2 - \beta)\\ -\mathbb{F_{p^6}} = \mathbb{F_{p^2}}[v]/(v^3 - \xi)\\ +\mathbb{F_{p^2}} = \mathbb{F_{p}}[u]/(u^2 - \beta)\\\\ +\mathbb{F_{p^6}} = \mathbb{F_{p^2}}[v]/(v^3 - \xi)\\\\ \mathbb{F_{p^{12}}} = \mathbb{F_{p^6}}[w]/(w^2 - v) $$ @@ -47,11 +47,11 @@ setup_0(); setup_all_complex_extensions(); ``` -There are two moduli defined internally in the Bls12_381 feature. The `moduli_init!` macro thus requires both of them to be initialized. However, we do not need the scalar field of BLS12-381 (which is at index 1), and thus we only initialize the modulus from index 0, thus we only use `setup_0()` (as opposed to `setup_all_moduli()`, which will save us some columns when generating the trace). +There are two moduli defined internally in the `Bls12_381` feature. The `moduli_init!` macro thus requires both of them to be initialized. However, we do not need the scalar field of BLS12-381 (which is at index 1), and thus we only initialize the modulus from index 0, thus we only use `setup_0()` (as opposed to `setup_all_moduli()`, which will save us some columns when generating the trace). ## Input values -The inputs to the pairing check are `AffinePoint`s in $\mathbb{F}_p$ and $\mathbb{F}_{p^2}$. They can be constructed via the `AffinePoint::new` function, with the inner `Fp` and `Fp2` values constructed via various `from_...` functions. +The inputs to the pairing check are `AffinePoint`s in \\(\mathbb{F}\_p\\) and \\(\mathbb{F}\_{p^2}\\). They can be constructed via the `AffinePoint::new` function, with the inner `Fp` and `Fp2` values constructed via various `from_...` functions. We can create a new struct to hold these `AffinePoint`s for the purpose of this guide. You may instead put them into a custom struct to serve your use case. From bcc5c421b8900b4bbcd9b684e6da8c89c79aeec6 Mon Sep 17 00:00:00 2001 From: stephenh-axiom-xyz Date: Mon, 16 Dec 2024 00:15:57 -0500 Subject: [PATCH 38/69] docs: include verifier contract location in verify.md (#1077) --- book/src/writing-apps/verify.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/book/src/writing-apps/verify.md b/book/src/writing-apps/verify.md index 5c95f69488..feedcfc1e6 100644 --- a/book/src/writing-apps/verify.md +++ b/book/src/writing-apps/verify.md @@ -24,6 +24,7 @@ cargo openvm verify evm --proof If `proof` is omitted, the command will search for the proof at `./openvm/evm.proof`. -As with all other EVM-level commands, `cargo openvm setup` is a prerequisite for `verify`. +As with all other EVM-level commands, `cargo openvm setup` is a prerequisite for `verify`. It generates the aggregation proving key and verifier contract at `~/.openvm/agg.pk` and `~/.openvm/verifier.sol` respectively. + > ⚠️ **WARNING** > `cargo openvm setup` requires very large amounts of computation and memory (~200 GB). From 11632d300f4e7dd1cf5968e51e34600ff6081ca4 Mon Sep 17 00:00:00 2001 From: Arayi Khalatyan <127004086+arayikhalatyan@users.noreply.github.com> Date: Mon, 16 Dec 2024 00:20:40 -0500 Subject: [PATCH 39/69] fix (#1082) --- book/src/custom-extensions/bigint.md | 14 +++++++++++++- book/src/custom-extensions/keccak.md | 8 +++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/book/src/custom-extensions/bigint.md b/book/src/custom-extensions/bigint.md index 8b0cfd53a5..1d79481277 100644 --- a/book/src/custom-extensions/bigint.md +++ b/book/src/custom-extensions/bigint.md @@ -76,6 +76,12 @@ pub fn main() { } ``` +To be able to import the `U256` struct, add the following to your `Cargo.toml` file: + +```toml +openvm-bigint-guest = { git = "https://github.com/openvm-org/openvm.git" } +``` + ## `I256` The `I256` struct is a 256-bit signed integer type. The `I256` struct is very similar to the `U256` struct. @@ -150,6 +156,12 @@ pub fn main() { } ``` +To be able to import the `I256` struct, add the following to your `Cargo.toml` file: + +```toml +openvm-bigint-guest = { git = "https://github.com/openvm-org/openvm.git" } +``` + ## External Functions The Bigint Guest extension provides another way to use the native implementation. It provides external functions that are meant to be linked to other external libraries. The external libraries can use these functions as a hook for the 256 bit integer native implementations. Enabled only when the `target_os = "zkvm"`. All of the functions are defined as `unsafe extern "C" fn`. Also, note that you must enable the feature `export-intrinsics` to make them globally linkable. @@ -194,4 +206,4 @@ For the guest program to build successfully add the following to your `.toml` fi ```toml [app_vm_config.bigint] -``` \ No newline at end of file +``` diff --git a/book/src/custom-extensions/keccak.md b/book/src/custom-extensions/keccak.md index 527ea6c25b..6440650f1c 100644 --- a/book/src/custom-extensions/keccak.md +++ b/book/src/custom-extensions/keccak.md @@ -32,6 +32,12 @@ pub fn main() { } ``` +To be able to import the `keccak256` function, add the following to your `Cargo.toml` file: + +```toml +openvm-keccak256-guest = { git = "https://github.com/openvm-org/openvm.git" } +``` + ## Native Keccak256 Keccak guest extension also provides another way to use the native Keccak-256 implementation. It provides a function that is meant to be linked to other external libraries. The external libraries can use this function as a hook for the Keccak-256 native implementation. Enabled only when the target is `zkvm`. @@ -65,4 +71,4 @@ For the guest program to build successfully add the following to your `.toml` fi ```toml [app_vm_config.keccak256] -``` \ No newline at end of file +``` From c170209d0623ec8dad67a9de3fe01bc100f5ad76 Mon Sep 17 00:00:00 2001 From: Arayi Khalatyan <127004086+arayikhalatyan@users.noreply.github.com> Date: Mon, 16 Dec 2024 00:24:30 -0500 Subject: [PATCH 40/69] docs: add config parameters to ecc Book (#1068) * fix: add config parameters * fix: toml explanation * review comments --- book/src/custom-extensions/ecc.md | 17 +++++++++++++++++ .../ecc/circuit/src/weierstrass_extension.rs | 12 ++++++++++++ 2 files changed, 29 insertions(+) diff --git a/book/src/custom-extensions/ecc.md b/book/src/custom-extensions/ecc.md index 9436d6b4a8..13e0127b26 100644 --- a/book/src/custom-extensions/ecc.md +++ b/book/src/custom-extensions/ecc.md @@ -113,3 +113,20 @@ pub fn main() { let p3 = &p1 + &p2; } ``` + +### Config parameters + +For the guest program to build successfully, all used moduli and curves must be declared in the `.toml` config file in the following format: + +```toml +[app_vm_config.modular] +supported_modulus = ["115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337"] + +[[app_vm_config.ecc.supported_curves]] +modulus = "115792089237316195423570985008687907853269984665640564039457584007908834671663" +scalar = "115792089237316195423570985008687907852837564279074904382605163141518161494337" +a = "0" +b = "7" +``` + +The `supported_modulus` parameter is a list of moduli that the guest program will use. The `ecc.supported_curves` parameter is a list of supported curves that the guest program will use. They must be provided in decimal format in the `.toml` file. For multiple curves create multiple `[[app_vm_config.ecc.supported_curves]]` sections. diff --git a/extensions/ecc/circuit/src/weierstrass_extension.rs b/extensions/ecc/circuit/src/weierstrass_extension.rs index d0754c3d96..597de1f188 100644 --- a/extensions/ecc/circuit/src/weierstrass_extension.rs +++ b/extensions/ecc/circuit/src/weierstrass_extension.rs @@ -27,15 +27,27 @@ use super::{EcAddNeChip, EcDoubleChip}; #[derive(Clone, Debug, derive_new::new, Serialize, Deserialize)] pub struct CurveConfig { /// The coordinate modulus of the curve. + #[serde(deserialize_with = "deserialize_biguint_from_str")] pub modulus: BigUint, /// The scalar field modulus of the curve. + #[serde(deserialize_with = "deserialize_biguint_from_str")] pub scalar: BigUint, /// The coefficient a of y^2 = x^3 + ax + b. + #[serde(deserialize_with = "deserialize_biguint_from_str")] pub a: BigUint, /// The coefficient b of y^2 = x^3 + ax + b. + #[serde(deserialize_with = "deserialize_biguint_from_str")] pub b: BigUint, } +fn deserialize_biguint_from_str<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + let s: String = Deserialize::deserialize(deserializer)?; + s.parse().map_err(serde::de::Error::custom) +} + pub static SECP256K1_CONFIG: Lazy = Lazy::new(|| CurveConfig { modulus: SECP256K1_MODULUS.clone(), scalar: SECP256K1_ORDER.clone(), From 4e5fd567acbb0b29a3a0fc6a6e98792ac1955edd Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Mon, 16 Dec 2024 00:39:40 -0500 Subject: [PATCH 41/69] chore: update acknowledgments (#1081) * chore: cleanup * add num2bits FIXMEs * chore: update acknowledgments * chore: colons * Apply suggestions from code review --------- Co-authored-by: Yi Sun --- README.md | 9 +++++++++ crates/toolchain/openvm/src/lib.rs | 4 ++-- .../toolchain/openvm/src/serde/deserializer.rs | 14 -------------- crates/toolchain/openvm/src/serde/err.rs | 16 +--------------- crates/toolchain/openvm/src/serde/serializer.rs | 14 -------------- crates/toolchain/platform/src/heap/bump.rs | 14 -------------- crates/toolchain/platform/src/heap/embedded.rs | 14 -------------- crates/toolchain/platform/src/heap/mod.rs | 14 -------------- crates/toolchain/platform/src/libm_extern.rs | 14 -------------- crates/toolchain/platform/src/memory.rs | 14 -------------- crates/toolchain/platform/src/rust_rt.rs | 14 -------------- crates/toolchain/tests/programs/README.md | 2 +- docs/specs/circuit.md | 6 ------ 13 files changed, 13 insertions(+), 136 deletions(-) diff --git a/README.md b/README.md index b87d1d6fb1..b40ee0774f 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,12 @@ | [User Book](https://book.openvm.dev) | [Contributor Docs](./docs) | [Crate Docs](https://docs.openvm.dev/openvm) + +## Acknowledgements + +OpenVM is a new zkVM design framework. In the process of building it, we studied and learned from the designs and implementations of other projects. We would like to thank these projects for sharing their code for open source development: + +- [Plonky3](https://github.com/Plonky3/Plonky3): The [STARK backend](https://github.com/openvm-org/stark-backend) and circuit writing interfaces are built on top of Plonky3, where we benefited from their modular design at the polynomial IOP level. +- [Valida](https://github.com/valida-xyz/valida): Many ideas around chips and chip interactions were pioneered by Valida and we were greatly inspired by their designs. Some parts of our ISA architecture also had inspirations from their ZK-specific ISA. +- [RISC Zero](https://github.com/risc0/risc0): We are extremely grateful to the RISC Zero team for merging their zkVM focused toolchain into [Rust upstream](https://doc.rust-lang.org/rustc/platform-support/riscv32im-risc0-zkvm-elf.html). Our Rust toolchain integration builds upon their work. +- [SP1](https://github.com/succinctlabs/sp1): We gained inspirations from various parts of SP1's design and interfaces. The native compiler and eDSL we use for the Native Field VM Extension originated from their recursion compiler. diff --git a/crates/toolchain/openvm/src/lib.rs b/crates/toolchain/openvm/src/lib.rs index 9a37608de1..374e67a0ac 100644 --- a/crates/toolchain/openvm/src/lib.rs +++ b/crates/toolchain/openvm/src/lib.rs @@ -58,7 +58,7 @@ fn _fault() -> ! { /// /// When `#![no_main]` is used, the programs entrypoint and main function is left undefined. The /// `entry` macro is required to indicate the main function and link it to an entrypoint provided -/// by the RISC Zero SDK. +/// by the `openvm` crate. /// /// When `std` is enabled, the entrypoint will be linked automatically and this macro is not /// required. @@ -69,7 +69,7 @@ fn _fault() -> ! { /// #![no_main] /// #![no_std] /// -/// risc0_zkvm::entry!(main); +/// openvm::entry!(main); /// /// fn main() { } /// ``` diff --git a/crates/toolchain/openvm/src/serde/deserializer.rs b/crates/toolchain/openvm/src/serde/deserializer.rs index 7906c900ce..195861efd2 100644 --- a/crates/toolchain/openvm/src/serde/deserializer.rs +++ b/crates/toolchain/openvm/src/serde/deserializer.rs @@ -1,17 +1,3 @@ -// Copyright 2024 RISC Zero, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - use alloc::{string::String, vec::Vec}; use bytemuck::Pod; diff --git a/crates/toolchain/openvm/src/serde/err.rs b/crates/toolchain/openvm/src/serde/err.rs index b5602f446d..25daf51e3a 100644 --- a/crates/toolchain/openvm/src/serde/err.rs +++ b/crates/toolchain/openvm/src/serde/err.rs @@ -1,17 +1,3 @@ -// Copyright 2024 RISC Zero, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - use alloc::string::{String, ToString}; use core::fmt::{Display, Formatter}; @@ -36,7 +22,7 @@ pub enum Error { SerializeBufferFull, } -/// A Result type for `risc0_zkvm::serde` operations that can fail +/// A Result type for `openvm::serde` operations that can fail pub type Result = core::result::Result; impl Display for Error { diff --git a/crates/toolchain/openvm/src/serde/serializer.rs b/crates/toolchain/openvm/src/serde/serializer.rs index a16daeeb3f..d3d13a0e4f 100644 --- a/crates/toolchain/openvm/src/serde/serializer.rs +++ b/crates/toolchain/openvm/src/serde/serializer.rs @@ -1,17 +1,3 @@ -// Copyright 2024 RISC Zero, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - use alloc::vec::Vec; use openvm_platform::WORD_SIZE; diff --git a/crates/toolchain/platform/src/heap/bump.rs b/crates/toolchain/platform/src/heap/bump.rs index 8032e00da2..b9e59c70ba 100644 --- a/crates/toolchain/platform/src/heap/bump.rs +++ b/crates/toolchain/platform/src/heap/bump.rs @@ -1,17 +1,3 @@ -// Copyright 2024 RISC Zero, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - use core::alloc::{GlobalAlloc, Layout}; use crate::memory::sys_alloc_aligned; diff --git a/crates/toolchain/platform/src/heap/embedded.rs b/crates/toolchain/platform/src/heap/embedded.rs index f2d4de24a4..8956d20a2d 100644 --- a/crates/toolchain/platform/src/heap/embedded.rs +++ b/crates/toolchain/platform/src/heap/embedded.rs @@ -1,17 +1,3 @@ -// Copyright 2024 RISC Zero, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - use critical_section::RawRestoreState; use embedded_alloc::LlffHeap as Heap; diff --git a/crates/toolchain/platform/src/heap/mod.rs b/crates/toolchain/platform/src/heap/mod.rs index 1206fe0939..82d048ec9c 100644 --- a/crates/toolchain/platform/src/heap/mod.rs +++ b/crates/toolchain/platform/src/heap/mod.rs @@ -1,17 +1,3 @@ -// Copyright 2024 RISC Zero, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - #[cfg(not(feature = "heap-embedded-alloc"))] mod bump; diff --git a/crates/toolchain/platform/src/libm_extern.rs b/crates/toolchain/platform/src/libm_extern.rs index 079b6ea62e..c32ac6f164 100644 --- a/crates/toolchain/platform/src/libm_extern.rs +++ b/crates/toolchain/platform/src/libm_extern.rs @@ -1,17 +1,3 @@ -// Copyright 2024 RISC Zero, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - //! Export symbols for math functions from libm to the global namespace. #![allow(dead_code)] diff --git a/crates/toolchain/platform/src/memory.rs b/crates/toolchain/platform/src/memory.rs index 3a7e0d421a..6f85fe8790 100644 --- a/crates/toolchain/platform/src/memory.rs +++ b/crates/toolchain/platform/src/memory.rs @@ -1,17 +1,3 @@ -// Copyright 2024 RISC Zero, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - use super::WORD_SIZE; pub const MEM_BITS: usize = 28; diff --git a/crates/toolchain/platform/src/rust_rt.rs b/crates/toolchain/platform/src/rust_rt.rs index d684a39d49..c8a8e55294 100644 --- a/crates/toolchain/platform/src/rust_rt.rs +++ b/crates/toolchain/platform/src/rust_rt.rs @@ -1,17 +1,3 @@ -// Copyright 2024 RISC Zero, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - //! This module contains the components required to link a Rust binary. //! //! In particular: diff --git a/crates/toolchain/tests/programs/README.md b/crates/toolchain/tests/programs/README.md index 1eba3e1cd4..21a475d5ed 100644 --- a/crates/toolchain/tests/programs/README.md +++ b/crates/toolchain/tests/programs/README.md @@ -4,7 +4,7 @@ To see list of all available built-in targets: rustc --print target-list ``` -We will currently use the risc0 target until we fork Rust to provide our own RISC-V target. +We will currently use the risc0 target until we contribute our own RISC-V target to Rust. WARNING: to prevent from building for your host machine, make sure you do not have `rustflags = ["-Ctarget-cpu=native"]` in your `~/.cargo/config.toml`. diff --git a/docs/specs/circuit.md b/docs/specs/circuit.md index d5747c5da5..5c5e3e2daf 100644 --- a/docs/specs/circuit.md +++ b/docs/specs/circuit.md @@ -169,9 +169,3 @@ where we allow `N` to be different powers of two. The values of $a, v_i$ that appear in the trace of the access adapter chip are generated on-demand based on the needs of the runtime memory access. In other words, the converter inserts additional writes into the MEMORY_BUS when needed in order to link up accesses of different word sizes. - -## Acknowledgements - -- Plonky3 -- Valida -- SP1 From e9c9c04ea209727b44e754eecddccaf15e33b10a Mon Sep 17 00:00:00 2001 From: Yi Sun Date: Mon, 16 Dec 2024 00:18:38 -0600 Subject: [PATCH 42/69] chore: intro edits (#1084) --- book/src/introduction.md | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/book/src/introduction.md b/book/src/introduction.md index 2e77c1675d..b6a77f72b3 100644 --- a/book/src/introduction.md +++ b/book/src/introduction.md @@ -1,6 +1,6 @@ # OpenVM -_A modular toolkit for extensible zkVMs_ +_A performant and modular zkVM framework built for customization and extensibility_ OpenVM is an open-source zero-knowledge virtual machine (zkVM) framework focused on modularity at every level of the stack. OpenVM is designed for customization and extensibility without sacrificing performance or maintainability. @@ -8,7 +8,14 @@ OpenVM is an open-source zero-knowledge virtual machine (zkVM) framework focused - **Modular no-CPU Architecture**: Unlike traditional machine architectures, the OpenVM architecture has no central processing unit. This design choice allows for seamless integration of custom chips, **without forking or modifying the core architecture**. -- **Extensible Instruction Set**: The instruction set architecture (ISA) is designed to be extended with new custom instructions that integrate directly with the virtual machine. +- **Extensible Instruction Set**: The instruction set architecture (ISA) is designed to be extended with new custom instructions that integrate directly with the virtual machine. Current extensions available for OpenVM include: + - RISC-V support via RV32IM + - A native field arithmetic extension for proof recursion and aggregation + - The Keccak-256 hash function + - Int256 arithmetic + - Modular arithmetic over arbitrary fields + - Elliptic curve operations, including multi-scalar multiplication and ECDSA scalar multiplication. + - Pairing operations on the BN254 and BLS12-381 curves. - **Rust Frontend**: ISA extensions are directly accessible through a Rust frontend via [intrinsic functions](https://en.wikipedia.org/wiki/Intrinsic_function), providing a smooth developer experience. @@ -22,3 +29,14 @@ The following chapters will guide you through: - [Writing applications](./writing-apps/overview.md) in Rust targeting OpenVM and generating proofs. - [Using existing extensions](./custom-extensions/overview.md) to optimize your Rust programs. - [How to add custom VM extensions](./advanced-usage/new-extension.md). + +## Security Status + +As of December 2024, OpenVM has not been audited and is currently not recommended for production use. We plan to continue development towards a production-ready release in 2025. + +> 📖 **About this book** +> +> The book is continuously rendered [here](https://book.openvm.dev/)! +> You can contribute to this book on [GitHub][gh-book]. + +[gh-book]: https://github.com/openvm-org/openvm/tree/main/book \ No newline at end of file From 8f3b2ad65b9809cccfa5223fa2192f4124cb0a8c Mon Sep 17 00:00:00 2001 From: Lun-Kai Hsu Date: Sun, 15 Dec 2024 22:31:34 -0800 Subject: [PATCH 43/69] update pairing config toml (#1085) --- book/src/custom-extensions/pairing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/src/custom-extensions/pairing.md b/book/src/custom-extensions/pairing.md index 7ccfa5aa9f..34912791fa 100644 --- a/book/src/custom-extensions/pairing.md +++ b/book/src/custom-extensions/pairing.md @@ -105,12 +105,12 @@ supported_curves = ["Bls12_381"] [app_vm_config.modular] supported_modulus = [ - "1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", + "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787", ] [app_vm_config.fp2] supported_modulus = [ - "1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", + "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787", ] ``` From c2f71aa942b72772b6a5a5a8584eb1a8dcd580f5 Mon Sep 17 00:00:00 2001 From: Arayi Khalatyan <127004086+arayikhalatyan@users.noreply.github.com> Date: Mon, 16 Dec 2024 01:44:10 -0500 Subject: [PATCH 44/69] docs: template openvm.toml (#1083) * feat: template openvm.toml * review comments --- book/src/custom-extensions/overview.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/book/src/custom-extensions/overview.md b/book/src/custom-extensions/overview.md index 5b2a1b62fe..6f6a3f6052 100644 --- a/book/src/custom-extensions/overview.md +++ b/book/src/custom-extensions/overview.md @@ -33,5 +33,26 @@ The template `openvm.toml` file is as follows: [app_vm_config.rv32i] [app_vm_config.rv32m] [app_vm_config.io] -# ... +[app_vm_config.keccak] +[app_vm_config.native] +[app_vm_config.bigint] +[app_vm_config.modular] +supported_modulus = ["", "", ...] +[app_vm_config.fp2] +supported_modulus = ["", "", ...] +[app_vm_config.pairing] +supported_curves = ["Bls12_381", "Bn254"] +[[app_vm_config.ecc.supported_curves]] +modulus = "" +scalar = "" +a = "" +b = "" +[[app_vm_config.ecc.supported_curves]] +modulus = "" +scalar = "" +a = "" +b = "" ``` + +`rv32i`, `io`, and `rv32m` need to be always included if you make an `openvm.toml` file while the rest are optional and should be included if you want to use the corresponding extension. +All moduli and scalars must be provided in decimal format. Currently `pairing` supports only pre-defined `Bls12_381` and `Bn254` curves. To add more `ecc` curves you need to add more `[[app_vm_config.ecc.supported_curves]]` entries. From 1c303bfa7fde5d8079b7c1eb13fe310111fdb8fe Mon Sep 17 00:00:00 2001 From: PangZhi <1611805+PangZhi@users.noreply.github.com> Date: Sun, 15 Dec 2024 23:29:00 -0800 Subject: [PATCH 45/69] chore: add solc setup and keygen time (#1087) * chore: add solc setup and keygen time * chore: update verify doc * chore: fix typo Co-authored-by: Yi Sun --------- Co-authored-by: Yi Sun --- book/src/writing-apps/verify.md | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/book/src/writing-apps/verify.md b/book/src/writing-apps/verify.md index feedcfc1e6..ee98dcc730 100644 --- a/book/src/writing-apps/verify.md +++ b/book/src/writing-apps/verify.md @@ -15,7 +15,33 @@ If you omit `--app_vk` and/or `--proof`, the command will search for those files Once again, if you omitted `--output` and `--vk_output` in the `keygen` and `prove` commands, you can omit `--app_vk` and `--proof` in the `verify` command. ## EVM Level +EVM level proof setup requires large amounts of computation and memory (~200GB). It is recommended to run this process on a server. +### Install Solc +Install `solc` `0.8.19` using `svm` +```bash +# Install svm +cargo install --version 0.5.7 svm-rs +# Add the binary to your path +export PATH="$HOME/.cargo/bin:$PATH" + +# Install solc 0.8.19 +svm install 0.8.19 +svm use 0.8.19 +``` + +### Generating the Aggregation Proving Key and EVM Verifier Contract +Generate the aggregation proving key and verifier contract at `~/.openvm/agg.pk` and `~/.openvm/verifier.sol` respectively by running + +```bash +cargo openvm setup +``` +> ⚠️ **WARNING** +> This command requires very large amounts of computation and memory (~200 GB). + +This command can take ~20mins on a `m6a.16xlarge` instance due to the keygen time. + +### Verify proof Verifying a proof at the EVM level requires just the proof, as the command uses the verifier generated when `cargo openvm setup` was called. ```bash @@ -23,8 +49,3 @@ cargo openvm verify evm --proof ``` If `proof` is omitted, the command will search for the proof at `./openvm/evm.proof`. - -As with all other EVM-level commands, `cargo openvm setup` is a prerequisite for `verify`. It generates the aggregation proving key and verifier contract at `~/.openvm/agg.pk` and `~/.openvm/verifier.sol` respectively. - -> ⚠️ **WARNING** -> `cargo openvm setup` requires very large amounts of computation and memory (~200 GB). From 03f7f6d94b8249528976e3db78dc5300e286cc89 Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Mon, 16 Dec 2024 02:39:27 -0500 Subject: [PATCH 46/69] chore: update project layout (#1086) * chore: update project layout * chore: readme * fix: link --- crates/toolchain/build/Cargo.toml | 2 +- crates/toolchain/openvm/README.md | 3 ++ crates/vm/README.md | 4 +- docs/crates/benchmarks.md | 20 +++++--- docs/repo/layout.md | 82 +++++++++++++++---------------- docs/specs/README.md | 17 ++++++- 6 files changed, 73 insertions(+), 55 deletions(-) create mode 100644 crates/toolchain/openvm/README.md diff --git a/crates/toolchain/build/Cargo.toml b/crates/toolchain/build/Cargo.toml index ec3be13dca..837ae7697a 100644 --- a/crates/toolchain/build/Cargo.toml +++ b/crates/toolchain/build/Cargo.toml @@ -5,7 +5,7 @@ authors.workspace = true edition.workspace = true homepage.workspace = true repository.workspace = true -description = "OpenVM build tool" +description = "OpenVM build tools" [dependencies] openvm-platform = { workspace = true } diff --git a/crates/toolchain/openvm/README.md b/crates/toolchain/openvm/README.md new file mode 100644 index 0000000000..737ccf9d53 --- /dev/null +++ b/crates/toolchain/openvm/README.md @@ -0,0 +1,3 @@ +# OpenVM Standard Library + +The OpenVM standard library for use in Rust guest programs. diff --git a/crates/vm/README.md b/crates/vm/README.md index 50619237d6..17e82f56a6 100644 --- a/crates/vm/README.md +++ b/crates/vm/README.md @@ -1,3 +1 @@ -# STARK Virtual Machine - -This crate contains STARK implementations for building minimal Virtual Machines. The ISA spec is [here](https://hackmd.io/i-EIS7KvSHeRLOnL6vdphA). +# OpenVM Circuit Framework diff --git a/docs/crates/benchmarks.md b/docs/crates/benchmarks.md index 150bf00002..bc9d811242 100644 --- a/docs/crates/benchmarks.md +++ b/docs/crates/benchmarks.md @@ -1,5 +1,16 @@ # Benchmarks +### Latest Benchmark Results + +Latest benchmark results can be found [here](https://github.com/openvm-org/openvm/blob/benchmark-results/index.md). +These are run via [github workflows](./.github/workflows/benchmark-call.yml) and should always be up to date with the latest `main` branch. + +### Adding a Benchmark + +See [How to Add a Benchmark](../../benchmarks/README.md#how-to-add-a-benchmark). + +### Running Benchmarks Locally + To run benchmarks, install python3 and run (from root of repo): ```bash @@ -18,7 +29,9 @@ Currently the processing is done automatically at the end of `bench.py`. The scr ### Flamegraphs -Flamegraphs to visualize the metrics collected by the VM cycle tracker can be generated if you have [inferno-flamegraph](https://crates.io/crates/inferno) installed. Install via +While traditional flamegraphs generated from instrumenting a proving binary run on the host machine are useful, +for more detailed profiling we generate special flamegraphs that visualize VM-specific metrics such as cycle counts and trace cell usage with stack traces. +These flamegraphs can be generated if you have [inferno-flamegraph](https://crates.io/crates/inferno) installed. Install via ```bash cargo install inferno @@ -31,8 +44,3 @@ python ci/scripts/metric_unify/flamegraph.py ``` The flamegraphs will be written to `*.svg` files in `.bench_metrics/flamegraphs` with respect to the repo root. - -### Latest Benchmark Results - -Latest benchmark results can be found [here](https://github.com/openvm-org/openvm/blob/benchmark-results/index.md). -These are run via [github workflows](./.github/workflows/benchmark-call.yml) and should always be up to date with the latest `main` branch. diff --git a/docs/repo/layout.md b/docs/repo/layout.md index 95995fe1de..1c5200eb24 100644 --- a/docs/repo/layout.md +++ b/docs/repo/layout.md @@ -7,23 +7,22 @@ The main components of the repository are: - [Benchmarks](#benchmarks) - [CI](#ci) - [CLI](#cli) - - [VM SDK](#vm-sdk) - - [Rust Toolchain](#rust-toolchain) - - [VM Framework](#vm-framework) + - [SDK](#sdk) + - [Toolchain](#toolchain) + - [Circuit Framework](#circuit-framework) - [Circuit Foundations](#circuit-foundations) - - [Proof System](#proof-system) - [Extensions](#extensions) - [RV32IM](#rv32im) - [Native Recursion](#native-recursion) - [Keccak256](#keccak256) - [Big Integers](#big-integers) - - [Modular Arithmetic](#modular-arithmetic) + - [Algebra (Modular Arithmetic)](#algebra-modular-arithmetic) - [Elliptic Curve Cryptography](#elliptic-curve-cryptography) - - [Pairing](#pairing) + - [Elliptic Curve Pairing](#elliptic-curve-pairing) ### Documentation -Contributor documentation is in [`docs`](../../docs) and end-user documentation is in [`book`](../../book). +Contributor documentation is in [`docs`](../../docs) and user documentation is in [`book`](../../book). ### Benchmarks @@ -37,36 +36,32 @@ Scripts for CI use and metrics post-processing are in [`ci`](../../ci). Command-line binary to compile, execute, and prove guest programs is in [`cli`](../../crates/cli). -### VM SDK +### SDK -- [`sdk`](../../crates/sdk): The developer SDK for the VM. It includes the OpenVM aggregation programs to support continuations for all VMs in the framework, and well as local aggregation scheduling implementation. It provides the final interface for proving an arbitrary program for a target VM. Includes utilities to generate final onchain SNARK verifier contract. +- [`sdk`](../../crates/sdk): The developer SDK for the VM. It includes the OpenVM aggregation programs to support continuations for all VMs in the framework, and well as a local aggregation scheduling implementation. It provides the final interface for proving an arbitrary program for a target VM. The SDK includes functionality to generate the final onchain SNARK verifier contract. -### Rust Toolchain +### Toolchain - [`openvm`](../../crates/toolchain/openvm): The OpenVM standard library to be imported by guest programs. Contains `main` function entrypoint and standard intrinsic functions for IO. -- [`openvm-platform`](../../crates/toolchain/platform): Rust runtime for RV32IM target using OpenVM intrinsic for system termination. -- [`openvm-transpiler`](../../crates/toolchain/transpiler): Transpiler for converting RISC-V ELF with custom instructions into OpenVM executable with OpenVM instructions. +- [`openvm-platform`](../../crates/toolchain/platform): Rust runtime for RV32IM target using OpenVM intrinsic for system termination. This crate is re-exported by the `openvm` crate. +- [`openvm-build`](../../crates/toolchain/build): Library of build tools for compiling Rust to the RISC-V target, built on top of `cargo`. +- [`openvm-transpiler`](../../crates/toolchain/transpiler): Transpiler for converting RISC-V ELF with custom instructions into OpenVM executable with OpenVM instructions. This crate contains the `TranspilerExtension` trait and a `Transpiler` struct which supports adding custom `TranspilerExtension` implementations. +- [`openvm-instructions`](../../crates/toolchain/instructions): OpenVM instruction struct and trait definitions. Also includes some system instruction definitions. +- [`openvm-instructions-derive`](../../crates/toolchain/instructions/derive): Procedural macros to derive traits for OpenVM instructions. - [`openvm-macros-common`](../../crates/toolchain/macros): Common library for parsing utilities shared across procedural macros used for custom instruction setup in guest programs. -- [`openvm-toolchain-tests`](../../crates/toolchain/tests): Testing of Rust toolchain including all official RISC-V 32-bit IM test vectors. +- [`openvm-toolchain-tests`](../../crates/toolchain/tests): Testing of Rust toolchain including all official RISC-V 32-bit IM test vectors. Currently this is a monolithic crate with tests across many different extensions. We will soon refactor the tests to be more modular. -### VM Framework +### Circuit Framework - [`openvm-circuit`](../../crates/vm): The VM circuit framework. It includes the struct and trait definitions used throughout the architecture, as well as the system chips. - [`openvm-circuit-derive`](../../crates/vm/derive): Procedural macros to derive traits in the VM circuit framework. -- [`openvm-instructions`](../../crates/toolchain/instructions): OpenVM instruction struct and trait definitions. -- [`openvm-instructions-derive`](../../crates/toolchain/instructions/derive): Procedural macros to derive traits for OpenVM instructions. ### Circuit Foundations - [`openvm-circuit-primitives`](../../crates/circuits/primitives): Primitive chips and sub-chips for standalone use in any circuit. -- [`openvm-circuit-primitives-derive`](../../crates/circuits/derive): Procedural macros for use in circuit to derive traits. -- [`openvm-poseidon2-air`](../../crates/circuits/poseidon2-air): Standalone poseidon2 AIR implementation. -- [`openvm-mod-circuit-builder`](../../crates/circuits/mod-builder): General builder for generating chip for any modular arithmetic expression for a compile-time modulus. - -### Proof System - -- [`openvm-stark-backend`](../../crates/stark-backend): General purpose STARK proving system with multi-trace and logup support, built on top of plonky3. -- [`openvm-stark-sdk`](../../crates/stark-sdk): Low-level SDK for use with STARK backend to generate proofs for specific STARK configurations. +- [`openvm-circuit-primitives-derive`](../../crates/circuits/primitives/derive): Procedural macros for use in circuit to derive traits. +- [`openvm-poseidon2-air`](../../crates/circuits/poseidon2-air): Standalone poseidon2 AIR implementation which is configurable based on the desired maximum constraint degree. +- [`openvm-mod-circuit-builder`](../../crates/circuits/mod-builder): General builder for generating a chip for any modular arithmetic expression for a modulus known at compile time. ### Extensions @@ -74,44 +69,45 @@ The toolchain, ISA, and VM are simultaenously extendable. All non-system functio #### RV32IM -- [`openvm-rv32im-circuit`](../../extensions/rv32im/circuit): VM circuit extension for RV32IM instructions, including IO operations. -- [`openvm-rv32im-transpiler`](../../extensions/rv32im/transpiler): Transpiler extension for RV32IM instructions. -- [`openvm-rv32im-guest`](../../extensions/rv32im/guest): Guest library for RV32IM instructions. +- [`openvm-rv32im-circuit`](../../extensions/rv32im/circuit): Circuit extension for RV32IM instructions and IO instructions. +- [`openvm-rv32im-transpiler`](../../extensions/rv32im/transpiler): Transpiler extension for RV32IM instructions and IO instructions. +- [`openvm-rv32im-guest`](../../extensions/rv32im/guest): Guest library for RV32IM instructions and IO instructions. This is re-exported by the `openvm` crate for convenience. #### Native Recursion -- [`openvm-native-circuit`](../../extensions/native/circuit/): VM circuit extension for native instructions operating on field elements. +- [`openvm-native-circuit`](../../extensions/native/circuit/): Circuit extension for native instructions operating on field elements. - [`openvm-native-compiler`](../../extensions/native/compiler/): Implementation of compiler from a Rust embedded DSL to OpenVM assembly targeting the native kernel extension. The eDSL also has a static mode to support compilation to a Halo2 circuit. -- [`openvm-native-recursion`](../../extensions/native/recursion): Library written in the native eDSL with functions to verify arbitrary STARK proofs. Library supports compilation to Halo2 circuit. +- [`openvm-native-recursion`](../../extensions/native/recursion): Library written in the native eDSL with functions to verify arbitrary STARK proofs. The library also supports compilation to a Halo2 circuit. #### Keccak256 -- [`openvm-keccak256-circuit`](../../extensions/keccak256/circuit): VM circuit extension for `keccak256` hash function. -- [`openvm-keccak256-transpiler`](../../extensions/keccak256/transpiler): Transpiler extension for `keccak256` hash function. -- [`openvm-keccak256-guest`](../../extensions/keccak256/guest): Guest library with intrinsic function for `keccak256` hash function. +- [`openvm-keccak256-circuit`](../../extensions/keccak256/circuit): Circuit extension for the `keccak256` hash function. +- [`openvm-keccak256-transpiler`](../../extensions/keccak256/transpiler): Transpiler extension for the `keccak256` hash function. +- [`openvm-keccak256-guest`](../../extensions/keccak256/guest): Guest library with intrinsic function for the `keccak256` hash function. #### Big Integers -- [`openvm-bigint-circuit`](../../extensions/bigint/circuit): VM circuit extension for `I256` and `U256` big integer operations. +- [`openvm-bigint-circuit`](../../extensions/bigint/circuit): Circuit extension for `I256` and `U256` big integer operations. - [`openvm-bigint-transpiler`](../../extensions/bigint/transpiler): Transpiler extension for `I256` and `U256` big integer operations. - [`openvm-bigint-guest`](../../extensions/bigint/guest): Guest library with `I256` and `U256` big integers operations using intrinsics for underlying operations. -#### Modular Arithmetic +#### Algebra (Modular Arithmetic) -- [`openvm-algebra-circuit`](../../extensions/algebra/circuit): VM circuit extension for modular arithmetic for arbitrary compile-time modulus. Supports modular arithmetic and Fp2 operations. -- [`openvm-algebra-transpiler`](../../extensions/algebra/transpiler): Transpiler extension for modular arithmetic for arbitrary compile-time modulus. Supports modular arithmetic and Fp2 operations. -- [`openvm-algebra-guest`](../../extensions/algebra/guest): Guest library with traits for modular arithmetic and Fp2 operations. +- [`openvm-algebra-circuit`](../../extensions/algebra/circuit): Circuit extension for modular arithmetic for arbitrary compile-time modulus. Supports modular arithmetic and complex field extension operations. +- [`openvm-algebra-transpiler`](../../extensions/algebra/transpiler): Transpiler extension for modular arithmetic for arbitrary compile-time modulus. Supports modular arithmetic and complex field extension operations. +- [`openvm-algebra-guest`](../../extensions/algebra/guest): Guest library with traits for modular arithmetic and complex field extension operations. - [`openvm-algebra-moduli-setup`](../../extensions/algebra/moduli-setup): Procedural macros for use in guest program to generate modular arithmetic struct with custom intrinsics for compile-time modulus. +- [`openvm-algebra-complex-macros`](../../extensions/algebra/guest/src/field/complex-macros): Procedural macros for use in guest program to generate complex field struct with custom intrinsics for compile-time modulus. #### Elliptic Curve Cryptography -- [`openvm-ecc-circuit`](../../extensions/ecc/circuit): VM circuit extension for Weierstrass elliptic curve operations for arbitrary compile-time curve. +- [`openvm-ecc-circuit`](../../extensions/ecc/circuit): Circuit extension for Weierstrass elliptic curve operations for arbitrary compile-time curve. - [`openvm-ecc-transpiler`](../../extensions/ecc/transpiler): Transpiler extension for Weierstrass elliptic curve operations for arbitrary compile-time curve. -- [`openvm-ecc-guest`](../../extensions/ecc/guest): Guest library with elliptic curve constants for Secp256k1 and functions using custom intrinsics, including ECDSA. +- [`openvm-ecc-guest`](../../extensions/ecc/guest): Guest library with traits for elliptic curve cryptography. Includes implementations of ECDSA and multi-scalar multiplication. - [`openvm-ecc-sw-setup`](../../extensions/ecc/sw-setup): Procedural macros for use in guest program to generate short Weierstrass curve struct with custom intrinsics for compile-time curve. -#### Pairing +#### Elliptic Curve Pairing -- [`openvm-pairing-circuit`](../../extensions/pairing/circuit): VM circuit extension for optimal Ate pairing on arbitrary compile-time elliptic curves, including BN254 and BLS12-381. -- [`openvm-pairing-transpiler`](../../extensions/pairing/transpiler): Transpiler extension for optimal Ate pairing on arbitrary compile-time elliptic curves, including BN254 and BLS12-381. -- [`openvm-pairing-guest`](../../extensions/pairing/guest): Guest library with optimal Ate pairing on elliptic curves, including BN254 and BLS12-381 and associated constants. Also includes elliptic curve operations for VM runtime with the `halo2curves` feature gate. +- [`openvm-pairing-circuit`](../../extensions/pairing/circuit): Circuit extension for optimal Ate pairing on BN254 and BLS12-381 curves. +- [`openvm-pairing-transpiler`](../../extensions/pairing/transpiler): Transpiler extension for optimal Ate pairing on BN254 and BLS12-381. +- [`openvm-pairing-guest`](../../extensions/pairing/guest): Guest library with optimal Ate pairing on BN254 and BLS12-381 and associated constants. Also includes elliptic curve operations for VM runtime with the `halo2curves` feature gate. diff --git a/docs/specs/README.md b/docs/specs/README.md index 79b84a9e25..8bb7e53c6b 100644 --- a/docs/specs/README.md +++ b/docs/specs/README.md @@ -20,6 +20,19 @@ The framework is designed to be extendable via external crates _without forking_ VM extensions provide a way to simultaneously extend the VM with new chips, opcodes, and toolchain support for these opcodes. A new extension of the overall architecture consists of three components: -- Library: the guest library that compiles program code (usually in Rust) into RISC-V assembly with custom instructions. +- Guest library: the guest library that compiles program code (usually in Rust) into RISC-V assembly with custom instructions. - Transpiler extension: extend the transpiler to specify how newly introduced custom RISC-V instructions should be transpiled into custom OpenVM instructions. -- VM extension: define new chips and assign them to handle the new opcodes. +- Circuit extension: define new chips and assign them to handle the new opcodes. + +These three components should be organized into three separate crates. When introducing a new extension with name `$name`, we recommend naming the crates as follows: + +- `openvm-$name-guest`: the guest library crate. This crate specifies the custom RISC-V instructions to be added. To avoid opcode collisions, we keep a list of currently supported custom instructions in [this](./RISCV.md) file. +- `openvm-$name-transpiler`: the transpiler extension crate. This crate needs to import `openvm-$name-guest` to get the custom RISC-V instruction definitions. The `openvm-$name-transpiler` crate specifies the new OpenVM instruction definitions (represented in field elements) as well as the transpiler extension. +- `openvm-$name-circuit`: the circuit extension crate that defines new chips. This crate needs to import `openvm-$name-transpiler` to get the new OpenVM instruction definitions. + +## Specifications + +- [Circuit Architecture](./circuit.md) +- [Instruction Set Architecture](./ISA.md) +- [RISC-V custom instructions and transpiler](./RISCV.md) +- [Continuations](./continuations.md) From 351e068246200b3e651b52d57fe6c032d939f41e Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Mon, 16 Dec 2024 03:08:04 -0500 Subject: [PATCH 47/69] chore: feature gate hard coded buses for testing only (#1088) --- crates/vm/src/arch/config.rs | 17 +++++++---------- crates/vm/src/arch/testing/mod.rs | 11 ++++++++++- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/crates/vm/src/arch/config.rs b/crates/vm/src/arch/config.rs index 31a50ec038..715a0f1542 100644 --- a/crates/vm/src/arch/config.rs +++ b/crates/vm/src/arch/config.rs @@ -5,22 +5,19 @@ use openvm_poseidon2_air::poseidon2::Poseidon2Config; use openvm_stark_backend::{p3_field::PrimeField32, ChipUsageGetter}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; +// TODO[jpw]: re-exporting hardcoded bus constants for tests. Import paths should be +// updated directly but it changes many files. +#[cfg(any(test, feature = "test-utils"))] +pub use super::testing::{ + BITWISE_OP_LOOKUP_BUS, BYTE_XOR_BUS, EXECUTION_BUS, MEMORY_BUS, MEMORY_MERKLE_BUS, + POSEIDON2_DIRECT_BUS, RANGE_TUPLE_CHECKER_BUS, READ_INSTRUCTION_BUS, +}; use super::{ AnyEnum, InstructionExecutor, SystemComplex, SystemExecutor, SystemPeriphery, VmChipComplex, VmInventoryError, PUBLIC_VALUES_AIR_ID, }; use crate::system::memory::BOUNDARY_AIR_OFFSET; -pub const EXECUTION_BUS: usize = 0; -pub const MEMORY_BUS: usize = 1; -pub const POSEIDON2_DIRECT_BUS: usize = 6; -pub const READ_INSTRUCTION_BUS: usize = 8; -pub const BITWISE_OP_LOOKUP_BUS: usize = 9; -pub const BYTE_XOR_BUS: usize = 10; -//pub const BYTE_XOR_BUS: XorBus = XorBus(8); -pub const RANGE_TUPLE_CHECKER_BUS: usize = 11; -pub const MEMORY_MERKLE_BUS: usize = 12; - const DEFAULT_MAX_SEGMENT_LEN: usize = (1 << 22) - 100; // sbox is decomposed to have this max degree for Poseidon2. We set to 3 so quotient_degree = 2 // allows log_blowup = 1 diff --git a/crates/vm/src/arch/testing/mod.rs b/crates/vm/src/arch/testing/mod.rs index 4ad33640ba..abf90d24db 100644 --- a/crates/vm/src/arch/testing/mod.rs +++ b/crates/vm/src/arch/testing/mod.rs @@ -25,7 +25,7 @@ use rand::{rngs::StdRng, RngCore, SeedableRng}; use tracing::Level; use crate::{ - arch::{ExecutionState, MemoryConfig, EXECUTION_BUS, MEMORY_BUS, READ_INSTRUCTION_BUS}, + arch::{ExecutionState, MemoryConfig}, system::{ memory::{offline_checker::MemoryBus, MemoryController}, program::ProgramBus, @@ -43,6 +43,15 @@ pub use test_adapter::TestAdapterChip; use super::{ExecutionBus, InstructionExecutor}; use crate::system::{memory::MemoryControllerRef, poseidon2::Poseidon2Chip}; +pub const EXECUTION_BUS: usize = 0; +pub const MEMORY_BUS: usize = 1; +pub const POSEIDON2_DIRECT_BUS: usize = 6; +pub const READ_INSTRUCTION_BUS: usize = 8; +pub const BITWISE_OP_LOOKUP_BUS: usize = 9; +pub const BYTE_XOR_BUS: usize = 10; +pub const RANGE_TUPLE_CHECKER_BUS: usize = 11; +pub const MEMORY_MERKLE_BUS: usize = 12; + const RANGE_CHECKER_BUS: usize = 4; #[derive(Debug)] From b823f277231d6c1c557385fb07511e365f1f6e8b Mon Sep 17 00:00:00 2001 From: PangZhi <1611805+PangZhi@users.noreply.github.com> Date: Mon, 16 Dec 2024 00:41:10 -0800 Subject: [PATCH 48/69] docs: merge verify and onchain-verify (#1089) --- book/src/advanced-usage/sdk.md | 2 +- book/src/writing-apps/onchain-verify.md | 29 ------------------------- book/src/writing-apps/verify.md | 18 +++++++++++---- 3 files changed, 15 insertions(+), 34 deletions(-) delete mode 100644 book/src/writing-apps/onchain-verify.md diff --git a/book/src/advanced-usage/sdk.md b/book/src/advanced-usage/sdk.md index 39d6d1fc5a..da21629b2c 100644 --- a/book/src/advanced-usage/sdk.md +++ b/book/src/advanced-usage/sdk.md @@ -147,7 +147,7 @@ assert!(success); > ⚠️ **WARNING** > The aggregation proving key `agg_pk` above is large. Avoid cloning it if possible. -Note that `DEFAULT_PARAMS_DIR` is the directory where Halo2 parameters are stored by the `cargo openvm setup` CLI command. For more information on the setup process, see the [onchain verify](../writing-apps/onchain-verify.md) doc. +Note that `DEFAULT_PARAMS_DIR` is the directory where Halo2 parameters are stored by the `cargo openvm setup` CLI command. For more information on the setup process, see the `EVM Level` section of the [verify](../writing-apps/verify.md) doc. > ⚠️ **WARNING** > `cargo openvm setup` requires very large amounts of computation and memory (~200 GB). diff --git a/book/src/writing-apps/onchain-verify.md b/book/src/writing-apps/onchain-verify.md deleted file mode 100644 index 60a0398552..0000000000 --- a/book/src/writing-apps/onchain-verify.md +++ /dev/null @@ -1,29 +0,0 @@ -# Onchain Verification - -## Generating the Aggregation Proving Key and EVM Verifier Contract - -The workflow for generating an end-to-end EVM proof requires first generating an aggregation proving key and EVM verifier contract. This can be done by running the following command: - -```bash -cargo openvm setup -``` -> ⚠️ **WARNING** -> This command requires very large amounts of computation and memory (~200 GB). - -Upon a successful run, the command will write `agg.pk` and `verifier.sol` to `~/.openvm/`, where `~` is the directory specified by environment variable `$HOME`. Every command that requires these files will look for them in this directory. - -> ⚠️ **WARNING** -> If the `$HOME` environment variable is not set, this command may fail. - -Note that `cargo openvm setup` may attempt to download other files (i.e. KZG parameters) from an AWS S3 bucket into `~/.openvm/`. - -## Generating and Verifying an EVM Proof - -To generate and verify an EVM proof, you need to run the following commands: - -```bash -cargo openvm prove evm --input -cargo openvm verify evm -``` - -These commands are very similar to their `app` subcommand counterparts. For more information on the `prove` and `verify` commands, see the [prove](./prove.md) and [verify](./verify.md) docs. diff --git a/book/src/writing-apps/verify.md b/book/src/writing-apps/verify.md index ee98dcc730..8577a5e234 100644 --- a/book/src/writing-apps/verify.md +++ b/book/src/writing-apps/verify.md @@ -31,7 +31,8 @@ svm use 0.8.19 ``` ### Generating the Aggregation Proving Key and EVM Verifier Contract -Generate the aggregation proving key and verifier contract at `~/.openvm/agg.pk` and `~/.openvm/verifier.sol` respectively by running + +The workflow for generating an end-to-end EVM proof requires first generating an aggregation proving key and EVM verifier contract. This can be done by running the following command: ```bash cargo openvm setup @@ -39,13 +40,22 @@ cargo openvm setup > ⚠️ **WARNING** > This command requires very large amounts of computation and memory (~200 GB). +Upon a successful run, the command will write `agg.pk` and `verifier.sol` to `~/.openvm/`, where `~` is the directory specified by environment variable `$HOME`. Every command that requires these files will look for them in this directory. + +> ⚠️ **WARNING** +> If the `$HOME` environment variable is not set, this command may fail. + +Note that `cargo openvm setup` may attempt to download other files (i.e. KZG parameters) from an AWS S3 bucket into `~/.openvm/`. + This command can take ~20mins on a `m6a.16xlarge` instance due to the keygen time. -### Verify proof -Verifying a proof at the EVM level requires just the proof, as the command uses the verifier generated when `cargo openvm setup` was called. +## Generating and Verifying an EVM Proof + +To generate and verify an EVM proof, you need to run the following commands: ```bash +cargo openvm prove evm --input cargo openvm verify evm --proof ``` -If `proof` is omitted, the command will search for the proof at `./openvm/evm.proof`. +If `proof` is omitted, the `verify` command will search for the proof at `./openvm/evm.proof`. From 1456ac1b14912f76eace8867f5a2f8c3f57c0fbb Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Mon, 16 Dec 2024 03:41:57 -0500 Subject: [PATCH 49/69] chore: add licenses and update descriptions in license (#1090) * chore: update license and description of crates * chore: add rv32-adapters to layout --- Cargo.lock | 14 +++++++------- Cargo.toml | 3 ++- benchmarks/Cargo.toml | 1 + crates/circuits/mod-builder/Cargo.toml | 8 ++++++-- crates/circuits/poseidon2-air/Cargo.toml | 3 +++ crates/circuits/primitives/Cargo.toml | 5 ++++- crates/circuits/primitives/derive/Cargo.toml | 6 +++++- crates/cli/Cargo.toml | 4 ++++ crates/sdk/Cargo.toml | 1 + crates/toolchain/build/Cargo.toml | 3 ++- crates/toolchain/instructions/Cargo.toml | 1 + crates/toolchain/instructions/derive/Cargo.toml | 1 + crates/toolchain/macros/Cargo.toml | 3 +-- crates/toolchain/openvm/Cargo.toml | 11 ++++++----- crates/toolchain/platform/Cargo.toml | 10 +++++----- crates/toolchain/tests/Cargo.toml | 2 +- crates/vm/Cargo.toml | 4 +++- crates/vm/derive/Cargo.toml | 1 + docs/repo/layout.md | 1 + extensions/algebra/circuit/Cargo.toml | 9 ++++++--- extensions/algebra/guest/Cargo.toml | 4 +++- extensions/algebra/transpiler/Cargo.toml | 4 +++- extensions/bigint/circuit/Cargo.toml | 4 +++- extensions/bigint/guest/Cargo.toml | 4 +++- extensions/bigint/transpiler/Cargo.toml | 4 +++- extensions/ecc/circuit/Cargo.toml | 9 ++++++--- extensions/ecc/guest/Cargo.toml | 4 +++- extensions/ecc/sw-setup/Cargo.toml | 7 +++++-- extensions/ecc/transpiler/Cargo.toml | 4 +++- extensions/keccak256/circuit/Cargo.toml | 4 +++- extensions/keccak256/guest/Cargo.toml | 6 ++++-- extensions/keccak256/transpiler/Cargo.toml | 4 +++- extensions/native/circuit/Cargo.toml | 4 +++- extensions/native/compiler/Cargo.toml | 7 +++++-- extensions/native/compiler/derive/Cargo.toml | 5 ++++- extensions/native/recursion/Cargo.toml | 2 ++ extensions/pairing/circuit/Cargo.toml | 9 ++++++--- extensions/pairing/guest/Cargo.toml | 4 +++- extensions/pairing/transpiler/Cargo.toml | 4 +++- extensions/rv32-adapters/Cargo.toml | 4 +++- extensions/rv32im/circuit/Cargo.toml | 4 +++- extensions/rv32im/guest/Cargo.toml | 4 +++- extensions/rv32im/transpiler/Cargo.toml | 4 +++- 43 files changed, 141 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8e7a4c094c..df9b515446 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addchain" @@ -2353,7 +2353,7 @@ dependencies = [ [[package]] name = "halo2curves-axiom" version = "0.7.0" -source = "git+https://github.com/axiom-crypto/halo2curves.git?branch=test/visibility#c148550bd309e92b8aaa954ec5e07df3dd137dab" +source = "git+https://github.com/axiom-crypto/halo2curves.git?branch=test%2Fvisibility#c148550bd309e92b8aaa954ec5e07df3dd137dab" dependencies = [ "blake2b_simd", "digest 0.10.7", @@ -3354,7 +3354,7 @@ dependencies = [ [[package]] name = "openvm-algebra-circuit" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "derive-new", "derive_more 1.0.0", @@ -3644,7 +3644,7 @@ dependencies = [ [[package]] name = "openvm-ecc-circuit" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "derive-new", "derive_more 1.0.0", @@ -3809,7 +3809,7 @@ dependencies = [ [[package]] name = "openvm-mod-circuit-builder" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "halo2curves-axiom 0.7.0", "hex-literal", @@ -3859,7 +3859,7 @@ dependencies = [ [[package]] name = "openvm-native-compiler" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "backtrace", "cfg-if", @@ -3937,7 +3937,7 @@ dependencies = [ [[package]] name = "openvm-pairing-circuit" -version = "0.1.0" +version = "0.1.0-alpha" dependencies = [ "derive-new", "derive_more 1.0.0", diff --git a/Cargo.toml b/Cargo.toml index c7b0865c92..e812773112 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,9 +2,10 @@ version = "0.1.0-alpha" edition = "2021" rust-version = "1.82" -authors = ["Intrinsic Technologies"] +authors = ["OpenVM Authors"] homepage = "https://openvm.dev" repository = "https://github.com/openvm-org/" +license = "MIT" [workspace] members = [ diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml index 4ad39c928d..b90d657548 100644 --- a/benchmarks/Cargo.toml +++ b/benchmarks/Cargo.toml @@ -5,6 +5,7 @@ authors.workspace = true edition.workspace = true homepage.workspace = true repository.workspace = true +license.workspace = true [dependencies] openvm-build.workspace = true diff --git a/crates/circuits/mod-builder/Cargo.toml b/crates/circuits/mod-builder/Cargo.toml index 6104a252ec..46d990236a 100644 --- a/crates/circuits/mod-builder/Cargo.toml +++ b/crates/circuits/mod-builder/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "openvm-mod-circuit-builder" -version = "0.1.0" -edition = "2021" description = "Modular arithmetic framework for building OpenVM circuits." +version.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true [dependencies] openvm-stark-backend = { workspace = true } diff --git a/crates/circuits/poseidon2-air/Cargo.toml b/crates/circuits/poseidon2-air/Cargo.toml index 4483e1f11f..b2058a737e 100644 --- a/crates/circuits/poseidon2-air/Cargo.toml +++ b/crates/circuits/poseidon2-air/Cargo.toml @@ -3,6 +3,9 @@ name = "openvm-poseidon2-air" version.workspace = true authors.workspace = true edition.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true [dependencies] p3-monty-31 = { workspace = true } diff --git a/crates/circuits/primitives/Cargo.toml b/crates/circuits/primitives/Cargo.toml index 0f0c049536..1197cff018 100644 --- a/crates/circuits/primitives/Cargo.toml +++ b/crates/circuits/primitives/Cargo.toml @@ -1,9 +1,12 @@ [package] name = "openvm-circuit-primitives" +description = "Library of plonky3 primitives for general purpose use in other ZK circuits." version.workspace = true authors.workspace = true edition.workspace = true -description = "Library of plonky3 primitives for general purpose use in other ZK circuits." +homepage.workspace = true +repository.workspace = true +license.workspace = true [dependencies] openvm-stark-backend = { workspace = true } diff --git a/crates/circuits/primitives/derive/Cargo.toml b/crates/circuits/primitives/derive/Cargo.toml index 309ec130bd..a3835aa986 100644 --- a/crates/circuits/primitives/derive/Cargo.toml +++ b/crates/circuits/primitives/derive/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "openvm-circuit-primitives-derive" +description = "Procedural macros for writing circuits as AIRs." version.workspace = true +authors.workspace = true edition.workspace = true -description = "Procedural macros for writing circuits as AIRs." +homepage.workspace = true +repository.workspace = true +license.workspace = true [lib] proc-macro = true diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index b780ff724f..528cdf6a94 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -3,7 +3,11 @@ name = "cargo-openvm" description = "OpenVM CLI tools" readme = "README.md" version.workspace = true +authors.workspace = true edition.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true [build-dependencies] vergen = { version = "8", default-features = false, features = [ diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml index e3c903e959..d538593a70 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -5,6 +5,7 @@ authors.workspace = true edition.workspace = true homepage.workspace = true repository.workspace = true +license.workspace = true [dependencies] openvm-algebra-circuit = { workspace = true } diff --git a/crates/toolchain/build/Cargo.toml b/crates/toolchain/build/Cargo.toml index 837ae7697a..4926e2ce7a 100644 --- a/crates/toolchain/build/Cargo.toml +++ b/crates/toolchain/build/Cargo.toml @@ -1,11 +1,12 @@ [package] name = "openvm-build" +description = "OpenVM build tools" version.workspace = true authors.workspace = true edition.workspace = true homepage.workspace = true repository.workspace = true -description = "OpenVM build tools" +license.workspace = true [dependencies] openvm-platform = { workspace = true } diff --git a/crates/toolchain/instructions/Cargo.toml b/crates/toolchain/instructions/Cargo.toml index 3e319651ab..38202a3e14 100644 --- a/crates/toolchain/instructions/Cargo.toml +++ b/crates/toolchain/instructions/Cargo.toml @@ -5,6 +5,7 @@ authors.workspace = true edition.workspace = true homepage.workspace = true repository.workspace = true +license.workspace = true [dependencies] openvm-stark-backend.workspace = true diff --git a/crates/toolchain/instructions/derive/Cargo.toml b/crates/toolchain/instructions/derive/Cargo.toml index fdb6deecc9..9f86ee8aed 100644 --- a/crates/toolchain/instructions/derive/Cargo.toml +++ b/crates/toolchain/instructions/derive/Cargo.toml @@ -5,6 +5,7 @@ authors.workspace = true edition.workspace = true homepage.workspace = true repository.workspace = true +license.workspace = true [lib] proc-macro = true diff --git a/crates/toolchain/macros/Cargo.toml b/crates/toolchain/macros/Cargo.toml index 86927cf508..d886edc535 100644 --- a/crates/toolchain/macros/Cargo.toml +++ b/crates/toolchain/macros/Cargo.toml @@ -1,9 +1,8 @@ [package] name = "openvm-macros-common" version.workspace = true -edition.workspace = true -rust-version.workspace = true authors.workspace = true +edition.workspace = true homepage.workspace = true repository.workspace = true diff --git a/crates/toolchain/openvm/Cargo.toml b/crates/toolchain/openvm/Cargo.toml index 29eb63ff44..a595c6b15d 100644 --- a/crates/toolchain/openvm/Cargo.toml +++ b/crates/toolchain/openvm/Cargo.toml @@ -1,11 +1,12 @@ [package] name = "openvm" description = "OpenVM standard Rust library for guest programs." -version = { workspace = true } -edition = { workspace = true } -# license = { workspace = true } -homepage = { workspace = true } -repository = { workspace = true } +version.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true [dependencies] openvm-platform = { workspace = true, features = [ diff --git a/crates/toolchain/platform/Cargo.toml b/crates/toolchain/platform/Cargo.toml index 6af9b14904..59e00fc1d1 100644 --- a/crates/toolchain/platform/Cargo.toml +++ b/crates/toolchain/platform/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "openvm-platform" description = "OpenVM Rust platform definitions." -version = { workspace = true } -edition = { workspace = true } -# license = { workspace = true } -homepage = { workspace = true } -repository = { workspace = true } +version.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true [dependencies] stability = "0.2" diff --git a/crates/toolchain/tests/Cargo.toml b/crates/toolchain/tests/Cargo.toml index 5536cd2015..83fee387fa 100644 --- a/crates/toolchain/tests/Cargo.toml +++ b/crates/toolchain/tests/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "openvm-toolchain-tests" +description = "Tests for the OpenVM toolchain starting from Rust" version.workspace = true authors.workspace = true edition.workspace = true homepage.workspace = true repository.workspace = true -description = "Tests for the OpenVM toolchain starting from Rust" [dependencies] openvm-circuit-primitives-derive.workspace = true diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index 1287a34d82..68bc24bccf 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "openvm-circuit" +description = "OpenVM circuits" version.workspace = true authors.workspace = true edition.workspace = true -description = "OpenVM circuits" +homepage.workspace = true +repository.workspace = true [dependencies] p3-symmetric = { workspace = true } diff --git a/crates/vm/derive/Cargo.toml b/crates/vm/derive/Cargo.toml index 8079e9dfcd..d2d11dcc78 100644 --- a/crates/vm/derive/Cargo.toml +++ b/crates/vm/derive/Cargo.toml @@ -4,6 +4,7 @@ version.workspace = true edition.workspace = true authors.workspace = true description = "Procedural macros for OpenVM circuits." +license.workspace = true [lib] proc-macro = true diff --git a/docs/repo/layout.md b/docs/repo/layout.md index 1c5200eb24..32c0f53135 100644 --- a/docs/repo/layout.md +++ b/docs/repo/layout.md @@ -72,6 +72,7 @@ The toolchain, ISA, and VM are simultaenously extendable. All non-system functio - [`openvm-rv32im-circuit`](../../extensions/rv32im/circuit): Circuit extension for RV32IM instructions and IO instructions. - [`openvm-rv32im-transpiler`](../../extensions/rv32im/transpiler): Transpiler extension for RV32IM instructions and IO instructions. - [`openvm-rv32im-guest`](../../extensions/rv32im/guest): Guest library for RV32IM instructions and IO instructions. This is re-exported by the `openvm` crate for convenience. +- [`openvm-rv32-adapters`](../../extensions/rv32-adapters): Circuit adapters for other circuit extensions to use to be compatible with the RISC-V 32-bit architecture. #### Native Recursion diff --git a/extensions/algebra/circuit/Cargo.toml b/extensions/algebra/circuit/Cargo.toml index 240edc1694..84a59b7fff 100644 --- a/extensions/algebra/circuit/Cargo.toml +++ b/extensions/algebra/circuit/Cargo.toml @@ -1,8 +1,11 @@ [package] name = "openvm-algebra-circuit" -version = "0.1.0" -edition = "2021" -description = "Modular arithmetic circuits." +description = "OpenVM circuit extension for algebra (modular arithmetic)" +version.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true [dependencies] openvm-circuit-primitives = { workspace = true } diff --git a/extensions/algebra/guest/Cargo.toml b/extensions/algebra/guest/Cargo.toml index c2fc9ca9d9..78dec0ba86 100644 --- a/extensions/algebra/guest/Cargo.toml +++ b/extensions/algebra/guest/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "openvm-algebra-guest" +description = "OpenVM guest library for algebra over rings and fields, including modular and complex field arithmetic." version.workspace = true authors.workspace = true edition.workspace = true -description = "OpenVM guest library for algebra over rings and fields, including modular and Fp2 arithmetic." +homepage.workspace = true +repository.workspace = true [dependencies] openvm = { workspace = true } diff --git a/extensions/algebra/transpiler/Cargo.toml b/extensions/algebra/transpiler/Cargo.toml index af97a61c7c..96fbe4545d 100644 --- a/extensions/algebra/transpiler/Cargo.toml +++ b/extensions/algebra/transpiler/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "openvm-algebra-transpiler" +description = "OpenVM transpiler extension for algebra (modular arithmetic)" version.workspace = true authors.workspace = true edition.workspace = true -description = "Transpiler extension for modular arithmetic" +homepage.workspace = true +repository.workspace = true [dependencies] openvm-stark-backend = { workspace = true } diff --git a/extensions/bigint/circuit/Cargo.toml b/extensions/bigint/circuit/Cargo.toml index 7b350acce4..33b033be6c 100644 --- a/extensions/bigint/circuit/Cargo.toml +++ b/extensions/bigint/circuit/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "openvm-bigint-circuit" +description = "OpenVM circuit extension for uint256 and int256" version.workspace = true authors.workspace = true edition.workspace = true -description = "OpenVM circuit extension for uint256 and int256" +homepage.workspace = true +repository.workspace = true [dependencies] openvm-stark-backend = { workspace = true } diff --git a/extensions/bigint/guest/Cargo.toml b/extensions/bigint/guest/Cargo.toml index 0da3e63b56..27db5f697e 100644 --- a/extensions/bigint/guest/Cargo.toml +++ b/extensions/bigint/guest/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "openvm-bigint-guest" +description = "OpenVM guest library for bigint" version.workspace = true authors.workspace = true edition.workspace = true -description = "Guest extension for Int256" +homepage.workspace = true +repository.workspace = true [dependencies] openvm = { workspace = true } diff --git a/extensions/bigint/transpiler/Cargo.toml b/extensions/bigint/transpiler/Cargo.toml index e19382afc9..d26b2672e5 100644 --- a/extensions/bigint/transpiler/Cargo.toml +++ b/extensions/bigint/transpiler/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "openvm-bigint-transpiler" +description = "OpenVM transpiler extension for bigint" version.workspace = true authors.workspace = true edition.workspace = true -description = "Transpiler extension for bigint" +homepage.workspace = true +repository.workspace = true [dependencies] openvm-stark-backend = { workspace = true } diff --git a/extensions/ecc/circuit/Cargo.toml b/extensions/ecc/circuit/Cargo.toml index 85a7d18cea..4c9c389c20 100644 --- a/extensions/ecc/circuit/Cargo.toml +++ b/extensions/ecc/circuit/Cargo.toml @@ -1,8 +1,11 @@ [package] name = "openvm-ecc-circuit" -version = "0.1.0" -edition = "2021" -description = "Elliptic curve circuits." +description = "OpenVM circuit extension for elliptic curve cryptography" +version.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true [dependencies] openvm-circuit-primitives-derive = { workspace = true } diff --git a/extensions/ecc/guest/Cargo.toml b/extensions/ecc/guest/Cargo.toml index ce337f8630..ce32fe3df7 100644 --- a/extensions/ecc/guest/Cargo.toml +++ b/extensions/ecc/guest/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "openvm-ecc-guest" +description = "OpenVM guest library for elliptic curve cryptography" version.workspace = true authors.workspace = true edition.workspace = true -description = "OpenVM guest elliptic curve cryptography library." +homepage.workspace = true +repository.workspace = true [dependencies] openvm = { workspace = true } diff --git a/extensions/ecc/sw-setup/Cargo.toml b/extensions/ecc/sw-setup/Cargo.toml index 35ada6191d..c8ba8d0616 100644 --- a/extensions/ecc/sw-setup/Cargo.toml +++ b/extensions/ecc/sw-setup/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "openvm-ecc-sw-setup" -version = { workspace = true } -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true [dependencies] syn = { version = "2.0", features = ["full"] } diff --git a/extensions/ecc/transpiler/Cargo.toml b/extensions/ecc/transpiler/Cargo.toml index d782855d9a..b5b605a015 100644 --- a/extensions/ecc/transpiler/Cargo.toml +++ b/extensions/ecc/transpiler/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "openvm-ecc-transpiler" +description = "OpenVM transpiler extension for elliptic curve cryptography" version.workspace = true authors.workspace = true edition.workspace = true -description = "Transpiler extension for ecc" +homepage.workspace = true +repository.workspace = true [dependencies] openvm-stark-backend = { workspace = true } diff --git a/extensions/keccak256/circuit/Cargo.toml b/extensions/keccak256/circuit/Cargo.toml index a10b900167..a8b8865cd6 100644 --- a/extensions/keccak256/circuit/Cargo.toml +++ b/extensions/keccak256/circuit/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "openvm-keccak256-circuit" +description = "OpenVM circuit extension for keccak256" version.workspace = true authors.workspace = true edition.workspace = true -description = "OpenVM circuit extension for keccak256" +homepage.workspace = true +repository.workspace = true [dependencies] openvm-stark-backend = { workspace = true } diff --git a/extensions/keccak256/guest/Cargo.toml b/extensions/keccak256/guest/Cargo.toml index da4435bdf0..8bce8630e2 100644 --- a/extensions/keccak256/guest/Cargo.toml +++ b/extensions/keccak256/guest/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "openvm-keccak256-guest" +description = "OpenVM guest library for keccak256" version.workspace = true authors.workspace = true edition.workspace = true -description = "Guest extension for Keccak256" +homepage.workspace = true +repository.workspace = true [dependencies] openvm-platform = { workspace = true } @@ -13,4 +15,4 @@ tiny-keccak.workspace = true [features] default = [] -std = ["serde/std"] \ No newline at end of file +std = ["serde/std"] diff --git a/extensions/keccak256/transpiler/Cargo.toml b/extensions/keccak256/transpiler/Cargo.toml index 2af49e1c60..b18e176f34 100644 --- a/extensions/keccak256/transpiler/Cargo.toml +++ b/extensions/keccak256/transpiler/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "openvm-keccak256-transpiler" +description = "OpenVM transpiler extension for keccak256" version.workspace = true authors.workspace = true edition.workspace = true -description = "Transpiler extension for Keccak256" +homepage.workspace = true +repository.workspace = true [dependencies] openvm-stark-backend = { workspace = true } diff --git a/extensions/native/circuit/Cargo.toml b/extensions/native/circuit/Cargo.toml index a23a100cfa..ede74d78b8 100644 --- a/extensions/native/circuit/Cargo.toml +++ b/extensions/native/circuit/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "openvm-native-circuit" +description = "OpenVM circuit extension for native instructions" version.workspace = true authors.workspace = true edition.workspace = true -description = "OpenVM circuit extension for native VM" +homepage.workspace = true +repository.workspace = true [dependencies] openvm-stark-backend = { workspace = true } diff --git a/extensions/native/compiler/Cargo.toml b/extensions/native/compiler/Cargo.toml index 04ad91c57a..3251847b2a 100644 --- a/extensions/native/compiler/Cargo.toml +++ b/extensions/native/compiler/Cargo.toml @@ -1,8 +1,11 @@ # Forked from sp1-recursion-compiler under MIT license. [package] name = "openvm-native-compiler" -version = "0.1.0" -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/extensions/native/compiler/derive/Cargo.toml b/extensions/native/compiler/derive/Cargo.toml index f0692128cc..d8a75a2191 100644 --- a/extensions/native/compiler/derive/Cargo.toml +++ b/extensions/native/compiler/derive/Cargo.toml @@ -1,8 +1,11 @@ [package] name = "openvm-native-compiler-derive" +description = "Procedural macros for OpenVM native compiler." version.workspace = true +authors.workspace = true edition.workspace = true -description = "Procedural macros for OpenVM native compiler." +homepage.workspace = true +repository.workspace = true [lib] proc-macro = true diff --git a/extensions/native/recursion/Cargo.toml b/extensions/native/recursion/Cargo.toml index c834d647a0..45c028f9b7 100644 --- a/extensions/native/recursion/Cargo.toml +++ b/extensions/native/recursion/Cargo.toml @@ -3,6 +3,8 @@ name = "openvm-native-recursion" version.workspace = true authors.workspace = true edition.workspace = true +homepage.workspace = true +repository.workspace = true [dependencies] openvm-stark-backend = { workspace = true } diff --git a/extensions/pairing/circuit/Cargo.toml b/extensions/pairing/circuit/Cargo.toml index c7fe0eda01..f895bc8a55 100644 --- a/extensions/pairing/circuit/Cargo.toml +++ b/extensions/pairing/circuit/Cargo.toml @@ -1,8 +1,11 @@ [package] name = "openvm-pairing-circuit" -version = "0.1.0" -edition = "2021" -description = "Elliptic curve pairing circuits." +description = "OpenVM circuit extension for elliptic curve pairing" +version.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true [dependencies] openvm-circuit-primitives-derive = { workspace = true } diff --git a/extensions/pairing/guest/Cargo.toml b/extensions/pairing/guest/Cargo.toml index 87ec17bb59..f2765cb146 100644 --- a/extensions/pairing/guest/Cargo.toml +++ b/extensions/pairing/guest/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "openvm-pairing-guest" +description = "OpenVM guest library for elliptic curve pairing" version.workspace = true authors.workspace = true edition.workspace = true -description = "OpenVM guest library for elliptic curve pairings." +homepage.workspace = true +repository.workspace = true [dependencies] openvm = { workspace = true } diff --git a/extensions/pairing/transpiler/Cargo.toml b/extensions/pairing/transpiler/Cargo.toml index 65036c5d26..a5557b03d1 100644 --- a/extensions/pairing/transpiler/Cargo.toml +++ b/extensions/pairing/transpiler/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "openvm-pairing-transpiler" +description = "OpenVM transpiler extension for elliptic curve pairing" version.workspace = true authors.workspace = true edition.workspace = true -description = "Transpiler extension for pairing" +homepage.workspace = true +repository.workspace = true [dependencies] openvm-stark-backend = { workspace = true } diff --git a/extensions/rv32-adapters/Cargo.toml b/extensions/rv32-adapters/Cargo.toml index d3d66f5f8a..6ca1714849 100644 --- a/extensions/rv32-adapters/Cargo.toml +++ b/extensions/rv32-adapters/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "openvm-rv32-adapters" +description = "OpenVM adapters for rv32 intrinsics" version.workspace = true authors.workspace = true edition.workspace = true -description = "OpenVM adapters for rv32 intrinsics" +homepage.workspace = true +repository.workspace = true [dependencies] openvm-stark-backend = { workspace = true } diff --git a/extensions/rv32im/circuit/Cargo.toml b/extensions/rv32im/circuit/Cargo.toml index d18fa436c1..d677a7d5f5 100644 --- a/extensions/rv32im/circuit/Cargo.toml +++ b/extensions/rv32im/circuit/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "openvm-rv32im-circuit" +description = "OpenVM circuit extension for RISC-V 32-bit IM instruction set" version.workspace = true authors.workspace = true edition.workspace = true -description = "OpenVM circuit extension for RISC-V 32-bit IM instruction set" +homepage.workspace = true +repository.workspace = true [dependencies] openvm-stark-backend = { workspace = true } diff --git a/extensions/rv32im/guest/Cargo.toml b/extensions/rv32im/guest/Cargo.toml index b829c7365c..32585164db 100644 --- a/extensions/rv32im/guest/Cargo.toml +++ b/extensions/rv32im/guest/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "openvm-rv32im-guest" +description = "OpenVM guest library for RISC-V 32-bit IM instruction set" version.workspace = true authors.workspace = true edition.workspace = true -description = "Guest extension for RISC-V 32IM" +homepage.workspace = true +repository.workspace = true [dependencies] openvm-platform = { workspace = true } diff --git a/extensions/rv32im/transpiler/Cargo.toml b/extensions/rv32im/transpiler/Cargo.toml index 911eea4a5d..537760b6ef 100644 --- a/extensions/rv32im/transpiler/Cargo.toml +++ b/extensions/rv32im/transpiler/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "openvm-rv32im-transpiler" +description = "OpenVM transpiler extension for RISC-V 32-bit IM instruction set" version.workspace = true authors.workspace = true edition.workspace = true -description = "Transpiler extension for RISC-V32IM" +homepage.workspace = true +repository.workspace = true [dependencies] openvm-stark-backend = { workspace = true } From 7a0f790241f0eb9022af84a3a5d9b33a9c95bfc3 Mon Sep 17 00:00:00 2001 From: Zach Langley Date: Mon, 16 Dec 2024 03:49:09 -0500 Subject: [PATCH 50/69] docs: Add a bit on system-level interactions (#1078) * docs: Add a bit on system-level interactions * fix --------- Co-authored-by: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> --- docs/crates/vm.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/crates/vm.md b/docs/crates/vm.md index ce873f37bf..6f22c60216 100644 --- a/docs/crates/vm.md +++ b/docs/crates/vm.md @@ -37,6 +37,18 @@ Together, these provide the following functionalities: execution records and then `Chip::::generate_air_proof_input()` which generates the trace using the corresponding records. +### VM AIR Integration + +At the AIR-level, for an AIR to integrate with the OpenVM architecture (constrain memory, read the instruction from the program, etc.), the AIR +communicates over different (virtual) buses. There are three main system buses: the memory bus, program bus, and the +execution bus. The memory bus is used to access memory, the program bus is used to read instructions from the program, +and the execution bus is used to constrain the execution flow. These buses are derivable from the `SystemPort` struct, +which is provided by the `VmInventoryBuilder`. + +The buses have very low-level APIs and are not intended to be used directly. "Bridges" are provided to provide a cleaner interface for +sending interactions over the buses and enforcing additional constraints for soundness. The two system bridges are +`MemoryBridge` and `ExecutionBridge`, which should respectively be used to constrain memory accesses and execution flow. + ### Phantom Sub-Instructions You can specify phantom sub-instruction executors by implementing the trait: @@ -186,13 +198,13 @@ pub trait VmAdapterChip { fn preprocess( &mut self, - memory: &mut MemoryChip, + memory: &mut MemoryController, instruction: &Instruction, ) -> Result<(>::Reads, Self::ReadRecord)>; fn postprocess( &mut self, - memory: &mut MemoryChip, + memory: &mut MemoryController, instruction: &Instruction, from_state: ExecutionState, ctx: AdapterRuntimeContext>, @@ -295,7 +307,7 @@ pub struct VmChipWrapper, C: VmCoreChip> { pub core: C, pub records: Vec<(A::ReadRecord, A::WriteRecord, C::Record)>, // For accessing memory - memory: MemoryChipRef, + memory: MemoryControllerRef, } pub struct VmAirWrapper { From 50c8826d26d09995a6775598fd29242a13f775f0 Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Mon, 16 Dec 2024 04:22:18 -0500 Subject: [PATCH 51/69] chore: update main readme (#1091) --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index b40ee0774f..fca070a9ad 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,38 @@ | [Contributor Docs](./docs) | [Crate Docs](https://docs.openvm.dev/openvm) +OpenVM is a performant and modular zkVM framework built for customization and extensibility. + +## Key Features + +- **Modular no-CPU Architecture**: Unlike traditional machine architectures, the OpenVM architecture has no central processing unit. This design choice allows for seamless integration of custom chips, **without forking or modifying the core architecture**. + +- **Extensible Instruction Set**: The instruction set architecture (ISA) is designed to be extended with new custom instructions that integrate directly with the virtual machine. Current extensions available for OpenVM include: + + - RISC-V support via RV32IM + - A native field arithmetic extension for proof recursion and aggregation + - The Keccak-256 hash function + - Int256 arithmetic + - Modular arithmetic over arbitrary fields + - Elliptic curve operations, including multi-scalar multiplication and ECDSA scalar multiplication. + - Pairing operations on the BN254 and BLS12-381 curves. + +- **Rust Frontend**: ISA extensions are directly accessible through a Rust frontend via [intrinsic functions](https://en.wikipedia.org/wiki/Intrinsic_function), providing a smooth developer experience. + +- **On-chain Verification**: Every VM made using the framework comes with out-of-the-box support for unbounded program proving with verification on Ethereum. + +## Security Status + +As of December 2024, OpenVM has not been audited and is currently not recommended for production use. We plan to continue development towards a production-ready release in 2025. + +## For Users + +See the [Book](https://book.openvm.dev) for more information on how to use OpenVM. + +## For Contributors + +See the [Contributor Docs](./docs) for more information on the project. A good starting point is [Project Layout](./docs/repo/layout.md). + ## Acknowledgements OpenVM is a new zkVM design framework. In the process of building it, we studied and learned from the designs and implementations of other projects. We would like to thank these projects for sharing their code for open source development: From 67f37336d89a0ec8cf73f03cb374a9164f78076a Mon Sep 17 00:00:00 2001 From: Lun-Kai Hsu Date: Mon, 16 Dec 2024 01:27:07 -0800 Subject: [PATCH 52/69] fix biguints serde with toml (#1092) --- Cargo.lock | 121 +++++++++++++++++- Cargo.toml | 1 + extensions/algebra/circuit/Cargo.toml | 1 + .../algebra/circuit/src/fp2_extension.rs | 9 +- extensions/algebra/circuit/src/lib.rs | 2 - .../algebra/circuit/src/modular_extension.rs | 13 +- extensions/algebra/circuit/src/util.rs | 16 --- extensions/ecc/circuit/Cargo.toml | 1 + .../ecc/circuit/src/weierstrass_extension.rs | 18 +-- 9 files changed, 133 insertions(+), 49 deletions(-) delete mode 100644 extensions/algebra/circuit/src/util.rs diff --git a/Cargo.lock b/Cargo.lock index df9b515446..ab80259407 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,7 +94,7 @@ dependencies = [ "foldhash", "hashbrown 0.15.2", "hex-literal", - "indexmap", + "indexmap 2.7.0", "itoa", "k256", "keccak-asm", @@ -130,6 +130,21 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anes" version = "0.1.6" @@ -798,6 +813,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64-simd" version = "0.8.0" @@ -1167,8 +1188,11 @@ version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ + "android-tzdata", + "iana-time-zone", "num-traits", "serde", + "windows-targets 0.52.6", ] [[package]] @@ -1597,6 +1621,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", + "serde", ] [[package]] @@ -2193,7 +2218,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -2391,6 +2416,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.5" @@ -2561,6 +2592,29 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "1.5.0" @@ -2732,6 +2786,17 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.7.0" @@ -2740,6 +2805,7 @@ checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown 0.15.2", + "serde", ] [[package]] @@ -2754,7 +2820,7 @@ dependencies = [ "crossbeam-utils", "dashmap", "env_logger", - "indexmap", + "indexmap 2.7.0", "is-terminal", "itoa", "log", @@ -3060,7 +3126,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62a6a1f7141f1d9bc7a886b87536bbfc97752e08b369e1e0453a9acfab5f5da4" dependencies = [ - "indexmap", + "indexmap 2.7.0", "itoa", "lockfree-object-pool", "metrics", @@ -3081,7 +3147,7 @@ dependencies = [ "crossbeam-epoch", "crossbeam-utils", "hashbrown 0.14.5", - "indexmap", + "indexmap 2.7.0", "metrics", "num_cpus", "ordered-float", @@ -3376,6 +3442,7 @@ dependencies = [ "openvm-stark-sdk", "rand", "serde", + "serde_with", "strum", "tracing", ] @@ -3668,6 +3735,7 @@ dependencies = [ "openvm-stark-backend", "openvm-stark-sdk", "serde", + "serde_with", "strum", ] @@ -5665,7 +5733,7 @@ version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ - "indexmap", + "indexmap 2.7.0", "itoa", "memchr", "ryu", @@ -5681,6 +5749,36 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.7.0", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "sha1" version = "0.10.6" @@ -6345,7 +6443,7 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap", + "indexmap 2.7.0", "serde", "serde_spanned", "toml_datetime", @@ -6731,6 +6829,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index e812773112..059b7ac8c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -177,6 +177,7 @@ parking_lot = "0.12.2" tracing = "0.1.40" bon = "3.2.0" serde_json = "1.0.117" +serde_with = "3.11.0" toml = "0.8.14" lazy_static = "1.5.0" once_cell = "1.19.0" diff --git a/extensions/algebra/circuit/Cargo.toml b/extensions/algebra/circuit/Cargo.toml index 84a59b7fff..d2addd60cd 100644 --- a/extensions/algebra/circuit/Cargo.toml +++ b/extensions/algebra/circuit/Cargo.toml @@ -29,6 +29,7 @@ derive_more = { workspace = true, features = ["from"] } strum = { workspace = true } derive-new = { workspace = true } serde.workspace = true +serde_with = { workspace = true } [dev-dependencies] halo2curves-axiom = { workspace = true } diff --git a/extensions/algebra/circuit/src/fp2_extension.rs b/extensions/algebra/circuit/src/fp2_extension.rs index 8942c47867..98a66a6431 100644 --- a/extensions/algebra/circuit/src/fp2_extension.rs +++ b/extensions/algebra/circuit/src/fp2_extension.rs @@ -17,16 +17,15 @@ use openvm_mod_circuit_builder::ExprBuilderConfig; use openvm_rv32_adapters::Rv32VecHeapAdapterChip; use openvm_stark_backend::p3_field::PrimeField32; use serde::{Deserialize, Serialize}; +use serde_with::{serde_as, DisplayFromStr}; use strum::EnumCount; -use crate::{ - fp2_chip::{Fp2AddSubChip, Fp2MulDivChip}, - util::deserialize_vec_biguint_from_str, -}; +use crate::fp2_chip::{Fp2AddSubChip, Fp2MulDivChip}; +#[serde_as] #[derive(Clone, Debug, derive_new::new, Serialize, Deserialize)] pub struct Fp2Extension { - #[serde(deserialize_with = "deserialize_vec_biguint_from_str")] + #[serde_as(as = "Vec")] pub supported_modulus: Vec, } diff --git a/extensions/algebra/circuit/src/lib.rs b/extensions/algebra/circuit/src/lib.rs index 7018513dcf..ffddacc61a 100644 --- a/extensions/algebra/circuit/src/lib.rs +++ b/extensions/algebra/circuit/src/lib.rs @@ -1,8 +1,6 @@ pub mod fp2_chip; pub mod modular_chip; -mod util; - mod fp2; pub use fp2::*; mod modular_extension; diff --git a/extensions/algebra/circuit/src/modular_extension.rs b/extensions/algebra/circuit/src/modular_extension.rs index ccce43ada0..ede5d170a6 100644 --- a/extensions/algebra/circuit/src/modular_extension.rs +++ b/extensions/algebra/circuit/src/modular_extension.rs @@ -18,19 +18,18 @@ use openvm_mod_circuit_builder::ExprBuilderConfig; use openvm_rv32_adapters::{Rv32IsEqualModAdapterChip, Rv32VecHeapAdapterChip}; use openvm_stark_backend::p3_field::PrimeField32; use serde::{Deserialize, Serialize}; +use serde_with::{serde_as, DisplayFromStr}; use strum::EnumCount; -use crate::{ - modular_chip::{ - ModularAddSubChip, ModularAddSubCoreChip, ModularIsEqualChip, ModularIsEqualCoreChip, - ModularMulDivChip, ModularMulDivCoreChip, - }, - util::deserialize_vec_biguint_from_str, +use crate::modular_chip::{ + ModularAddSubChip, ModularAddSubCoreChip, ModularIsEqualChip, ModularIsEqualCoreChip, + ModularMulDivChip, ModularMulDivCoreChip, }; +#[serde_as] #[derive(Clone, Debug, derive_new::new, Serialize, Deserialize)] pub struct ModularExtension { - #[serde(deserialize_with = "deserialize_vec_biguint_from_str")] + #[serde_as(as = "Vec")] pub supported_modulus: Vec, } diff --git a/extensions/algebra/circuit/src/util.rs b/extensions/algebra/circuit/src/util.rs deleted file mode 100644 index 54bdba4348..0000000000 --- a/extensions/algebra/circuit/src/util.rs +++ /dev/null @@ -1,16 +0,0 @@ -use num_bigint_dig::BigUint; -use serde::Deserialize; - -pub(crate) fn deserialize_vec_biguint_from_str<'de, D>( - deserializer: D, -) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - let v: Vec = Deserialize::deserialize(deserializer)?; - let res = v.into_iter().map(|s| s.parse()).collect::>(); - if res.iter().any(|x| x.is_err()) { - return Err(serde::de::Error::custom("Failed to parse BigUint")); - } - Ok(res.into_iter().map(|x| x.unwrap()).collect()) -} diff --git a/extensions/ecc/circuit/Cargo.toml b/extensions/ecc/circuit/Cargo.toml index 4c9c389c20..7c6adaad81 100644 --- a/extensions/ecc/circuit/Cargo.toml +++ b/extensions/ecc/circuit/Cargo.toml @@ -29,6 +29,7 @@ once_cell = { workspace = true } eyre = { workspace = true } num-integer = { workspace = true } serde = { workspace = true } +serde_with = { workspace = true } [dev-dependencies] openvm-stark-sdk = { workspace = true } diff --git a/extensions/ecc/circuit/src/weierstrass_extension.rs b/extensions/ecc/circuit/src/weierstrass_extension.rs index 597de1f188..cccb2cd9a9 100644 --- a/extensions/ecc/circuit/src/weierstrass_extension.rs +++ b/extensions/ecc/circuit/src/weierstrass_extension.rs @@ -20,34 +20,28 @@ use openvm_mod_circuit_builder::ExprBuilderConfig; use openvm_rv32_adapters::Rv32VecHeapAdapterChip; use openvm_stark_backend::p3_field::PrimeField32; use serde::{Deserialize, Serialize}; +use serde_with::{serde_as, DisplayFromStr}; use strum::EnumCount; use super::{EcAddNeChip, EcDoubleChip}; +#[serde_as] #[derive(Clone, Debug, derive_new::new, Serialize, Deserialize)] pub struct CurveConfig { /// The coordinate modulus of the curve. - #[serde(deserialize_with = "deserialize_biguint_from_str")] + #[serde_as(as = "DisplayFromStr")] pub modulus: BigUint, /// The scalar field modulus of the curve. - #[serde(deserialize_with = "deserialize_biguint_from_str")] + #[serde_as(as = "DisplayFromStr")] pub scalar: BigUint, /// The coefficient a of y^2 = x^3 + ax + b. - #[serde(deserialize_with = "deserialize_biguint_from_str")] + #[serde_as(as = "DisplayFromStr")] pub a: BigUint, /// The coefficient b of y^2 = x^3 + ax + b. - #[serde(deserialize_with = "deserialize_biguint_from_str")] + #[serde_as(as = "DisplayFromStr")] pub b: BigUint, } -fn deserialize_biguint_from_str<'de, D>(deserializer: D) -> Result -where - D: serde::Deserializer<'de>, -{ - let s: String = Deserialize::deserialize(deserializer)?; - s.parse().map_err(serde::de::Error::custom) -} - pub static SECP256K1_CONFIG: Lazy = Lazy::new(|| CurveConfig { modulus: SECP256K1_MODULUS.clone(), scalar: SECP256K1_ORDER.clone(), From 43b2e9d83387e57475edcdc667a2ce2f8240de9d Mon Sep 17 00:00:00 2001 From: Lun-Kai Hsu Date: Mon, 16 Dec 2024 01:38:07 -0800 Subject: [PATCH 53/69] small update (#1094) --- book/src/custom-extensions/pairing.md | 5 +++++ book/src/writing-apps/build.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/book/src/custom-extensions/pairing.md b/book/src/custom-extensions/pairing.md index 34912791fa..dc412456d5 100644 --- a/book/src/custom-extensions/pairing.md +++ b/book/src/custom-extensions/pairing.md @@ -114,6 +114,11 @@ supported_modulus = [ ] ``` +Also note that since this is a complicated computation, the `keygen` step requires quite a lot of memory. Run it with `RUST_MIN_STACK` set to a large value, e.g. +```bash +RUST_MIN_STACK=8388608 cargo openvm keygen +``` + ### Full example code This example code contains hardcoded values and no inputs as an example that can be run via the CLI. diff --git a/book/src/writing-apps/build.md b/book/src/writing-apps/build.md index b5b0a8fd56..2dbac169e3 100644 --- a/book/src/writing-apps/build.md +++ b/book/src/writing-apps/build.md @@ -5,7 +5,7 @@ First let's define some key terms used in cross-compilation: - **host** - the machine you're compiling and/or proving on. Note that one can compile and prove on different machines, but they are both called _host_ as they are traditional machine architectures. - **guest** - the executable to be run in a different VM architecture (e.g. the OpenVM runtime, or Android app). -There are multiple things happening in the `cargo openvm build` command as in the section [here](./write-program.md). In short, this command compiles on host to an executable for guest target. +The command `cargo openvm build` compiles the program on host to an executable for guest target. It first compiles the program normally on your _host_ platform with RISC-V and then transpiles it to a different target. See here for some explanation of [cross-compilation](https://rust-lang.github.io/rustup/cross-compilation.html). Right now we use `riscv32im-risc0-zkvm-elf` target which is available in the [Rust toolchain](https://doc.rust-lang.org/rustc/platform-support/riscv32im-risc0-zkvm-elf.html), but we will contribute an OpenVM target to Rust in the future. From 1ddb32208c0959ea370ab0b0d560feeda3a279db Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Mon, 16 Dec 2024 04:44:51 -0500 Subject: [PATCH 54/69] chore: tag `stark-backend` release and ssh -> http (#1093) --- .github/workflows/algebra-extension.yml | 7 ------- .github/workflows/benchmark-call.yml | 7 ------- .github/workflows/bigint-extension.yml | 7 ------- .github/workflows/build.yml | 7 ------- .github/workflows/cli.yml | 7 ------- .github/workflows/docs.yml | 7 ------- .github/workflows/ecc.yml | 7 ------- .github/workflows/keccak256-extension.yml | 7 ------- .github/workflows/lints.yml | 7 ------- .github/workflows/native-extension.yml | 7 ------- .github/workflows/primitives.yml | 7 ------- .github/workflows/recursion.yml | 7 ------- .github/workflows/riscv.yml | 7 ------- .github/workflows/rv32im-extension.yml | 7 ------- .github/workflows/sdk.yml | 7 ------- .github/workflows/toolchain-edsl.yml | 7 ------- .github/workflows/toolchain.yml | 7 ------- .github/workflows/vm.yml | 7 ------- Cargo.lock | 4 ++-- Cargo.toml | 4 ++-- 20 files changed, 4 insertions(+), 130 deletions(-) diff --git a/.github/workflows/algebra-extension.yml b/.github/workflows/algebra-extension.yml index fabe254aaa..cbc1d60c03 100644 --- a/.github/workflows/algebra-extension.yml +++ b/.github/workflows/algebra-extension.yml @@ -30,13 +30,6 @@ jobs: cache-on-failure: true - uses: taiki-e/install-action@nextest - # TEMPORARY - - name: Give GitHub Actions access to private repositories - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: | - ${{ secrets.GH_ACTIONS_DEPLOY_PRIVATE_KEY }} - - name: Run algebra extension crate tests working-directory: extensions/algebra/circuit run: | diff --git a/.github/workflows/benchmark-call.yml b/.github/workflows/benchmark-call.yml index 7d5fc12cb0..03f346cdd6 100644 --- a/.github/workflows/benchmark-call.yml +++ b/.github/workflows/benchmark-call.yml @@ -129,13 +129,6 @@ jobs: # Environment setup # ########################################################################## - # TEMPORARY - - name: Give GitHub Actions access to private repositories - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: | - ${{ secrets.GH_ACTIONS_DEPLOY_PRIVATE_KEY }} - - uses: actions/checkout@v4 with: ref: ${{ github.head_ref || github.ref }} diff --git a/.github/workflows/bigint-extension.yml b/.github/workflows/bigint-extension.yml index 6584decaf4..901973a48d 100644 --- a/.github/workflows/bigint-extension.yml +++ b/.github/workflows/bigint-extension.yml @@ -30,13 +30,6 @@ jobs: cache-on-failure: true - uses: taiki-e/install-action@nextest - # TEMPORARY - - name: Give GitHub Actions access to private repositories - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: | - ${{ secrets.GH_ACTIONS_DEPLOY_PRIVATE_KEY }} - - name: Run bigint extension crate tests working-directory: extensions/bigint/circuit run: | diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4b0263e0b8..43204ca14a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,13 +23,6 @@ jobs: with: cache-on-failure: true - # TEMPORARY - - name: Give GitHub Actions access to private repositories - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: | - ${{ secrets.GH_ACTIONS_DEPLOY_PRIVATE_KEY }} - - name: Run build run: | cargo build --verbose diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 390ed6e06e..f939196356 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -57,13 +57,6 @@ jobs: run: | bash ./extensions/native/recursion/trusted_setup_s3.sh - # TEMPORARY - - name: Give GitHub Actions access to private repositories - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: | - ${{ secrets.GH_ACTIONS_DEPLOY_PRIVATE_KEY }} - # TODO: CLI build, transpile, run, (keygen), prove, contract, verify - name: Run app-level CLI commands working-directory: crates/cli diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 140fa57f28..ddc1c1ae26 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -28,13 +28,6 @@ jobs: - name: Set up Rust toolchain uses: dtolnay/rust-toolchain@nightly - # TEMPORARY - - name: Give GitHub Actions access to private repositories - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: | - ${{ secrets.GH_ACTIONS_DEPLOY_PRIVATE_KEY }} - - name: Cargo check run: cargo check diff --git a/.github/workflows/ecc.yml b/.github/workflows/ecc.yml index 5269998bd3..16fe9814a6 100644 --- a/.github/workflows/ecc.yml +++ b/.github/workflows/ecc.yml @@ -35,13 +35,6 @@ jobs: cache-on-failure: true - uses: taiki-e/install-action@nextest - # TEMPORARY - - name: Give GitHub Actions access to private repositories - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: | - ${{ secrets.GH_ACTIONS_DEPLOY_PRIVATE_KEY }} - - name: Run pairing-guest crate tests working-directory: extensions/pairing/guest run: | diff --git a/.github/workflows/keccak256-extension.yml b/.github/workflows/keccak256-extension.yml index aee12b5229..9b99d0f1a9 100644 --- a/.github/workflows/keccak256-extension.yml +++ b/.github/workflows/keccak256-extension.yml @@ -30,13 +30,6 @@ jobs: cache-on-failure: true - uses: taiki-e/install-action@nextest - # TEMPORARY - - name: Give GitHub Actions access to private repositories - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: | - ${{ secrets.GH_ACTIONS_DEPLOY_PRIVATE_KEY }} - - name: Run keccak256 extension crate tests working-directory: extensions/keccak256/circuit run: | diff --git a/.github/workflows/lints.yml b/.github/workflows/lints.yml index 3bafac4f11..6a80a56d7a 100644 --- a/.github/workflows/lints.yml +++ b/.github/workflows/lints.yml @@ -23,13 +23,6 @@ jobs: with: cache-on-failure: true - # TEMPORARY - - name: Give GitHub Actions access to private repositories - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: | - ${{ secrets.GH_ACTIONS_DEPLOY_PRIVATE_KEY }} - - name: Run fmt run: | cargo fmt --all -- --check diff --git a/.github/workflows/native-extension.yml b/.github/workflows/native-extension.yml index abcf52e397..3c4379d2c2 100644 --- a/.github/workflows/native-extension.yml +++ b/.github/workflows/native-extension.yml @@ -30,13 +30,6 @@ jobs: cache-on-failure: true - uses: taiki-e/install-action@nextest - # TEMPORARY - - name: Give GitHub Actions access to private repositories - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: | - ${{ secrets.GH_ACTIONS_DEPLOY_PRIVATE_KEY }} - - name: Run native extension crate tests working-directory: extensions/native/circuit run: | diff --git a/.github/workflows/primitives.yml b/.github/workflows/primitives.yml index 8dc0a3e9f8..663681a538 100644 --- a/.github/workflows/primitives.yml +++ b/.github/workflows/primitives.yml @@ -31,13 +31,6 @@ jobs: cache-on-failure: true - uses: taiki-e/install-action@nextest - # TEMPORARY - - name: Give GitHub Actions access to private repositories - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: | - ${{ secrets.GH_ACTIONS_DEPLOY_PRIVATE_KEY }} - - name: Run tests for primitives working-directory: crates/circuits/primitives run: | diff --git a/.github/workflows/recursion.yml b/.github/workflows/recursion.yml index 7e504c0ee9..a77379dc76 100644 --- a/.github/workflows/recursion.yml +++ b/.github/workflows/recursion.yml @@ -35,13 +35,6 @@ jobs: - name: Install solc # svm should support arm64 linux run: (hash svm 2>/dev/null || cargo install --version 0.2.23 svm-rs) && svm install 0.8.19 && solc --version - # TEMPORARY - - name: Give GitHub Actions access to private repositories - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: | - ${{ secrets.GH_ACTIONS_DEPLOY_PRIVATE_KEY }} - - name: Run recursion crate tests working-directory: extensions/native/recursion run: | diff --git a/.github/workflows/riscv.yml b/.github/workflows/riscv.yml index 344d42a68c..7b4c27e77d 100644 --- a/.github/workflows/riscv.yml +++ b/.github/workflows/riscv.yml @@ -40,13 +40,6 @@ jobs: with: submodules: recursive - # TEMPORARY - - name: Give GitHub Actions access to private repositories - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: | - ${{ secrets.GH_ACTIONS_DEPLOY_PRIVATE_KEY }} - - name: Run Makefile working-directory: crates/toolchain/tests/rv32im-test-vectors run: | diff --git a/.github/workflows/rv32im-extension.yml b/.github/workflows/rv32im-extension.yml index 0eda272f05..9e69f3a7ef 100644 --- a/.github/workflows/rv32im-extension.yml +++ b/.github/workflows/rv32im-extension.yml @@ -30,13 +30,6 @@ jobs: cache-on-failure: true - uses: taiki-e/install-action@nextest - # TEMPORARY - - name: Give GitHub Actions access to private repositories - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: | - ${{ secrets.GH_ACTIONS_DEPLOY_PRIVATE_KEY }} - - name: Run rv32im extension crate tests working-directory: extensions/rv32im/circuit run: | diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml index 6ca42473de..aab2ac091d 100644 --- a/.github/workflows/sdk.yml +++ b/.github/workflows/sdk.yml @@ -35,13 +35,6 @@ jobs: - name: Install solc # svm should support arm64 linux run: (hash svm 2>/dev/null || cargo install --version 0.2.23 svm-rs) && svm install 0.8.19 && solc --version - # TEMPORARY - - name: Give GitHub Actions access to private repositories - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: | - ${{ secrets.GH_ACTIONS_DEPLOY_PRIVATE_KEY }} - - name: Install architecture specific tools run: | arch=$(uname -m) diff --git a/.github/workflows/toolchain-edsl.yml b/.github/workflows/toolchain-edsl.yml index 3b2c70835b..fa19137c0f 100644 --- a/.github/workflows/toolchain-edsl.yml +++ b/.github/workflows/toolchain-edsl.yml @@ -32,13 +32,6 @@ jobs: cache-on-failure: true - uses: taiki-e/install-action@nextest - # TEMPORARY - - name: Give GitHub Actions access to private repositories - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: | - ${{ secrets.GH_ACTIONS_DEPLOY_PRIVATE_KEY }} - - name: Run circuit and compiler tests working-directory: extensions/native/compiler run: | diff --git a/.github/workflows/toolchain.yml b/.github/workflows/toolchain.yml index ef7f1d98f5..2064a0e9d8 100644 --- a/.github/workflows/toolchain.yml +++ b/.github/workflows/toolchain.yml @@ -32,13 +32,6 @@ jobs: cache-on-failure: true - uses: taiki-e/install-action@nextest - # TEMPORARY - - name: Give GitHub Actions access to private repositories - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: | - ${{ secrets.GH_ACTIONS_DEPLOY_PRIVATE_KEY }} - - name: Run toolchain tests working-directory: crates/toolchain/tests run: | diff --git a/.github/workflows/vm.yml b/.github/workflows/vm.yml index e7f93c3889..32a26309f5 100644 --- a/.github/workflows/vm.yml +++ b/.github/workflows/vm.yml @@ -31,13 +31,6 @@ jobs: cache-on-failure: true - uses: taiki-e/install-action@nextest - # TEMPORARY - - name: Give GitHub Actions access to private repositories - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: | - ${{ secrets.GH_ACTIONS_DEPLOY_PRIVATE_KEY }} - - name: Run vm crate tests working-directory: crates/vm run: | diff --git a/Cargo.lock b/Cargo.lock index ab80259407..8a204162c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4225,7 +4225,7 @@ dependencies = [ [[package]] name = "openvm-stark-backend" version = "0.1.0-alpha" -source = "git+ssh://git@github.com/openvm-org/stark-backend#75f79da9da485c2091637207201fc40f0985ef14" +source = "git+https://github.com/openvm-org/stark-backend.git?tag=v0.1.0-alpha#83b0d048c00bbfba60a5b0732cf923bed5dd6e76" dependencies = [ "async-trait", "cfg-if", @@ -4251,7 +4251,7 @@ dependencies = [ [[package]] name = "openvm-stark-sdk" version = "0.1.0-alpha" -source = "git+ssh://git@github.com/openvm-org/stark-backend#75f79da9da485c2091637207201fc40f0985ef14" +source = "git+https://github.com/openvm-org/stark-backend.git?tag=v0.1.0-alpha#83b0d048c00bbfba60a5b0732cf923bed5dd6e76" dependencies = [ "derive_more 0.99.18", "ff 0.13.0", diff --git a/Cargo.toml b/Cargo.toml index 059b7ac8c2..5ffb5b07a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,8 +106,8 @@ openvm-platform = { path = "crates/toolchain/platform", default-features = false openvm-transpiler = { path = "crates/toolchain/transpiler", default-features = false } openvm-circuit = { path = "crates/vm", default-features = false } openvm-circuit-derive = { path = "crates/vm/derive", default-features = false } -openvm-stark-backend = { git = "ssh://git@github.com/openvm-org/stark-backend", default-features = false } -openvm-stark-sdk = { git = "ssh://git@github.com/openvm-org/stark-backend", default-features = false } +openvm-stark-backend = { git = "https://github.com/openvm-org/stark-backend.git", tag = "v0.1.0-alpha", default-features = false } +openvm-stark-sdk = { git = "https://github.com/openvm-org/stark-backend.git", tag = "v0.1.0-alpha", default-features = false } # Extensions openvm-algebra-circuit = { path = "extensions/algebra/circuit", default-features = false } From 89049c48f060de6aa36a4e89f2084ccca8f1cfd5 Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Mon, 16 Dec 2024 05:02:57 -0500 Subject: [PATCH 55/69] [book] `cargo-openvm` install requires nightly (#1096) --- book/src/getting-started/install.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/book/src/getting-started/install.md b/book/src/getting-started/install.md index 18ecdbac82..31d3ab08e5 100644 --- a/book/src/getting-started/install.md +++ b/book/src/getting-started/install.md @@ -6,8 +6,16 @@ To use OpenVM for generating proofs, you must install the OpenVM command line to ## Option 1: Install Via Git URL (Recommended) +You will need the nightly toolchain. You can install it with: + +```bash +rustup toolchain install nightly +``` + +Then, begin the installation. + ```bash -cargo install --git http://github.com/openvm-org/openvm.git cargo-openvm +cargo +nightly install --git http://github.com/openvm-org/openvm.git cargo-openvm ``` This will globally install `cargo-openvm`. You can validate a successful installation with: @@ -29,7 +37,7 @@ Then, clone the repository and begin the installation. ```bash git clone https://github.com/openvm-org/openvm.git cd openvm -cargo install --force --path crates/cli +cargo +nightly install --force --path crates/cli ``` This will globally install `cargo-openvm`. You can validate a successful installation with: From 1eb00580c75bfb40d80dbfe1ce09d93111c724b1 Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Mon, 16 Dec 2024 05:23:52 -0500 Subject: [PATCH 56/69] chore: add tracing to cli (#1097) --- crates/cli/src/bin/cargo-openvm.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/cli/src/bin/cargo-openvm.rs b/crates/cli/src/bin/cargo-openvm.rs index 1f98e12d21..46b9263d29 100644 --- a/crates/cli/src/bin/cargo-openvm.rs +++ b/crates/cli/src/bin/cargo-openvm.rs @@ -4,6 +4,8 @@ use cargo_openvm::{ }; use clap::{Parser, Subcommand}; use eyre::Result; +use openvm_stark_sdk::config::setup_tracing_with_log_level; +use tracing::Level; #[derive(Parser)] #[command(name = "cargo", bin_name = "cargo")] @@ -34,6 +36,7 @@ pub enum VmCliCommands { async fn main() -> Result<()> { let Cargo::OpenVm(args) = Cargo::parse(); let command = args.command; + setup_tracing_with_log_level(Level::WARN); match command { VmCliCommands::Bench(cmd) => cmd.run(), VmCliCommands::Build(cmd) => cmd.run(), From 160cdacc5e9b710334688307f4ec8271ccac6404 Mon Sep 17 00:00:00 2001 From: Lun-Kai Hsu Date: Mon, 16 Dec 2024 02:25:33 -0800 Subject: [PATCH 57/69] [book] fix toml in keccak section (#1098) * fix toml in keccak section * add hex dep --- book/src/custom-extensions/keccak.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/book/src/custom-extensions/keccak.md b/book/src/custom-extensions/keccak.md index 6440650f1c..40c76b9b6f 100644 --- a/book/src/custom-extensions/keccak.md +++ b/book/src/custom-extensions/keccak.md @@ -14,6 +14,7 @@ See the full example [here](https://github.com/openvm-org/openvm/blob/main/crate ### Example: ```rust +use hex::FromHex; use openvm_keccak256_guest::keccak256; pub fn main() { @@ -36,6 +37,7 @@ To be able to import the `keccak256` function, add the following to your `Cargo. ```toml openvm-keccak256-guest = { git = "https://github.com/openvm-org/openvm.git" } +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } ``` ## Native Keccak256 @@ -70,5 +72,5 @@ fn keccak256(input: &[u8]) -> [u8; 32] { For the guest program to build successfully add the following to your `.toml` file: ```toml -[app_vm_config.keccak256] +[app_vm_config.keccak] ``` From 31c5b18f9f69860a2284efba7dbd2e47966471b8 Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Mon, 16 Dec 2024 05:52:06 -0500 Subject: [PATCH 58/69] chore: add imports in book (#1099) * chore: add imports in book * fix: bad fmt * fix: fmt * chore: save an import --- book/src/custom-extensions/algebra.md | 58 ++++++++++++++++++++------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/book/src/custom-extensions/algebra.md b/book/src/custom-extensions/algebra.md index 47585c97be..11eee7d389 100644 --- a/book/src/custom-extensions/algebra.md +++ b/book/src/custom-extensions/algebra.md @@ -7,20 +7,22 @@ The functional part is provided by the `openvm-algebra-guest` crate, which is a ## Available traits and methods - `IntMod` trait: - Defines the type `Repr` and constants `MODULUS`, `NUM_LIMBS`, `ZERO`, and `ONE`. It also provides basic methods for constructing a modular arithmetic object and performing arithmetic operations. - - `Repr` typically is `[u8; NUM_LIMBS]`, representing the number's underlying storage. - - `MODULUS` is the compile-time known modulus. - - `ZERO` and `ONE` represent the additive and multiplicative identities, respectively. - - Constructors include `from_repr`, `from_le_bytes`, `from_be_bytes`, `from_u8`, `from_u32`, and `from_u64`. + Defines the type `Repr` and constants `MODULUS`, `NUM_LIMBS`, `ZERO`, and `ONE`. It also provides basic methods for constructing a modular arithmetic object and performing arithmetic operations. + + - `Repr` typically is `[u8; NUM_LIMBS]`, representing the number's underlying storage. + - `MODULUS` is the compile-time known modulus. + - `ZERO` and `ONE` represent the additive and multiplicative identities, respectively. + - Constructors include `from_repr`, `from_le_bytes`, `from_be_bytes`, `from_u8`, `from_u32`, and `from_u64`. - `Field` trait: - Provides constants `ZERO` and `ONE` and methods for basic arithmetic operations within a field. + Provides constants `ZERO` and `ONE` and methods for basic arithmetic operations within a field. ## Modular arithmetic To [leverage](./overview.md) compile-time known moduli for performance, you declare, initialize, and then set up the arithmetic structures: 1. **Declare**: Use the `moduli_declare!` macro to define a modular arithmetic struct. This can be done multiple times in various crates or modules: + ```rust moduli_declare! { Bls12_381Fp { modulus = "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab" }, @@ -44,6 +46,7 @@ This step enumerates the declared moduli (e.g., `0` for the first one, `1` for t 3. **Setup**: At runtime, before performing arithmetic, a setup instruction must be sent to ensure security and correctness. For the \\(i\\)-th modulus, you call `setup_()` (e.g., `setup_0()` or `setup_1()`). Alternatively, `setup_all_moduli()` can be used to handle all declared moduli. **Summary**: + - `moduli_declare!`: Declares modular arithmetic structures and can be done multiple times. - `moduli_init!`: Called once in the final binary to assign and lock in the moduli. - `setup_()`/`setup_all_moduli()`: Ensures at runtime that the correct modulus is in use, providing a security check and finalizing the environment for safe arithmetic operations. @@ -90,27 +93,42 @@ Here, `mod_idx` refers to the index of the underlying modulus as initialized by 3. **Setup**: Similar to moduli, call `setup_complex_()` or `setup_all_complex_extensions()` at runtime to secure the environment. +### Config parameters + +For the guest program to build successfully, all used moduli must be declared in the `.toml` config file in the following format: + +```toml +[app_vm_config.modular] +supported_modulus = ["115792089237316195423570985008687907853269984665640564039457584007908834671663"] + +[app_vm_config.fp2] +supported_modulus = ["115792089237316195423570985008687907853269984665640564039457584007908834671663"] +``` + +The `supported_modulus` parameter is a list of moduli that the guest program will use. They must be provided in decimal format in the `.toml` file. + ### Example program Here is a toy example using both the modular arithmetic and complex field extension capabilities: + ```rust #![cfg_attr(not(feature = "std"), no_main)] #![cfg_attr(not(feature = "std"), no_std)] -use openvm_algebra_guest::IntMod; +use openvm_algebra_guest::{IntMod, moduli_setup::*}; openvm::entry!(main); // This macro will create two structs, `Mod1` and `Mod2`, // one for arithmetic modulo 998244353, and the other for arithmetic modulo 1000000007. -openvm_algebra_moduli_setup::moduli_declare! { +moduli_declare! { Mod1 { modulus = "998244353" }, Mod2 { modulus = "1000000007" } } // This macro will initialize the moduli. // Now, `Mod1` is the "zeroth" modular struct, and `Mod2` is the "first" one. -openvm_algebra_moduli_setup::moduli_init! { +moduli_init! { "998244353", "1000000007" } @@ -141,16 +159,26 @@ pub fn main() { } ``` -### Config parameters +To have the correct imports for the above example, add the following to the `Cargo.toml` file: -For the guest program to build successfully, all used moduli must be declared in the `.toml` config file in the following format: +```toml +[dependencies] +openvm = { git = "https://github.com/openvm-org/openvm.git" } +openvm-platform = { git = "https://github.com/openvm-org/openvm.git" } +openvm-algebra-guest = { git = "https://github.com/openvm-org/openvm.git" } +openvm-algebra-complex-macros = { git = "https://github.com/openvm-org/openvm.git" } +serde = { version = "1.0.216", default-features = false } +``` + +Here is the full `openvm.toml` to accompany the above example: ```toml +[app_vm_config.rv32i] +[app_vm_config.rv32m] +[app_vm_config.io] [app_vm_config.modular] -supported_modulus = ["115792089237316195423570985008687907853269984665640564039457584007908834671663"] +supported_modulus = ["998244353","1000000007"] [app_vm_config.fp2] -supported_modulus = ["115792089237316195423570985008687907853269984665640564039457584007908834671663"] +supported_modulus = ["998244353","1000000007"] ``` - -The `supported_modulus` parameter is a list of moduli that the guest program will use. They must be provided in decimal format in the `.toml` file. From 325f872adb537e43c2104ee5032df80be628baff Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Mon, 16 Dec 2024 17:27:28 -0500 Subject: [PATCH 59/69] chore: remove old link in keccak readme (#1103) --- extensions/keccak256/circuit/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/keccak256/circuit/README.md b/extensions/keccak256/circuit/README.md index 56f4226f9b..5085c961cc 100644 --- a/extensions/keccak256/circuit/README.md +++ b/extensions/keccak256/circuit/README.md @@ -25,7 +25,7 @@ It seems to handle padding in a single AIR row there is no alternate to having ` The absorb step must correctly constrain that the input bytes are XORed with the end-state in the last round and equals the next permutation's `preimage`. The end-state is accessed via `a_prime_prime_prime()`. Note that both `preimage` and `a_prime_prime_prime()` are represented as `u16`s. However we can only XOR at most 8-bit limbs. Without changing the `keccak-f` AIR itself, we can use a trick: if we already have a 16-bit limb `x` and we also provide a 8-bit limb `hi = x >> 8`, assuming `x` and `hi` have been range checked, we can use the expression `lo = x - hi * 256` for the low byte. If `lo` is range checked to `8`-bits, this constrains a valid byte decomposition of `x` into `hi, lo`. This means in terms of trace cells, it is equivalent to provide `x, hi` versus `hi, lo`. -The constraints are separated into those that don't involve interactions in [air.rs](./air.rs) and those that do in [bridge.rs](./bridge.rs). Notably we use an XOR lookup table for byte XORs in the absorb step. +The constraints are in [air.rs](./air.rs). Notably we use an XOR lookup table for byte XORs in the absorb step. ## Future Improvement From 3ca85e83a2ab9e6a5fadfcb6f5047e171a817981 Mon Sep 17 00:00:00 2001 From: Arayi Khalatyan <127004086+arayikhalatyan@users.noreply.github.com> Date: Mon, 16 Dec 2024 17:36:47 -0500 Subject: [PATCH 60/69] Fix: short fix of a link in bigint extension doc (#1105) --- book/src/custom-extensions/bigint.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/custom-extensions/bigint.md b/book/src/custom-extensions/bigint.md index 1d79481277..96ee245b06 100644 --- a/book/src/custom-extensions/bigint.md +++ b/book/src/custom-extensions/bigint.md @@ -111,7 +111,7 @@ When using the `I256` struct with `target_os = "zkvm"`, the struct utilizes effi ### Example matrix multiplication using `I256` -See the full example [here](https://github.com/openvm-org/openvm/blob/main/crates/toolchain/tests/programs/examples/signed-matrix-power.rs). +See the full example [here](https://github.com/openvm-org/openvm/blob/main/crates/toolchain/tests/programs/examples/matrix-power-signed.rs). ```rust #![cfg_attr(not(feature = "std"), no_main)] From 2950707749996203a7fc909402379b0d58ab1e46 Mon Sep 17 00:00:00 2001 From: yanziseeker <153156292+AdventureSeeker987@users.noreply.github.com> Date: Wed, 18 Dec 2024 02:34:11 +0800 Subject: [PATCH 61/69] Update README.md (#1108) --- benchmarks/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/README.md b/benchmarks/README.md index 4b6a8a5d89..d3bd2ba55a 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -29,7 +29,7 @@ lint or use rust-analyzer on the crate while in the workspace, so the recommende ### Adding the Benchmark -Our proving benchmarks are written as standalone rust binaries. Add one by making a new file in [bin](./src/bin) by following the [fibonacci example](./bin/fibonacci.rs). We currently only run aggregation proofs when feature "aggregation" is on (off by default). Any general benchmarking utility functions can be added to the library in [`src`](./src). There are utility functions `build_bench_program` which compiles the guest program crate with target set to `openvm` and reads the output RISC-V ELF file. +Our proving benchmarks are written as standalone rust binaries. Add one by making a new file in [bin](./src/bin) by following the [fibonacci example](./src/bin/fibonacci.rs). We currently only run aggregation proofs when feature "aggregation" is on (off by default). Any general benchmarking utility functions can be added to the library in [`src`](./src). There are utility functions `build_bench_program` which compiles the guest program crate with target set to `openvm` and reads the output RISC-V ELF file. This can then be fed into `bench_from_exe` which will generate a proof of the execution of the ELF (any other `VmExe`) from a given `VmConfig`. #### Providing Inputs From 3be4942c0dd3caa53866b2dddb384d6df1e86c6f Mon Sep 17 00:00:00 2001 From: Zach Langley Date: Tue, 17 Dec 2024 14:34:53 -0500 Subject: [PATCH 62/69] perf: Use u32 for `address_space` and `address` internally (#1101) * perf: Use usize for address_space internally everywhere * u32 instead of usize * Comments --- crates/sdk/src/verifier/leaf/mod.rs | 4 +- crates/toolchain/instructions/src/exe.rs | 2 +- crates/toolchain/transpiler/src/util.rs | 9 +- crates/vm/src/arch/config.rs | 2 +- .../src/system/memory/manager/dimensions.rs | 9 +- .../vm/src/system/memory/manager/interface.rs | 2 +- crates/vm/src/system/memory/manager/memory.rs | 132 +++++++++--------- crates/vm/src/system/memory/manager/mod.rs | 41 +++--- crates/vm/src/system/memory/merkle/mod.rs | 10 +- .../vm/src/system/memory/merkle/tests/mod.rs | 49 +++---- crates/vm/src/system/memory/merkle/trace.rs | 17 ++- crates/vm/src/system/memory/persistent.rs | 22 +-- crates/vm/src/system/memory/tree/mod.rs | 4 +- .../src/system/memory/tree/public_values.rs | 19 +-- crates/vm/src/system/memory/volatile/mod.rs | 19 +-- crates/vm/src/system/memory/volatile/tests.rs | 29 ++-- crates/vm/tests/integration_test.rs | 9 +- 17 files changed, 179 insertions(+), 200 deletions(-) diff --git a/crates/sdk/src/verifier/leaf/mod.rs b/crates/sdk/src/verifier/leaf/mod.rs index 5cc1cfa113..71659bbc3d 100644 --- a/crates/sdk/src/verifier/leaf/mod.rs +++ b/crates/sdk/src/verifier/leaf/mod.rs @@ -112,9 +112,7 @@ impl LeafVmVerifierConfig { builder: &mut Builder, ) -> ([Felt; DIGEST_SIZE], [Felt; DIGEST_SIZE]) { let memory_dimensions = self.app_system_config.memory_config.memory_dimensions(); - let pv_as = F::from_canonical_usize( - PUBLIC_VALUES_ADDRESS_SPACE_OFFSET + memory_dimensions.as_offset, - ); + let pv_as = PUBLIC_VALUES_ADDRESS_SPACE_OFFSET + memory_dimensions.as_offset; let pv_start_idx = memory_dimensions.label_to_index((pv_as, 0)); let pv_height = log2_strict_usize(self.app_system_config.num_public_values / DIGEST_SIZE); let proof_len = memory_dimensions.overall_height() - pv_height; diff --git a/crates/toolchain/instructions/src/exe.rs b/crates/toolchain/instructions/src/exe.rs index 9e5299a22f..fb84ec7da5 100644 --- a/crates/toolchain/instructions/src/exe.rs +++ b/crates/toolchain/instructions/src/exe.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use crate::program::Program; /// Memory image is a map from (address space, address) to word. -pub type MemoryImage = BTreeMap<(F, F), F>; +pub type MemoryImage = BTreeMap<(u32, u32), F>; /// Stores the starting address, end address, and name of a set of function. pub type FnBounds = BTreeMap; diff --git a/crates/toolchain/transpiler/src/util.rs b/crates/toolchain/transpiler/src/util.rs index 8e5cfcefa9..b1f8a24e7c 100644 --- a/crates/toolchain/transpiler/src/util.rs +++ b/crates/toolchain/transpiler/src/util.rs @@ -1,8 +1,11 @@ use std::collections::BTreeMap; use openvm_instructions::{ - exe::MemoryImage, instruction::Instruction, riscv::RV32_REGISTER_NUM_LIMBS, - utils::isize_to_field, SystemOpcode, VmOpcode, + exe::MemoryImage, + instruction::Instruction, + riscv::{RV32_MEMORY_AS, RV32_REGISTER_NUM_LIMBS}, + utils::isize_to_field, + SystemOpcode, VmOpcode, }; use openvm_stark_backend::p3_field::PrimeField32; use rrs_lib::instruction_formats::{BType, IType, ITypeShamt, JType, RType, SType, UType}; @@ -167,7 +170,7 @@ pub fn elf_memory_image_to_openvm_memory_image( for (addr, word) in memory_image { for (i, byte) in word.to_le_bytes().into_iter().enumerate() { result.insert( - (F::TWO, F::from_canonical_u32(addr + i as u32)), + (RV32_MEMORY_AS, addr + i as u32), F::from_canonical_u8(byte), ); } diff --git a/crates/vm/src/arch/config.rs b/crates/vm/src/arch/config.rs index 715a0f1542..41a926d92b 100644 --- a/crates/vm/src/arch/config.rs +++ b/crates/vm/src/arch/config.rs @@ -47,7 +47,7 @@ pub struct MemoryConfig { /// The maximum height of the address space. This means the trie has `as_height` layers for searching the address space. The allowed address spaces are those in the range `[as_offset, as_offset + 2^as_height)` where `as_offset` is currently fixed to `1` to not allow address space `0` in memory. pub as_height: usize, /// The offset of the address space. - pub as_offset: usize, + pub as_offset: u32, pub pointer_max_bits: usize, pub clk_max_bits: usize, /// Limb size used by the range checker diff --git a/crates/vm/src/system/memory/manager/dimensions.rs b/crates/vm/src/system/memory/manager/dimensions.rs index e91fe86988..ebe3612198 100644 --- a/crates/vm/src/system/memory/manager/dimensions.rs +++ b/crates/vm/src/system/memory/manager/dimensions.rs @@ -1,5 +1,5 @@ use derive_new::new; -use openvm_stark_backend::{p3_field::PrimeField32, p3_util::log2_strict_usize}; +use openvm_stark_backend::p3_util::log2_strict_usize; use crate::{arch::MemoryConfig, system::memory::CHUNK}; @@ -12,7 +12,7 @@ pub struct MemoryDimensions { /// Pointer height pub address_height: usize, /// Address space offset - pub as_offset: usize, + pub as_offset: u32, } impl MemoryDimensions { @@ -20,10 +20,9 @@ impl MemoryDimensions { self.as_height + self.address_height } /// Convert an address label (address space, block id) to its index in the memory merkle tree. - pub fn label_to_index(&self, label: (F, usize)) -> usize { + pub fn label_to_index(&self, label: (u32, u32)) -> u64 { let (addr_space, block_id) = label; - ((addr_space.as_canonical_u32() as usize - self.as_offset) << self.address_height) - + block_id + (((addr_space - self.as_offset) as u64) << self.address_height) + block_id as u64 } } diff --git a/crates/vm/src/system/memory/manager/interface.rs b/crates/vm/src/system/memory/manager/interface.rs index a3a69d8b1a..a950afdb45 100644 --- a/crates/vm/src/system/memory/manager/interface.rs +++ b/crates/vm/src/system/memory/manager/interface.rs @@ -21,7 +21,7 @@ pub enum MemoryInterface { } impl MemoryInterface { - pub fn touch_address(&mut self, addr_space: F, pointer: F) { + pub fn touch_address(&mut self, addr_space: u32, pointer: u32) { match self { MemoryInterface::Volatile { boundary_chip } => { boundary_chip.touch_address(addr_space, pointer); diff --git a/crates/vm/src/system/memory/manager/memory.rs b/crates/vm/src/system/memory/manager/memory.rs index ba9f4c176c..c38016219f 100644 --- a/crates/vm/src/system/memory/manager/memory.rs +++ b/crates/vm/src/system/memory/manager/memory.rs @@ -48,11 +48,11 @@ impl MemoryReadRecord { pub const INITIAL_TIMESTAMP: u32 = 0; /// (address_space, pointer) -type Address = (usize, usize); +type Address = (u32, u32); #[derive(Clone, Copy, PartialEq, Eq, Debug)] struct BlockData { - pointer: usize, + pointer: u32, size: usize, timestamp: u32, } @@ -76,16 +76,15 @@ impl Memory { let mut block_data = FxHashMap::default(); let mut data = FxHashMap::default(); for (&(address_space, block_idx), values) in initial_memory { - let address_space_usize = address_space.as_canonical_u32() as usize; - let pointer = block_idx * N; + let pointer = block_idx * N as u32; let block = BlockData { pointer, size: N, timestamp: INITIAL_TIMESTAMP, }; for (i, value) in values.iter().enumerate() { - data.insert((address_space_usize, pointer + i), *value); - block_data.insert((address_space_usize, pointer + i), block); + data.insert((address_space, pointer + i as u32), *value); + block_data.insert((address_space, pointer + i as u32), block); } } Self { @@ -113,8 +112,8 @@ impl Memory { /// Writes an array of values to the memory at the specified address space and start index. pub fn write( &mut self, - address_space: usize, - pointer: usize, + address_space: u32, + pointer: u32, values: [F; N], ) -> (MemoryWriteRecord, Vec>) { assert!(N.is_power_of_two()); @@ -127,13 +126,13 @@ impl Memory { let prev_data = array::from_fn(|i| { self.data - .insert((address_space, pointer + i), values[i]) + .insert((address_space, pointer + i as u32), values[i]) .unwrap_or(F::ZERO) }); let record = MemoryWriteRecord { - address_space: F::from_canonical_usize(address_space), - pointer: F::from_canonical_usize(pointer), + address_space: F::from_canonical_u32(address_space), + pointer: F::from_canonical_u32(pointer), timestamp: self.timestamp, prev_timestamp, data: values, @@ -146,8 +145,8 @@ impl Memory { /// Reads an array of values from the memory at the specified address space and start index. pub fn read( &mut self, - address_space: usize, - pointer: usize, + address_space: u32, + pointer: u32, ) -> (MemoryReadRecord, Vec>) { assert!(N.is_power_of_two()); @@ -158,8 +157,8 @@ impl Memory { debug_assert!(prev_timestamp < self.timestamp); let record = MemoryReadRecord { - address_space: F::from_canonical_usize(address_space), - pointer: F::from_canonical_usize(pointer), + address_space: F::from_canonical_u32(address_space), + pointer: F::from_canonical_u32(pointer), timestamp: self.timestamp, prev_timestamp, data: self.range_array::(address_space, pointer), @@ -179,7 +178,7 @@ impl Memory { let to_access: FxHashSet<_> = self .block_data .keys() - .map(|&(address_space, pointer)| (address_space, (pointer / N) * N)) + .map(|&(address_space, pointer)| (address_space, (pointer / N as u32) * N as u32)) .collect(); for &(address_space, pointer) in to_access.iter() { @@ -193,11 +192,11 @@ impl Memory { for (address_space, pointer) in to_access { let block = self.block_data.get(&(address_space, pointer)).unwrap(); - debug_assert_eq!(block.pointer % N, 0); + debug_assert_eq!(block.pointer % N as u32, 0); debug_assert_eq!(block.size, N); equipartition.insert( - (F::from_canonical_usize(address_space), pointer / N), + (address_space, pointer / N as u32), TimestampedValues { timestamp: block.timestamp, values: self.range_array::(address_space, pointer), @@ -211,8 +210,8 @@ impl Memory { // Modifies the partition to ensure that there is a block starting at (address_space, query). fn split_to_make_boundary( &mut self, - address_space: usize, - query: usize, + address_space: u32, + query: u32, records: &mut Vec>, ) { let original_block = self.block_containing(address_space, query); @@ -230,44 +229,43 @@ impl Memory { // Split. records.push(AccessAdapterRecord { timestamp, - address_space: F::from_canonical_usize(address_space), - start_index: F::from_canonical_usize(cur_ptr), - data: data - [cur_ptr - original_block.pointer..cur_ptr - original_block.pointer + cur_size] + address_space: F::from_canonical_u32(address_space), + start_index: F::from_canonical_u32(cur_ptr), + data: data[(cur_ptr - original_block.pointer) as usize + ..(cur_ptr - original_block.pointer) as usize + cur_size] .to_vec(), kind: AccessAdapterRecordKind::Split, }); let half_size = cur_size / 2; + let half_size_u32 = half_size as u32; + let mid_ptr = cur_ptr + half_size_u32; - if query <= cur_ptr + half_size { + if query <= mid_ptr { // The right is finalized; add it to the partition. let block = BlockData { - pointer: cur_ptr + half_size, + pointer: mid_ptr, size: half_size, timestamp, }; - for i in 0..half_size { - self.block_data - .insert((address_space, cur_ptr + half_size + i), block); + for i in 0..half_size_u32 { + self.block_data.insert((address_space, mid_ptr + i), block); } } - if query >= cur_ptr + half_size { + if query >= cur_ptr + half_size_u32 { // The left is finalized; add it to the partition. let block = BlockData { pointer: cur_ptr, size: half_size, timestamp, }; - for i in 0..half_size { + for i in 0..half_size_u32 { self.block_data.insert((address_space, cur_ptr + i), block); } } - - if cur_ptr + half_size <= query { - cur_ptr += half_size; + if mid_ptr <= query { + cur_ptr = mid_ptr; } - if cur_ptr == query { break; } @@ -277,8 +275,8 @@ impl Memory { fn access_updating_timestamp( &mut self, - address_space: usize, - pointer: usize, + address_space: u32, + pointer: u32, size: usize, records: &mut Vec>, ) -> u32 { @@ -286,7 +284,7 @@ impl Memory { let mut prev_timestamp = None; - for i in 0..size { + for i in 0..size as u32 { let block = self .block_data .get_mut(&(address_space, pointer + i)) @@ -300,13 +298,13 @@ impl Memory { fn access( &mut self, - address_space: usize, - pointer: usize, + address_space: u32, + pointer: u32, size: usize, records: &mut Vec>, ) { self.split_to_make_boundary(address_space, pointer, records); - self.split_to_make_boundary(address_space, pointer + size, records); + self.split_to_make_boundary(address_space, pointer + size as u32, records); let block_data = self .block_data @@ -315,8 +313,8 @@ impl Memory { .unwrap_or_else(|| { for i in 0..size { self.block_data.insert( - (address_space, pointer + i), - self.initial_block_data(pointer + i), + (address_space, pointer + i as u32), + self.initial_block_data(pointer + i as u32), ); } self.initial_block_data(pointer) @@ -330,7 +328,12 @@ impl Memory { // Now recursively access left and right blocks to ensure they are in the partition. let half_size = size / 2; self.access(address_space, pointer, half_size, records); - self.access(address_space, pointer + half_size, half_size, records); + self.access( + address_space, + pointer + half_size as u32, + half_size, + records, + ); self.merge_block_with_next(address_space, pointer, records); } @@ -341,8 +344,8 @@ impl Memory { /// do not have the same size. fn merge_block_with_next( &mut self, - address_space: usize, - pointer: usize, + address_space: u32, + pointer: u32, records: &mut Vec>, ) { let left_block = self.block_data.get(&(address_space, pointer)).unwrap(); @@ -352,12 +355,12 @@ impl Memory { let right_timestamp = self .block_data - .get(&(address_space, pointer + size)) + .get(&(address_space, pointer + size as u32)) .map(|b| b.timestamp) .unwrap_or(INITIAL_TIMESTAMP); let timestamp = max(left_timestamp, right_timestamp); - for i in 0..2 * size { + for i in 0..2 * size as u32 { self.block_data.insert( (address_space, pointer + i), BlockData { @@ -369,8 +372,8 @@ impl Memory { } records.push(AccessAdapterRecord { timestamp, - address_space: F::from_canonical_usize(address_space), - start_index: F::from_canonical_usize(pointer), + address_space: F::from_canonical_u32(address_space), + start_index: F::from_canonical_u32(pointer), data: self.range_vec(address_space, pointer, 2 * size), kind: AccessAdapterRecordKind::Merge { left_timestamp, @@ -379,7 +382,7 @@ impl Memory { }); } - fn block_containing(&mut self, address_space: usize, pointer: usize) -> BlockData { + fn block_containing(&mut self, address_space: u32, pointer: u32) -> BlockData { if let Some(block_data) = self.block_data.get(&(address_space, pointer)) { *block_data } else { @@ -387,8 +390,9 @@ impl Memory { } } - fn initial_block_data(&self, pointer: usize) -> BlockData { - let aligned_pointer = (pointer / self.initial_block_size) * self.initial_block_size; + fn initial_block_data(&self, pointer: u32) -> BlockData { + let aligned_pointer = + (pointer / self.initial_block_size as u32) * self.initial_block_size as u32; BlockData { pointer: aligned_pointer, size: self.initial_block_size, @@ -396,17 +400,17 @@ impl Memory { } } - pub fn get(&self, address_space: usize, pointer: usize) -> F { + pub fn get(&self, address_space: u32, pointer: u32) -> F { *self.data.get(&(address_space, pointer)).unwrap_or(&F::ZERO) } - fn range_array(&self, address_space: usize, pointer: usize) -> [F; N] { - array::from_fn(|i| self.get(address_space, pointer + i)) + fn range_array(&self, address_space: u32, pointer: u32) -> [F; N] { + array::from_fn(|i| self.get(address_space, pointer + i as u32)) } - fn range_vec(&self, address_space: usize, pointer: usize, len: usize) -> Vec { + fn range_vec(&self, address_space: u32, pointer: u32, len: usize) -> Vec { (0..len) - .map(|i| self.get(address_space, pointer + i)) + .map(|i| self.get(address_space, pointer + i as u32)) .collect() } } @@ -811,7 +815,7 @@ mod tests { let (final_memory, records) = memory.finalize::<8>(); assert_eq!(final_memory.len(), 4); assert_eq!( - final_memory.get(&(bb!(1), 0)), + final_memory.get(&(1, 0)), Some(&TimestampedValues { values: bba![1, 2, 3, 4, 0, 0, 0, 0], timestamp: 1, @@ -819,7 +823,7 @@ mod tests { ); // start_index = 16 corresponds to label = 2 assert_eq!( - final_memory.get(&(bb!(1), 2)), + final_memory.get(&(1, 2)), Some(&TimestampedValues { values: bba![1, 1, 1, 1, 1, 1, 1, 1], timestamp: 2, @@ -827,7 +831,7 @@ mod tests { ); // start_index = 24 corresponds to label = 3 assert_eq!( - final_memory.get(&(bb!(1), 3)), + final_memory.get(&(1, 3)), Some(&TimestampedValues { values: bba![1, 1, 1, 1, 1, 1, 1, 1], timestamp: 2, @@ -835,7 +839,7 @@ mod tests { ); // start_index = 64 corresponds to label = 8 assert_eq!( - final_memory.get(&(bb!(2), 8)), + final_memory.get(&(2, 8)), Some(&TimestampedValues { values: bba![8, 7, 6, 5, 4, 3, 2, 1], timestamp: 3, @@ -852,8 +856,8 @@ mod tests { // Initialize initial memory with blocks at indices 0 and 2 let mut initial_memory = Equipartition::::new(); - initial_memory.insert((F::ONE, 0), bba![1, 2, 3, 4, 5, 6, 7, 8]); // Block 0, pointers 0–8 - initial_memory.insert((F::ONE, 2), bba![1, 2, 3, 4, 5, 6, 7, 8]); // Block 2, pointers 16–24 + initial_memory.insert((1, 0), bba![1, 2, 3, 4, 5, 6, 7, 8]); // Block 0, pointers 0–8 + initial_memory.insert((1, 2), bba![1, 2, 3, 4, 5, 6, 7, 8]); // Block 2, pointers 16–24 let mut memory = Memory::new(&initial_memory); diff --git a/crates/vm/src/system/memory/manager/mod.rs b/crates/vm/src/system/memory/manager/mod.rs index 595257a3c4..21ceb931a9 100644 --- a/crates/vm/src/system/memory/manager/mod.rs +++ b/crates/vm/src/system/memory/manager/mod.rs @@ -67,22 +67,22 @@ pub struct TimestampedValues { pub type MemoryControllerRef = Rc>>; -/// A equipartition of memory, with timestamps and values. +/// An equipartition of memory, with timestamps and values. /// /// The key is a pair `(address_space, label)`, where `label` is the index of the block in the /// partition. I.e., the starting address of the block is `(address_space, label * N)`. /// /// If a key is not present in the map, then the block is uninitialized (and therefore zero). pub type TimestampedEquipartition = - BTreeMap<(F, usize), TimestampedValues>; + BTreeMap<(u32, u32), TimestampedValues>; -/// A equipartition of memory values. +/// An equipartition of memory values. /// /// The key is a pair `(address_space, label)`, where `label` is the index of the block in the /// partition. I.e., the starting address of the block is `(address_space, label * N)`. /// /// If a key is not present in the map, then the block is uninitialized (and therefore zero). -pub type Equipartition = BTreeMap<(F, usize), [F; N]>; +pub type Equipartition = BTreeMap<(u32, u32), [F; N]>; #[derive(Debug, Getters)] pub struct MemoryController { @@ -346,6 +346,7 @@ impl MemoryController { } pub fn read(&mut self, address_space: F, pointer: F) -> MemoryReadRecord { + let address_space_u32 = address_space.as_canonical_u32(); let ptr_u32 = pointer.as_canonical_u32(); assert!( address_space == F::ZERO || ptr_u32 < (1 << self.mem_config.pointer_max_bits), @@ -367,16 +368,14 @@ impl MemoryController { }; } - let (record, adapter_records) = self - .memory - .read::(address_space.as_canonical_u32() as usize, ptr_u32 as usize); + let (record, adapter_records) = self.memory.read::(address_space_u32, ptr_u32); for record in adapter_records { self.access_adapters.add_record(record); } for i in 0..N as u32 { - let ptr = F::from_canonical_u32(ptr_u32 + i); - self.interface_chip.touch_address(address_space, ptr); + self.interface_chip + .touch_address(address_space_u32, ptr_u32 + i); } record @@ -393,9 +392,9 @@ impl MemoryController { /// /// Any value returned is unconstrained. pub fn unsafe_read(&self, addr_space: F, ptr: F) -> [F; N] { - let addr_space = addr_space.as_canonical_u32() as usize; - let ptr = ptr.as_canonical_u32() as usize; - from_fn(|i| self.memory.get(addr_space, ptr + i)) + let addr_space = addr_space.as_canonical_u32(); + let ptr = ptr.as_canonical_u32(); + from_fn(|i| self.memory.get(addr_space, ptr + i as u32)) } pub fn write_cell(&mut self, address_space: F, pointer: F, data: F) -> MemoryWriteRecord { @@ -409,24 +408,21 @@ impl MemoryController { data: [F; N], ) -> MemoryWriteRecord { assert_ne!(address_space, F::ZERO); + let address_space_u32 = address_space.as_canonical_u32(); let ptr_u32 = pointer.as_canonical_u32(); assert!( ptr_u32 < (1 << self.mem_config.pointer_max_bits), "memory out of bounds: {ptr_u32:?}", ); - let (record, adapter_records) = self.memory.write( - address_space.as_canonical_u32() as usize, - ptr_u32 as usize, - data, - ); + let (record, adapter_records) = self.memory.write(address_space_u32, ptr_u32, data); for record in adapter_records { self.access_adapters.add_record(record); } for i in 0..N as u32 { - let ptr = F::from_canonical_u32(ptr_u32 + i); - self.interface_chip.touch_address(address_space, ptr); + self.interface_chip + .touch_address(address_space_u32, ptr_u32 + i); } record @@ -715,12 +711,11 @@ impl MemoryAuxColsFactory { pub fn memory_image_to_equipartition( memory_image: MemoryImage, -) -> Equipartition { +) -> Equipartition { let mut result = Equipartition::new(); for ((addr_space, addr), word) in memory_image { - let addr_usize = addr.as_canonical_u32() as usize; - let shift = addr_usize % N; - let key = (addr_space, addr_usize / N); + let shift = (addr % N as u32) as usize; + let key = (addr_space, addr / N as u32); result.entry(key).or_insert([F::ZERO; N])[shift] = word; } result diff --git a/crates/vm/src/system/memory/merkle/mod.rs b/crates/vm/src/system/memory/merkle/mod.rs index ad01e25c8b..6dc75ecf6c 100644 --- a/crates/vm/src/system/memory/merkle/mod.rs +++ b/crates/vm/src/system/memory/merkle/mod.rs @@ -17,7 +17,7 @@ mod tests; #[derive(Debug)] pub struct MemoryMerkleChip { pub air: MemoryMerkleAir, - touched_nodes: FxHashSet<(usize, usize, usize)>, + touched_nodes: FxHashSet<(usize, u32, u32)>, num_touched_nonleaves: usize, final_state: Option>, overridden_height: Option, @@ -56,7 +56,7 @@ impl MemoryMerkleChip { self.overridden_height = Some(override_height); } - fn touch_node(&mut self, height: usize, as_label: usize, address_label: usize) { + fn touch_node(&mut self, height: usize, as_label: u32, address_label: u32) { if self.touched_nodes.insert((height, as_label, address_label)) { assert_ne!(height, self.air.memory_dimensions.overall_height()); if height != 0 { @@ -70,11 +70,11 @@ impl MemoryMerkleChip { } } - pub fn touch_address(&mut self, address_space: F, address: F) { + pub fn touch_address(&mut self, address_space: u32, address: u32) { self.touch_node( 0, - (address_space.as_canonical_u32() as usize) - self.air.memory_dimensions.as_offset, - (address.as_canonical_u32() as usize) / CHUNK, + address_space - self.air.memory_dimensions.as_offset, + address / CHUNK as u32, ); } } diff --git a/crates/vm/src/system/memory/merkle/tests/mod.rs b/crates/vm/src/system/memory/merkle/tests/mod.rs index fb800c2d13..11353a1a0f 100644 --- a/crates/vm/src/system/memory/merkle/tests/mod.rs +++ b/crates/vm/src/system/memory/merkle/tests/mod.rs @@ -6,11 +6,8 @@ use std::{ }; use openvm_stark_backend::{ - interaction::InteractionType, - p3_field::{AbstractField, PrimeField32}, - p3_matrix::dense::RowMajorMatrix, - prover::types::AirProofInput, - Chip, ChipUsageGetter, + interaction::InteractionType, p3_field::AbstractField, p3_matrix::dense::RowMajorMatrix, + prover::types::AirProofInput, Chip, ChipUsageGetter, }; use openvm_stark_sdk::{ config::baby_bear_poseidon2::BabyBearPoseidon2Engine, @@ -40,7 +37,7 @@ const COMPRESSION_BUS: DirectCompressionBus = DirectCompressionBus(POSEIDON2_DIR fn test( memory_dimensions: MemoryDimensions, initial_memory: &Equipartition, - touched_labels: BTreeSet<(BabyBear, usize)>, + touched_labels: BTreeSet<(u32, u32)>, final_memory: &Equipartition, ) { let MemoryDimensions { @@ -52,7 +49,7 @@ fn test( // checking validity of test data for (&(address_space, label), value) in final_memory { - assert!((address_space.as_canonical_u32() as usize) - as_offset < (1 << as_height)); + assert!(address_space - as_offset < (1 << as_height)); assert!(label < (1 << address_height)); if initial_memory.get(&(address_space, label)) != Some(value) { assert!(touched_labels.contains(&(address_space, label))); @@ -75,11 +72,8 @@ fn test( let mut chip = MemoryMerkleChip::::new(memory_dimensions, merkle_bus, COMPRESSION_BUS); for &(address_space, label) in touched_labels.iter() { - for i in 0..CHUNK { - chip.touch_address( - address_space, - BabyBear::from_canonical_usize(label * CHUNK + i), - ); + for i in 0..CHUNK as u32 { + chip.touch_address(address_space, label * CHUNK as u32 + i); } } @@ -96,8 +90,8 @@ fn test( let mut interaction = |interaction_type: InteractionType, is_compress: bool, height: usize, - as_label: usize, - address_label: usize, + as_label: u32, + address_label: u32, hash: [BabyBear; CHUNK]| { let expand_direction = if is_compress { BabyBear::NEG_ONE @@ -111,8 +105,8 @@ fn test( dummy_interaction_trace_rows.extend([ expand_direction, BabyBear::from_canonical_usize(height), - BabyBear::from_canonical_usize(as_label), - BabyBear::from_canonical_usize(address_label), + BabyBear::from_canonical_u32(as_label), + BabyBear::from_canonical_u32(address_label), ]); dummy_interaction_trace_rows.extend(hash); }; @@ -121,7 +115,7 @@ fn test( let initial_values = *initial_memory .get(&(address_space, address_label)) .unwrap_or(&[BabyBear::ZERO; CHUNK]); - let as_label = address_space.as_canonical_u32() as usize - as_offset; + let as_label = address_space - as_offset; interaction( InteractionType::Send, false, @@ -163,12 +157,12 @@ fn test( fn random_test( height: usize, - max_value: usize, + max_value: u32, mut num_initial_addresses: usize, mut num_touched_addresses: usize, ) { let mut rng = create_seeded_rng(); - let mut next_usize = || rng.next_u64() as usize; + let mut next_u32 = || rng.next_u64() as u32; let mut initial_memory = Equipartition::new(); let mut final_memory = Equipartition::new(); @@ -176,15 +170,15 @@ fn random_test( let mut touched_labels = BTreeSet::new(); while num_initial_addresses != 0 || num_touched_addresses != 0 { - let address_space = BabyBear::from_canonical_usize((next_usize() & 1) + 1); - let label = next_usize() % (1 << height); + let address_space = (next_u32() & 1) + 1; + let label = next_u32() % (1 << height); if seen_labels.insert(label) { - let is_initial = next_usize() & 1 == 0; + let is_initial = next_u32() & 1 == 0; let initial_values = - array::from_fn(|_| BabyBear::from_canonical_usize(next_usize() % max_value)); - let is_touched = next_usize() & 1 == 0; - let value_changes = next_usize() & 1 == 0; + array::from_fn(|_| BabyBear::from_canonical_u32(next_u32() % max_value)); + let is_touched = next_u32() & 1 == 0; + let value_changes = next_u32() & 1 == 0; if is_initial && num_initial_addresses != 0 { num_initial_addresses -= 1; @@ -195,9 +189,8 @@ fn random_test( num_touched_addresses -= 1; touched_labels.insert((address_space, label)); if value_changes || !is_initial { - let changed_values = array::from_fn(|_| { - BabyBear::from_canonical_usize(next_usize() % max_value) - }); + let changed_values = + array::from_fn(|_| BabyBear::from_canonical_u32(next_u32() % max_value)); final_memory.insert((address_space, label), changed_values); } } diff --git a/crates/vm/src/system/memory/merkle/trace.rs b/crates/vm/src/system/memory/merkle/trace.rs index 8045e7fc92..68f5caa30a 100644 --- a/crates/vm/src/system/memory/merkle/trace.rs +++ b/crates/vm/src/system/memory/merkle/trace.rs @@ -119,7 +119,7 @@ impl ChipUsageGetter for MemoryMerkleChip { memory_dimensions: MemoryDimensions, final_memory: &'a Equipartition, - touched_nodes: &'a FxHashSet<(usize, usize, usize)>, + touched_nodes: &'a FxHashSet<(usize, u32, u32)>, trace_rows: &'a mut Vec>, } @@ -128,13 +128,12 @@ impl TreeHelper<'_, CHUNK, F> { &mut self, height: usize, initial_node: &MemoryNode, - as_label: usize, - address_label: usize, + as_label: u32, + address_label: u32, hasher: &mut impl HasherChip, ) -> MemoryNode { if height == 0 { - let address_space = - F::from_canonical_usize(as_label + self.memory_dimensions.as_offset); + let address_space = as_label + self.memory_dimensions.as_offset; let leaf_values = *self .final_memory .get(&(address_space, address_label)) @@ -215,8 +214,8 @@ impl TreeHelper<'_, CHUNK, F> { fn add_trace_row( &mut self, parent_height: usize, - as_label: usize, - address_label: usize, + as_label: u32, + address_label: u32, node: &MemoryNode, direction_changes: Option<[bool; 2]>, ) { @@ -232,8 +231,8 @@ impl TreeHelper<'_, CHUNK, F> { height_section: F::from_bool(parent_height > self.memory_dimensions.address_height), parent_height: F::from_canonical_usize(parent_height), is_root: F::from_bool(parent_height == self.memory_dimensions.overall_height()), - parent_as_label: F::from_canonical_usize(as_label), - parent_address_label: F::from_canonical_usize(address_label), + parent_as_label: F::from_canonical_u32(as_label), + parent_address_label: F::from_canonical_u32(address_label), parent_hash: *hash, left_child_hash: left.hash(), right_child_hash: right.hash(), diff --git a/crates/vm/src/system/memory/persistent.rs b/crates/vm/src/system/memory/persistent.rs index 57eb71d633..bbd0ab0b9e 100644 --- a/crates/vm/src/system/memory/persistent.rs +++ b/crates/vm/src/system/memory/persistent.rs @@ -87,7 +87,7 @@ impl Air for PersistentBoundaryA // direction = -1 => is_final = 1 local.expand_direction.into(), AB::Expr::ZERO, - local.address_space - AB::F::from_canonical_usize(self.memory_dims.as_offset), + local.address_space - AB::F::from_canonical_u32(self.memory_dims.as_offset), local.leaf_label.into(), ]; expand_fields.extend(local.hash.map(Into::into)); @@ -128,14 +128,14 @@ pub struct PersistentBoundaryChip { #[derive(Debug)] enum TouchedLabels { - Running(FxHashSet<(F, usize)>), + Running(FxHashSet<(u32, u32)>), Final(Vec>), } #[derive(Debug)] struct FinalTouchedLabel { - address_space: F, - label: usize, + address_space: u32, + label: u32, init_values: [F; CHUNK], final_values: [F; CHUNK], init_exists: bool, @@ -151,7 +151,7 @@ impl Default for TouchedLabels { } impl TouchedLabels { - fn touch(&mut self, address_space: F, label: usize) { + fn touch(&mut self, address_space: u32, label: u32) { match self { TouchedLabels::Running(touched_labels) => { touched_labels.insert((address_space, label)); @@ -190,8 +190,8 @@ impl PersistentBoundaryChip { self.overridden_height = Some(overridden_height); } - pub fn touch_address(&mut self, address_space: F, pointer: F) { - let label = pointer.as_canonical_u32() as usize / CHUNK; + pub fn touch_address(&mut self, address_space: u32, pointer: u32) { + let label = pointer / CHUNK as u32; self.touched_labels.touch(address_space, label); } @@ -272,8 +272,8 @@ where let (initial_row, final_row) = row.split_at_mut(width); *initial_row.borrow_mut() = PersistentBoundaryCols { expand_direction: Val::::ONE, - address_space: touched_label.address_space, - leaf_label: Val::::from_canonical_usize(touched_label.label), + address_space: Val::::from_canonical_u32(touched_label.address_space), + leaf_label: Val::::from_canonical_u32(touched_label.label), values: touched_label.init_values, hash: touched_label.init_hash, timestamp: if touched_label.init_exists { @@ -285,8 +285,8 @@ where *final_row.borrow_mut() = PersistentBoundaryCols { expand_direction: Val::::NEG_ONE, - address_space: touched_label.address_space, - leaf_label: Val::::from_canonical_usize(touched_label.label), + address_space: Val::::from_canonical_u32(touched_label.address_space), + leaf_label: Val::::from_canonical_u32(touched_label.label), values: touched_label.final_values, hash: touched_label.final_hash, timestamp: Val::::from_canonical_u32(touched_label.final_timestamp), diff --git a/crates/vm/src/system/memory/tree/mod.rs b/crates/vm/src/system/memory/tree/mod.rs index 00a8281152..51e58a2ab8 100644 --- a/crates/vm/src/system/memory/tree/mod.rs +++ b/crates/vm/src/system/memory/tree/mod.rs @@ -66,9 +66,9 @@ impl MemoryNode { } fn from_memory( - memory: &BTreeMap, + memory: &BTreeMap, height: usize, - from: usize, + from: u64, hasher: &impl Hasher, ) -> MemoryNode { let mut range = memory.range(from..from + (1 << height)); diff --git a/crates/vm/src/system/memory/tree/public_values.rs b/crates/vm/src/system/memory/tree/public_values.rs index 385e1582bd..f31ca63226 100644 --- a/crates/vm/src/system/memory/tree/public_values.rs +++ b/crates/vm/src/system/memory/tree/public_values.rs @@ -8,7 +8,7 @@ use crate::{ system::memory::{dimensions::MemoryDimensions, tree::MemoryNode, Equipartition}, }; -pub const PUBLIC_VALUES_ADDRESS_SPACE_OFFSET: usize = 2; +pub const PUBLIC_VALUES_ADDRESS_SPACE_OFFSET: u32 = 2; /// Merkle proof for user public values in the memory state. #[derive(Clone, Debug, Serialize, Deserialize)] @@ -111,11 +111,8 @@ pub fn extract_public_values( final_memory: &Equipartition, ) -> Vec { // All (addr, value) pairs in the public value address space. - let f_as_start = - F::from_canonical_usize(PUBLIC_VALUES_ADDRESS_SPACE_OFFSET + memory_dimensions.as_offset); - let f_as_end = F::from_canonical_usize( - PUBLIC_VALUES_ADDRESS_SPACE_OFFSET + memory_dimensions.as_offset + 1, - ); + let f_as_start = PUBLIC_VALUES_ADDRESS_SPACE_OFFSET + memory_dimensions.as_offset; + let f_as_end = PUBLIC_VALUES_ADDRESS_SPACE_OFFSET + memory_dimensions.as_offset + 1; let used_pvs: Vec<_> = final_memory .range((f_as_start, 0)..(f_as_end, 0)) @@ -123,7 +120,7 @@ pub fn extract_public_values( value .iter() .enumerate() - .map(|(i, &v)| (*block_id * CHUNK + i, v)) + .map(|(i, &v)| (*block_id as usize * CHUNK + i, v)) }) .collect(); if let Some(last_pv) = used_pvs.last() { @@ -161,13 +158,9 @@ mod tests { vm_config.memory_config.as_height = 4; vm_config.memory_config.pointer_max_bits = 5; let memory_dimensions = vm_config.memory_config.memory_dimensions(); - let pv_as = F::from_canonical_usize( - PUBLIC_VALUES_ADDRESS_SPACE_OFFSET + memory_dimensions.as_offset, - ); + let pv_as = PUBLIC_VALUES_ADDRESS_SPACE_OFFSET + memory_dimensions.as_offset; let num_public_values = 16; - let memory: MemoryImage = [((pv_as, F::from_canonical_u32(15)), F::ONE)] - .into_iter() - .collect(); + let memory: MemoryImage = [((pv_as, 15), F::ONE)].into_iter().collect(); let mut expected_pvs = F::zero_vec(num_public_values); expected_pvs[15] = F::ONE; diff --git a/crates/vm/src/system/memory/volatile/mod.rs b/crates/vm/src/system/memory/volatile/mod.rs index 04e0c68579..a831d768b3 100644 --- a/crates/vm/src/system/memory/volatile/mod.rs +++ b/crates/vm/src/system/memory/volatile/mod.rs @@ -1,6 +1,5 @@ use std::{ borrow::{Borrow, BorrowMut}, - collections::HashSet, sync::Arc, }; @@ -24,6 +23,7 @@ use openvm_stark_backend::{ rap::{AnyRap, BaseAirWithPublicValues, PartitionedBaseAir}, Chip, ChipUsageGetter, }; +use rustc_hash::FxHashSet; use super::TimestampedEquipartition; use crate::system::memory::{ @@ -133,7 +133,7 @@ impl Air for VolatileBoundaryAir { #[derive(Debug)] pub struct VolatileBoundaryChip { pub air: VolatileBoundaryAir, - touched_addresses: HashSet<(F, F)>, + touched_addresses: FxHashSet<(u32, u32)>, range_checker: Arc, overridden_height: Option, final_memory: Option>, @@ -154,18 +154,18 @@ impl VolatileBoundaryChip { pointer_max_bits, range_bus, ), - touched_addresses: HashSet::new(), + touched_addresses: FxHashSet::default(), range_checker, overridden_height: None, final_memory: None, } } - pub fn touch_address(&mut self, addr_space: F, pointer: F) { + pub fn touch_address(&mut self, addr_space: u32, pointer: u32) { self.touched_addresses.insert((addr_space, pointer)); } - pub fn all_addresses(&self) -> Vec<(F, F)> { + pub fn all_addresses(&self) -> Vec<(u32, u32)> { self.touched_addresses.iter().cloned().collect() } } @@ -220,8 +220,8 @@ where // `pointer` is the same as `label` since the equipartition has block size 1 let [data] = timestamped_values.values; let row: &mut VolatileBoundaryCols<_> = row.borrow_mut(); - row.addr_space = *addr_space; - row.pointer = Val::::from_canonical_usize(*ptr); + row.addr_space = Val::::from_canonical_u32(*addr_space); + row.pointer = Val::::from_canonical_u32(*ptr); row.initial_data = Val::::ZERO; row.final_data = data; row.final_timestamp = Val::::from_canonical_u32(timestamped_values.timestamp); @@ -235,7 +235,10 @@ where ( &self.range_checker, &[row.addr_space, row.pointer], - &[next_addr_space, Val::::from_canonical_usize(next_ptr)], + &[ + Val::::from_canonical_u32(next_addr_space), + Val::::from_canonical_u32(next_ptr), + ], ), ((&mut row.addr_lt_aux).into(), &mut out), ); diff --git a/crates/vm/src/system/memory/volatile/tests.rs b/crates/vm/src/system/memory/volatile/tests.rs index eaaf3bb674..cec8f05cd9 100644 --- a/crates/vm/src/system/memory/volatile/tests.rs +++ b/crates/vm/src/system/memory/volatile/tests.rs @@ -2,10 +2,7 @@ use std::{collections::HashSet, iter, sync::Arc}; use openvm_circuit_primitives::var_range::{VariableRangeCheckerBus, VariableRangeCheckerChip}; use openvm_stark_backend::{ - p3_field::{AbstractField, PrimeField32}, - p3_matrix::dense::RowMajorMatrix, - prover::types::AirProofInput, - Chip, + p3_field::AbstractField, p3_matrix::dense::RowMajorMatrix, prover::types::AirProofInput, Chip, }; use openvm_stark_sdk::{ config::baby_bear_poseidon2::{BabyBearPoseidon2Config, BabyBearPoseidon2Engine}, @@ -30,17 +27,17 @@ fn boundary_air_test() { const MEMORY_BUS: usize = 1; const RANGE_CHECKER_BUS: usize = 3; - const MAX_ADDRESS_SPACE: usize = 4; + const MAX_ADDRESS_SPACE: u32 = 4; const LIMB_BITS: usize = 15; - const MAX_VAL: usize = 1 << LIMB_BITS; + const MAX_VAL: u32 = 1 << LIMB_BITS; const DECOMP: usize = 8; let memory_bus = MemoryBus(MEMORY_BUS); let num_addresses = 10; let mut distinct_addresses = HashSet::new(); while distinct_addresses.len() < num_addresses { - let addr_space = Val::from_canonical_usize(rng.gen_range(0..MAX_ADDRESS_SPACE)); - let pointer = Val::from_canonical_usize(rng.gen_range(0..MAX_VAL)); + let addr_space = rng.gen_range(0..MAX_ADDRESS_SPACE); + let pointer = rng.gen_range(0..MAX_VAL); distinct_addresses.insert((addr_space, pointer)); } @@ -52,11 +49,11 @@ fn boundary_air_test() { let mut final_memory = TimestampedEquipartition::new(); for (addr_space, pointer) in distinct_addresses.iter().cloned() { - let final_data = Val::from_canonical_usize(rng.gen_range(0..MAX_VAL)); + let final_data = Val::from_canonical_u32(rng.gen_range(0..MAX_VAL)); let final_clk = rng.gen_range(1..MAX_VAL) as u32; final_memory.insert( - (addr_space, pointer.as_canonical_u32() as usize), + (addr_space, pointer), TimestampedValues { values: [final_data], timestamp: final_clk, @@ -75,8 +72,8 @@ fn boundary_air_test() { .flat_map(|(addr_space, pointer)| { vec![ Val::ONE, - *addr_space, - *pointer, + Val::from_canonical_u32(*addr_space), + Val::from_canonical_u32(*pointer), Val::ZERO, Val::ZERO, Val::ONE, @@ -91,14 +88,12 @@ fn boundary_air_test() { distinct_addresses .iter() .flat_map(|(addr_space, pointer)| { - let timestamped_value = final_memory - .get(&(*addr_space, pointer.as_canonical_u32() as usize)) - .unwrap(); + let timestamped_value = final_memory.get(&(*addr_space, *pointer)).unwrap(); vec![ Val::ONE, - *addr_space, - *pointer, + Val::from_canonical_u32(*addr_space), + Val::from_canonical_u32(*pointer), timestamped_value.values[0], Val::from_canonical_u32(timestamped_value.timestamp), Val::ONE, diff --git a/crates/vm/tests/integration_test.rs b/crates/vm/tests/integration_test.rs index c07790c4e4..224388ac2a 100644 --- a/crates/vm/tests/integration_test.rs +++ b/crates/vm/tests/integration_test.rs @@ -352,12 +352,9 @@ fn test_vm_initial_memory() { ), ]); - let init_memory: BTreeMap<_, _> = [( - (BabyBear::ONE, BabyBear::from_canonical_u32(7)), - BabyBear::from_canonical_u32(101), - )] - .into_iter() - .collect(); + let init_memory: BTreeMap<_, _> = [((1, 7), BabyBear::from_canonical_u32(101))] + .into_iter() + .collect(); let config = NativeConfig::aggregation(0, 3).with_continuations(); let exe = VmExe { From 15d85585205a6430f565f1567004341e1d81db08 Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:35:07 -0500 Subject: [PATCH 63/69] chore: fix CI for fork repos (#1112) --- .github/workflows/benchmark-call.yml | 1 + .github/workflows/benchmarks.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/benchmark-call.yml b/.github/workflows/benchmark-call.yml index 03f346cdd6..2d74817d8f 100644 --- a/.github/workflows/benchmark-call.yml +++ b/.github/workflows/benchmark-call.yml @@ -283,6 +283,7 @@ jobs: echo "BENCHMARK_RESULTS_PATH=${BENCHMARK_RESULTS_PATH}" >> $GITHUB_ENV - name: Update PR github pages with new bench results + if: github.event.pull_request.head.repo.fork == false # forks do not have write access run: | mkdir -p ${BENCHMARK_RESULTS_PATH} s3_md_file="${METRIC_NAME}-${current_sha}.md" diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index e88ff179d6..9c6f912c15 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -213,7 +213,7 @@ jobs: git config --global user.name "github-actions[bot]" - name: Update github pages with new bench results - if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/main') + if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) || (github.event_name == 'push' && github.ref == 'refs/heads/main') run: | mkdir -p ${BENCHMARK_RESULTS_PATH} cp /tmp/benchmark-results/summary.md ${BENCHMARK_RESULTS_PATH}/summary.md From 3a83db068b2393beabeab498c866be73334e3981 Mon Sep 17 00:00:00 2001 From: osrm <90407222+osrm@users.noreply.github.com> Date: Wed, 18 Dec 2024 05:05:44 +0900 Subject: [PATCH 64/69] docs: cleanup documents (#1111) * fix invalid link README.md [air.rs](./air.rs) is invalid. Changed to [air.rs](./src/air.rs) * fix invalid link README.md "[Writing the Guest Program](../../benchmarks/README.md#writing-the-guest-program" is invalid. Changed to "[Writing the Guest Program](../../../benchmarks/README.md#writing-the-guest-program" * fix invalid link README.md "[fibonacci example](./bin/fibonacci.rs)" is not working. Changed to "[fibonacci example](./src/bin/fibonacci.rs)". * fix invalid link sdk.md "[Overview of Basic Usage](./overview.md)" is invalid. Changed to "[Overview of Basic Usage](../writing-apps/overview.md)". * fix invalid link benchmarks.md "[github workflows](./.github/workflows/benchmark-call.yml)" is invalid link. Changed to "[github workflows](../../.github/workflows/benchmark-call.yml)". * fix typo ISA.md - "an memory access" -> "a memory access" The phrase "an memory access" is considered incorrect because the article "an" is used before words that begin with a vowel sound. Since "memory" starts with a consonant sound, the correct article to use is "a," making it "a memory access." --- book/src/advanced-usage/sdk.md | 2 +- crates/toolchain/tests/README.md | 2 +- docs/crates/benchmarks.md | 2 +- docs/specs/ISA.md | 2 +- extensions/keccak256/circuit/README.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/book/src/advanced-usage/sdk.md b/book/src/advanced-usage/sdk.md index da21629b2c..50baf12a86 100644 --- a/book/src/advanced-usage/sdk.md +++ b/book/src/advanced-usage/sdk.md @@ -2,7 +2,7 @@ While the CLI provides a convenient way to build, prove, and verify programs, you may want more fine-grained control over the process. The OpenVM Rust SDK allows you to customize various aspects of the workflow programmatically. -For more information on the basic CLI flow, see [Overview of Basic Usage](./overview.md). Writing a guest program is the same as in the CLI. +For more information on the basic CLI flow, see [Overview of Basic Usage](../writing-apps/overview.md). Writing a guest program is the same as in the CLI. ## Imports and Setup diff --git a/crates/toolchain/tests/README.md b/crates/toolchain/tests/README.md index dff2f7afdd..89372257b2 100644 --- a/crates/toolchain/tests/README.md +++ b/crates/toolchain/tests/README.md @@ -6,7 +6,7 @@ This crate includes tests for OpenVM toolchain that involve starting from a Rust 1. Add a new guest program file to [programs/examples](./programs/examples). -See [Writing the Guest Program](../../benchmarks/README.md#writing-the-guest-program) for more detailed instructions. +See [Writing the Guest Program](../../../benchmarks/README.md#writing-the-guest-program) for more detailed instructions. The `programs` directory is a single crate to make it easier to add small test programs. The crate is **not** part of the main workspace. Your IDE will likely not lint or use rust-analyzer on the crate while in the workspace, so you should open a separate IDE workspace from `programs` while writing your guest program. diff --git a/docs/crates/benchmarks.md b/docs/crates/benchmarks.md index bc9d811242..20fdf62433 100644 --- a/docs/crates/benchmarks.md +++ b/docs/crates/benchmarks.md @@ -3,7 +3,7 @@ ### Latest Benchmark Results Latest benchmark results can be found [here](https://github.com/openvm-org/openvm/blob/benchmark-results/index.md). -These are run via [github workflows](./.github/workflows/benchmark-call.yml) and should always be up to date with the latest `main` branch. +These are run via [github workflows](../../.github/workflows/benchmark-call.yml) and should always be up to date with the latest `main` branch. ### Adding a Benchmark diff --git a/docs/specs/ISA.md b/docs/specs/ISA.md index ad8337ed1f..87d17418f3 100644 --- a/docs/specs/ISA.md +++ b/docs/specs/ISA.md @@ -21,7 +21,7 @@ The program code is committed as a cached trace. The validity of the program cod Memory is comprised of addressable cells, each cell containing a single field element. Instructions of the VM may access (read or write) memory as single cells or as a contiguous list of cells. Such a contiguous list is called a _block_, and -an memory access (read/write) to a block is a _block access_. +a memory access (read/write) to a block is a _block access_. The architecture distinguishes between block accesses of different sizes as this has significant performance implications. The number of cells in a block access is restricted to powers of two, of which the following are supported: 1, 2, 4, 8, 16, 32, 64. Block accesses must be aligned, meaning that in a block access of size $N$, the starting pointer must be divisible by $N$ (as an integer). diff --git a/extensions/keccak256/circuit/README.md b/extensions/keccak256/circuit/README.md index 5085c961cc..afd6881f65 100644 --- a/extensions/keccak256/circuit/README.md +++ b/extensions/keccak256/circuit/README.md @@ -25,7 +25,7 @@ It seems to handle padding in a single AIR row there is no alternate to having ` The absorb step must correctly constrain that the input bytes are XORed with the end-state in the last round and equals the next permutation's `preimage`. The end-state is accessed via `a_prime_prime_prime()`. Note that both `preimage` and `a_prime_prime_prime()` are represented as `u16`s. However we can only XOR at most 8-bit limbs. Without changing the `keccak-f` AIR itself, we can use a trick: if we already have a 16-bit limb `x` and we also provide a 8-bit limb `hi = x >> 8`, assuming `x` and `hi` have been range checked, we can use the expression `lo = x - hi * 256` for the low byte. If `lo` is range checked to `8`-bits, this constrains a valid byte decomposition of `x` into `hi, lo`. This means in terms of trace cells, it is equivalent to provide `x, hi` versus `hi, lo`. -The constraints are in [air.rs](./air.rs). Notably we use an XOR lookup table for byte XORs in the absorb step. +The constraints are in [air.rs](./src/air.rs). Notably we use an XOR lookup table for byte XORs in the absorb step. ## Future Improvement From 3b505ac9b02da23d695fbc0f07dd0266482e8841 Mon Sep 17 00:00:00 2001 From: Zach Langley Date: Tue, 17 Dec 2024 18:50:27 -0500 Subject: [PATCH 65/69] fix: Incorrect lazy initialization of memory data structure (#1113) --- crates/toolchain/tests/src/pairing_tests.rs | 2 +- crates/vm/src/system/memory/manager/memory.rs | 31 +++++++------------ 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/crates/toolchain/tests/src/pairing_tests.rs b/crates/toolchain/tests/src/pairing_tests.rs index 65912bfb86..91faf1e2be 100644 --- a/crates/toolchain/tests/src/pairing_tests.rs +++ b/crates/toolchain/tests/src/pairing_tests.rs @@ -219,7 +219,7 @@ mod bn254 { let io_all = io0.into_iter().chain(io1).collect::>(); - new_air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1, false); + new_air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1, true); Ok(()) } diff --git a/crates/vm/src/system/memory/manager/memory.rs b/crates/vm/src/system/memory/manager/memory.rs index c38016219f..b7d2b1860d 100644 --- a/crates/vm/src/system/memory/manager/memory.rs +++ b/crates/vm/src/system/memory/manager/memory.rs @@ -287,8 +287,8 @@ impl Memory { for i in 0..size as u32 { let block = self .block_data - .get_mut(&(address_space, pointer + i)) - .unwrap(); + .entry((address_space, pointer + i)) + .or_insert_with(|| Self::initial_block_data(pointer + i, self.initial_block_size)); debug_assert!(i == 0 || prev_timestamp == Some(block.timestamp)); prev_timestamp = Some(block.timestamp); block.timestamp = self.timestamp; @@ -310,15 +310,7 @@ impl Memory { .block_data .get(&(address_space, pointer)) .copied() - .unwrap_or_else(|| { - for i in 0..size { - self.block_data.insert( - (address_space, pointer + i as u32), - self.initial_block_data(pointer + i as u32), - ); - } - self.initial_block_data(pointer) - }); + .unwrap_or_else(|| Self::initial_block_data(pointer, self.initial_block_size)); if block_data.pointer == pointer && block_data.size == size { return; @@ -348,10 +340,12 @@ impl Memory { pointer: u32, records: &mut Vec>, ) { - let left_block = self.block_data.get(&(address_space, pointer)).unwrap(); + let left_block = self.block_data.get(&(address_space, pointer)); - let left_timestamp = left_block.timestamp; - let size = left_block.size; + let left_timestamp = left_block.map(|b| b.timestamp).unwrap_or(INITIAL_TIMESTAMP); + let size = left_block + .map(|b| b.size) + .unwrap_or(self.initial_block_size); let right_timestamp = self .block_data @@ -386,16 +380,15 @@ impl Memory { if let Some(block_data) = self.block_data.get(&(address_space, pointer)) { *block_data } else { - self.initial_block_data(pointer) + Self::initial_block_data(pointer, self.initial_block_size) } } - fn initial_block_data(&self, pointer: u32) -> BlockData { - let aligned_pointer = - (pointer / self.initial_block_size as u32) * self.initial_block_size as u32; + fn initial_block_data(pointer: u32, initial_block_size: usize) -> BlockData { + let aligned_pointer = (pointer / initial_block_size as u32) * initial_block_size as u32; BlockData { pointer: aligned_pointer, - size: self.initial_block_size, + size: initial_block_size, timestamp: INITIAL_TIMESTAMP, } } From 4250ce882f1290902de163efd279330c87ba163f Mon Sep 17 00:00:00 2001 From: Golovanov399 Date: Wed, 18 Dec 2024 03:48:10 +0300 Subject: [PATCH 66/69] [fix] `build_guest_package` didn't target any specific package, there was no way to build examples with it (#1114) * Fix the functionality, change the signature * Update the book --- benchmarks/src/utils.rs | 2 +- book/src/writing-apps/build.md | 22 +++------- crates/cli/src/commands/build.rs | 42 +++++++++--------- crates/sdk/src/lib.rs | 4 +- crates/toolchain/build/src/lib.rs | 66 +++++++++++++++-------------- crates/toolchain/tests/src/utils.rs | 13 ++++-- 6 files changed, 73 insertions(+), 76 deletions(-) diff --git a/benchmarks/src/utils.rs b/benchmarks/src/utils.rs index aad24b8af8..592d046efd 100644 --- a/benchmarks/src/utils.rs +++ b/benchmarks/src/utils.rs @@ -60,7 +60,7 @@ pub fn build_bench_program(program_name: &str) -> Result { let target_dir = tempdir()?; // Build guest with default features let guest_opts = GuestOptions::default().with_target_dir(target_dir.path()); - if let Err(Some(code)) = build_guest_package(&pkg, &guest_opts, None) { + if let Err(Some(code)) = build_guest_package(&pkg, &guest_opts, None, &None) { std::process::exit(code); } // Assumes the package has a single target binary diff --git a/book/src/writing-apps/build.md b/book/src/writing-apps/build.md index 2dbac169e3..dc61d50f63 100644 --- a/book/src/writing-apps/build.md +++ b/book/src/writing-apps/build.md @@ -37,34 +37,24 @@ The following flags are available for the `cargo openvm build` command: cargo openvm build --features my_feature ``` -- `--bin` +- `--bin ` - **Description**: Restricts the build to binary targets. If your project has multiple target types (binaries, libraries, examples, etc.), using `--bin` ensures only binary targets are considered. + **Description**: Restricts the build to the binary target with the given name, similar to `cargo build --bin `. If your project has multiple target types (binaries, libraries, examples, etc.), using `--bin ` narrows down the build to the binary target with the given name. **Usage Example**: ```bash - cargo openvm build --bin + cargo openvm build --bin my_bin ``` -- `--example` +- `--example ` - **Description**: Restricts the build to example targets. Projects often include code samples or demos under the examples directory, and this flag focuses on compiling those. + **Description**: Restricts the build to the example target with the given name, similar to `cargo build --example `. Projects often include code samples or demos under the examples directory, and this flag focuses on compiling a specific example. **Usage Example**: ```bash - cargo openvm build --example - ``` - -- `--name ` - - **Description**: Filters targets by name. Only targets whose names contain the given substring will be built. - - **Usage Example**: To build only targets that have `client` in their name: - - ```bash - cargo openvm build --name client + cargo openvm build --example my_example ``` - `--no-transpile` diff --git a/crates/cli/src/commands/build.rs b/crates/cli/src/commands/build.rs index 95841b646f..4db666e821 100644 --- a/crates/cli/src/commands/build.rs +++ b/crates/cli/src/commands/build.rs @@ -42,9 +42,6 @@ pub struct BuildArgs { #[clap(flatten, help = "Filter the target to build")] pub bin_type_filter: BinTypeFilter, - #[arg(long, help = "Target name substring filter")] - pub name: Option, - #[arg( long, default_value = "false", @@ -73,31 +70,30 @@ pub struct BuildArgs { #[derive(Clone, clap::Args)] #[group(required = false, multiple = false)] pub struct BinTypeFilter { - #[arg( - long, - help = "Specifies that the target should be a binary kind when set" - )] - pub bin: bool, + #[arg(long, help = "Specifies that the bin target to build")] + pub bin: Option, - #[arg( - long, - help = "Specifies that the target should be an example kind when set" - )] - pub example: bool, + #[arg(long, help = "Specifies that the example target to build")] + pub example: Option, } // Returns the path to the ELF file if it is unique. pub(crate) fn build(build_args: &BuildArgs) -> Result> { println!("[openvm] Building the package..."); - let target_filter = TargetFilter { - name_substr: build_args.name.clone(), - kind: if build_args.bin_type_filter.bin { - Some("bin".to_string()) - } else if build_args.bin_type_filter.example { - Some("example".to_string()) - } else { - None - }, + let target_filter = if let Some(bin) = &build_args.bin_type_filter.bin { + Some(TargetFilter { + name: bin.clone(), + kind: "bin".to_string(), + }) + } else { + build_args + .bin_type_filter + .example + .as_ref() + .map(|example| TargetFilter { + name: example.clone(), + kind: "example".to_string(), + }) }; let guest_options = GuestOptions { features: build_args.features.clone(), @@ -106,7 +102,7 @@ pub(crate) fn build(build_args: &BuildArgs) -> Result> { let pkg = get_package(&build_args.manifest_dir); // We support builds of libraries with 0 or >1 executables. - let elf_path = match build_guest_package(&pkg, &guest_options, None) { + let elf_path = match build_guest_package(&pkg, &guest_options, None, &target_filter) { Ok(target_dir) => { find_unique_executable(&build_args.manifest_dir, &target_dir, &target_filter) } diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs index 8a182fc0cf..433d0acac7 100644 --- a/crates/sdk/src/lib.rs +++ b/crates/sdk/src/lib.rs @@ -71,10 +71,10 @@ impl Sdk { &self, guest_opts: GuestOptions, pkg_dir: P, - target_filter: &TargetFilter, + target_filter: &Option, ) -> Result { let pkg = get_package(pkg_dir.as_ref()); - let target_dir = match build_guest_package(&pkg, &guest_opts, None) { + let target_dir = match build_guest_package(&pkg, &guest_opts, None, target_filter) { Ok(target_dir) => target_dir, Err(Some(code)) => { return Err(eyre::eyre!("Failed to build guest: code = {}", code)); diff --git a/crates/toolchain/build/src/lib.rs b/crates/toolchain/build/src/lib.rs index 7b95e1729f..b8e0c8a36f 100644 --- a/crates/toolchain/build/src/lib.rs +++ b/crates/toolchain/build/src/lib.rs @@ -69,11 +69,20 @@ pub fn get_target_dir(manifest_path: impl AsRef) -> PathBuf { } /// Returns the target executable directory given `target_dir` and `profile`. -pub fn get_dir_with_profile(target_dir: impl AsRef, profile: &str) -> PathBuf { - target_dir +pub fn get_dir_with_profile( + target_dir: impl AsRef, + profile: &str, + examples: bool, +) -> PathBuf { + let res = target_dir .as_ref() .join("riscv32im-risc0-zkvm-elf") - .join(profile) + .join(profile); + if examples { + res.join("examples") + } else { + res + } } /// When called from a build.rs, returns the current package being built. @@ -241,6 +250,7 @@ pub fn build_guest_package( pkg: &Package, guest_opts: &GuestOptions, runtime_lib: Option<&str>, + target_filter: &Option, ) -> Result> { if is_skip_build() { return Err(None); @@ -279,6 +289,13 @@ pub fn build_guest_package( target_dir.to_str().unwrap(), ]); + if let Some(target_filter) = target_filter { + cmd.args([ + format!("--{}", target_filter.kind).as_str(), + target_filter.name.as_str(), + ]); + } + let profile = if let Some(profile) = &guest_opts.profile { profile } else if is_debug() { @@ -320,31 +337,24 @@ pub fn build_guest_package( if !res.success() { Err(res.code()) } else { - Ok(get_dir_with_profile(&target_dir, profile)) + Ok(get_dir_with_profile( + &target_dir, + profile, + target_filter + .as_ref() + .map(|t| t.kind == "example") + .unwrap_or(false), + )) } } /// A filter for selecting a target from a package. #[derive(Default)] pub struct TargetFilter { - /// A substring of the target name to match. - pub name_substr: Option, + /// The target name to match. + pub name: String, /// The kind of target to match. - pub kind: Option, -} - -impl TargetFilter { - /// Set substring of target name to match. - pub fn with_name_substr(mut self, name_substr: String) -> Self { - self.name_substr = Some(name_substr); - self - } - - /// Set kind of target to match. - pub fn with_kind(mut self, kind: String) -> Self { - self.kind = Some(kind); - self - } + pub kind: String, } /// Finds the unique executable target in the given package and target directory, @@ -352,22 +362,16 @@ impl TargetFilter { pub fn find_unique_executable, Q: AsRef>( pkg_dir: P, target_dir: Q, - target_filter: &TargetFilter, + target_filter: &Option, ) -> eyre::Result { let pkg = get_package(pkg_dir.as_ref()); let elf_paths = pkg .targets .into_iter() .filter(move |target| { - if let Some(name_substr) = &target_filter.name_substr { - if !target.name.contains(name_substr) { - return false; - } - } - if let Some(kind) = &target_filter.kind { - if !target.kind.iter().any(|k| k == kind) { - return false; - } + if let Some(target_filter) = target_filter { + return target.kind.iter().any(|k| k == &target_filter.kind) + && target.name == target_filter.name; } true }) diff --git a/crates/toolchain/tests/src/utils.rs b/crates/toolchain/tests/src/utils.rs index ab44c61144..eb743efd04 100644 --- a/crates/toolchain/tests/src/utils.rs +++ b/crates/toolchain/tests/src/utils.rs @@ -4,7 +4,7 @@ use std::{ }; use eyre::Result; -use openvm_build::{build_guest_package, get_package, is_debug, GuestOptions}; +use openvm_build::{build_guest_package, get_package, is_debug, GuestOptions, TargetFilter}; use openvm_transpiler::{elf::Elf, openvm_platform::memory::MEM_SIZE}; use tempfile::tempdir; @@ -41,10 +41,17 @@ pub fn build_example_program_at_path_with_features>( let target_dir = tempdir()?; // Build guest with default features let guest_opts = GuestOptions::default() - .with_options(["--example", example_name]) .with_features(features) .with_target_dir(target_dir.path()); - if let Err(Some(code)) = build_guest_package(&pkg, &guest_opts, None) { + if let Err(Some(code)) = build_guest_package( + &pkg, + &guest_opts, + None, + &Some(TargetFilter { + name: example_name.to_string(), + kind: "example".to_string(), + }), + ) { std::process::exit(code); } // Assumes the package has a single target binary From d226647eb343d72f9a4c01bc8aacc87dad1c29c7 Mon Sep 17 00:00:00 2001 From: Yi Sun Date: Tue, 17 Dec 2024 23:46:17 -0600 Subject: [PATCH 67/69] chore: fix links and comments (#1115) * chore: fix links and comments * chore: make link relative --- ci/scripts/utils.sh | 6 +++--- docs/specs/circuit.md | 2 +- extensions/native/compiler/Cargo.toml | 2 +- .../pairing/circuit/src/pairing_chip/miller_double_step.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ci/scripts/utils.sh b/ci/scripts/utils.sh index 7c45813bb8..c93bfc9877 100644 --- a/ci/scripts/utils.sh +++ b/ci/scripts/utils.sh @@ -2,7 +2,7 @@ generate_markdown() { local metric_path="$1" local metric_name="$2" local s3_metrics_path="$3" - local afs_root="$4" + local openvm_root="$4" if [[ -f $metric_path ]]; then prev_path="${s3_metrics_path}/main-${metric_name}.json" @@ -10,10 +10,10 @@ generate_markdown() { if [[ $count -gt 0 ]]; then s5cmd cp $prev_path prev.json - python3 ${afs_root}/ci/scripts/metric_unify/main.py $metric_path --prev prev.json --aggregation-json ${afs_root}/ci/scripts/metric_unify/aggregation.json > results.md + python3 ${openvm_root}/ci/scripts/metric_unify/main.py $metric_path --prev prev.json --aggregation-json ${openvm_root}/ci/scripts/metric_unify/aggregation.json > results.md else echo "No previous benchmark on main branch found" - python3 ${afs_root}/ci/scripts/metric_unify/main.py $metric_path --aggregation-json ${afs_root}/ci/scripts/metric_unify/aggregation.json > results.md + python3 ${openvm_root}/ci/scripts/metric_unify/main.py $metric_path --aggregation-json ${openvm_root}/ci/scripts/metric_unify/aggregation.json > results.md fi else echo "No benchmark metrics found at ${metric_path}" diff --git a/docs/specs/circuit.md b/docs/specs/circuit.md index 5c5e3e2daf..82a8fad7dc 100644 --- a/docs/specs/circuit.md +++ b/docs/specs/circuit.md @@ -59,7 +59,7 @@ operands (setting the rest to zero) without paying the cost for the unused opera **Note:** each chip receives an _offset_ on construction, and this offset basically means "where does the class of operations which this chip supports start". For example, if a `FieldArithmeticChip` has offset `0x100`, then its `SUB` operation would be encoded with opcode `0x100 + 1` and not just `1`. -See [ISA spec](https://github.com/axiom-crypto/afs-prototype/blob/main/docs/specs/vm/ISA.md#instruction-list) for +See [ISA spec](./ISA.md) for details. Each chip receives `(timestamp, pc)` on EXECUTION_BUS and "after" diff --git a/extensions/native/compiler/Cargo.toml b/extensions/native/compiler/Cargo.toml index 3251847b2a..569e36cf90 100644 --- a/extensions/native/compiler/Cargo.toml +++ b/extensions/native/compiler/Cargo.toml @@ -20,7 +20,7 @@ openvm-circuit-primitives = { workspace = true } openvm-stark-sdk = { workspace = true } openvm-circuit = { workspace = true } openvm-rv32im-transpiler = { workspace = true } -# disable jemalloc to be compatible with afs-starkbackend +# disable jemalloc to be compatible with stark-backend snark-verifier-sdk = { workspace = true, optional = true } tracing.workspace = true diff --git a/extensions/pairing/circuit/src/pairing_chip/miller_double_step.rs b/extensions/pairing/circuit/src/pairing_chip/miller_double_step.rs index f57460510c..16a3997603 100644 --- a/extensions/pairing/circuit/src/pairing_chip/miller_double_step.rs +++ b/extensions/pairing/circuit/src/pairing_chip/miller_double_step.rs @@ -55,7 +55,7 @@ impl< } } -// Ref: https://github.com/axiom-crypto/afs-prototype/blob/f7d6fa7b8ef247e579740eb652fcdf5a04259c28/lib/ecc-execution/src/common/miller_step.rs#L7 +// Ref: https://github.com/openvm-org/openvm/blob/f7d6fa7b8ef247e579740eb652fcdf5a04259c28/lib/ecc-execution/src/common/miller_step.rs#L7 pub fn miller_double_step_expr( config: ExprBuilderConfig, range_bus: VariableRangeCheckerBus, From 74f0b4f69c420cc50f08c966309b04c47d3238f2 Mon Sep 17 00:00:00 2001 From: Xinding Wei Date: Wed, 18 Dec 2024 08:50:55 -0800 Subject: [PATCH 68/69] Update openvm-stark-backend to v0.1.1-alpha (#1117) --- Cargo.lock | 5 +++-- Cargo.toml | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8a204162c6..eb0ac9efee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4225,7 +4225,7 @@ dependencies = [ [[package]] name = "openvm-stark-backend" version = "0.1.0-alpha" -source = "git+https://github.com/openvm-org/stark-backend.git?tag=v0.1.0-alpha#83b0d048c00bbfba60a5b0732cf923bed5dd6e76" +source = "git+https://github.com/openvm-org/stark-backend.git?tag=v0.1.1-alpha#a995f8e03662e0a18c89edbe4424933b99c42e52" dependencies = [ "async-trait", "cfg-if", @@ -4242,6 +4242,7 @@ dependencies = [ "p3-uni-stark", "p3-util", "rayon", + "rustc-hash 2.1.0", "serde", "thiserror 1.0.69", "tikv-jemallocator", @@ -4251,7 +4252,7 @@ dependencies = [ [[package]] name = "openvm-stark-sdk" version = "0.1.0-alpha" -source = "git+https://github.com/openvm-org/stark-backend.git?tag=v0.1.0-alpha#83b0d048c00bbfba60a5b0732cf923bed5dd6e76" +source = "git+https://github.com/openvm-org/stark-backend.git?tag=v0.1.1-alpha#a995f8e03662e0a18c89edbe4424933b99c42e52" dependencies = [ "derive_more 0.99.18", "ff 0.13.0", diff --git a/Cargo.toml b/Cargo.toml index 5ffb5b07a2..4224139302 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,8 +106,8 @@ openvm-platform = { path = "crates/toolchain/platform", default-features = false openvm-transpiler = { path = "crates/toolchain/transpiler", default-features = false } openvm-circuit = { path = "crates/vm", default-features = false } openvm-circuit-derive = { path = "crates/vm/derive", default-features = false } -openvm-stark-backend = { git = "https://github.com/openvm-org/stark-backend.git", tag = "v0.1.0-alpha", default-features = false } -openvm-stark-sdk = { git = "https://github.com/openvm-org/stark-backend.git", tag = "v0.1.0-alpha", default-features = false } +openvm-stark-backend = { git = "https://github.com/openvm-org/stark-backend.git", tag = "v0.1.1-alpha", default-features = false } +openvm-stark-sdk = { git = "https://github.com/openvm-org/stark-backend.git", tag = "v0.1.1-alpha", default-features = false } # Extensions openvm-algebra-circuit = { path = "extensions/algebra/circuit", default-features = false } From 56caed25346902c25258d03a8e0f4b7dca708066 Mon Sep 17 00:00:00 2001 From: Arayi Khalatyan <127004086+arayikhalatyan@users.noreply.github.com> Date: Wed, 18 Dec 2024 17:43:17 -0500 Subject: [PATCH 69/69] feat: modularize toolchain tests (#1102) * feat: modularize toolchain tests * feat: name integration tests and update layout * feat: remove new_air_test_with_segments * feat: add to CI * feat: initial program lints * feat: book examples crate * fix: integration-tests.md --- .github/workflows/algebra-extension.yml | 6 + .github/workflows/bigint-extension.yml | 6 + .github/workflows/cli.yml | 41 ++-- .github/workflows/ecc.yml | 14 +- .github/workflows/keccak256-extension.yml | 6 + .github/workflows/riscv.yml | 2 +- .github/workflows/rv32im-extension.yml | 6 + .github/workflows/toolchain.yml | 6 - Cargo.lock | 127 ++++++++++ Cargo.toml | 6 + .../tests/programs/.cargo/config.toml | 10 - .../tests/programs/examples/empty.rs | 6 - .../tests/{programs => src}/README.md | 2 +- crates/toolchain/tests/src/basic_tests.rs | 228 ------------------ crates/toolchain/tests/src/ecc_tests.rs | 214 ---------------- crates/toolchain/tests/src/lib.rs | 93 ++++++- crates/toolchain/tests/src/utils.rs | 75 ------ .../tests/tests/riscv_test_vectors.rs | 6 +- .../toolchain/tests/tests/transpiler_tests.rs | 4 +- crates/vm/src/utils/stark_utils.rs | 52 +--- docs/crates/integration-tests.md | 62 +++++ docs/repo/layout.md | 13 +- examples/algebra/Cargo.toml | 22 ++ examples/algebra/openvm.toml | 8 + examples/algebra/src/main.rs | 45 ++++ examples/ecc/Cargo.toml | 23 ++ examples/ecc/openvm.toml | 11 + examples/ecc/src/main.rs | 39 +++ examples/i256/Cargo.toml | 19 ++ examples/i256/openvm.toml | 4 + examples/i256/src/main.rs | 40 +++ examples/keccak/Cargo.toml | 20 ++ examples/keccak/openvm.toml | 4 + examples/keccak/src/main.rs | 27 +++ examples/pairing/Cargo.toml | 27 +++ examples/pairing/openvm.toml | 15 ++ examples/pairing/src/main.rs | 58 +++++ examples/u256/Cargo.toml | 19 ++ examples/u256/openvm.toml | 4 + examples/u256/src/main.rs | 40 +++ extensions/algebra/tests/Cargo.toml | 30 +++ extensions/algebra/tests/programs/Cargo.toml | 32 +++ .../programs/examples/complex-secp256k1.rs | 0 .../programs/examples/complex-two-modulos.rs | 3 +- .../tests/programs/examples/little.rs | 6 +- .../tests/programs/examples/moduli_setup.rs | 0 extensions/algebra/tests/src/lib.rs | 92 +++++++ extensions/bigint/tests/Cargo.toml | 30 +++ extensions/bigint/tests/programs/Cargo.toml | 29 +++ .../tests/programs/examples/book-example2.rs | 40 +++ .../programs/examples/matrix-power-signed.rs | 0 .../examples/matrix-power-unsigned.rs | 0 extensions/bigint/tests/src/lib.rs | 48 ++++ extensions/ecc/tests/Cargo.toml | 29 +++ extensions/ecc/tests/programs/Cargo.toml | 45 ++++ .../tests/programs/examples/decompress.rs | 0 .../ecc}/tests/programs/examples/ec.rs | 0 .../ecc}/tests/programs/examples/ecdsa.rs | 0 extensions/ecc/tests/src/lib.rs | 101 ++++++++ extensions/keccak256/tests/Cargo.toml | 27 +++ .../keccak256/tests/programs/Cargo.toml | 29 +++ .../tests/programs/examples/keccak.rs | 0 extensions/keccak256/tests/src/lib.rs | 31 +++ extensions/pairing/tests/Cargo.toml | 34 +++ .../pairing}/tests/programs/Cargo.toml | 24 +- .../tests/programs/examples/final_exp_hint.rs | 0 .../tests/programs/examples/fp12_mul.rs | 0 .../tests/programs/examples/pairing_check.rs | 0 .../tests/programs/examples/pairing_line.rs | 0 .../programs/examples/pairing_miller_loop.rs | 0 .../programs/examples/pairing_miller_step.rs | 0 .../pairing/tests/src/lib.rs | 162 +++++++++---- extensions/rv32im/tests/Cargo.toml | 28 +++ extensions/rv32im/tests/programs/Cargo.toml | 29 +++ .../tests/programs/examples/collatz.rs | 0 .../tests/programs/examples/fibonacci.rs | 0 .../rv32im}/tests/programs/examples/hint.rs | 0 .../rv32im}/tests/programs/examples/print.rs | 0 .../rv32im}/tests/programs/examples/read.rs | 0 .../rv32im}/tests/programs/examples/reveal.rs | 0 .../tests/programs/examples/tiny-mem-test.rs | 0 extensions/rv32im/tests/src/lib.rs | 187 ++++++++++++++ 82 files changed, 1745 insertions(+), 701 deletions(-) delete mode 100644 crates/toolchain/tests/programs/.cargo/config.toml delete mode 100644 crates/toolchain/tests/programs/examples/empty.rs rename crates/toolchain/tests/{programs => src}/README.md (96%) delete mode 100644 crates/toolchain/tests/src/basic_tests.rs delete mode 100644 crates/toolchain/tests/src/ecc_tests.rs create mode 100644 docs/crates/integration-tests.md create mode 100644 examples/algebra/Cargo.toml create mode 100644 examples/algebra/openvm.toml create mode 100644 examples/algebra/src/main.rs create mode 100644 examples/ecc/Cargo.toml create mode 100644 examples/ecc/openvm.toml create mode 100644 examples/ecc/src/main.rs create mode 100644 examples/i256/Cargo.toml create mode 100644 examples/i256/openvm.toml create mode 100644 examples/i256/src/main.rs create mode 100644 examples/keccak/Cargo.toml create mode 100644 examples/keccak/openvm.toml create mode 100644 examples/keccak/src/main.rs create mode 100644 examples/pairing/Cargo.toml create mode 100644 examples/pairing/openvm.toml create mode 100644 examples/pairing/src/main.rs create mode 100644 examples/u256/Cargo.toml create mode 100644 examples/u256/openvm.toml create mode 100644 examples/u256/src/main.rs create mode 100644 extensions/algebra/tests/Cargo.toml create mode 100644 extensions/algebra/tests/programs/Cargo.toml rename crates/toolchain/tests/programs/examples/complex.rs => extensions/algebra/tests/programs/examples/complex-secp256k1.rs (100%) rename {crates/toolchain => extensions/algebra}/tests/programs/examples/complex-two-modulos.rs (86%) rename {crates/toolchain => extensions/algebra}/tests/programs/examples/little.rs (92%) rename {crates/toolchain => extensions/algebra}/tests/programs/examples/moduli_setup.rs (100%) create mode 100644 extensions/algebra/tests/src/lib.rs create mode 100644 extensions/bigint/tests/Cargo.toml create mode 100644 extensions/bigint/tests/programs/Cargo.toml create mode 100644 extensions/bigint/tests/programs/examples/book-example2.rs rename {crates/toolchain => extensions/bigint}/tests/programs/examples/matrix-power-signed.rs (100%) rename crates/toolchain/tests/programs/examples/matrix-power.rs => extensions/bigint/tests/programs/examples/matrix-power-unsigned.rs (100%) create mode 100644 extensions/bigint/tests/src/lib.rs create mode 100644 extensions/ecc/tests/Cargo.toml create mode 100644 extensions/ecc/tests/programs/Cargo.toml rename {crates/toolchain => extensions/ecc}/tests/programs/examples/decompress.rs (100%) rename {crates/toolchain => extensions/ecc}/tests/programs/examples/ec.rs (100%) rename {crates/toolchain => extensions/ecc}/tests/programs/examples/ecdsa.rs (100%) create mode 100644 extensions/ecc/tests/src/lib.rs create mode 100644 extensions/keccak256/tests/Cargo.toml create mode 100644 extensions/keccak256/tests/programs/Cargo.toml rename {crates/toolchain => extensions/keccak256}/tests/programs/examples/keccak.rs (100%) create mode 100644 extensions/keccak256/tests/src/lib.rs create mode 100644 extensions/pairing/tests/Cargo.toml rename {crates/toolchain => extensions/pairing}/tests/programs/Cargo.toml (72%) rename {crates/toolchain => extensions/pairing}/tests/programs/examples/final_exp_hint.rs (100%) rename {crates/toolchain => extensions/pairing}/tests/programs/examples/fp12_mul.rs (100%) rename {crates/toolchain => extensions/pairing}/tests/programs/examples/pairing_check.rs (100%) rename {crates/toolchain => extensions/pairing}/tests/programs/examples/pairing_line.rs (100%) rename {crates/toolchain => extensions/pairing}/tests/programs/examples/pairing_miller_loop.rs (100%) rename {crates/toolchain => extensions/pairing}/tests/programs/examples/pairing_miller_step.rs (100%) rename crates/toolchain/tests/src/pairing_tests.rs => extensions/pairing/tests/src/lib.rs (79%) create mode 100644 extensions/rv32im/tests/Cargo.toml create mode 100644 extensions/rv32im/tests/programs/Cargo.toml rename {crates/toolchain => extensions/rv32im}/tests/programs/examples/collatz.rs (100%) rename {crates/toolchain => extensions/rv32im}/tests/programs/examples/fibonacci.rs (100%) rename {crates/toolchain => extensions/rv32im}/tests/programs/examples/hint.rs (100%) rename {crates/toolchain => extensions/rv32im}/tests/programs/examples/print.rs (100%) rename {crates/toolchain => extensions/rv32im}/tests/programs/examples/read.rs (100%) rename {crates/toolchain => extensions/rv32im}/tests/programs/examples/reveal.rs (100%) rename {crates/toolchain => extensions/rv32im}/tests/programs/examples/tiny-mem-test.rs (100%) create mode 100644 extensions/rv32im/tests/src/lib.rs diff --git a/.github/workflows/algebra-extension.yml b/.github/workflows/algebra-extension.yml index cbc1d60c03..86d0b22365 100644 --- a/.github/workflows/algebra-extension.yml +++ b/.github/workflows/algebra-extension.yml @@ -34,3 +34,9 @@ jobs: working-directory: extensions/algebra/circuit run: | cargo nextest run --cargo-profile=fast + + - name: Run algebra integration tests + working-directory: extensions/algebra/tests + run: | + rustup component add rust-src --toolchain nightly-2024-10-30 + cargo nextest run --cargo-profile=fast diff --git a/.github/workflows/bigint-extension.yml b/.github/workflows/bigint-extension.yml index 901973a48d..d4f11e637d 100644 --- a/.github/workflows/bigint-extension.yml +++ b/.github/workflows/bigint-extension.yml @@ -34,3 +34,9 @@ jobs: working-directory: extensions/bigint/circuit run: | cargo nextest run --cargo-profile=fast + + - name: Run bigint integration tests + working-directory: extensions/bigint/tests + run: | + rustup component add rust-src --toolchain nightly-2024-10-30 + cargo nextest run --cargo-profile=fast diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index f939196356..c41924b180 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -10,6 +10,7 @@ on: - "crates/vm/**" - "crates/sdk/**" - "crates/cli/**" + - "examples/**" concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} @@ -23,13 +24,12 @@ env: jobs: app-level-cli: runs-on: - - runs-on - - run-id=${{ github.run_id }} - - family=m7 + - runs-on=${{ github.run_id }} + - runner=32cpu-linux-arm64 steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@nightly - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true @@ -37,25 +37,26 @@ jobs: - name: Install solc # svm should support arm64 linux run: (hash svm 2>/dev/null || cargo install --version 0.2.23 svm-rs) && svm install 0.8.19 && solc --version - - name: Install architecture specific tools + - name: Install tools run: | - arch=$(uname -m) - case $arch in - arm64|aarch64) - rustup component add rust-src --toolchain nightly-2024-10-30-aarch64-unknown-linux-gnu - ;; - x86_64|amd64) - rustup component add rust-src --toolchain nightly-2024-10-30-x86_64-unknown-linux-gnu - ;; - *) - echo "Unsupported architecture: $arch" - exit 1 - ;; - esac + rustup component add rust-src --toolchain nightly-2024-10-30 - - name: Setup halo2 + - name: Install cargo-openvm + working-directory: crates/cli + run: | + cargo install --force --locked --path . + + - name: Build book examples + working-directory: examples run: | - bash ./extensions/native/recursion/trusted_setup_s3.sh + for dir in */; do + if [ -f "${dir}Cargo.toml" ]; then + echo "Building ${dir%/}" + cd "$dir" + cargo openvm build + cd .. + fi + done # TODO: CLI build, transpile, run, (keygen), prove, contract, verify - name: Run app-level CLI commands diff --git a/.github/workflows/ecc.yml b/.github/workflows/ecc.yml index 16fe9814a6..bc8e017d1b 100644 --- a/.github/workflows/ecc.yml +++ b/.github/workflows/ecc.yml @@ -50,13 +50,25 @@ jobs: run: | RUST_MIN_STACK=8388608 cargo nextest run --cargo-profile=fast + - name: Run pairing integration tests + working-directory: extensions/pairing/tests + run: | + rustup component add rust-src --toolchain nightly-2024-10-30 + RUST_MIN_STACK=8388608 cargo nextest run --cargo-profile=fast + - name: Install cargo-openvm working-directory: crates/cli run: | - rustup component add rust-src --toolchain nightly-2024-10-30-aarch64-unknown-linux-gnu + rustup component add rust-src --toolchain nightly-2024-10-30 cargo install --force --locked --path . - name: Build openvm-ecc-guest crate for openvm working-directory: extensions/ecc/guest run: | cargo openvm build --no-transpile + + - name: Run ecc integration tests + working-directory: extensions/ecc/tests + run: | + rustup component add rust-src --toolchain nightly-2024-10-30 + cargo nextest run --cargo-profile=fast diff --git a/.github/workflows/keccak256-extension.yml b/.github/workflows/keccak256-extension.yml index 9b99d0f1a9..49ebcebbea 100644 --- a/.github/workflows/keccak256-extension.yml +++ b/.github/workflows/keccak256-extension.yml @@ -34,3 +34,9 @@ jobs: working-directory: extensions/keccak256/circuit run: | cargo nextest run --cargo-profile=fast + + - name: Run keccak256 integration tests + working-directory: extensions/keccak256/tests + run: | + rustup component add rust-src --toolchain nightly-2024-10-30 + cargo nextest run --cargo-profile=fast diff --git a/.github/workflows/riscv.yml b/.github/workflows/riscv.yml index 7b4c27e77d..2cfe053fa4 100644 --- a/.github/workflows/riscv.yml +++ b/.github/workflows/riscv.yml @@ -49,5 +49,5 @@ jobs: - name: Run RISC-V test vector tests working-directory: crates/toolchain/tests run: | - rustup component add rust-src --toolchain nightly-2024-10-30-aarch64-unknown-linux-gnu + rustup component add rust-src --toolchain nightly-2024-10-30 cargo nextest run --cargo-profile=fast --run-ignored only -- test_rv32im_riscv_vector_runtime diff --git a/.github/workflows/rv32im-extension.yml b/.github/workflows/rv32im-extension.yml index 9e69f3a7ef..8431bf845a 100644 --- a/.github/workflows/rv32im-extension.yml +++ b/.github/workflows/rv32im-extension.yml @@ -34,3 +34,9 @@ jobs: working-directory: extensions/rv32im/circuit run: | cargo nextest run --cargo-profile=fast + + - name: Run rv32im integration tests + working-directory: extensions/rv32im/tests + run: | + rustup component add rust-src --toolchain nightly-2024-10-30 + cargo nextest run --cargo-profile=fast diff --git a/.github/workflows/toolchain.yml b/.github/workflows/toolchain.yml index 2064a0e9d8..5a0c6b852c 100644 --- a/.github/workflows/toolchain.yml +++ b/.github/workflows/toolchain.yml @@ -31,9 +31,3 @@ jobs: with: cache-on-failure: true - uses: taiki-e/install-action@nextest - - - name: Run toolchain tests - working-directory: crates/toolchain/tests - run: | - rustup component add rust-src --toolchain nightly-2024-10-30-aarch64-unknown-linux-gnu - RUST_MIN_STACK=8388608 cargo nextest run --cargo-profile=fast diff --git a/Cargo.lock b/Cargo.lock index eb0ac9efee..d397fcacdb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3479,6 +3479,27 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "openvm-algebra-tests" +version = "0.1.0-alpha" +dependencies = [ + "eyre", + "num-bigint-dig", + "openvm", + "openvm-algebra-circuit", + "openvm-algebra-transpiler", + "openvm-build", + "openvm-circuit", + "openvm-circuit-primitives-derive", + "openvm-ecc-circuit", + "openvm-instructions", + "openvm-platform", + "openvm-rv32im-transpiler", + "openvm-stark-sdk", + "openvm-toolchain-tests", + "openvm-transpiler", +] + [[package]] name = "openvm-algebra-transpiler" version = "0.1.0-alpha" @@ -3572,6 +3593,26 @@ dependencies = [ "strum_macros", ] +[[package]] +name = "openvm-bigint-integration-tests" +version = "0.1.0-alpha" +dependencies = [ + "eyre", + "num-bigint-dig", + "openvm", + "openvm-bigint-circuit", + "openvm-bigint-transpiler", + "openvm-build", + "openvm-circuit", + "openvm-circuit-primitives-derive", + "openvm-instructions", + "openvm-platform", + "openvm-rv32im-transpiler", + "openvm-stark-sdk", + "openvm-toolchain-tests", + "openvm-transpiler", +] + [[package]] name = "openvm-bigint-transpiler" version = "0.1.0-alpha" @@ -3765,6 +3806,27 @@ dependencies = [ "strum_macros", ] +[[package]] +name = "openvm-ecc-integration-tests" +version = "0.1.0-alpha" +dependencies = [ + "eyre", + "openvm-algebra-circuit", + "openvm-algebra-transpiler", + "openvm-circuit", + "openvm-circuit-primitives-derive", + "openvm-ecc-circuit", + "openvm-ecc-guest", + "openvm-ecc-transpiler", + "openvm-instructions", + "openvm-keccak256-transpiler", + "openvm-rv32im-transpiler", + "openvm-sdk", + "openvm-stark-sdk", + "openvm-toolchain-tests", + "openvm-transpiler", +] + [[package]] name = "openvm-ecc-sw-setup" version = "0.1.0-alpha" @@ -3855,6 +3917,25 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "openvm-keccak256-integration-tests" +version = "0.1.0-alpha" +dependencies = [ + "eyre", + "openvm", + "openvm-build", + "openvm-circuit", + "openvm-circuit-primitives-derive", + "openvm-instructions", + "openvm-keccak256-circuit", + "openvm-keccak256-transpiler", + "openvm-platform", + "openvm-rv32im-transpiler", + "openvm-stark-sdk", + "openvm-toolchain-tests", + "openvm-transpiler", +] + [[package]] name = "openvm-keccak256-transpiler" version = "0.1.0-alpha" @@ -4061,6 +4142,32 @@ dependencies = [ "subtle", ] +[[package]] +name = "openvm-pairing-integration-tests" +version = "0.1.0-alpha" +dependencies = [ + "eyre", + "num-bigint-dig", + "openvm", + "openvm-algebra-circuit", + "openvm-algebra-transpiler", + "openvm-build", + "openvm-circuit", + "openvm-circuit-primitives-derive", + "openvm-ecc-circuit", + "openvm-ecc-guest", + "openvm-instructions", + "openvm-pairing-circuit", + "openvm-pairing-guest", + "openvm-pairing-transpiler", + "openvm-platform", + "openvm-rv32im-transpiler", + "openvm-stark-sdk", + "openvm-toolchain-tests", + "openvm-transpiler", + "rand", +] + [[package]] name = "openvm-pairing-transpiler" version = "0.1.0-alpha" @@ -4162,6 +4269,26 @@ dependencies = [ "strum_macros", ] +[[package]] +name = "openvm-rv32im-integration-tests" +version = "0.1.0-alpha" +dependencies = [ + "eyre", + "openvm", + "openvm-build", + "openvm-circuit", + "openvm-circuit-primitives-derive", + "openvm-instructions", + "openvm-platform", + "openvm-rv32im-circuit", + "openvm-rv32im-transpiler", + "openvm-stark-sdk", + "openvm-toolchain-tests", + "openvm-transpiler", + "serde", + "test-case", +] + [[package]] name = "openvm-rv32im-transpiler" version = "0.1.0-alpha" diff --git a/Cargo.toml b/Cargo.toml index 4224139302..c8625a62d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,12 +29,15 @@ members = [ "extensions/algebra/transpiler", "extensions/algebra/guest", "extensions/algebra/moduli-setup", + "extensions/algebra/tests", "extensions/bigint/circuit", "extensions/bigint/transpiler", "extensions/bigint/guest", + "extensions/bigint/tests", "extensions/keccak256/circuit", "extensions/keccak256/transpiler", "extensions/keccak256/guest", + "extensions/keccak256/tests", "extensions/native/circuit", "extensions/native/compiler", "extensions/native/compiler/derive", @@ -46,10 +49,13 @@ members = [ "extensions/ecc/transpiler", "extensions/ecc/guest", "extensions/ecc/sw-setup", + "extensions/ecc/tests", "extensions/pairing/circuit", "extensions/pairing/transpiler", "extensions/pairing/guest", + "extensions/pairing/tests", "extensions/rv32-adapters", + "extensions/rv32im/tests", ] exclude = ["crates/sdk/example"] resolver = "2" diff --git a/crates/toolchain/tests/programs/.cargo/config.toml b/crates/toolchain/tests/programs/.cargo/config.toml deleted file mode 100644 index af89db93c0..0000000000 --- a/crates/toolchain/tests/programs/.cargo/config.toml +++ /dev/null @@ -1,10 +0,0 @@ -# # Uncomment to build for openvm -# [build] -# target = "riscv32im-risc0-zkvm-elf" -# -# [target.riscv32im-risc0-zkvm-elf] -# rustflags = ["-C", "passes=lower-atomic", "-C", "link-arg=-Ttext=0x002008000"] -# -# [unstable] -# build-std = ["core", "alloc", "proc_macro", "panic_abort", "std"] -# build-std-features = ["compiler-builtins-mem"] diff --git a/crates/toolchain/tests/programs/examples/empty.rs b/crates/toolchain/tests/programs/examples/empty.rs deleted file mode 100644 index 59aed62604..0000000000 --- a/crates/toolchain/tests/programs/examples/empty.rs +++ /dev/null @@ -1,6 +0,0 @@ -#![no_main] -#![no_std] - -openvm::entry!(main); - -pub fn main() {} diff --git a/crates/toolchain/tests/programs/README.md b/crates/toolchain/tests/src/README.md similarity index 96% rename from crates/toolchain/tests/programs/README.md rename to crates/toolchain/tests/src/README.md index 21a475d5ed..3134c7a697 100644 --- a/crates/toolchain/tests/programs/README.md +++ b/crates/toolchain/tests/src/README.md @@ -29,4 +29,4 @@ To disassemble the ELF to read the instructions, [install cargo-binutils](https: rust-objdump -d target/riscv32im-risc0-zkvm-elf/debug/examples/openvm-fibonacci-program ``` -where `-d` is short for `--disassemble`. +where `-d` is short for `--disassemble`. \ No newline at end of file diff --git a/crates/toolchain/tests/src/basic_tests.rs b/crates/toolchain/tests/src/basic_tests.rs deleted file mode 100644 index 13b6d8c487..0000000000 --- a/crates/toolchain/tests/src/basic_tests.rs +++ /dev/null @@ -1,228 +0,0 @@ -use eyre::Result; -use openvm_bigint_circuit::Int256Rv32Config; -use openvm_bigint_transpiler::Int256TranspilerExtension; -use openvm_circuit::{ - arch::{hasher::poseidon2::vm_poseidon2_hasher, instructions::exe::VmExe, VmExecutor}, - system::memory::tree::public_values::UserPublicValuesProof, - utils::new_air_test_with_min_segments, -}; -use openvm_keccak256_circuit::Keccak256Rv32Config; -use openvm_keccak256_transpiler::Keccak256TranspilerExtension; -use openvm_rv32im_circuit::{Rv32IConfig, Rv32ImConfig}; -use openvm_rv32im_transpiler::{ - Rv32ITranspilerExtension, Rv32IoTranspilerExtension, Rv32MTranspilerExtension, -}; -use openvm_stark_sdk::{openvm_stark_backend::p3_field::AbstractField, p3_baby_bear::BabyBear}; -use openvm_transpiler::{elf::ELF_DEFAULT_MAX_NUM_PUBLIC_VALUES, transpiler::Transpiler, FromElf}; -use test_case::test_case; - -use crate::utils::{build_example_program, build_example_program_with_features}; - -type F = BabyBear; - -#[test_case("fibonacci", 1)] -fn test_rv32i_prove(example_name: &str, min_segments: usize) -> Result<()> { - let elf = build_example_program(example_name)?; - let exe = VmExe::from_elf( - elf, - Transpiler::::default() - .with_extension(Rv32ITranspilerExtension) - .with_extension(Rv32MTranspilerExtension) - .with_extension(Rv32IoTranspilerExtension), - )?; - let config = Rv32IConfig::default(); - new_air_test_with_min_segments(config, exe, vec![], min_segments, true); - Ok(()) -} - -#[test_case("collatz", 1)] -fn test_rv32im_prove(example_name: &str, min_segments: usize) -> Result<()> { - let elf = build_example_program(example_name)?; - let exe = VmExe::from_elf( - elf, - Transpiler::::default() - .with_extension(Rv32ITranspilerExtension) - .with_extension(Rv32IoTranspilerExtension) - .with_extension(Rv32MTranspilerExtension), - )?; - let config = Rv32ImConfig::default(); - new_air_test_with_min_segments(config, exe, vec![], min_segments, true); - Ok(()) -} - -// #[test_case("fibonacci", 1)] -#[test_case("collatz", 1)] -fn test_rv32im_std_prove(example_name: &str, min_segments: usize) -> Result<()> { - let elf = build_example_program_with_features(example_name, ["std"])?; - let exe = VmExe::from_elf( - elf, - Transpiler::::default() - .with_extension(Rv32ITranspilerExtension) - .with_extension(Rv32IoTranspilerExtension) - .with_extension(Rv32MTranspilerExtension), - )?; - let config = Rv32ImConfig::default(); - new_air_test_with_min_segments(config, exe, vec![], min_segments, true); - Ok(()) -} - -#[test] -fn test_read_vec_runtime() -> Result<()> { - let elf = build_example_program("hint")?; - let exe = VmExe::from_elf( - elf, - Transpiler::::default() - .with_extension(Rv32ITranspilerExtension) - .with_extension(Rv32MTranspilerExtension) - .with_extension(Rv32IoTranspilerExtension), - )?; - let config = Rv32IConfig::default(); - let executor = VmExecutor::::new(config); - executor.execute(exe, vec![[0, 1, 2, 3].map(F::from_canonical_u8).to_vec()])?; - Ok(()) -} - -#[test] -fn test_read_runtime() -> Result<()> { - let elf = build_example_program("read")?; - let exe = VmExe::from_elf( - elf, - Transpiler::::default() - .with_extension(Rv32ITranspilerExtension) - .with_extension(Rv32MTranspilerExtension) - .with_extension(Rv32IoTranspilerExtension), - )?; - let config = Rv32IConfig::default(); - let executor = VmExecutor::::new(config); - - #[derive(serde::Serialize)] - struct Foo { - bar: u32, - baz: Vec, - } - let foo = Foo { - bar: 42, - baz: vec![0, 1, 2, 3], - }; - let serialized_foo = openvm::serde::to_vec(&foo).unwrap(); - let input = serialized_foo - .into_iter() - .flat_map(|w| w.to_le_bytes()) - .map(F::from_canonical_u8) - .collect(); - executor.execute(exe, vec![input]).unwrap(); - Ok(()) -} - -#[test] -fn test_reveal_runtime() -> Result<()> { - let elf = build_example_program("reveal")?; - let exe = VmExe::from_elf( - elf, - Transpiler::::default() - .with_extension(Rv32ITranspilerExtension) - .with_extension(Rv32MTranspilerExtension) - .with_extension(Rv32IoTranspilerExtension), - )?; - let config = Rv32IConfig::default(); - let executor = VmExecutor::::new(config.clone()); - let final_memory = executor.execute(exe, vec![])?.unwrap(); - let hasher = vm_poseidon2_hasher(); - let pv_proof = UserPublicValuesProof::compute( - config.system.memory_config.memory_dimensions(), - ELF_DEFAULT_MAX_NUM_PUBLIC_VALUES, - &hasher, - &final_memory, - ); - assert_eq!( - pv_proof.public_values, - [123, 0, 456, 0u32, 0u32, 0u32, 0u32, 0u32] - .into_iter() - .flat_map(|x| x.to_le_bytes()) - .map(F::from_canonical_u8) - .collect::>() - ); - Ok(()) -} - -#[test] -fn test_keccak256_runtime() -> Result<()> { - let elf = build_example_program("keccak")?; - let openvm_exe = VmExe::from_elf( - elf, - Transpiler::::default() - .with_extension(Keccak256TranspilerExtension) - .with_extension(Rv32ITranspilerExtension) - .with_extension(Rv32MTranspilerExtension) - .with_extension(Rv32IoTranspilerExtension), - )?; - let executor = VmExecutor::::new(Keccak256Rv32Config::default()); - executor.execute(openvm_exe, vec![])?; - Ok(()) -} - -#[test] -fn test_print_runtime() -> Result<()> { - let elf = build_example_program("print")?; - let exe = VmExe::from_elf( - elf, - Transpiler::::default() - .with_extension(Rv32ITranspilerExtension) - .with_extension(Rv32MTranspilerExtension) - .with_extension(Rv32IoTranspilerExtension), - )?; - let config = Rv32IConfig::default(); - let executor = VmExecutor::::new(config); - executor.execute(exe, vec![])?; - Ok(()) -} - -#[test] -fn test_matrix_power_runtime() -> Result<()> { - let elf = build_example_program("matrix-power")?; - let openvm_exe = VmExe::from_elf( - elf, - Transpiler::::default() - .with_extension(Rv32ITranspilerExtension) - .with_extension(Rv32MTranspilerExtension) - .with_extension(Rv32IoTranspilerExtension) - .with_extension(Int256TranspilerExtension), - )?; - let config = Int256Rv32Config::default(); - let executor = VmExecutor::::new(config); - executor.execute(openvm_exe, vec![])?; - Ok(()) -} - -#[test] -fn test_matrix_power_signed_runtime() -> Result<()> { - let elf = build_example_program("matrix-power-signed")?; - let openvm_exe = VmExe::from_elf( - elf, - Transpiler::::default() - .with_extension(Rv32ITranspilerExtension) - .with_extension(Rv32MTranspilerExtension) - .with_extension(Rv32IoTranspilerExtension) - .with_extension(Int256TranspilerExtension), - )?; - let config = Int256Rv32Config::default(); - let executor = VmExecutor::::new(config); - executor.execute(openvm_exe, vec![])?; - Ok(()) -} - -#[test] -fn test_tiny_mem_test_runtime() -> Result<()> { - let elf = build_example_program_with_features("tiny-mem-test", ["heap-embedded-alloc"])?; - let exe = VmExe::from_elf( - elf, - Transpiler::::default() - .with_extension(Rv32ITranspilerExtension) - .with_extension(Rv32MTranspilerExtension) - .with_extension(Rv32IoTranspilerExtension), - )?; - let config = Rv32ImConfig::default(); - let executor = VmExecutor::::new(config); - executor.execute(exe, vec![])?; - Ok(()) -} diff --git a/crates/toolchain/tests/src/ecc_tests.rs b/crates/toolchain/tests/src/ecc_tests.rs deleted file mode 100644 index 510a94e5e3..0000000000 --- a/crates/toolchain/tests/src/ecc_tests.rs +++ /dev/null @@ -1,214 +0,0 @@ -use std::str::FromStr; - -use derive_more::derive::From; -use eyre::Result; -use num_bigint_dig::BigUint; -use openvm_algebra_circuit::{ - ModularExtension, ModularExtensionExecutor, ModularExtensionPeriphery, Rv32ModularConfig, - Rv32ModularWithFp2Config, -}; -use openvm_algebra_transpiler::{Fp2TranspilerExtension, ModularTranspilerExtension}; -use openvm_circuit::{ - arch::{ - instructions::exe::VmExe, SystemConfig, SystemExecutor, SystemPeriphery, VmChipComplex, - VmConfig, VmInventoryError, - }, - derive::{AnyEnum, InstructionExecutor, VmConfig}, - utils::new_air_test_with_min_segments, -}; -use openvm_circuit_primitives_derive::{Chip, ChipUsageGetter}; -use openvm_ecc_circuit::{ - CurveConfig, Rv32WeierstrassConfig, WeierstrassExtension, WeierstrassExtensionExecutor, - WeierstrassExtensionPeriphery, SECP256K1_CONFIG, -}; -use openvm_ecc_transpiler::EccTranspilerExtension; -use openvm_keccak256_circuit::{Keccak256, Keccak256Executor, Keccak256Periphery}; -use openvm_keccak256_transpiler::Keccak256TranspilerExtension; -use openvm_rv32im_circuit::{ - Rv32I, Rv32IExecutor, Rv32IPeriphery, Rv32Io, Rv32IoExecutor, Rv32IoPeriphery, Rv32M, - Rv32MExecutor, Rv32MPeriphery, -}; -use openvm_rv32im_transpiler::{ - Rv32ITranspilerExtension, Rv32IoTranspilerExtension, Rv32MTranspilerExtension, -}; -use openvm_stark_backend::p3_field::{AbstractField, PrimeField32}; -use openvm_stark_sdk::p3_baby_bear::BabyBear; -use openvm_transpiler::{transpiler::Transpiler, FromElf}; -use serde::{Deserialize, Serialize}; - -use crate::utils::{build_example_program, build_example_program_with_features}; - -type F = BabyBear; - -#[test] -fn test_moduli_setup_runtime() -> Result<()> { - let elf = build_example_program("moduli_setup")?; - let openvm_exe = VmExe::from_elf( - elf, - Transpiler::::default() - .with_extension(Rv32ITranspilerExtension) - .with_extension(Rv32MTranspilerExtension) - .with_extension(Rv32IoTranspilerExtension) - .with_extension(ModularTranspilerExtension), - )?; - - let moduli = ["4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787", "1000000000000000003", "2305843009213693951"] - .map(|s| num_bigint_dig::BigUint::from_str(s).unwrap()); - let config = Rv32ModularConfig::new(moduli.to_vec()); - new_air_test_with_min_segments(config, openvm_exe, vec![], 1, false); - Ok(()) -} - -#[test] -fn test_modular_runtime() -> Result<()> { - let elf = build_example_program("little")?; - let openvm_exe = VmExe::from_elf( - elf, - Transpiler::::default() - .with_extension(Rv32ITranspilerExtension) - .with_extension(Rv32MTranspilerExtension) - .with_extension(Rv32IoTranspilerExtension) - .with_extension(ModularTranspilerExtension), - )?; - let config = Rv32ModularConfig::new(vec![SECP256K1_CONFIG.modulus.clone()]); - new_air_test_with_min_segments(config, openvm_exe, vec![], 1, false); - Ok(()) -} - -#[test] -fn test_complex_runtime() -> Result<()> { - let elf = build_example_program("complex")?; - let openvm_exe = VmExe::from_elf( - elf, - Transpiler::::default() - .with_extension(Rv32ITranspilerExtension) - .with_extension(Rv32MTranspilerExtension) - .with_extension(Rv32IoTranspilerExtension) - .with_extension(Fp2TranspilerExtension) - .with_extension(ModularTranspilerExtension), - )?; - let config = Rv32ModularWithFp2Config::new(vec![SECP256K1_CONFIG.modulus.clone()]); - // Always run prove, as this caught a bug before. - new_air_test_with_min_segments(config, openvm_exe, vec![], 1, true); - Ok(()) -} - -#[test] -fn test_complex_two_moduli_runtime() -> Result<()> { - let elf = build_example_program("complex-two-modulos")?; - let openvm_exe = VmExe::from_elf( - elf, - Transpiler::::default() - .with_extension(Rv32ITranspilerExtension) - .with_extension(Rv32MTranspilerExtension) - .with_extension(Rv32IoTranspilerExtension) - .with_extension(Fp2TranspilerExtension) - .with_extension(ModularTranspilerExtension), - )?; - let config = Rv32ModularWithFp2Config::new(vec![ - BigUint::from_str("998244353").unwrap(), - BigUint::from_str("1000000007").unwrap(), - ]); - new_air_test_with_min_segments(config, openvm_exe, vec![], 1, false); - Ok(()) -} - -#[test] -fn test_ec_runtime() -> Result<()> { - let elf = build_example_program_with_features("ec", ["k256"])?; - let openvm_exe = VmExe::from_elf( - elf, - Transpiler::::default() - .with_extension(Rv32ITranspilerExtension) - .with_extension(Rv32MTranspilerExtension) - .with_extension(Rv32IoTranspilerExtension) - .with_extension(EccTranspilerExtension) - .with_extension(ModularTranspilerExtension), - )?; - let config = Rv32WeierstrassConfig::new(vec![SECP256K1_CONFIG.clone()]); - new_air_test_with_min_segments(config, openvm_exe, vec![], 1, false); - Ok(()) -} - -#[test] -fn test_decompress() -> Result<()> { - use openvm_ecc_guest::halo2curves::{group::Curve, secp256k1::Secp256k1Affine}; - - let elf = build_example_program_with_features("decompress", ["k256"])?; - let openvm_exe = VmExe::from_elf( - elf, - Transpiler::::default() - .with_extension(Rv32ITranspilerExtension) - .with_extension(Rv32MTranspilerExtension) - .with_extension(Rv32IoTranspilerExtension) - .with_extension(EccTranspilerExtension) - .with_extension(ModularTranspilerExtension), - )?; - let config = Rv32WeierstrassConfig::new(vec![SECP256K1_CONFIG.clone()]); - - let p = Secp256k1Affine::generator(); - let p = (p + p + p).to_affine(); - println!("decompressed: {:?}", p); - let coords: Vec<_> = [p.x.to_bytes(), p.y.to_bytes()] - .concat() - .into_iter() - .map(AbstractField::from_canonical_u8) - .collect(); - new_air_test_with_min_segments(config, openvm_exe, vec![coords], 1, false); - Ok(()) -} - -#[derive(Clone, Debug, VmConfig, Serialize, Deserialize)] -pub struct Rv32ModularKeccak256Config { - #[system] - pub system: SystemConfig, - #[extension] - pub base: Rv32I, - #[extension] - pub mul: Rv32M, - #[extension] - pub io: Rv32Io, - #[extension] - pub modular: ModularExtension, - #[extension] - pub keccak: Keccak256, - #[extension] - pub weierstrass: WeierstrassExtension, -} - -impl Rv32ModularKeccak256Config { - pub fn new(curves: Vec) -> Self { - let primes: Vec = curves - .iter() - .flat_map(|c| [c.modulus.clone(), c.scalar.clone()]) - .collect(); - Self { - system: SystemConfig::default().with_continuations(), - base: Default::default(), - mul: Default::default(), - io: Default::default(), - modular: ModularExtension::new(primes), - keccak: Default::default(), - weierstrass: WeierstrassExtension::new(curves), - } - } -} - -#[test] -fn test_ecdsa_runtime() -> Result<()> { - let elf = build_example_program_with_features("ecdsa", ["k256"])?; - let config = Rv32ModularKeccak256Config::new(vec![SECP256K1_CONFIG.clone()]); - - let openvm_exe = VmExe::from_elf( - elf, - Transpiler::::default() - .with_extension(Rv32ITranspilerExtension) - .with_extension(Rv32MTranspilerExtension) - .with_extension(Rv32IoTranspilerExtension) - .with_extension(Keccak256TranspilerExtension) - .with_extension(EccTranspilerExtension) - .with_extension(ModularTranspilerExtension), - )?; - new_air_test_with_min_segments(config, openvm_exe, vec![], 1, true); - Ok(()) -} diff --git a/crates/toolchain/tests/src/lib.rs b/crates/toolchain/tests/src/lib.rs index 4fcef2270b..8e87641d1c 100644 --- a/crates/toolchain/tests/src/lib.rs +++ b/crates/toolchain/tests/src/lib.rs @@ -1,10 +1,87 @@ -//! Unit tests for OpenVM toolchain starting from rust +use std::{ + fs::read, + path::{Path, PathBuf}, +}; -pub mod utils; +use eyre::Result; +use openvm_build::{build_guest_package, get_package, is_debug, GuestOptions, TargetFilter}; +use openvm_transpiler::{elf::Elf, openvm_platform::memory::MEM_SIZE}; +use tempfile::tempdir; -#[cfg(test)] -pub mod basic_tests; -#[cfg(test)] -pub mod ecc_tests; -#[cfg(test)] -pub mod pairing_tests; +#[macro_export] +macro_rules! get_programs_dir { + () => {{ + let mut dir = ::std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).to_path_buf(); + dir.push("programs"); + dir + }}; + ($subdir:expr) => {{ + let mut dir = ::std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).to_path_buf(); + dir.push($subdir); + dir + }}; +} + +pub fn decode_elf(elf_path: impl AsRef) -> Result { + let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let data = read(dir.join(elf_path))?; + Elf::decode(&data, MEM_SIZE as u32) +} + +pub fn build_example_program(example_name: &str) -> Result { + build_example_program_with_features::<&str>(example_name, []) +} + +pub fn build_example_program_with_features>( + example_name: &str, + features: impl IntoIterator, +) -> Result { + let manifest_dir = get_programs_dir!(); + build_example_program_at_path_with_features(manifest_dir, example_name, features) +} + +pub fn build_example_program_at_path(manifest_dir: PathBuf, example_name: &str) -> Result { + build_example_program_at_path_with_features::<&str>(manifest_dir, example_name, []) +} + +pub fn build_example_program_at_path_with_features>( + manifest_dir: PathBuf, + example_name: &str, + features: impl IntoIterator, +) -> Result { + let pkg = get_package(manifest_dir); + let target_dir = tempdir()?; + // Build guest with default features + let guest_opts = GuestOptions::default() + .with_features(features) + .with_target_dir(target_dir.path()); + if let Err(Some(code)) = build_guest_package( + &pkg, + &guest_opts, + None, + &Some(TargetFilter { + name: example_name.to_string(), + kind: "example".to_string(), + }), + ) { + std::process::exit(code); + } + // Assumes the package has a single target binary + let profile = if is_debug() { "debug" } else { "release" }; + let elf_path = pkg + .targets + .iter() + .find(|target| target.name == example_name) + .map(|target| { + target_dir + .as_ref() + .join("riscv32im-risc0-zkvm-elf") + .join(profile) + .join("examples") + .join(&target.name) + .to_path_buf() + }) + .expect("Could not find target binary"); + let data = read(elf_path)?; + Elf::decode(&data, MEM_SIZE as u32) +} diff --git a/crates/toolchain/tests/src/utils.rs b/crates/toolchain/tests/src/utils.rs index eb743efd04..e69de29bb2 100644 --- a/crates/toolchain/tests/src/utils.rs +++ b/crates/toolchain/tests/src/utils.rs @@ -1,75 +0,0 @@ -use std::{ - fs::read, - path::{Path, PathBuf}, -}; - -use eyre::Result; -use openvm_build::{build_guest_package, get_package, is_debug, GuestOptions, TargetFilter}; -use openvm_transpiler::{elf::Elf, openvm_platform::memory::MEM_SIZE}; -use tempfile::tempdir; - -fn get_programs_dir() -> PathBuf { - let mut dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).to_path_buf(); - dir.push("programs"); - dir -} - -pub fn decode_elf(elf_path: impl AsRef) -> Result { - let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let data = read(dir.join(elf_path))?; - Elf::decode(&data, MEM_SIZE as u32) -} - -pub fn build_example_program(example_name: &str) -> Result { - build_example_program_with_features::<&str>(example_name, []) -} - -pub fn build_example_program_with_features>( - example_name: &str, - features: impl IntoIterator, -) -> Result { - let manifest_dir = get_programs_dir(); - build_example_program_at_path_with_features(manifest_dir, example_name, features) -} - -pub fn build_example_program_at_path_with_features>( - manifest_dir: PathBuf, - example_name: &str, - features: impl IntoIterator, -) -> Result { - let pkg = get_package(manifest_dir); - let target_dir = tempdir()?; - // Build guest with default features - let guest_opts = GuestOptions::default() - .with_features(features) - .with_target_dir(target_dir.path()); - if let Err(Some(code)) = build_guest_package( - &pkg, - &guest_opts, - None, - &Some(TargetFilter { - name: example_name.to_string(), - kind: "example".to_string(), - }), - ) { - std::process::exit(code); - } - // Assumes the package has a single target binary - let profile = if is_debug() { "debug" } else { "release" }; - let elf_path = pkg - .targets - .iter() - .find(|target| target.name == example_name) - .map(|target| { - target_dir - .as_ref() - .join("riscv32im-risc0-zkvm-elf") - .join(profile) - .join("examples") - .join(&target.name) - .to_path_buf() - }) - .expect("Could not find target binary"); - let data = read(elf_path)?; - Elf::decode(&data, MEM_SIZE as u32) -} diff --git a/crates/toolchain/tests/tests/riscv_test_vectors.rs b/crates/toolchain/tests/tests/riscv_test_vectors.rs index 655381cf12..9516b0cd7b 100644 --- a/crates/toolchain/tests/tests/riscv_test_vectors.rs +++ b/crates/toolchain/tests/tests/riscv_test_vectors.rs @@ -3,14 +3,14 @@ use std::{fs::read_dir, path::PathBuf}; use eyre::Result; use openvm_circuit::{ arch::{instructions::exe::VmExe, VmExecutor}, - utils::new_air_test_with_min_segments, + utils::air_test, }; use openvm_rv32im_circuit::Rv32ImConfig; use openvm_rv32im_transpiler::{ Rv32ITranspilerExtension, Rv32IoTranspilerExtension, Rv32MTranspilerExtension, }; use openvm_stark_sdk::p3_baby_bear::BabyBear; -use openvm_toolchain_tests::utils::decode_elf; +use openvm_toolchain_tests::decode_elf; use openvm_transpiler::{transpiler::Transpiler, FromElf}; type F = BabyBear; @@ -80,7 +80,7 @@ fn test_rv32im_riscv_vector_prove() -> Result<()> { )?; let result = std::panic::catch_unwind(|| { - new_air_test_with_min_segments(config.clone(), exe, vec![], 1, true); + air_test(config.clone(), exe); }); match result { diff --git a/crates/toolchain/tests/tests/transpiler_tests.rs b/crates/toolchain/tests/tests/transpiler_tests.rs index 75af59b1a2..da224dd14b 100644 --- a/crates/toolchain/tests/tests/transpiler_tests.rs +++ b/crates/toolchain/tests/tests/transpiler_tests.rs @@ -18,7 +18,7 @@ use openvm_circuit::{ VmInventoryError, }, derive::{AnyEnum, InstructionExecutor, VmConfig}, - utils::new_air_test_with_min_segments, + utils::air_test, }; use openvm_circuit_primitives_derive::{Chip, ChipUsageGetter}; use openvm_ecc_guest::k256::{SECP256K1_MODULUS, SECP256K1_ORDER}; @@ -159,6 +159,6 @@ fn test_terminate_prove() -> Result<()> { .with_extension(Rv32IoTranspilerExtension) .with_extension(ModularTranspilerExtension), )?; - new_air_test_with_min_segments(config, openvm_exe, vec![], 1, true); + air_test(config, openvm_exe); Ok(()) } diff --git a/crates/vm/src/utils/stark_utils.rs b/crates/vm/src/utils/stark_utils.rs index 8281763489..2edae976b4 100644 --- a/crates/vm/src/utils/stark_utils.rs +++ b/crates/vm/src/utils/stark_utils.rs @@ -1,9 +1,7 @@ -use std::borrow::Borrow; - use openvm_instructions::{exe::VmExe, program::Program}; use openvm_stark_backend::{ config::{StarkGenericConfig, Val}, - p3_field::{AbstractField, PrimeField32}, + p3_field::PrimeField32, verifier::VerificationError, Chip, }; @@ -16,12 +14,9 @@ use openvm_stark_sdk::{ p3_baby_bear::BabyBear, }; -use crate::{ - arch::{ - vm::{VirtualMachine, VmExecutor}, - ExitCode, Streams, VmConfig, VmMemoryState, CONNECTOR_AIR_ID, - }, - system::connector::VmConnectorPvs, +use crate::arch::{ + vm::{VirtualMachine, VmExecutor}, + Streams, VmConfig, VmMemoryState, }; pub fn air_test(config: VC, exe: impl Into>) @@ -59,45 +54,6 @@ where final_memory } -/// Executes the VM and returns the final memory state. -pub fn new_air_test_with_min_segments( - config: VC, - exe: impl Into>, - input: impl Into>, - min_segments: usize, - always_prove: bool, -) -> Option> -where - VC: VmConfig, - VC::Executor: Chip, - VC::Periphery: Chip, -{ - setup_tracing(); - let engine = BabyBearPoseidon2Engine::new(FriParameters::standard_fast()); - let vm = VirtualMachine::new(engine, config); - let pk = vm.keygen(); - let mut result = vm.execute_and_generate(exe, input).unwrap(); - let connector_pvs = &result.per_segment.last().unwrap().per_air[CONNECTOR_AIR_ID] - .1 - .raw - .public_values[..]; - let pvs: &VmConnectorPvs<_> = connector_pvs.borrow(); - assert_eq!( - pvs.exit_code, - AbstractField::from_canonical_u32(ExitCode::Success as u32), - "Runtime did not exit successfully" - ); - let final_memory = result.final_memory.take(); - if std::env::var("RUN_AIR_TEST_PROVING").is_ok() || always_prove { - let proofs = vm.prove(&pk, result); - - assert!(proofs.len() >= min_segments); - vm.verify(&pk.get_vk(), proofs) - .expect("segment proofs should verify"); - } - final_memory -} - // TODO[jpw]: this should be deleted once tests switch to new API /// Generates the VM STARK circuit, in the form of AIRs and traces, but does not /// do any proving. Output is the payload of everything the prover needs. diff --git a/docs/crates/integration-tests.md b/docs/crates/integration-tests.md new file mode 100644 index 0000000000..ac02a59b3d --- /dev/null +++ b/docs/crates/integration-tests.md @@ -0,0 +1,62 @@ +# How to write integration tests for an extension + +Make a `test` crate inside the extension folder. As an example, here is the structure of the `rv32im-extension-test` crate: + +``` +extensions/rv32im/tests/ +├── Cargo.toml +├── src +│ └── lib.rs +├── programs +│ └── Cargo.toml +│ └── examples +│ └── example1.rs +│ └── example2.rs +│ └── ... +``` + +The `examples` folder contains the test programs in `rust`. + +`fibonacci.rs` example: +```rust +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +openvm::entry!(main); + +pub fn main() { + let n = core::hint::black_box(1 << 10); + let mut a: u32 = 0; + let mut b: u32 = 1; + for _ in 1..n { + let sum = a + b; + a = b; + b = sum; + } + if a == 0 { + panic!(); + } +} +``` + + +And then to `transpile`, `run`, and `prove` the above program, in the `src/lib.rs` file, you can do: + +```rust +#[test] +fn test_fibonacci_prove() -> Result<()> { + let elf = build_example_program_at_path(get_programs_dir!(), "fibonacci")?; + let exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension), + )?; + let config = Rv32IConfig::default(); + air_test(config, exe, vec![]); + Ok(()) +} +``` + +Note: If the crate with example is not in `./programs`, specify the relative path with `get_programs_dir!("path to the programs crate")`. diff --git a/docs/repo/layout.md b/docs/repo/layout.md index 32c0f53135..39dd77020e 100644 --- a/docs/repo/layout.md +++ b/docs/repo/layout.md @@ -11,6 +11,7 @@ The main components of the repository are: - [Toolchain](#toolchain) - [Circuit Framework](#circuit-framework) - [Circuit Foundations](#circuit-foundations) + - [Examples](#examples) - [Extensions](#extensions) - [RV32IM](#rv32im) - [Native Recursion](#native-recursion) @@ -49,7 +50,7 @@ Command-line binary to compile, execute, and prove guest programs is in [`cli`]( - [`openvm-instructions`](../../crates/toolchain/instructions): OpenVM instruction struct and trait definitions. Also includes some system instruction definitions. - [`openvm-instructions-derive`](../../crates/toolchain/instructions/derive): Procedural macros to derive traits for OpenVM instructions. - [`openvm-macros-common`](../../crates/toolchain/macros): Common library for parsing utilities shared across procedural macros used for custom instruction setup in guest programs. -- [`openvm-toolchain-tests`](../../crates/toolchain/tests): Testing of Rust toolchain including all official RISC-V 32-bit IM test vectors. Currently this is a monolithic crate with tests across many different extensions. We will soon refactor the tests to be more modular. +- [`openvm-toolchain-tests`](../../crates/toolchain/tests): Includes all official RISC-V 32-bit IM test vectors and transpiler tests. Also, provides utilities for writing integration tests for custom extensions. ### Circuit Framework @@ -63,6 +64,10 @@ Command-line binary to compile, execute, and prove guest programs is in [`cli`]( - [`openvm-poseidon2-air`](../../crates/circuits/poseidon2-air): Standalone poseidon2 AIR implementation which is configurable based on the desired maximum constraint degree. - [`openvm-mod-circuit-builder`](../../crates/circuits/mod-builder): General builder for generating a chip for any modular arithmetic expression for a modulus known at compile time. +### Examples + +- [`examples`](../../examples): Examples of guest programs using the OpenVM framework. All of the examples can be built and run using the CLI. + ### Extensions The toolchain, ISA, and VM are simultaenously extendable. All non-system functionality is implemented via extensions, which may be moved to standalone repositories in the future but are presently in this repository for maintainer convenience. @@ -73,6 +78,7 @@ The toolchain, ISA, and VM are simultaenously extendable. All non-system functio - [`openvm-rv32im-transpiler`](../../extensions/rv32im/transpiler): Transpiler extension for RV32IM instructions and IO instructions. - [`openvm-rv32im-guest`](../../extensions/rv32im/guest): Guest library for RV32IM instructions and IO instructions. This is re-exported by the `openvm` crate for convenience. - [`openvm-rv32-adapters`](../../extensions/rv32-adapters): Circuit adapters for other circuit extensions to use to be compatible with the RISC-V 32-bit architecture. +- [`openvm-rv32im-tests`](../../extensions/rv32im/tests): Integration tests for the RV32IM extension. #### Native Recursion @@ -85,12 +91,14 @@ The toolchain, ISA, and VM are simultaenously extendable. All non-system functio - [`openvm-keccak256-circuit`](../../extensions/keccak256/circuit): Circuit extension for the `keccak256` hash function. - [`openvm-keccak256-transpiler`](../../extensions/keccak256/transpiler): Transpiler extension for the `keccak256` hash function. - [`openvm-keccak256-guest`](../../extensions/keccak256/guest): Guest library with intrinsic function for the `keccak256` hash function. +- [`openvm-keccak256-tests`](../../extensions/keccak256/tests): Integration tests for the keccak256 extension. #### Big Integers - [`openvm-bigint-circuit`](../../extensions/bigint/circuit): Circuit extension for `I256` and `U256` big integer operations. - [`openvm-bigint-transpiler`](../../extensions/bigint/transpiler): Transpiler extension for `I256` and `U256` big integer operations. - [`openvm-bigint-guest`](../../extensions/bigint/guest): Guest library with `I256` and `U256` big integers operations using intrinsics for underlying operations. +- [`openvm-bigint-tests`](../../extensions/bigint/tests): Integration tests for the bigint extension. #### Algebra (Modular Arithmetic) @@ -99,6 +107,7 @@ The toolchain, ISA, and VM are simultaenously extendable. All non-system functio - [`openvm-algebra-guest`](../../extensions/algebra/guest): Guest library with traits for modular arithmetic and complex field extension operations. - [`openvm-algebra-moduli-setup`](../../extensions/algebra/moduli-setup): Procedural macros for use in guest program to generate modular arithmetic struct with custom intrinsics for compile-time modulus. - [`openvm-algebra-complex-macros`](../../extensions/algebra/guest/src/field/complex-macros): Procedural macros for use in guest program to generate complex field struct with custom intrinsics for compile-time modulus. +- [`openvm-algebra-tests`](../../extensions/algebra/tests): Integration tests for the algebra extension. #### Elliptic Curve Cryptography @@ -106,9 +115,11 @@ The toolchain, ISA, and VM are simultaenously extendable. All non-system functio - [`openvm-ecc-transpiler`](../../extensions/ecc/transpiler): Transpiler extension for Weierstrass elliptic curve operations for arbitrary compile-time curve. - [`openvm-ecc-guest`](../../extensions/ecc/guest): Guest library with traits for elliptic curve cryptography. Includes implementations of ECDSA and multi-scalar multiplication. - [`openvm-ecc-sw-setup`](../../extensions/ecc/sw-setup): Procedural macros for use in guest program to generate short Weierstrass curve struct with custom intrinsics for compile-time curve. +- [`openvm-ecc-tests`](../../extensions/ecc/tests): Integration tests for the elliptic curve cryptography extension. #### Elliptic Curve Pairing - [`openvm-pairing-circuit`](../../extensions/pairing/circuit): Circuit extension for optimal Ate pairing on BN254 and BLS12-381 curves. - [`openvm-pairing-transpiler`](../../extensions/pairing/transpiler): Transpiler extension for optimal Ate pairing on BN254 and BLS12-381. - [`openvm-pairing-guest`](../../extensions/pairing/guest): Guest library with optimal Ate pairing on BN254 and BLS12-381 and associated constants. Also includes elliptic curve operations for VM runtime with the `halo2curves` feature gate. +- [`openvm-pairing-tests`](../../extensions/pairing/tests): Integration tests for the pairing extension. \ No newline at end of file diff --git a/examples/algebra/Cargo.toml b/examples/algebra/Cargo.toml new file mode 100644 index 0000000000..c9dec9ab49 --- /dev/null +++ b/examples/algebra/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "algebra-example" +version = "0.0.0" +edition = "2021" + +[workspace] +members = [] + +[dependencies] +openvm = { git = "https://github.com/openvm-org/openvm.git" } +openvm-platform = { git = "https://github.com/openvm-org/openvm.git" } +openvm-algebra-guest = { git = "https://github.com/openvm-org/openvm.git" } +openvm-algebra-complex-macros = { git = "https://github.com/openvm-org/openvm.git" } +serde = { version = "1.0.216", default-features = false } + +[features] +default = [] +std = [ + "serde/std", + "openvm/std", + "openvm-algebra-guest/std", +] diff --git a/examples/algebra/openvm.toml b/examples/algebra/openvm.toml new file mode 100644 index 0000000000..fcdce806e4 --- /dev/null +++ b/examples/algebra/openvm.toml @@ -0,0 +1,8 @@ +[app_vm_config.rv32i] +[app_vm_config.rv32m] +[app_vm_config.io] +[app_vm_config.modular] +supported_modulus = ["998244353","1000000007"] + +[app_vm_config.fp2] +supported_modulus = ["998244353","1000000007"] \ No newline at end of file diff --git a/examples/algebra/src/main.rs b/examples/algebra/src/main.rs new file mode 100644 index 0000000000..6aa6e90355 --- /dev/null +++ b/examples/algebra/src/main.rs @@ -0,0 +1,45 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +use openvm_algebra_guest::{IntMod, moduli_setup::*}; + +openvm::entry!(main); + +// This macro will create two structs, `Mod1` and `Mod2`, +// one for arithmetic modulo 998244353, and the other for arithmetic modulo 1000000007. +moduli_declare! { + Mod1 { modulus = "998244353" }, + Mod2 { modulus = "1000000007" } +} + +// This macro will initialize the moduli. +// Now, `Mod1` is the "zeroth" modular struct, and `Mod2` is the "first" one. +moduli_init! { + "998244353", "1000000007" +} + +// This macro will create two structs, `Complex1` and `Complex2`, +// one for arithmetic in the field $\mathbb{F}_{998244353}[x]/(x^2 + 1)$, +// and the other for arithmetic in the field $\mathbb{F}_{1000000007}[x]/(x^2 + 1)$. +openvm_algebra_complex_macros::complex_declare! { + Complex1 { mod_type = Mod1 }, + Complex2 { mod_type = Mod2 }, +} + +// The order of these structs does not matter, +// given that we specify the `mod_idx` parameters properly. +openvm_algebra_complex_macros::complex_init! { + Complex2 { mod_idx = 1 }, Complex1 { mod_idx = 0 }, +} + +pub fn main() { + // Since we only use an arithmetic operation with `Mod1` and not `Mod2`, + // we only need to call `setup_0()` here. + setup_0(); + setup_all_complex_extensions(); + let a = Complex1::new(Mod1::ZERO, Mod1::from_u32(0x3b8) * Mod1::from_u32(0x100000)); // a = -i in the corresponding field + let b = Complex2::new(Mod2::ZERO, Mod2::from_u32(1000000006)); // b = -i in the corresponding field + assert_eq!(a.clone() * &a * &a * &a * &a, a); // a^5 = a + assert_eq!(b.clone() * &b * &b * &b * &b, b); // b^5 = b + // Note that these assertions would fail, have we provided the `mod_idx` parameters wrongly. +} \ No newline at end of file diff --git a/examples/ecc/Cargo.toml b/examples/ecc/Cargo.toml new file mode 100644 index 0000000000..79c5390720 --- /dev/null +++ b/examples/ecc/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "ecc-example" +version = "0.0.0" +edition = "2021" + +[workspace] +members = [] + +[dependencies] +openvm = { git = "https://github.com/openvm-org/openvm.git" } +openvm-platform = { git = "https://github.com/openvm-org/openvm.git" } +openvm-algebra-guest = { git = "https://github.com/openvm-org/openvm.git" } +openvm-ecc-guest = { git = "https://github.com/openvm-org/openvm.git", features = ["k256"] } +hex-literal = { version = "0.4.1", default-features = false } + +[features] +default = [] +std = [ + "openvm/std", + "openvm-algebra-guest/std", + "openvm-ecc-guest/std", +] + diff --git a/examples/ecc/openvm.toml b/examples/ecc/openvm.toml new file mode 100644 index 0000000000..bd968d85a2 --- /dev/null +++ b/examples/ecc/openvm.toml @@ -0,0 +1,11 @@ +[app_vm_config.rv32i] +[app_vm_config.rv32m] +[app_vm_config.io] +[app_vm_config.modular] +supported_modulus = ["115792089237316195423570985008687907853269984665640564039457584007908834671663", "115792089237316195423570985008687907852837564279074904382605163141518161494337"] + +[[app_vm_config.ecc.supported_curves]] +modulus = "115792089237316195423570985008687907853269984665640564039457584007908834671663" +scalar = "115792089237316195423570985008687907852837564279074904382605163141518161494337" +a = "0" +b = "7" \ No newline at end of file diff --git a/examples/ecc/src/main.rs b/examples/ecc/src/main.rs new file mode 100644 index 0000000000..5ca871b4cf --- /dev/null +++ b/examples/ecc/src/main.rs @@ -0,0 +1,39 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +use openvm_algebra_guest::IntMod; + +openvm::entry!(main); + +use hex_literal::hex; +use openvm_ecc_guest::{ + k256::{Secp256k1Coord, Secp256k1Point}, + weierstrass::WeierstrassPoint, +}; + +openvm_algebra_guest::moduli_setup::moduli_init! { + "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F", + "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141" +} + +openvm_ecc_guest::sw_setup::sw_init! { + Secp256k1Coord, +} + +pub fn main() { + setup_all_moduli(); + setup_all_curves(); + let x1 = Secp256k1Coord::from_u32(1); + let y1 = Secp256k1Coord::from_le_bytes(&hex!( + "EEA7767E580D75BC6FDD7F58D2A84C2614FB22586068DB63B346C6E60AF21842" + )); + let p1 = Secp256k1Point::from_xy_nonidentity(x1, y1).unwrap(); + + let x2 = Secp256k1Coord::from_u32(2); + let y2 = Secp256k1Coord::from_le_bytes(&hex!( + "D1A847A8F879E0AEE32544DA5BA0B3BD1703A1F52867A5601FF6454DD8180499" + )); + let p2 = Secp256k1Point::from_xy_nonidentity(x2, y2).unwrap(); + + let _p3 = &p1 + &p2; +} diff --git a/examples/i256/Cargo.toml b/examples/i256/Cargo.toml new file mode 100644 index 0000000000..f26c2b69ce --- /dev/null +++ b/examples/i256/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "i256-example" +version = "0.0.0" +edition = "2021" + +[workspace] +members = [] + +[dependencies] +openvm = { git = "https://github.com/openvm-org/openvm.git" } +openvm-platform = { git = "https://github.com/openvm-org/openvm.git" } +openvm-bigint-guest = { git = "https://github.com/openvm-org/openvm.git" } + +[features] +default = [] +std = [ + "openvm/std", + "openvm-bigint-guest/std", +] diff --git a/examples/i256/openvm.toml b/examples/i256/openvm.toml new file mode 100644 index 0000000000..91288aade5 --- /dev/null +++ b/examples/i256/openvm.toml @@ -0,0 +1,4 @@ +[app_vm_config.rv32i] +[app_vm_config.rv32m] +[app_vm_config.io] +[app_vm_config.bigint] \ No newline at end of file diff --git a/examples/i256/src/main.rs b/examples/i256/src/main.rs new file mode 100644 index 0000000000..1331abbcc6 --- /dev/null +++ b/examples/i256/src/main.rs @@ -0,0 +1,40 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +openvm::entry!(main); +use core::array; +use openvm_bigint_guest::I256; + +const N: usize = 16; +type Matrix = [[I256; N]; N]; + +pub fn get_matrix(val: i32) -> Matrix { + array::from_fn(|_| array::from_fn(|_| I256::from_i32(val))) +} + +pub fn mult(a: &Matrix, b: &Matrix) -> Matrix { + let mut c = get_matrix(0); + for i in 0..N { + for j in 0..N { + for k in 0..N { + c[i][j] += &a[i][k] * &b[k][j]; + } + } + } + c +} + +pub fn get_identity_matrix() -> Matrix { + let mut res = get_matrix(0); + for i in 0..N { + res[i][i] = I256::from_i32(1); + } + res +} + +pub fn main() { + let a: Matrix = get_identity_matrix(); + let b: Matrix = get_matrix(-28); + let c: Matrix = mult(&a, &b); + assert_eq!(c, b); +} \ No newline at end of file diff --git a/examples/keccak/Cargo.toml b/examples/keccak/Cargo.toml new file mode 100644 index 0000000000..f976a40750 --- /dev/null +++ b/examples/keccak/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "keccak-example" +version = "0.0.0" +edition = "2021" + +[workspace] +members = [] + +[dependencies] +openvm = { git = "https://github.com/openvm-org/openvm.git" } +openvm-platform = { git = "https://github.com/openvm-org/openvm.git" } +openvm-keccak256-guest = { git = "https://github.com/openvm-org/openvm.git" } +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } + +[features] +default = [] +std = [ + "openvm/std", + "openvm-keccak256-guest/std", +] diff --git a/examples/keccak/openvm.toml b/examples/keccak/openvm.toml new file mode 100644 index 0000000000..90e80fa97f --- /dev/null +++ b/examples/keccak/openvm.toml @@ -0,0 +1,4 @@ +[app_vm_config.rv32i] +[app_vm_config.rv32m] +[app_vm_config.io] +[app_vm_config.keccak] \ No newline at end of file diff --git a/examples/keccak/src/main.rs b/examples/keccak/src/main.rs new file mode 100644 index 0000000000..1db5e697b6 --- /dev/null +++ b/examples/keccak/src/main.rs @@ -0,0 +1,27 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use alloc::vec::Vec; +use core::hint::black_box; + +use openvm_keccak256_guest::keccak256; +use hex::FromHex; + +openvm::entry!(main); + +pub fn main() { + let test_vectors = [ + ("", "C5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470"), + ("CC", "EEAD6DBFC7340A56CAEDC044696A168870549A6A7F6F56961E84A54BD9970B8A"), + ]; + for (input, expected_output) in test_vectors.iter() { + let input = Vec::from_hex(input).unwrap(); + let expected_output = Vec::from_hex(expected_output).unwrap(); + let output = keccak256(&black_box(input)); + if output != *expected_output { + panic!(); + } + } +} \ No newline at end of file diff --git a/examples/pairing/Cargo.toml b/examples/pairing/Cargo.toml new file mode 100644 index 0000000000..3f22ff107b --- /dev/null +++ b/examples/pairing/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "pairing-example" +version = "0.0.0" +edition = "2021" + +[workspace] +members = [] + +[dependencies] +openvm = { git = "https://github.com/openvm-org/openvm.git" } +openvm-platform = { git = "https://github.com/openvm-org/openvm.git" } +openvm-algebra-guest = { git = "https://github.com/openvm-org/openvm.git" } +openvm-algebra-moduli-setup = { git = "https://github.com/openvm-org/openvm.git" } +openvm-algebra-complex-macros = { git = "https://github.com/openvm-org/openvm.git" } +openvm-ecc-guest = { git = "https://github.com/openvm-org/openvm.git" } +openvm-pairing-guest = { git = "https://github.com/openvm-org/openvm.git", features = ["bls12_381"] } +hex-literal = { version = "0.4.1", default-features = false } + +[features] +default = [] +std = [ + "openvm/std", + "openvm-algebra-guest/std", + "openvm-ecc-guest/std", + "openvm-pairing-guest/std", +] + diff --git a/examples/pairing/openvm.toml b/examples/pairing/openvm.toml new file mode 100644 index 0000000000..94337448fe --- /dev/null +++ b/examples/pairing/openvm.toml @@ -0,0 +1,15 @@ +[app_vm_config.rv32i] +[app_vm_config.rv32m] +[app_vm_config.io] +[app_vm_config.pairing] +supported_curves = ["Bls12_381"] + +[app_vm_config.modular] +supported_modulus = [ + "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787", +] + +[app_vm_config.fp2] +supported_modulus = [ + "4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787", +] \ No newline at end of file diff --git a/examples/pairing/src/main.rs b/examples/pairing/src/main.rs new file mode 100644 index 0000000000..1adf39c20c --- /dev/null +++ b/examples/pairing/src/main.rs @@ -0,0 +1,58 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +use hex_literal::hex; +use openvm_algebra_guest::{field::FieldExtension, IntMod}; +use openvm_ecc_guest::AffinePoint; +use openvm_pairing_guest::{ + bls12_381::{Bls12_381, Fp, Fp2}, + pairing::PairingCheck, +}; + +openvm::entry!(main); + +openvm_algebra_moduli_setup::moduli_init! { + "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", + "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" +} + +openvm_algebra_complex_macros::complex_init! { + Bls12_381Fp2 { mod_idx = 0 }, +} + +pub fn main() { + setup_0(); + setup_all_complex_extensions(); + + let p0 = AffinePoint::new( + Fp::from_be_bytes(&hex!("17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb")), + Fp::from_be_bytes(&hex!("08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1")) + ); + let p1 = AffinePoint::new( + Fp2::from_coeffs([ + Fp::from_be_bytes(&hex!("1638533957d540a9d2370f17cc7ed5863bc0b995b8825e0ee1ea1e1e4d00dbae81f14b0bf3611b78c952aacab827a053")), + Fp::from_be_bytes(&hex!("0a4edef9c1ed7f729f520e47730a124fd70662a904ba1074728114d1031e1572c6c886f6b57ec72a6178288c47c33577")) + ]), + Fp2::from_coeffs([ + Fp::from_be_bytes(&hex!("0468fb440d82b0630aeb8dca2b5256789a66da69bf91009cbfe6bd221e47aa8ae88dece9764bf3bd999d95d71e4c9899")), + Fp::from_be_bytes(&hex!("0f6d4552fa65dd2638b361543f887136a43253d9c66c411697003f7a13c308f5422e1aa0a59c8967acdefd8b6e36ccf3")) + ]), + ); + let q0 = AffinePoint::new( + Fp::from_be_bytes(&hex!("0572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e")), + Fp::from_be_bytes(&hex!("166a9d8cabc673a322fda673779d8e3822ba3ecb8670e461f73bb9021d5fd76a4c56d9d4cd16bd1bba86881979749d28")) + ); + let q1 = AffinePoint::new( + Fp2::from_coeffs([ + Fp::from_be_bytes(&hex!("024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8")), + Fp::from_be_bytes(&hex!("13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e")) + ]), + Fp2::from_coeffs([ + Fp::from_be_bytes(&hex!("0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801")), + Fp::from_be_bytes(&hex!("0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be")) + ]), + ); + + let res = Bls12_381::pairing_check(&[p0, -q0], &[p1, q1]); + assert!(res.is_ok()); +} diff --git a/examples/u256/Cargo.toml b/examples/u256/Cargo.toml new file mode 100644 index 0000000000..eafbf4b7ea --- /dev/null +++ b/examples/u256/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "u256-example" +version = "0.0.0" +edition = "2021" + +[workspace] +members = [] + +[dependencies] +openvm = { git = "https://github.com/openvm-org/openvm.git" } +openvm-platform = { git = "https://github.com/openvm-org/openvm.git" } +openvm-bigint-guest = { git = "https://github.com/openvm-org/openvm.git" } + +[features] +default = [] +std = [ + "openvm/std", + "openvm-bigint-guest/std", +] diff --git a/examples/u256/openvm.toml b/examples/u256/openvm.toml new file mode 100644 index 0000000000..91288aade5 --- /dev/null +++ b/examples/u256/openvm.toml @@ -0,0 +1,4 @@ +[app_vm_config.rv32i] +[app_vm_config.rv32m] +[app_vm_config.io] +[app_vm_config.bigint] \ No newline at end of file diff --git a/examples/u256/src/main.rs b/examples/u256/src/main.rs new file mode 100644 index 0000000000..1331abbcc6 --- /dev/null +++ b/examples/u256/src/main.rs @@ -0,0 +1,40 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +openvm::entry!(main); +use core::array; +use openvm_bigint_guest::I256; + +const N: usize = 16; +type Matrix = [[I256; N]; N]; + +pub fn get_matrix(val: i32) -> Matrix { + array::from_fn(|_| array::from_fn(|_| I256::from_i32(val))) +} + +pub fn mult(a: &Matrix, b: &Matrix) -> Matrix { + let mut c = get_matrix(0); + for i in 0..N { + for j in 0..N { + for k in 0..N { + c[i][j] += &a[i][k] * &b[k][j]; + } + } + } + c +} + +pub fn get_identity_matrix() -> Matrix { + let mut res = get_matrix(0); + for i in 0..N { + res[i][i] = I256::from_i32(1); + } + res +} + +pub fn main() { + let a: Matrix = get_identity_matrix(); + let b: Matrix = get_matrix(-28); + let c: Matrix = mult(&a, &b); + assert_eq!(c, b); +} \ No newline at end of file diff --git a/extensions/algebra/tests/Cargo.toml b/extensions/algebra/tests/Cargo.toml new file mode 100644 index 0000000000..e3d3bdd1a9 --- /dev/null +++ b/extensions/algebra/tests/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "openvm-algebra-tests" +description = "Integration tests for the OpenVM algebra extension" +version.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +openvm-circuit-primitives-derive.workspace = true +openvm-instructions = { workspace = true } +openvm-stark-sdk.workspace = true +openvm-circuit = { workspace = true, features = ["test-utils"] } +openvm-transpiler.workspace = true +openvm-build.workspace = true +openvm-algebra-transpiler.workspace = true +openvm-algebra-circuit.workspace = true +openvm-rv32im-transpiler.workspace = true +openvm-platform = { workspace = true } +openvm = { workspace = true } +openvm-toolchain-tests = { path = "../../../crates/toolchain/tests" } +openvm-ecc-circuit.workspace = true +eyre.workspace = true +num-bigint-dig.workspace = true + + +[features] +default = ["parallel"] +parallel = ["openvm-circuit/parallel"] diff --git a/extensions/algebra/tests/programs/Cargo.toml b/extensions/algebra/tests/programs/Cargo.toml new file mode 100644 index 0000000000..a8a92e4ea1 --- /dev/null +++ b/extensions/algebra/tests/programs/Cargo.toml @@ -0,0 +1,32 @@ +[workspace] +[package] +name = "openvm-algebra-test-programs" +version = "0.0.0" +edition = "2021" + +[dependencies] +openvm = { path = "../../../../crates/toolchain/openvm" } +openvm-platform = { path = "../../../../crates/toolchain/platform" } + +openvm-algebra-guest = { path = "../../guest" } +openvm-algebra-moduli-setup = { path = "../../moduli-setup", default-features = false } +openvm-algebra-complex-macros = { path = "../../guest/src/field/complex-macros", default-features = false } +num-bigint-dig = { version = "0.8", default-features = false } +serde = { version = "1.0", default-features = false, features = [ + "alloc", + "derive", +] } + + +[features] +default = [] +std = [ + "serde/std", + "openvm/std", + "openvm-algebra-guest/std", +] + +[profile.release] +panic = "abort" +lto = "thin" # turn on lto = fat to decrease binary size, but this optimizes out some missing extern links so we shouldn't use it for testing +# strip = "symbols" diff --git a/crates/toolchain/tests/programs/examples/complex.rs b/extensions/algebra/tests/programs/examples/complex-secp256k1.rs similarity index 100% rename from crates/toolchain/tests/programs/examples/complex.rs rename to extensions/algebra/tests/programs/examples/complex-secp256k1.rs diff --git a/crates/toolchain/tests/programs/examples/complex-two-modulos.rs b/extensions/algebra/tests/programs/examples/complex-two-modulos.rs similarity index 86% rename from crates/toolchain/tests/programs/examples/complex-two-modulos.rs rename to extensions/algebra/tests/programs/examples/complex-two-modulos.rs index d9aaa76bdb..0b9f20d03b 100644 --- a/crates/toolchain/tests/programs/examples/complex-two-modulos.rs +++ b/extensions/algebra/tests/programs/examples/complex-two-modulos.rs @@ -1,8 +1,7 @@ #![cfg_attr(not(feature = "std"), no_main)] #![cfg_attr(not(feature = "std"), no_std)] -// use openvm_algebra_guest::{field::ComplexConjugate, DivAssignUnsafe, DivUnsafe, IntMod}; -use openvm_algebra_guest::IntMod; +use openvm_algebra_guest::{DivUnsafe, IntMod}; openvm::entry!(main); diff --git a/crates/toolchain/tests/programs/examples/little.rs b/extensions/algebra/tests/programs/examples/little.rs similarity index 92% rename from crates/toolchain/tests/programs/examples/little.rs rename to extensions/algebra/tests/programs/examples/little.rs index d116fd3e41..117080ff68 100644 --- a/crates/toolchain/tests/programs/examples/little.rs +++ b/extensions/algebra/tests/programs/examples/little.rs @@ -22,10 +22,10 @@ pub fn main() { let mut res = Secp256k1Coord::from_u32(1); let inv = res.clone().div_unsafe(&a); - for i in 0..32 { + for pow_bit in pow { for j in 0..8 { - if pow[i] & (1 << j) != 0 { - res = res * &a; + if pow_bit & (1 << j) != 0 { + res *= &a; } a *= a.clone(); } diff --git a/crates/toolchain/tests/programs/examples/moduli_setup.rs b/extensions/algebra/tests/programs/examples/moduli_setup.rs similarity index 100% rename from crates/toolchain/tests/programs/examples/moduli_setup.rs rename to extensions/algebra/tests/programs/examples/moduli_setup.rs diff --git a/extensions/algebra/tests/src/lib.rs b/extensions/algebra/tests/src/lib.rs new file mode 100644 index 0000000000..efc1253df1 --- /dev/null +++ b/extensions/algebra/tests/src/lib.rs @@ -0,0 +1,92 @@ +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use eyre::Result; + use num_bigint_dig::BigUint; + use openvm_algebra_circuit::{Rv32ModularConfig, Rv32ModularWithFp2Config}; + use openvm_algebra_transpiler::{Fp2TranspilerExtension, ModularTranspilerExtension}; + use openvm_circuit::utils::air_test; + use openvm_ecc_circuit::SECP256K1_CONFIG; + use openvm_instructions::exe::VmExe; + use openvm_rv32im_transpiler::{ + Rv32ITranspilerExtension, Rv32IoTranspilerExtension, Rv32MTranspilerExtension, + }; + use openvm_stark_sdk::p3_baby_bear::BabyBear; + use openvm_toolchain_tests::{build_example_program_at_path, get_programs_dir}; + use openvm_transpiler::{transpiler::Transpiler, FromElf}; + + type F = BabyBear; + + #[test] + fn test_moduli_setup() -> Result<()> { + let elf = build_example_program_at_path(get_programs_dir!(), "moduli_setup")?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + + let moduli = ["4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787", "1000000000000000003", "2305843009213693951"] + .map(|s| BigUint::from_str(s).unwrap()); + let config = Rv32ModularConfig::new(moduli.to_vec()); + air_test(config, openvm_exe); + Ok(()) + } + + #[test] + fn test_modular() -> Result<()> { + let elf = build_example_program_at_path(get_programs_dir!(), "little")?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + let config = Rv32ModularConfig::new(vec![SECP256K1_CONFIG.modulus.clone()]); + air_test(config, openvm_exe); + Ok(()) + } + + #[test] + fn test_complex_two_moduli() -> Result<()> { + let elf = build_example_program_at_path(get_programs_dir!(), "complex-two-modulos")?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(Fp2TranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + let config = Rv32ModularWithFp2Config::new(vec![ + BigUint::from_str("998244353").unwrap(), + BigUint::from_str("1000000007").unwrap(), + ]); + air_test(config, openvm_exe); + Ok(()) + } + + #[test] + fn test_complex() -> Result<()> { + let elf = build_example_program_at_path(get_programs_dir!(), "complex-secp256k1")?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(Fp2TranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + let config = Rv32ModularWithFp2Config::new(vec![SECP256K1_CONFIG.modulus.clone()]); + air_test(config, openvm_exe); + Ok(()) + } +} diff --git a/extensions/bigint/tests/Cargo.toml b/extensions/bigint/tests/Cargo.toml new file mode 100644 index 0000000000..b40c508c07 --- /dev/null +++ b/extensions/bigint/tests/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "openvm-bigint-integration-tests" +description = "Integration tests for the OpenVM bigint extension" +version.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +openvm-circuit-primitives-derive.workspace = true +openvm-instructions = { workspace = true } +openvm-stark-sdk.workspace = true +openvm-circuit = { workspace = true, features = ["test-utils"] } +openvm-transpiler.workspace = true +openvm-build.workspace = true +openvm-bigint-transpiler.workspace = true +openvm-bigint-circuit.workspace = true +openvm-rv32im-transpiler.workspace = true +openvm-platform = { workspace = true } +openvm = { workspace = true } +openvm-toolchain-tests = { path = "../../../crates/toolchain/tests" } +eyre.workspace = true + +[target.'cfg(not(target_os = "zkvm"))'.dependencies] +num-bigint-dig.workspace = true + +[features] +default = ["parallel"] +parallel = ["openvm-circuit/parallel"] diff --git a/extensions/bigint/tests/programs/Cargo.toml b/extensions/bigint/tests/programs/Cargo.toml new file mode 100644 index 0000000000..3725557c4d --- /dev/null +++ b/extensions/bigint/tests/programs/Cargo.toml @@ -0,0 +1,29 @@ +[workspace] +[package] +name = "openvm-bigint-test-programs" +version = "0.0.0" +edition = "2021" + +[dependencies] +openvm = { path = "../../../../crates/toolchain/openvm" } +openvm-platform = { path = "../../../../crates/toolchain/platform" } + +openvm-bigint-guest = { path = "../../guest" } +serde = { version = "1.0", default-features = false, features = [ + "alloc", + "derive", +] } + + +[features] +default = [] +std = [ + "serde/std", + "openvm/std", + "openvm-bigint-guest/std", +] + +[profile.release] +panic = "abort" +lto = "thin" # turn on lto = fat to decrease binary size, but this optimizes out some missing extern links so we shouldn't use it for testing +# strip = "symbols" diff --git a/extensions/bigint/tests/programs/examples/book-example2.rs b/extensions/bigint/tests/programs/examples/book-example2.rs new file mode 100644 index 0000000000..1331abbcc6 --- /dev/null +++ b/extensions/bigint/tests/programs/examples/book-example2.rs @@ -0,0 +1,40 @@ +#![cfg_attr(not(feature = "std"), no_main)] +#![cfg_attr(not(feature = "std"), no_std)] + +openvm::entry!(main); +use core::array; +use openvm_bigint_guest::I256; + +const N: usize = 16; +type Matrix = [[I256; N]; N]; + +pub fn get_matrix(val: i32) -> Matrix { + array::from_fn(|_| array::from_fn(|_| I256::from_i32(val))) +} + +pub fn mult(a: &Matrix, b: &Matrix) -> Matrix { + let mut c = get_matrix(0); + for i in 0..N { + for j in 0..N { + for k in 0..N { + c[i][j] += &a[i][k] * &b[k][j]; + } + } + } + c +} + +pub fn get_identity_matrix() -> Matrix { + let mut res = get_matrix(0); + for i in 0..N { + res[i][i] = I256::from_i32(1); + } + res +} + +pub fn main() { + let a: Matrix = get_identity_matrix(); + let b: Matrix = get_matrix(-28); + let c: Matrix = mult(&a, &b); + assert_eq!(c, b); +} \ No newline at end of file diff --git a/crates/toolchain/tests/programs/examples/matrix-power-signed.rs b/extensions/bigint/tests/programs/examples/matrix-power-signed.rs similarity index 100% rename from crates/toolchain/tests/programs/examples/matrix-power-signed.rs rename to extensions/bigint/tests/programs/examples/matrix-power-signed.rs diff --git a/crates/toolchain/tests/programs/examples/matrix-power.rs b/extensions/bigint/tests/programs/examples/matrix-power-unsigned.rs similarity index 100% rename from crates/toolchain/tests/programs/examples/matrix-power.rs rename to extensions/bigint/tests/programs/examples/matrix-power-unsigned.rs diff --git a/extensions/bigint/tests/src/lib.rs b/extensions/bigint/tests/src/lib.rs new file mode 100644 index 0000000000..866be2f672 --- /dev/null +++ b/extensions/bigint/tests/src/lib.rs @@ -0,0 +1,48 @@ +#[cfg(test)] +mod tests { + use eyre::Result; + use openvm_bigint_circuit::Int256Rv32Config; + use openvm_bigint_transpiler::Int256TranspilerExtension; + use openvm_circuit::utils::air_test; + use openvm_instructions::exe::VmExe; + use openvm_rv32im_transpiler::{ + Rv32ITranspilerExtension, Rv32IoTranspilerExtension, Rv32MTranspilerExtension, + }; + use openvm_stark_sdk::p3_baby_bear::BabyBear; + use openvm_toolchain_tests::{build_example_program_at_path, get_programs_dir}; + use openvm_transpiler::{transpiler::Transpiler, FromElf}; + + type F = BabyBear; + + #[test] + fn test_matrix_power() -> Result<()> { + let elf = build_example_program_at_path(get_programs_dir!(), "matrix-power-unsigned")?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(Int256TranspilerExtension), + )?; + let config = Int256Rv32Config::default(); + air_test(config, openvm_exe); + Ok(()) + } + + #[test] + fn test_matrix_power_signed() -> Result<()> { + let elf = build_example_program_at_path(get_programs_dir!(), "matrix-power-signed")?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(Int256TranspilerExtension), + )?; + let config = Int256Rv32Config::default(); + air_test(config, openvm_exe); + Ok(()) + } +} diff --git a/extensions/ecc/tests/Cargo.toml b/extensions/ecc/tests/Cargo.toml new file mode 100644 index 0000000000..ca7c896228 --- /dev/null +++ b/extensions/ecc/tests/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "openvm-ecc-integration-tests" +description = "Integration tests for the OpenVM ecc extension" +version.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +openvm-circuit-primitives-derive.workspace = true +openvm-instructions = { workspace = true } +openvm-stark-sdk.workspace = true +openvm-circuit = { workspace = true, features = ["test-utils"] } +openvm-transpiler.workspace = true +openvm-algebra-circuit.workspace = true +openvm-algebra-transpiler.workspace = true +openvm-ecc-transpiler.workspace = true +openvm-ecc-circuit.workspace = true +openvm-ecc-guest.workspace = true +openvm-rv32im-transpiler.workspace = true +openvm-keccak256-transpiler.workspace = true +openvm-toolchain-tests = { path = "../../../crates/toolchain/tests" } +openvm-sdk.workspace = true +eyre.workspace = true + +[features] +default = ["parallel"] +parallel = ["openvm-circuit/parallel"] diff --git a/extensions/ecc/tests/programs/Cargo.toml b/extensions/ecc/tests/programs/Cargo.toml new file mode 100644 index 0000000000..ef91d26479 --- /dev/null +++ b/extensions/ecc/tests/programs/Cargo.toml @@ -0,0 +1,45 @@ +[workspace] +[package] +name = "openvm-ecc-test-programs" +version = "0.0.0" +edition = "2021" + +[dependencies] +openvm = { path = "../../../../crates/toolchain/openvm" } +openvm-platform = { path = "../../../../crates/toolchain/platform" } + +openvm-ecc-guest = { path = "../../guest", default-features = false } +openvm-ecc-sw-setup = { path = "../../../../extensions/ecc/sw-setup", default-features = false } +openvm-algebra-guest = { path = "../../../algebra/guest", default-features = false } +openvm-algebra-moduli-setup = { path = "../../../algebra/moduli-setup", default-features = false } +openvm-keccak256-guest = { path = "../../../keccak256/guest", default-features = false } + +serde = { version = "1.0", default-features = false, features = [ + "alloc", + "derive", +] } +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } +hex-literal = { version = "0.4.1", default-features = false } +k256 = { version = "0.13.3", default-features = false, features = [ + "ecdsa-core", + "ecdsa", +], optional = true } + +[features] +default = [] +std = [ + "serde/std", + "openvm/std", + "openvm-ecc-guest/std", +] + +k256 = ["openvm-ecc-guest/k256", "dep:k256"] + +[profile.release] +panic = "abort" +lto = "thin" # turn on lto = fat to decrease binary size, but this optimizes out some missing extern links so we shouldn't use it for testing +# strip = "symbols" + +[[example]] +name = "ec" +required-features = ["k256"] diff --git a/crates/toolchain/tests/programs/examples/decompress.rs b/extensions/ecc/tests/programs/examples/decompress.rs similarity index 100% rename from crates/toolchain/tests/programs/examples/decompress.rs rename to extensions/ecc/tests/programs/examples/decompress.rs diff --git a/crates/toolchain/tests/programs/examples/ec.rs b/extensions/ecc/tests/programs/examples/ec.rs similarity index 100% rename from crates/toolchain/tests/programs/examples/ec.rs rename to extensions/ecc/tests/programs/examples/ec.rs diff --git a/crates/toolchain/tests/programs/examples/ecdsa.rs b/extensions/ecc/tests/programs/examples/ecdsa.rs similarity index 100% rename from crates/toolchain/tests/programs/examples/ecdsa.rs rename to extensions/ecc/tests/programs/examples/ecdsa.rs diff --git a/extensions/ecc/tests/src/lib.rs b/extensions/ecc/tests/src/lib.rs new file mode 100644 index 0000000000..3afd8e155b --- /dev/null +++ b/extensions/ecc/tests/src/lib.rs @@ -0,0 +1,101 @@ +#[cfg(test)] +mod tests { + use eyre::Result; + use openvm_algebra_circuit::ModularExtension; + use openvm_algebra_transpiler::ModularTranspilerExtension; + use openvm_circuit::{ + arch::{instructions::exe::VmExe, SystemConfig}, + utils::{air_test, air_test_with_min_segments}, + }; + use openvm_ecc_circuit::{Rv32WeierstrassConfig, WeierstrassExtension, SECP256K1_CONFIG}; + use openvm_ecc_transpiler::EccTranspilerExtension; + use openvm_keccak256_transpiler::Keccak256TranspilerExtension; + use openvm_rv32im_transpiler::{ + Rv32ITranspilerExtension, Rv32IoTranspilerExtension, Rv32MTranspilerExtension, + }; + use openvm_sdk::config::SdkVmConfig; + use openvm_stark_backend::p3_field::AbstractField; + use openvm_stark_sdk::{openvm_stark_backend, p3_baby_bear::BabyBear}; + use openvm_toolchain_tests::{build_example_program_at_path_with_features, get_programs_dir}; + use openvm_transpiler::{transpiler::Transpiler, FromElf}; + type F = BabyBear; + + #[test] + fn test_ec() -> Result<()> { + let elf = build_example_program_at_path_with_features(get_programs_dir!(), "ec", ["k256"])?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(EccTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + let config = Rv32WeierstrassConfig::new(vec![SECP256K1_CONFIG.clone()]); + air_test(config, openvm_exe); + Ok(()) + } + + #[test] + fn test_decompress() -> Result<()> { + use openvm_ecc_guest::halo2curves::{group::Curve, secp256k1::Secp256k1Affine}; + + let elf = build_example_program_at_path_with_features( + get_programs_dir!(), + "decompress", + ["k256"], + )?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(EccTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + let config = Rv32WeierstrassConfig::new(vec![SECP256K1_CONFIG.clone()]); + + let p = Secp256k1Affine::generator(); + let p = (p + p + p).to_affine(); + println!("decompressed: {:?}", p); + let coords: Vec<_> = [p.x.to_bytes(), p.y.to_bytes()] + .concat() + .into_iter() + .map(AbstractField::from_canonical_u8) + .collect(); + air_test_with_min_segments(config, openvm_exe, vec![coords], 1); + Ok(()) + } + + #[test] + fn test_ecdsa() -> Result<()> { + let elf = + build_example_program_at_path_with_features(get_programs_dir!(), "ecdsa", ["k256"])?; + let config = SdkVmConfig::builder() + .system(SystemConfig::default().with_continuations().into()) + .rv32i(Default::default()) + .rv32m(Default::default()) + .io(Default::default()) + .modular(ModularExtension::new(vec![ + SECP256K1_CONFIG.modulus.clone(), + SECP256K1_CONFIG.scalar.clone(), + ])) + .keccak(Default::default()) + .ecc(WeierstrassExtension::new(vec![SECP256K1_CONFIG.clone()])) + .build(); + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(Keccak256TranspilerExtension) + .with_extension(EccTranspilerExtension) + .with_extension(ModularTranspilerExtension), + )?; + air_test(config, openvm_exe); + Ok(()) + } +} diff --git a/extensions/keccak256/tests/Cargo.toml b/extensions/keccak256/tests/Cargo.toml new file mode 100644 index 0000000000..6148313ea3 --- /dev/null +++ b/extensions/keccak256/tests/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "openvm-keccak256-integration-tests" +description = "Integration tests for the OpenVM keccak256 extension" +version.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +openvm-circuit-primitives-derive.workspace = true +openvm-instructions = { workspace = true } +openvm-stark-sdk.workspace = true +openvm-circuit = { workspace = true, features = ["test-utils"] } +openvm-transpiler.workspace = true +openvm-build.workspace = true +openvm-keccak256-transpiler.workspace = true +openvm-keccak256-circuit.workspace = true +openvm-rv32im-transpiler.workspace = true +openvm-platform = { workspace = true } +openvm = { workspace = true } +openvm-toolchain-tests = { path = "../../../crates/toolchain/tests" } +eyre.workspace = true + +[features] +default = ["parallel"] +parallel = ["openvm-circuit/parallel"] diff --git a/extensions/keccak256/tests/programs/Cargo.toml b/extensions/keccak256/tests/programs/Cargo.toml new file mode 100644 index 0000000000..e5e0adce43 --- /dev/null +++ b/extensions/keccak256/tests/programs/Cargo.toml @@ -0,0 +1,29 @@ +[workspace] +[package] +name = "openvm-keccak256-test-programs" +version = "0.0.0" +edition = "2021" + +[dependencies] +openvm = { path = "../../../../crates/toolchain/openvm" } +openvm-platform = { path = "../../../../crates/toolchain/platform" } +openvm-keccak256-guest = { path = "../../guest" } +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } +serde = { version = "1.0", default-features = false, features = [ + "alloc", + "derive", +] } + + +[features] +default = [] +std = [ + "serde/std", + "openvm/std", + "openvm-keccak256-guest/std", +] + +[profile.release] +panic = "abort" +lto = "thin" # turn on lto = fat to decrease binary size, but this optimizes out some missing extern links so we shouldn't use it for testing +# strip = "symbols" diff --git a/crates/toolchain/tests/programs/examples/keccak.rs b/extensions/keccak256/tests/programs/examples/keccak.rs similarity index 100% rename from crates/toolchain/tests/programs/examples/keccak.rs rename to extensions/keccak256/tests/programs/examples/keccak.rs diff --git a/extensions/keccak256/tests/src/lib.rs b/extensions/keccak256/tests/src/lib.rs new file mode 100644 index 0000000000..ecb2f524ee --- /dev/null +++ b/extensions/keccak256/tests/src/lib.rs @@ -0,0 +1,31 @@ +#[cfg(test)] +mod tests { + use eyre::Result; + use openvm_circuit::utils::air_test; + use openvm_instructions::exe::VmExe; + use openvm_keccak256_circuit::Keccak256Rv32Config; + use openvm_keccak256_transpiler::Keccak256TranspilerExtension; + use openvm_rv32im_transpiler::{ + Rv32ITranspilerExtension, Rv32IoTranspilerExtension, Rv32MTranspilerExtension, + }; + use openvm_stark_sdk::p3_baby_bear::BabyBear; + use openvm_toolchain_tests::{build_example_program_at_path, get_programs_dir}; + use openvm_transpiler::{transpiler::Transpiler, FromElf}; + + type F = BabyBear; + + #[test] + fn test_keccak256() -> Result<()> { + let elf = build_example_program_at_path(get_programs_dir!(), "keccak")?; + let openvm_exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Keccak256TranspilerExtension) + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension), + )?; + air_test(Keccak256Rv32Config::default(), openvm_exe); + Ok(()) + } +} diff --git a/extensions/pairing/tests/Cargo.toml b/extensions/pairing/tests/Cargo.toml new file mode 100644 index 0000000000..b536d32ad1 --- /dev/null +++ b/extensions/pairing/tests/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "openvm-pairing-integration-tests" +description = "Integration tests for the OpenVM pairing extension" +version.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +openvm-circuit-primitives-derive.workspace = true +openvm-instructions = { workspace = true } +openvm-stark-sdk.workspace = true +openvm-circuit = { workspace = true, features = ["test-utils"] } +openvm-transpiler.workspace = true +openvm-build.workspace = true +openvm-algebra-circuit.workspace = true +openvm-algebra-transpiler.workspace = true +openvm-pairing-circuit.workspace = true +openvm-pairing-transpiler.workspace = true +openvm-pairing-guest.workspace = true +openvm-ecc-circuit.workspace = true +openvm-ecc-guest.workspace = true +openvm-rv32im-transpiler.workspace = true +openvm-platform = { workspace = true } +openvm = { workspace = true } +openvm-toolchain-tests = { path = "../../../crates/toolchain/tests" } +eyre.workspace = true +num-bigint-dig.workspace = true +rand.workspace = true + +[features] +default = ["parallel"] +parallel = ["openvm-circuit/parallel"] diff --git a/crates/toolchain/tests/programs/Cargo.toml b/extensions/pairing/tests/programs/Cargo.toml similarity index 72% rename from crates/toolchain/tests/programs/Cargo.toml rename to extensions/pairing/tests/programs/Cargo.toml index bdbce056ef..891e806dcc 100644 --- a/crates/toolchain/tests/programs/Cargo.toml +++ b/extensions/pairing/tests/programs/Cargo.toml @@ -1,21 +1,20 @@ [workspace] [package] -name = "openvm-test-programs" +name = "openvm-pairing-test-programs" version = "0.0.0" edition = "2021" [dependencies] -openvm = { path = "../../openvm" } -openvm-platform = { path = "../../platform" } +openvm = { path = "../../../../crates/toolchain/openvm" } +openvm-platform = { path = "../../../../crates/toolchain/platform" } openvm-algebra-guest = { path = "../../../../extensions/algebra/guest", default-features = false } openvm-algebra-moduli-setup = { path = "../../../../extensions/algebra/moduli-setup", default-features = false } openvm-algebra-complex-macros = { path = "../../../../extensions/algebra/guest/src/field/complex-macros", default-features = false } -openvm-bigint-guest = { path = "../../../../extensions/bigint/guest" } openvm-ecc-guest = { path = "../../../../extensions/ecc/guest", default-features = false } openvm-ecc-sw-setup = { path = "../../../../extensions/ecc/sw-setup", default-features = false } -openvm-keccak256-guest = { path = "../../../../extensions/keccak256/guest" } openvm-pairing-guest = { path = "../../../../extensions/pairing/guest", default-features = false } + serde = { version = "1.0", default-features = false, features = [ "alloc", "derive", @@ -32,30 +31,17 @@ default = [] std = [ "serde/std", "openvm/std", - "openvm-algebra-guest/std", - "openvm-bigint-guest/std", - "openvm-ecc-guest/std", - "openvm-keccak256-guest/std", "openvm-pairing-guest/std", ] + bn254 = ["openvm-pairing-guest/bn254"] bls12_381 = ["openvm-pairing-guest/bls12_381"] -k256 = ["openvm-ecc-guest/k256", "dep:k256"] -heap-embedded-alloc = ["openvm/heap-embedded-alloc"] [profile.release] panic = "abort" lto = "thin" # turn on lto = fat to decrease binary size, but this optimizes out some missing extern links so we shouldn't use it for testing # strip = "symbols" -[[example]] -name = "ec" -required-features = ["k256"] - -[[example]] -name = "ecdsa" -required-features = ["k256"] - [[example]] name = "final_exp_hint" required-features = ["bls12_381"] diff --git a/crates/toolchain/tests/programs/examples/final_exp_hint.rs b/extensions/pairing/tests/programs/examples/final_exp_hint.rs similarity index 100% rename from crates/toolchain/tests/programs/examples/final_exp_hint.rs rename to extensions/pairing/tests/programs/examples/final_exp_hint.rs diff --git a/crates/toolchain/tests/programs/examples/fp12_mul.rs b/extensions/pairing/tests/programs/examples/fp12_mul.rs similarity index 100% rename from crates/toolchain/tests/programs/examples/fp12_mul.rs rename to extensions/pairing/tests/programs/examples/fp12_mul.rs diff --git a/crates/toolchain/tests/programs/examples/pairing_check.rs b/extensions/pairing/tests/programs/examples/pairing_check.rs similarity index 100% rename from crates/toolchain/tests/programs/examples/pairing_check.rs rename to extensions/pairing/tests/programs/examples/pairing_check.rs diff --git a/crates/toolchain/tests/programs/examples/pairing_line.rs b/extensions/pairing/tests/programs/examples/pairing_line.rs similarity index 100% rename from crates/toolchain/tests/programs/examples/pairing_line.rs rename to extensions/pairing/tests/programs/examples/pairing_line.rs diff --git a/crates/toolchain/tests/programs/examples/pairing_miller_loop.rs b/extensions/pairing/tests/programs/examples/pairing_miller_loop.rs similarity index 100% rename from crates/toolchain/tests/programs/examples/pairing_miller_loop.rs rename to extensions/pairing/tests/programs/examples/pairing_miller_loop.rs diff --git a/crates/toolchain/tests/programs/examples/pairing_miller_step.rs b/extensions/pairing/tests/programs/examples/pairing_miller_step.rs similarity index 100% rename from crates/toolchain/tests/programs/examples/pairing_miller_step.rs rename to extensions/pairing/tests/programs/examples/pairing_miller_step.rs diff --git a/crates/toolchain/tests/src/pairing_tests.rs b/extensions/pairing/tests/src/lib.rs similarity index 79% rename from crates/toolchain/tests/src/pairing_tests.rs rename to extensions/pairing/tests/src/lib.rs index 91faf1e2be..9ec3002c6f 100644 --- a/crates/toolchain/tests/src/pairing_tests.rs +++ b/extensions/pairing/tests/src/lib.rs @@ -1,41 +1,40 @@ #![allow(non_snake_case)] -use eyre::Result; -use openvm_algebra_circuit::{Fp2Extension, ModularExtension}; -use openvm_circuit::{ - arch::{instructions::exe::VmExe, SystemConfig}, - utils::new_air_test_with_min_segments, -}; -use openvm_ecc_circuit::WeierstrassExtension; -use openvm_ecc_guest::{algebra::field::FieldExtension, halo2curves::ff::Field, AffinePoint}; -use openvm_pairing_circuit::{PairingCurve, PairingExtension, Rv32PairingConfig}; -use openvm_pairing_guest::pairing::{EvaluatedLine, FinalExp, LineMulDType, MultiMillerLoop}; -use openvm_stark_sdk::{openvm_stark_backend::p3_field::AbstractField, p3_baby_bear::BabyBear}; -use openvm_transpiler::{transpiler::Transpiler, FromElf}; -use rand::SeedableRng; - -type F = BabyBear; - +#[cfg(test)] mod bn254 { use std::iter; + use eyre::Result; + use openvm_algebra_circuit::{Fp2Extension, ModularExtension}; use openvm_algebra_transpiler::{Fp2TranspilerExtension, ModularTranspilerExtension}; - use openvm_ecc_guest::halo2curves::{ - bn256::{Fq12, Fq2, Fr, G1Affine, G2Affine}, - ff::Field, + use openvm_circuit::{arch::SystemConfig, utils::air_test_with_min_segments}; + use openvm_ecc_circuit::WeierstrassExtension; + use openvm_ecc_guest::{ + algebra::field::FieldExtension, + halo2curves::{ + bn256::{Fq12, Fq2, Fr, G1Affine, G2Affine}, + ff::Field, + }, + AffinePoint, }; + use openvm_instructions::exe::VmExe; + use openvm_pairing_circuit::{PairingCurve, PairingExtension, Rv32PairingConfig}; use openvm_pairing_guest::{ - affine_point::AffineCoords, bn254::BN254_MODULUS, halo2curves_shims::bn254::Bn254, - pairing::MillerStep, + affine_point::AffineCoords, + bn254::BN254_MODULUS, + halo2curves_shims::bn254::Bn254, + pairing::{EvaluatedLine, LineMulDType, MillerStep, MultiMillerLoop}, }; use openvm_pairing_transpiler::PairingTranspilerExtension; use openvm_rv32im_transpiler::{ Rv32ITranspilerExtension, Rv32IoTranspilerExtension, Rv32MTranspilerExtension, }; - use openvm_transpiler::transpiler::Transpiler; + use openvm_stark_sdk::{openvm_stark_backend::p3_field::AbstractField, p3_baby_bear::BabyBear}; + use openvm_toolchain_tests::{build_example_program_at_path_with_features, get_programs_dir}; + use openvm_transpiler::{transpiler::Transpiler, FromElf}; + use rand::SeedableRng; - use super::*; - use crate::utils::build_example_program_with_features; + type F = BabyBear; pub fn get_testing_config() -> Rv32PairingConfig { let primes = [BN254_MODULUS.clone()]; @@ -53,7 +52,11 @@ mod bn254 { #[test] fn test_bn254_fp12_mul() -> Result<()> { - let elf = build_example_program_with_features("fp12_mul", ["bn254"])?; + let elf = build_example_program_at_path_with_features( + get_programs_dir!(), + "fp12_mul", + ["bn254"], + )?; let openvm_exe = VmExe::from_elf( elf, Transpiler::::default() @@ -77,13 +80,17 @@ mod bn254 { .map(AbstractField::from_canonical_u8) .collect::>(); - new_air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io], 1, false); + air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io], 1); Ok(()) } #[test] fn test_bn254_line_functions() -> Result<()> { - let elf = build_example_program_with_features("pairing_line", ["bn254"])?; + let elf = build_example_program_at_path_with_features( + get_programs_dir!(), + "pairing_line", + ["bn254"], + )?; let openvm_exe = VmExe::from_elf( elf, Transpiler::::default() @@ -129,13 +136,17 @@ mod bn254 { let io_all = io0.into_iter().chain(io1).collect::>(); - new_air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1, false); + air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1); Ok(()) } #[test] fn test_bn254_miller_step() -> Result<()> { - let elf = build_example_program_with_features("pairing_miller_step", ["bn254"])?; + let elf = build_example_program_at_path_with_features( + get_programs_dir!(), + "pairing_miller_step", + ["bn254"], + )?; let openvm_exe = VmExe::from_elf( elf, Transpiler::::default() @@ -172,13 +183,17 @@ mod bn254 { let io_all = io0.into_iter().chain(io1).collect::>(); - new_air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1, false); + air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1); Ok(()) } #[test] fn test_bn254_miller_loop() -> Result<()> { - let elf = build_example_program_with_features("pairing_miller_loop", ["bn254"])?; + let elf = build_example_program_at_path_with_features( + get_programs_dir!(), + "pairing_miller_loop", + ["bn254"], + )?; let openvm_exe = VmExe::from_elf( elf, Transpiler::::default() @@ -219,13 +234,17 @@ mod bn254 { let io_all = io0.into_iter().chain(io1).collect::>(); - new_air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1, true); + air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1); Ok(()) } #[test] fn test_bn254_pairing_check() -> Result<()> { - let elf = build_example_program_with_features("pairing_check", ["bn254"])?; + let elf = build_example_program_at_path_with_features( + get_programs_dir!(), + "pairing_check", + ["bn254"], + )?; let openvm_exe = VmExe::from_elf( elf, Transpiler::::default() @@ -270,31 +289,45 @@ mod bn254 { let io_all = io0.into_iter().chain(io1).collect::>(); - // Always run proving for just pairing check - new_air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1, true); + air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1); Ok(()) } } +#[cfg(test)] mod bls12_381 { + use eyre::Result; + use openvm_algebra_circuit::{Fp2Extension, ModularExtension}; use openvm_algebra_transpiler::{Fp2TranspilerExtension, ModularTranspilerExtension}; + use openvm_circuit::{ + arch::{instructions::exe::VmExe, SystemConfig}, + utils::air_test_with_min_segments, + }; + use openvm_ecc_circuit::WeierstrassExtension; use openvm_ecc_guest::{ - algebra::IntMod, - halo2curves::bls12_381::{Fq12, Fq2, Fr, G1Affine, G2Affine}, + algebra::{field::FieldExtension, IntMod}, + halo2curves::{ + bls12_381::{Fq12, Fq2, Fr, G1Affine, G2Affine}, + ff::Field, + }, AffinePoint, }; + use openvm_pairing_circuit::{PairingCurve, PairingExtension, Rv32PairingConfig}; use openvm_pairing_guest::{ bls12_381::BLS12_381_MODULUS, halo2curves_shims::bls12_381::Bls12_381, - pairing::{LineMulMType, MillerStep}, + pairing::{EvaluatedLine, FinalExp, LineMulMType, MillerStep, MultiMillerLoop}, }; use openvm_pairing_transpiler::PairingTranspilerExtension; use openvm_rv32im_transpiler::{ Rv32ITranspilerExtension, Rv32IoTranspilerExtension, Rv32MTranspilerExtension, }; + use openvm_stark_sdk::{openvm_stark_backend::p3_field::AbstractField, p3_baby_bear::BabyBear}; + use openvm_toolchain_tests::{build_example_program_at_path_with_features, get_programs_dir}; + use openvm_transpiler::{transpiler::Transpiler, FromElf}; + use rand::SeedableRng; - use super::*; - use crate::utils::build_example_program_with_features; + type F = BabyBear; pub fn get_testing_config() -> Rv32PairingConfig { let primes = [BLS12_381_MODULUS.clone()]; @@ -312,7 +345,11 @@ mod bls12_381 { #[test] fn test_bls12_381_fp12_mul() -> Result<()> { - let elf = build_example_program_with_features("fp12_mul", ["bls12_381"])?; + let elf = build_example_program_at_path_with_features( + get_programs_dir!(), + "fp12_mul", + ["bls12_381"], + )?; let openvm_exe = VmExe::from_elf( elf, Transpiler::::default() @@ -336,13 +373,17 @@ mod bls12_381 { .map(AbstractField::from_canonical_u8) .collect::>(); - new_air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io], 1, false); + air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io], 1); Ok(()) } #[test] fn test_bls12_381_line_functions() -> Result<()> { - let elf = build_example_program_with_features("pairing_line", ["bls12_381"])?; + let elf = build_example_program_at_path_with_features( + get_programs_dir!(), + "pairing_line", + ["bls12_381"], + )?; let openvm_exe = VmExe::from_elf( elf, Transpiler::::default() @@ -389,13 +430,17 @@ mod bls12_381 { let io_all = io0.into_iter().chain(io1).collect::>(); - new_air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1, false); + air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1); Ok(()) } #[test] fn test_bls12_381_miller_step() -> Result<()> { - let elf = build_example_program_with_features("pairing_miller_step", ["bls12_381"])?; + let elf = build_example_program_at_path_with_features( + get_programs_dir!(), + "pairing_miller_step", + ["bls12_381"], + )?; let openvm_exe = VmExe::from_elf( elf, Transpiler::::default() @@ -432,13 +477,17 @@ mod bls12_381 { let io_all = io0.into_iter().chain(io1).collect::>(); - new_air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1, false); + air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1); Ok(()) } #[test] fn test_bls12_381_miller_loop() -> Result<()> { - let elf = build_example_program_with_features("pairing_miller_loop", ["bls12_381"])?; + let elf = build_example_program_at_path_with_features( + get_programs_dir!(), + "pairing_miller_loop", + ["bls12_381"], + )?; let openvm_exe = VmExe::from_elf( elf, Transpiler::::default() @@ -485,13 +534,17 @@ mod bls12_381 { let io_all = io0.into_iter().chain(io1).collect::>(); - new_air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1, false); + air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1); Ok(()) } #[test] fn test_bls12_381_pairing_check() -> Result<()> { - let elf = build_example_program_with_features("pairing_check", ["bls12_381"])?; + let elf = build_example_program_at_path_with_features( + get_programs_dir!(), + "pairing_check", + ["bls12_381"], + )?; let openvm_exe = VmExe::from_elf( elf, Transpiler::::default() @@ -535,14 +588,17 @@ mod bls12_381 { let io_all = io0.into_iter().chain(io1).collect::>(); - // Always run proving for just pairing check - new_air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1, true); + air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io_all], 1); Ok(()) } #[test] fn test_bls12_381_final_exp_hint() -> Result<()> { - let elf = build_example_program_with_features("final_exp_hint", ["bls12_381"])?; + let elf = build_example_program_at_path_with_features( + get_programs_dir!(), + "final_exp_hint", + ["bls12_381"], + )?; let openvm_exe = VmExe::from_elf( elf, Transpiler::::default() @@ -585,7 +641,7 @@ mod bls12_381 { .flat_map(|w| w.to_le_bytes()) .map(F::from_canonical_u8) .collect(); - new_air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io], 1, false); + air_test_with_min_segments(get_testing_config(), openvm_exe, vec![io], 1); Ok(()) } } diff --git a/extensions/rv32im/tests/Cargo.toml b/extensions/rv32im/tests/Cargo.toml new file mode 100644 index 0000000000..731ebb0729 --- /dev/null +++ b/extensions/rv32im/tests/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "openvm-rv32im-integration-tests" +description = "Integration tests for the OpenVM rv32im extension" +version.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +openvm-circuit-primitives-derive.workspace = true +openvm-instructions = { workspace = true } +openvm-stark-sdk.workspace = true +openvm-circuit = { workspace = true, features = ["test-utils"] } +openvm-transpiler.workspace = true +openvm-build.workspace = true +openvm-rv32im-circuit.workspace = true +openvm-rv32im-transpiler.workspace = true +openvm-platform = { workspace = true } +openvm = { workspace = true } +openvm-toolchain-tests = { path = "../../../crates/toolchain/tests" } +eyre.workspace = true +test-case.workspace = true +serde = { workspace = true, features = ["alloc"] } + +[features] +default = ["parallel"] +parallel = ["openvm-circuit/parallel"] diff --git a/extensions/rv32im/tests/programs/Cargo.toml b/extensions/rv32im/tests/programs/Cargo.toml new file mode 100644 index 0000000000..3239eed123 --- /dev/null +++ b/extensions/rv32im/tests/programs/Cargo.toml @@ -0,0 +1,29 @@ +[workspace] +[package] +name = "openvm-rv32im-test-programs" +version = "0.0.0" +edition = "2021" + +[dependencies] +openvm = { path = "../../../../crates/toolchain/openvm", default-features = false } +openvm-platform = { path = "../../../../crates/toolchain/platform", default-features = false } +openvm-rv32im-guest = { path = "../../guest", default-features = false } +serde = { version = "1.0", default-features = false, features = [ + "alloc", + "derive", +] } + + +[features] +default = [] +std = [ + "serde/std", + "openvm/std", +] + +heap-embedded-alloc = ["openvm/heap-embedded-alloc"] + +[profile.release] +panic = "abort" +lto = "thin" # turn on lto = fat to decrease binary size, but this optimizes out some missing extern links so we shouldn't use it for testing +# strip = "symbols" diff --git a/crates/toolchain/tests/programs/examples/collatz.rs b/extensions/rv32im/tests/programs/examples/collatz.rs similarity index 100% rename from crates/toolchain/tests/programs/examples/collatz.rs rename to extensions/rv32im/tests/programs/examples/collatz.rs diff --git a/crates/toolchain/tests/programs/examples/fibonacci.rs b/extensions/rv32im/tests/programs/examples/fibonacci.rs similarity index 100% rename from crates/toolchain/tests/programs/examples/fibonacci.rs rename to extensions/rv32im/tests/programs/examples/fibonacci.rs diff --git a/crates/toolchain/tests/programs/examples/hint.rs b/extensions/rv32im/tests/programs/examples/hint.rs similarity index 100% rename from crates/toolchain/tests/programs/examples/hint.rs rename to extensions/rv32im/tests/programs/examples/hint.rs diff --git a/crates/toolchain/tests/programs/examples/print.rs b/extensions/rv32im/tests/programs/examples/print.rs similarity index 100% rename from crates/toolchain/tests/programs/examples/print.rs rename to extensions/rv32im/tests/programs/examples/print.rs diff --git a/crates/toolchain/tests/programs/examples/read.rs b/extensions/rv32im/tests/programs/examples/read.rs similarity index 100% rename from crates/toolchain/tests/programs/examples/read.rs rename to extensions/rv32im/tests/programs/examples/read.rs diff --git a/crates/toolchain/tests/programs/examples/reveal.rs b/extensions/rv32im/tests/programs/examples/reveal.rs similarity index 100% rename from crates/toolchain/tests/programs/examples/reveal.rs rename to extensions/rv32im/tests/programs/examples/reveal.rs diff --git a/crates/toolchain/tests/programs/examples/tiny-mem-test.rs b/extensions/rv32im/tests/programs/examples/tiny-mem-test.rs similarity index 100% rename from crates/toolchain/tests/programs/examples/tiny-mem-test.rs rename to extensions/rv32im/tests/programs/examples/tiny-mem-test.rs diff --git a/extensions/rv32im/tests/src/lib.rs b/extensions/rv32im/tests/src/lib.rs new file mode 100644 index 0000000000..d44ee4c7eb --- /dev/null +++ b/extensions/rv32im/tests/src/lib.rs @@ -0,0 +1,187 @@ +#[cfg(test)] +mod tests { + use eyre::Result; + use openvm_circuit::{ + arch::{hasher::poseidon2::vm_poseidon2_hasher, VmExecutor}, + system::memory::tree::public_values::UserPublicValuesProof, + utils::{air_test, air_test_with_min_segments}, + }; + use openvm_instructions::exe::VmExe; + use openvm_rv32im_circuit::{Rv32IConfig, Rv32ImConfig}; + use openvm_rv32im_transpiler::{ + Rv32ITranspilerExtension, Rv32IoTranspilerExtension, Rv32MTranspilerExtension, + }; + use openvm_stark_sdk::{openvm_stark_backend::p3_field::AbstractField, p3_baby_bear::BabyBear}; + use openvm_toolchain_tests::{ + build_example_program_at_path, build_example_program_at_path_with_features, + get_programs_dir, + }; + use openvm_transpiler::{ + elf::ELF_DEFAULT_MAX_NUM_PUBLIC_VALUES, transpiler::Transpiler, FromElf, + }; + use test_case::test_case; + + type F = BabyBear; + + #[test_case("fibonacci", 1)] + fn test_rv32i(example_name: &str, min_segments: usize) -> Result<()> { + let elf = build_example_program_at_path(get_programs_dir!(), example_name)?; + let exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension), + )?; + let config = Rv32IConfig::default(); + air_test_with_min_segments(config, exe, vec![], min_segments); + Ok(()) + } + + #[test_case("collatz", 1)] + fn test_rv32im(example_name: &str, min_segments: usize) -> Result<()> { + let elf = build_example_program_at_path(get_programs_dir!(), example_name)?; + let exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(Rv32MTranspilerExtension), + )?; + let config = Rv32ImConfig::default(); + air_test_with_min_segments(config, exe, vec![], min_segments); + Ok(()) + } + + // #[test_case("fibonacci", 1)] + #[test_case("collatz", 1)] + fn test_rv32im_std(example_name: &str, min_segments: usize) -> Result<()> { + let elf = build_example_program_at_path_with_features( + get_programs_dir!(), + example_name, + ["std"], + )?; + let exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32IoTranspilerExtension) + .with_extension(Rv32MTranspilerExtension), + )?; + let config = Rv32ImConfig::default(); + air_test_with_min_segments(config, exe, vec![], min_segments); + Ok(()) + } + + #[test] + fn test_read_vec() -> Result<()> { + let elf = build_example_program_at_path(get_programs_dir!(), "hint")?; + let exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension), + )?; + let config = Rv32IConfig::default(); + let input = vec![[0, 1, 2, 3].map(F::from_canonical_u8).to_vec()]; + air_test_with_min_segments(config, exe, input, 1); + Ok(()) + } + + #[test] + fn test_read() -> Result<()> { + let elf = build_example_program_at_path(get_programs_dir!(), "read")?; + let exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension), + )?; + let config = Rv32IConfig::default(); + + #[derive(serde::Serialize)] + struct Foo { + bar: u32, + baz: Vec, + } + let foo = Foo { + bar: 42, + baz: vec![0, 1, 2, 3], + }; + let serialized_foo = openvm::serde::to_vec(&foo).unwrap(); + let input = serialized_foo + .into_iter() + .flat_map(|w| w.to_le_bytes()) + .map(F::from_canonical_u8) + .collect(); + air_test_with_min_segments(config, exe, vec![input], 1); + Ok(()) + } + + #[test] + fn test_reveal() -> Result<()> { + let elf = build_example_program_at_path(get_programs_dir!(), "reveal")?; + let exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension), + )?; + let config = Rv32IConfig::default(); + let executor = VmExecutor::::new(config.clone()); + let final_memory = executor.execute(exe, vec![])?.unwrap(); + let hasher = vm_poseidon2_hasher(); + let pv_proof = UserPublicValuesProof::compute( + config.system.memory_config.memory_dimensions(), + ELF_DEFAULT_MAX_NUM_PUBLIC_VALUES, + &hasher, + &final_memory, + ); + assert_eq!( + pv_proof.public_values, + [123, 0, 456, 0u32, 0u32, 0u32, 0u32, 0u32] + .into_iter() + .flat_map(|x| x.to_le_bytes()) + .map(F::from_canonical_u8) + .collect::>() + ); + Ok(()) + } + + #[test] + fn test_print() -> Result<()> { + let elf = build_example_program_at_path(get_programs_dir!(), "print")?; + let exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension), + )?; + let config = Rv32IConfig::default(); + air_test(config, exe); + Ok(()) + } + + #[test] + fn test_tiny_mem_test() -> Result<()> { + let elf = build_example_program_at_path_with_features( + get_programs_dir!(), + "tiny-mem-test", + ["heap-embedded-alloc"], + )?; + let exe = VmExe::from_elf( + elf, + Transpiler::::default() + .with_extension(Rv32ITranspilerExtension) + .with_extension(Rv32MTranspilerExtension) + .with_extension(Rv32IoTranspilerExtension), + )?; + let config = Rv32ImConfig::default(); + air_test(config, exe); + Ok(()) + } +}