diff --git a/Cargo.lock b/Cargo.lock index 1ae4054..881712d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,93 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addchain" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e33f6a175ec6a9e0aca777567f9ff7c3deefc255660df887e7fa3585e9801d8" +dependencies = [ + "num-bigint 0.3.3", + "num-integer", + "num-traits", +] + [[package]] name = "anyhow" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest", + "itertools 0.10.5", + "num-bigint 0.4.6", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std", + "digest", + "num-bigint 0.4.6", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.7.6" @@ -28,7 +109,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -67,6 +148,15 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "2.10.0" @@ -85,6 +175,40 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79834656f71332577234b50bfc009996f7449e0c056884e6a02492ded0ca2f3" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "blake3" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -94,6 +218,19 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bls12_381" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c196a77437e7cc2fb515ce413a6401291578b5afc8ecb29a3c7ab957f05941" +dependencies = [ + "ff 0.12.1", + "group 0.12.1", + "pairing", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "borsh" version = "1.6.0" @@ -114,7 +251,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -211,6 +348,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "convert_case" version = "0.10.0" @@ -260,6 +403,16 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + [[package]] name = "crossbeam-epoch" version = "0.9.18" @@ -320,7 +473,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -364,6 +517,7 @@ dependencies = [ "serde_json", "sha2", "signature", + "sp1-lib", "tokio", "tracing", "urlencoding", @@ -411,7 +565,18 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] @@ -440,7 +605,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -453,7 +618,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.114", "unicode-xid", ] @@ -477,7 +642,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -491,6 +656,7 @@ dependencies = [ "elliptic-curve", "rfc6979", "signature", + "spki", ] [[package]] @@ -514,6 +680,18 @@ dependencies = [ "subtle", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "elf" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" + [[package]] name = "elliptic-curve" version = "0.13.8" @@ -523,9 +701,12 @@ dependencies = [ "base16ct", "crypto-bigint", "digest", - "ff", + "ff 0.13.1", "generic-array", - "group", + "group 0.13.0", + "hkdf", + "pem-rfc7468", + "pkcs8", "rand_core 0.6.4", "sec1", "subtle", @@ -556,7 +737,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -581,16 +762,45 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "bitvec", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "ff" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ + "bitvec", + "byteorder", + "ff_derive", "rand_core 0.6.4", "subtle", ] +[[package]] +name = "ff_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f10d12652036b0e99197587c6ba87a8fc3031986499973c030d8b44fcc151b60" +dependencies = [ + "addchain", + "num-bigint 0.3.3", + "num-integer", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -707,7 +917,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -740,6 +950,12 @@ dependencies = [ "slab", ] +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + [[package]] name = "generic-array" version = "0.14.9" @@ -791,13 +1007,25 @@ dependencies = [ "wasip3", ] +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff 0.12.1", + "memuse", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "group" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "ff", + "ff 0.13.1", "rand_core 0.6.4", "subtle", ] @@ -821,6 +1049,29 @@ dependencies = [ "tracing", ] +[[package]] +name = "halo2" +version = "0.1.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a23c779b38253fe1538102da44ad5bd5378495a61d2c4ee18d64eaa61ae5995" +dependencies = [ + "halo2_proofs", +] + +[[package]] +name = "halo2_proofs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e925780549adee8364c7f2b685c753f6f3df23bde520c67416e93bf615933760" +dependencies = [ + "blake2b_simd", + "ff 0.12.1", + "group 0.12.1", + "pasta_curves 0.4.1", + "rand_core 0.6.4", + "rayon", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -900,6 +1151,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -1144,7 +1404,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1208,6 +1468,33 @@ dependencies = [ "serde", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.17" @@ -1224,6 +1511,29 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jubjub" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a575df5f985fe1cd5b2b05664ff6accfc46559032b954529fd225a2168d27b0f" +dependencies = [ + "bitvec", + "bls12_381", + "ff 0.12.1", + "group 0.12.1", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1299,6 +1609,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memuse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d97bbf43eb4f088f8ca469930cde17fa036207c9a5e02ccc5107c4e8b17c964" + [[package]] name = "mime" version = "0.3.17" @@ -1350,6 +1666,27 @@ dependencies = [ "tempfile", ] +[[package]] +name = "num-bigint" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint-dig" version = "0.8.6" @@ -1429,7 +1766,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1462,6 +1799,147 @@ dependencies = [ "sha2", ] +[[package]] +name = "p3-bn254-fr" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9abf208fbfe540d6e2a6caaa2a9a345b1c8cb23ffdcdfcc6987244525d4fc821" +dependencies = [ + "ff 0.13.1", + "num-bigint 0.4.6", + "p3-field", + "p3-poseidon2", + "p3-symmetric", + "rand 0.8.5", + "serde", +] + +[[package]] +name = "p3-challenger" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b725b453bbb35117a1abf0ddfd900b0676063d6e4231e0fa6bb0d76018d8ad" +dependencies = [ + "p3-field", + "p3-maybe-rayon", + "p3-symmetric", + "p3-util", + "serde", + "tracing", +] + +[[package]] +name = "p3-dft" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56a1f81101bff744b7ebba7f4497e917a2c6716d6e62736e4a56e555a2d98cb7" +dependencies = [ + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-util", + "tracing", +] + +[[package]] +name = "p3-field" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36459d4acb03d08097d713f336c7393990bb489ab19920d4f68658c7a5c10968" +dependencies = [ + "itertools 0.12.1", + "num-bigint 0.4.6", + "num-traits", + "p3-util", + "rand 0.8.5", + "serde", +] + +[[package]] +name = "p3-koala-bear" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1f52bcb6be38bdc8fa6b38b3434d4eedd511f361d4249fd798c6a5ef817b40" +dependencies = [ + "num-bigint 0.4.6", + "p3-field", + "p3-mds", + "p3-poseidon2", + "p3-symmetric", + "rand 0.8.5", + "serde", +] + +[[package]] +name = "p3-matrix" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5583e9cd136a4095a25c41a9edfdcce2dfae58ef01639317813bdbbd5b55c583" +dependencies = [ + "itertools 0.12.1", + "p3-field", + "p3-maybe-rayon", + "p3-util", + "rand 0.8.5", + "serde", + "tracing", +] + +[[package]] +name = "p3-maybe-rayon" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e524d47a49fb4265611303339c4ef970d892817b006cc330dad18afb91e411b1" + +[[package]] +name = "p3-mds" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f6cb8edcb276033d43769a3725570c340d2ed6f35c3cca4cddeee07718fa376" +dependencies = [ + "itertools 0.12.1", + "p3-dft", + "p3-field", + "p3-matrix", + "p3-symmetric", + "p3-util", + "rand 0.8.5", +] + +[[package]] +name = "p3-poseidon2" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a26197df2097b98ab7038d59a01e1fe1a0f545e7e04aa9436b2454b1836654f" +dependencies = [ + "gcd", + "p3-field", + "p3-mds", + "p3-symmetric", + "rand 0.8.5", + "serde", +] + +[[package]] +name = "p3-symmetric" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a1d3b5202096bca57cde912fbbb9cbaedaf5ac7c42a924c7166b98709d64d21" +dependencies = [ + "itertools 0.12.1", + "p3-field", + "serde", +] + +[[package]] +name = "p3-util" +version = "0.3.2-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec5f0388aa6d935ca3a17444086120f393f0b2f0816010b5ff95998c1c4095e3" +dependencies = [ + "serde", +] + [[package]] name = "p384" version = "0.13.1" @@ -1474,6 +1952,15 @@ dependencies = [ "sha2", ] +[[package]] +name = "pairing" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135590d8bdba2b31346f9cd1fb2a912329f5135e832a4f422942eb6ead8b6b3b" +dependencies = [ + "group 0.12.1", +] + [[package]] name = "parity-scale-codec" version = "3.7.5" @@ -1499,7 +1986,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1525,6 +2012,42 @@ dependencies = [ "windows-link", ] +[[package]] +name = "pasta_curves" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc65faf8e7313b4b1fbaa9f7ca917a0eed499a9663be71477f87993604341d8" +dependencies = [ + "blake2b_simd", + "ff 0.12.1", + "group 0.12.1", + "lazy_static", + "rand 0.8.5", + "static_assertions", + "subtle", +] + +[[package]] +name = "pasta_curves" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095" +dependencies = [ + "blake2b_simd", + "ff 0.13.1", + "group 0.13.0", + "lazy_static", + "rand 0.8.5", + "static_assertions", + "subtle", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pem" version = "3.0.6" @@ -1620,7 +2143,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.114", ] [[package]] @@ -1710,7 +2233,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1723,7 +2246,7 @@ dependencies = [ "proc-macro2", "pyo3-build-config", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1817,6 +2340,7 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", "rand_chacha 0.3.1", "rand_core 0.6.4", ] @@ -1869,6 +2393,26 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -2074,7 +2618,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -2101,6 +2645,7 @@ dependencies = [ "base16ct", "der", "generic-array", + "pkcs8", "subtle", "zeroize", ] @@ -2183,7 +2728,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -2223,6 +2768,16 @@ dependencies = [ "digest", ] +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + [[package]] name = "shlex" version = "1.3.0" @@ -2261,6 +2816,88 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "slop-algebra" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "691beea96fd18d4881f9ca1cb4e58194dac6366f24956a2fdae00c8ee382a0c9" +dependencies = [ + "itertools 0.14.0", + "p3-field", + "serde", +] + +[[package]] +name = "slop-bn254" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1852499c245f7f3dec23408b4930b3ea7570ae914b9c31f12950ac539d85ee" +dependencies = [ + "ff 0.13.1", + "p3-bn254-fr", + "serde", + "slop-algebra", + "slop-challenger", + "slop-poseidon2", + "slop-symmetric", + "zkhash", +] + +[[package]] +name = "slop-challenger" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4349af93602f3876a3eda948a74d9d16d774c401dfe25f41a45ffd84f230bc1" +dependencies = [ + "futures", + "p3-challenger", + "serde", + "slop-algebra", + "slop-symmetric", +] + +[[package]] +name = "slop-koala-bear" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "574784c044d11cf9d8238dc18bce9b897bc34d0fb1daaceafd75ebb400084016" +dependencies = [ + "lazy_static", + "p3-koala-bear", + "serde", + "slop-algebra", + "slop-challenger", + "slop-poseidon2", + "slop-symmetric", +] + +[[package]] +name = "slop-poseidon2" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5af617970b63e8d7199204bc02996745b6c35c39f2b513a118c62c7b1a0b2f1b" +dependencies = [ + "p3-poseidon2", +] + +[[package]] +name = "slop-primitives" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58d82c53508f3ebff8acdabb5db2584f37686257a2549a17c977cf30cd9e24e6" +dependencies = [ + "slop-algebra", +] + +[[package]] +name = "slop-symmetric" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15acfa7f567ffa4f36de134492632a397c33fa6af2e48894e50978b52eeeb871" +dependencies = [ + "p3-symmetric", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -2287,6 +2924,42 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "sp1-lib" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "517e820776910468611149dda66791bdb700c1b7d68b96f0ea2e604f00ad8771" +dependencies = [ + "bincode", + "elliptic-curve", + "serde", + "sp1-primitives", +] + +[[package]] +name = "sp1-primitives" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f395525b4fc46d37136f45be264c81718a67f4409c14c547ff491a263e019e7" +dependencies = [ + "bincode", + "blake3", + "elf", + "hex", + "itertools 0.14.0", + "lazy_static", + "num-bigint 0.4.6", + "serde", + "sha2", + "slop-algebra", + "slop-bn254", + "slop-challenger", + "slop-koala-bear", + "slop-poseidon2", + "slop-primitives", + "slop-symmetric", +] + [[package]] name = "spin" version = "0.9.8" @@ -2309,12 +2982,29 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.114" @@ -2343,7 +3033,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -2415,7 +3105,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -2468,7 +3158,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -2598,7 +3288,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -2778,7 +3468,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.114", "wasm-bindgen-shared", ] @@ -3175,7 +3865,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn", + "syn 2.0.114", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -3191,7 +3881,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.114", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -3278,7 +3968,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", "synstructure", ] @@ -3299,7 +3989,7 @@ checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -3319,7 +4009,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", "synstructure", ] @@ -3328,6 +4018,20 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] [[package]] name = "zerotrie" @@ -3359,7 +4063,34 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", +] + +[[package]] +name = "zkhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4352d1081da6922701401cdd4cbf29a2723feb4cfabb5771f6fee8e9276da1c7" +dependencies = [ + "ark-ff", + "ark-std", + "bitvec", + "blake2", + "bls12_381", + "byteorder", + "cfg-if", + "group 0.12.1", + "group 0.13.0", + "halo2", + "hex", + "jubjub", + "lazy_static", + "pasta_curves 0.5.1", + "rand 0.8.5", + "serde", + "sha2", + "sha3", + "subtle", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b79477c..89cf758 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,7 @@ futures = { version = "0.3", optional = true } getrandom = { version = "0.2", optional = true } serde-wasm-bindgen = { version = "0.6.5", optional = true } wasm-bindgen = { version = "0.2.95", optional = true } -wasm-bindgen-futures = { version = "0.4.56" } +wasm-bindgen-futures = { version = "0.4.56", optional = true } serde_bytes = { package = "serde-human-bytes", version = "0.1" } # Python bindings @@ -76,6 +76,9 @@ signature = { version = "2.2", default-features = false, optional = true } dcap-qvl-webpki = { version = "=0.103.4", features = ["alloc"] } +# SP1 zkVM backend +sp1-lib = { version = "6.0", optional = true } + [dev-dependencies] hex-literal = "1.1.0" @@ -83,6 +86,8 @@ insta = "1.46.3" reqwest = "0.12.24" serde_json = "1.0.148" tokio = { version = "1.49.0", features = ["full"] } +p256 = { version = "0.13", features = ["ecdsa"] } +signature = "2.2" [lib] name = "dcap_qvl" @@ -106,10 +111,12 @@ std = [ borsh = ["dep:borsh"] borsh_schema = ["borsh", "borsh/unstable__schema"] report = ["std", "tracing", "futures", "reqwest"] -js = ["getrandom/js", "serde-wasm-bindgen", "wasm-bindgen"] +js = ["getrandom/js", "serde-wasm-bindgen", "wasm-bindgen", "wasm-bindgen-futures"] python = ["pyo3", "pyo3-async-runtimes", "tokio", "std", "report", "ring"] ring = ["dep:ring", "dcap-qvl-webpki/ring", "_anycrypto"] rustcrypto = ["dep:sha2", "dep:p256", "dep:signature", "dcap-qvl-webpki/rustcrypto", "_anycrypto"] +cosmwasm = ["dep:sha2", "dcap-qvl-webpki/rustcrypto", "_anycrypto"] +sp1 = ["dep:sp1-lib", "dep:sha2", "dep:p256", "dep:signature", "dcap-qvl-webpki/rustcrypto", "_anycrypto"] _anycrypto = [] contract = ["getrandom"] diff --git a/src/cosmwasm_backend.rs b/src/cosmwasm_backend.rs new file mode 100644 index 0000000..ac73403 --- /dev/null +++ b/src/cosmwasm_backend.rs @@ -0,0 +1,328 @@ +//! CosmWasm P-256 ECDSA verification backend. +//! +//! Uses the CosmWasm host's native `secp256r1_verify` function for ECDSA P-256 +//! signature verification, which is orders of magnitude cheaper than pure-WASM +//! implementations. Requires wasmd v0.51+ / CosmWasm 2.1+. + +use rustls_pki_types::{AlgorithmIdentifier, InvalidSignature, SignatureVerificationAlgorithm}; +use sha2::Digest; + +// --------------------------------------------------------------------------- +// CosmWasm FFI types +// --------------------------------------------------------------------------- + +/// Memory region descriptor matching the CosmWasm VM ABI. +/// Must be 12 bytes: offset (u32) + capacity (u32) + length (u32). +#[cfg(target_arch = "wasm32")] +#[repr(C)] +struct Region { + offset: u32, + capacity: u32, + length: u32, +} + +#[cfg(target_arch = "wasm32")] +impl Region { + /// Create a Region pointing to an existing slice (borrowed, no allocation). + fn from_slice(slice: &[u8]) -> Self { + Self { + offset: slice.as_ptr() as u32, + capacity: slice.len() as u32, + length: slice.len() as u32, + } + } + + fn as_ptr(&self) -> u32 { + (self as *const Self) as u32 + } +} + +#[cfg(target_arch = "wasm32")] +extern "C" { + /// CosmWasm host function: secp256r1 ECDSA verification. + /// Returns 0 on success, 1 on verification failure, >1 on error. + fn secp256r1_verify(message_hash_ptr: u32, signature_ptr: u32, public_key_ptr: u32) -> u32; +} + +// --------------------------------------------------------------------------- +// OID constants — must match the exact DER bytes used by webpki/rustcrypto +// --------------------------------------------------------------------------- + +/// AlgorithmIdentifier for id-ecPublicKey with secp256r1. +/// OID 1.2.840.10045.2.1 + 1.2.840.10045.3.1.7 +const ECDSA_P256_ALG_ID: AlgorithmIdentifier = AlgorithmIdentifier::from_slice(&[ + 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x03, 0x01, 0x07, +]); + +/// AlgorithmIdentifier for ecdsa-with-SHA256. +/// OID 1.2.840.10045.4.3.2 +const ECDSA_SHA256_ALG_ID: AlgorithmIdentifier = AlgorithmIdentifier::from_slice(&[ + 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, +]); + +// --------------------------------------------------------------------------- +// DER signature parsing +// --------------------------------------------------------------------------- + +/// Parse a DER-encoded ECDSA signature into raw 64-byte (r || s) form. +/// +/// DER: SEQUENCE { INTEGER r, INTEGER s } +/// Each integer may have a leading 0x00 byte for sign padding. +/// We must extract exactly 32 bytes for each, left-padded with zeros. +fn der_sig_to_raw(der: &[u8]) -> Result<[u8; 64], InvalidSignature> { + // Minimal DER parsing for SEQUENCE { INTEGER, INTEGER } + let mut pos = 0; + + // SEQUENCE tag + if der.get(pos).copied() != Some(0x30) { + return Err(InvalidSignature); + } + pos = pos.wrapping_add(1); + + // SEQUENCE length + let (_seq_len, consumed) = parse_der_length(der.get(pos..).ok_or(InvalidSignature)?)?; + pos = pos.wrapping_add(consumed); + + // First INTEGER (r) + let (r_bytes, consumed) = parse_der_integer(der.get(pos..).ok_or(InvalidSignature)?)?; + pos = pos.wrapping_add(consumed); + + // Second INTEGER (s) + let (s_bytes, _consumed) = parse_der_integer(der.get(pos..).ok_or(InvalidSignature)?)?; + + let mut raw = [0u8; 64]; + copy_integer_to_fixed(&mut raw, 0, r_bytes)?; + copy_integer_to_fixed(&mut raw, 32, s_bytes)?; + Ok(raw) +} + +/// Parse a DER length field, returning (length, bytes_consumed). +fn parse_der_length(data: &[u8]) -> Result<(usize, usize), InvalidSignature> { + let first = *data.first().ok_or(InvalidSignature)?; + if first < 0x80 { + Ok((first as usize, 1)) + } else if first == 0x81 { + let len = *data.get(1).ok_or(InvalidSignature)? as usize; + Ok((len, 2)) + } else { + // Signatures shouldn't need longer length encodings + Err(InvalidSignature) + } +} + +/// Parse a DER INTEGER, returning (value_bytes, total_bytes_consumed). +fn parse_der_integer(data: &[u8]) -> Result<(&[u8], usize), InvalidSignature> { + if data.first().copied() != Some(0x02) { + return Err(InvalidSignature); + } + let (len, len_size) = parse_der_length(data.get(1..).ok_or(InvalidSignature)?)?; + let start = 1usize.wrapping_add(len_size); + let end = start.wrapping_add(len); + let value = data.get(start..end).ok_or(InvalidSignature)?; + Ok((value, end)) +} + +/// Copy a variable-length big-endian integer into a fixed 32-byte slot. +/// Handles leading zero bytes (sign padding) and short values. +fn copy_integer_to_fixed( + out: &mut [u8; 64], + offset: usize, + int_bytes: &[u8], +) -> Result<(), InvalidSignature> { + // Strip leading zeros + let stripped = match int_bytes.iter().position(|&b| b != 0) { + Some(p) => int_bytes.get(p..).ok_or(InvalidSignature)?, + None => &[0u8], // all zeros + }; + if stripped.len() > 32 { + return Err(InvalidSignature); + } + let pad = 32usize.wrapping_sub(stripped.len()); + let dest = out + .get_mut(offset.wrapping_add(pad)..offset.wrapping_add(32)) + .ok_or(InvalidSignature)?; + dest.copy_from_slice(stripped); + Ok(()) +} + +// --------------------------------------------------------------------------- +// SignatureVerificationAlgorithm implementation +// --------------------------------------------------------------------------- + +/// ECDSA P-256 SHA-256 verification using CosmWasm's native host function. +#[derive(Debug)] +pub struct CosmWasmEcdsaP256; + +impl SignatureVerificationAlgorithm for CosmWasmEcdsaP256 { + fn public_key_alg_id(&self) -> AlgorithmIdentifier { + ECDSA_P256_ALG_ID + } + + fn signature_alg_id(&self) -> AlgorithmIdentifier { + ECDSA_SHA256_ALG_ID + } + + fn verify_signature( + &self, + public_key: &[u8], + message: &[u8], + signature: &[u8], + ) -> Result<(), InvalidSignature> { + // 1. Hash the message (CosmWasm expects pre-hashed 32-byte digest) + let hash: [u8; 32] = sha2::Sha256::digest(message).into(); + + // 2. DER-decode signature to raw 64-byte (r || s) + let raw_sig = der_sig_to_raw(signature)?; + + // 3. Call the host function + #[cfg(target_arch = "wasm32")] + { + let hash_region = Region::from_slice(&hash); + let sig_region = Region::from_slice(&raw_sig); + let pk_region = Region::from_slice(public_key); + + let result = unsafe { + secp256r1_verify(hash_region.as_ptr(), sig_region.as_ptr(), pk_region.as_ptr()) + }; + + match result { + 0 => Ok(()), + _ => Err(InvalidSignature), + } + } + + #[cfg(not(target_arch = "wasm32"))] + { + let _ = (&hash, &raw_sig, public_key); + Err(InvalidSignature) + } + } +} + +/// ECDSA P-256 SHA-256 algorithm using CosmWasm native host verification. +pub static ECDSA_P256_SHA256: &CosmWasmEcdsaP256 = &CosmWasmEcdsaP256; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_der_sig_parsing() { + // A known DER-encoded ECDSA signature (simplified test vector) + // SEQUENCE { INTEGER(32 bytes r), INTEGER(32 bytes s) } + let mut der = alloc::vec![0x30, 0x44]; // SEQUENCE, length 68 + der.push(0x02); + der.push(0x20); // INTEGER, length 32 + der.extend_from_slice(&[0x01; 32]); // r = 0x0101...01 + der.push(0x02); + der.push(0x20); // INTEGER, length 32 + der.extend_from_slice(&[0x02; 32]); // s = 0x0202...02 + + let raw = der_sig_to_raw(&der).expect("should parse"); + assert_eq!(&raw[..32], &[0x01; 32]); + assert_eq!(&raw[32..], &[0x02; 32]); + } + + #[test] + fn test_der_sig_with_leading_zero() { + // r has leading 0x00 (sign padding), actual value is 31 bytes + let mut der = alloc::vec![0x30, 0x45]; // SEQUENCE, length 69 + der.push(0x02); + der.push(0x21); // INTEGER, length 33 + der.push(0x00); // leading zero + der.extend_from_slice(&[0xFF; 32]); // r = 0x00FF...FF + der.push(0x02); + der.push(0x20); // INTEGER, length 32 + der.extend_from_slice(&[0x01; 32]); // s + + let raw = der_sig_to_raw(&der).expect("should parse"); + assert_eq!(&raw[..32], &[0xFF; 32]); + assert_eq!(&raw[32..], &[0x01; 32]); + } + + #[test] + fn test_der_sig_short_integer() { + // r is only 20 bytes (should be zero-padded to 32) + let mut der = alloc::vec![0x30, 0x38]; // SEQUENCE, length 56 + der.push(0x02); + der.push(0x14); // INTEGER, length 20 + der.extend_from_slice(&[0xAB; 20]); // r = short + der.push(0x02); + der.push(0x20); // INTEGER, length 32 + der.extend_from_slice(&[0xCD; 32]); // s + + let raw = der_sig_to_raw(&der).expect("should parse"); + // r should be zero-padded: 12 zeros + 20 bytes of 0xAB + assert_eq!(&raw[..12], &[0x00; 12]); + assert_eq!(&raw[12..32], &[0xAB; 20]); + assert_eq!(&raw[32..], &[0xCD; 32]); + } + + #[test] + fn test_algorithm_ids_match_rustcrypto() { + // Verify our OID bytes match what rustls-pki-types defines + use rustls_pki_types::alg_id; + assert_eq!( + ECDSA_P256_ALG_ID.as_ref(), + alg_id::ECDSA_P256.as_ref(), + "P-256 public key AlgorithmIdentifier mismatch" + ); + assert_eq!( + ECDSA_SHA256_ALG_ID.as_ref(), + alg_id::ECDSA_SHA256.as_ref(), + "ECDSA-SHA256 signature AlgorithmIdentifier mismatch" + ); + } + + #[test] + fn test_der_parsing_matches_p256_crate() { + // Generate a real P-256 signature and verify our DER parsing matches p256's + use p256::ecdsa::{signature::Signer, SigningKey, Signature}; + + let signing_key = SigningKey::from_bytes(&[0xAB; 32].into()).unwrap(); + let message = b"test message for cosmwasm backend"; + + // Sign (produces DER-encoded signature) + let sig: Signature = signing_key.sign(message); + let der_sig = sig.to_der(); + + // Parse with our code + let our_raw = der_sig_to_raw(der_sig.as_bytes()).expect("our DER parser should work"); + + // Compare with p256's raw representation + let p256_raw: [u8; 64] = sig.to_bytes().into(); + + assert_eq!(our_raw, p256_raw, "DER parsing mismatch:\nours: {:02x?}\np256: {:02x?}", our_raw, p256_raw); + } + + #[test] + fn test_full_verify_flow_native() { + // End-to-end test: sign, hash, DER-parse, prehash-verify + // This mirrors what happens in WASM but using p256 directly + use p256::ecdsa::{signature::Signer, SigningKey, VerifyingKey}; + use p256::ecdsa::signature::hazmat::PrehashVerifier; + + let signing_key = SigningKey::from_bytes(&[0xCD; 32].into()).unwrap(); + let verifying_key = VerifyingKey::from(&signing_key); + let message = b"certificate TBS data simulation"; + + // 1. Sign the message (p256 internally hashes with SHA-256) + let sig: p256::ecdsa::Signature = signing_key.sign(message); + let der_sig = sig.to_der(); + + // 2. Our flow: hash message, parse DER signature + let hash: [u8; 32] = sha2::Sha256::digest(message).into(); + let raw_sig = der_sig_to_raw(der_sig.as_bytes()).expect("DER parse"); + + // 3. Verify using prehash (same as cosmwasm host would do) + let p256_sig = p256::ecdsa::Signature::from_bytes((&raw_sig).into()).unwrap(); + let result = verifying_key.verify_prehash(&hash, &p256_sig); + assert!(result.is_ok(), "Prehash verification failed: {:?}", result); + + // 4. Also verify the public key format matches what webpki would pass + let pubkey_bytes = verifying_key.to_encoded_point(false); + assert_eq!(pubkey_bytes.as_bytes().len(), 65); + assert_eq!(pubkey_bytes.as_bytes()[0], 0x04); + } +} diff --git a/src/lib.rs b/src/lib.rs index 1ba7bd5..5b5293b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,13 +83,19 @@ pub mod oids; mod constants; pub mod intel; -mod qe_identity; -mod tcb_info; +pub mod qe_identity; +pub mod tcb_info; mod utils; pub mod quote; pub mod verify; +#[cfg(feature = "cosmwasm")] +mod cosmwasm_backend; + +#[cfg(feature = "sp1")] +mod sp1_backend; + #[cfg(feature = "python")] pub mod python; diff --git a/src/sp1_backend.rs b/src/sp1_backend.rs new file mode 100644 index 0000000..5075490 --- /dev/null +++ b/src/sp1_backend.rs @@ -0,0 +1,408 @@ +//! SP1 zkVM P-256 ECDSA verification backend. +//! +//! Uses SP1's native secp256r1 precompiles (`syscall_secp256r1_add`, +//! `syscall_secp256r1_double`) for accelerated elliptic curve point operations. +//! Scalar field arithmetic uses the `p256` crate. When running outside the +//! zkVM (tests, host), SP1-lib falls back to software implementations. + +use alloc::vec::Vec; +use p256::elliptic_curve::{ops::Reduce, Field, PrimeField}; +use rustls_pki_types::{AlgorithmIdentifier, InvalidSignature, SignatureVerificationAlgorithm}; +use sha2::Digest; +use sp1_lib::secp256r1::Secp256r1Point; +use sp1_lib::utils::AffinePoint; + +// --------------------------------------------------------------------------- +// OID constants +// --------------------------------------------------------------------------- + +/// AlgorithmIdentifier for id-ecPublicKey with secp256r1. +const ECDSA_P256_ALG_ID: AlgorithmIdentifier = AlgorithmIdentifier::from_slice(&[ + 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x03, 0x01, 0x07, +]); + +/// AlgorithmIdentifier for ecdsa-with-SHA256. +const ECDSA_SHA256_ALG_ID: AlgorithmIdentifier = AlgorithmIdentifier::from_slice(&[ + 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, +]); + +// --------------------------------------------------------------------------- +// DER signature parsing +// --------------------------------------------------------------------------- + +/// Parse a DER-encoded ECDSA signature into raw 64-byte (r || s) form. +fn der_sig_to_raw(der: &[u8]) -> Result<[u8; 64], InvalidSignature> { + let mut pos = 0usize; + + if der.get(pos).copied() != Some(0x30) { + return Err(InvalidSignature); + } + pos = pos.wrapping_add(1); + + let (_seq_len, consumed) = parse_der_length(der.get(pos..).ok_or(InvalidSignature)?)?; + pos = pos.wrapping_add(consumed); + + let (r_bytes, consumed) = parse_der_integer(der.get(pos..).ok_or(InvalidSignature)?)?; + pos = pos.wrapping_add(consumed); + + let (s_bytes, _consumed) = parse_der_integer(der.get(pos..).ok_or(InvalidSignature)?)?; + + let mut raw = [0u8; 64]; + copy_integer_to_fixed(&mut raw, 0, r_bytes)?; + copy_integer_to_fixed(&mut raw, 32, s_bytes)?; + Ok(raw) +} + +fn parse_der_length(data: &[u8]) -> Result<(usize, usize), InvalidSignature> { + let first = *data.first().ok_or(InvalidSignature)?; + if first < 0x80 { + Ok((first as usize, 1)) + } else if first == 0x81 { + let len = *data.get(1).ok_or(InvalidSignature)? as usize; + Ok((len, 2)) + } else { + Err(InvalidSignature) + } +} + +fn parse_der_integer(data: &[u8]) -> Result<(&[u8], usize), InvalidSignature> { + if data.first().copied() != Some(0x02) { + return Err(InvalidSignature); + } + let (len, len_size) = parse_der_length(data.get(1..).ok_or(InvalidSignature)?)?; + let start = 1usize.wrapping_add(len_size); + let end = start.wrapping_add(len); + let value = data.get(start..end).ok_or(InvalidSignature)?; + Ok((value, end)) +} + +fn copy_integer_to_fixed( + out: &mut [u8; 64], + offset: usize, + int_bytes: &[u8], +) -> Result<(), InvalidSignature> { + let stripped = match int_bytes.iter().position(|&b| b != 0) { + Some(p) => int_bytes.get(p..).ok_or(InvalidSignature)?, + None => &[0u8], + }; + if stripped.len() > 32 { + return Err(InvalidSignature); + } + let pad = 32usize.wrapping_sub(stripped.len()); + let dest = out + .get_mut(offset.wrapping_add(pad)..offset.wrapping_add(32)) + .ok_or(InvalidSignature)?; + dest.copy_from_slice(stripped); + Ok(()) +} + +// --------------------------------------------------------------------------- +// Conversions +// --------------------------------------------------------------------------- + +/// Convert a `p256::Scalar` to a little-endian bit vector (LSB first, 256 bits). +#[allow(clippy::arithmetic_side_effects)] +fn scalar_to_le_bits(scalar: &p256::Scalar) -> Vec { + let be_bytes: p256::FieldBytes = scalar.to_repr(); + let mut bits = Vec::with_capacity(256); + // Iterate bytes from LSB (last byte) to MSB (first byte) + for &byte in be_bytes.iter().rev() { + for bit in 0..8u32 { + bits.push((byte.wrapping_shr(bit)) & 1 == 1); + } + } + bits +} + +/// Reverse a 32-byte slice (big-endian ↔ little-endian). +fn reverse_32(bytes: &[u8]) -> Result<[u8; 32], InvalidSignature> { + if bytes.len() != 32 { + return Err(InvalidSignature); + } + let mut out = [0u8; 32]; + out.copy_from_slice(bytes); + out.reverse(); + Ok(out) +} + +/// Extract 32 bytes from a slice into an array. +fn to_array_32(slice: &[u8]) -> Result<[u8; 32], InvalidSignature> { + if slice.len() != 32 { + return Err(InvalidSignature); + } + let mut out = [0u8; 32]; + out.copy_from_slice(slice); + Ok(out) +} + +/// Parse big-endian bytes as a nonzero P-256 scalar (must be in [1, n-1]). +fn be_to_nonzero_scalar(be: &[u8; 32]) -> Result { + let repr = p256::FieldBytes::from(*be); + let scalar: p256::Scalar = + Option::from(p256::Scalar::from_repr(repr)).ok_or(InvalidSignature)?; + if bool::from(scalar.is_zero()) { + return Err(InvalidSignature); + } + Ok(scalar) +} + +/// Parse big-endian bytes as a P-256 scalar, reducing mod n. +/// Used for the message hash which can theoretically be >= n. +fn be_to_scalar_reduce(be: &[u8; 32]) -> p256::Scalar { + >::reduce_bytes(&p256::FieldBytes::from(*be)) +} + +// --------------------------------------------------------------------------- +// SignatureVerificationAlgorithm implementation +// --------------------------------------------------------------------------- + +/// ECDSA P-256 SHA-256 verification using SP1's secp256r1 precompiles. +#[derive(Debug)] +pub struct Sp1EcdsaP256; + +impl SignatureVerificationAlgorithm for Sp1EcdsaP256 { + fn public_key_alg_id(&self) -> AlgorithmIdentifier { + ECDSA_P256_ALG_ID + } + + fn signature_alg_id(&self) -> AlgorithmIdentifier { + ECDSA_SHA256_ALG_ID + } + + #[allow(clippy::arithmetic_side_effects)] + fn verify_signature( + &self, + public_key: &[u8], + message: &[u8], + signature: &[u8], + ) -> Result<(), InvalidSignature> { + // 1. Hash the message with SHA-256 + let z_bytes: [u8; 32] = sha2::Sha256::digest(message).into(); + + // 2. Parse DER-encoded signature to raw (r || s) + let raw_sig = der_sig_to_raw(signature)?; + let r_bytes = to_array_32(raw_sig.get(..32).ok_or(InvalidSignature)?)?; + let s_bytes = to_array_32(raw_sig.get(32..).ok_or(InvalidSignature)?)?; + + // 3. Parse SEC1 uncompressed public key: 0x04 || x (32 BE) || y (32 BE) + if public_key.first() != Some(&0x04) || public_key.len() != 65 { + return Err(InvalidSignature); + } + let pk_x_be = to_array_32(public_key.get(1..33).ok_or(InvalidSignature)?)?; + let pk_y_be = to_array_32(public_key.get(33..65).ok_or(InvalidSignature)?)?; + + // 4. Scalar field arithmetic (ECDSA verify algorithm) + let r = be_to_nonzero_scalar(&r_bytes)?; + let s = be_to_nonzero_scalar(&s_bytes)?; + let z = be_to_scalar_reduce(&z_bytes); + + // w = s⁻¹ mod n (s is nonzero, so invert always succeeds) + let w: p256::Scalar = + Option::::from(s.invert()).ok_or(InvalidSignature)?; + + // u1 = z·w mod n, u2 = r·w mod n + let u1 = z * w; + let u2 = r * w; + + // 5. Build SP1 points: convert BE coords to LE for SP1 + let pk_x_le = reverse_32(&pk_x_be)?; + let pk_y_le = reverse_32(&pk_y_be)?; + let q = >::from(&pk_x_le, &pk_y_le); + + // 6. Compute R = u1·G + u2·Q using SP1 precompiles (Shamir's trick) + let g = Secp256r1Point::GENERATOR_T; + let u1_bits = scalar_to_le_bits(&u1); + let u2_bits = scalar_to_le_bits(&u2); + let result = + Secp256r1Point::multi_scalar_multiplication(&u1_bits, g, &u2_bits, q); + + // 7. Check R is not the point at infinity + if result.is_identity() { + return Err(InvalidSignature); + } + + // 8. Extract R.x (LE bytes from SP1) → BE and compare with r + let result_le_bytes = result.to_le_bytes(); + let rx_le = result_le_bytes.get(..32).ok_or(InvalidSignature)?; + let rx_be = reverse_32(rx_le)?; + + // For P-256, p > n but p - n ≈ 2^128, so Pr[R.x ∈ [n, p)] ≈ 2^-128. + // Standard ECDSA: check R.x mod n == r. Since the probability of + // R.x >= n is negligible, comparing bytes directly is safe in practice. + if rx_be == r_bytes { + Ok(()) + } else { + Err(InvalidSignature) + } + } +} + +/// ECDSA P-256 SHA-256 algorithm using SP1 secp256r1 precompiles. +pub static ECDSA_P256_SHA256: &Sp1EcdsaP256 = &Sp1EcdsaP256; + +// --------------------------------------------------------------------------- +// Native syscall stubs (software fallback for testing on non-zkVM targets) +// --------------------------------------------------------------------------- + +/// Software implementations of SP1's secp256r1 syscalls for native testing. +/// These are only compiled when not targeting the zkVM. +#[cfg(not(target_os = "zkvm"))] +mod native_stubs { + use p256::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint}; + use p256::{AffinePoint, EncodedPoint, ProjectivePoint}; + + /// Convert SP1's LE u64 limbs to a p256 AffinePoint. + #[allow(clippy::arithmetic_side_effects, clippy::indexing_slicing)] + fn limbs_to_projective(limbs: &[u64; 8]) -> ProjectivePoint { + // Convert limbs to LE bytes, then reverse to BE for each coordinate + let mut x_le = [0u8; 32]; + let mut y_le = [0u8; 32]; + for i in 0..4 { + let xb = limbs[i].to_le_bytes(); + let yb = limbs[i + 4].to_le_bytes(); + x_le[i * 8..(i + 1) * 8].copy_from_slice(&xb); + y_le[i * 8..(i + 1) * 8].copy_from_slice(&yb); + } + x_le.reverse(); + y_le.reverse(); + + let encoded = EncodedPoint::from_affine_coordinates( + &x_le.into(), + &y_le.into(), + false, + ); + let affine: Option = AffinePoint::from_encoded_point(&encoded).into(); + ProjectivePoint::from(affine.unwrap_or(AffinePoint::IDENTITY)) + } + + /// Convert a p256 ProjectivePoint back to SP1's LE u64 limbs. + #[allow(clippy::arithmetic_side_effects, clippy::indexing_slicing)] + fn projective_to_limbs(point: ProjectivePoint, limbs: &mut [u64; 8]) { + let affine = point.to_affine(); + let encoded = affine.to_encoded_point(false); + let x_be = encoded.x().expect("not identity"); + let y_be = encoded.y().expect("not identity"); + + let mut x_le = [0u8; 32]; + let mut y_le = [0u8; 32]; + x_le.copy_from_slice(x_be); + y_le.copy_from_slice(y_be); + x_le.reverse(); + y_le.reverse(); + + for i in 0..4 { + limbs[i] = u64::from_le_bytes([ + x_le[i * 8], + x_le[i * 8 + 1], + x_le[i * 8 + 2], + x_le[i * 8 + 3], + x_le[i * 8 + 4], + x_le[i * 8 + 5], + x_le[i * 8 + 6], + x_le[i * 8 + 7], + ]); + limbs[i + 4] = u64::from_le_bytes([ + y_le[i * 8], + y_le[i * 8 + 1], + y_le[i * 8 + 2], + y_le[i * 8 + 3], + y_le[i * 8 + 4], + y_le[i * 8 + 5], + y_le[i * 8 + 6], + y_le[i * 8 + 7], + ]); + } + } + + #[no_mangle] + unsafe extern "C" fn syscall_secp256r1_add(p: *mut [u64; 8], q: *const [u64; 8]) { + let a = limbs_to_projective(&*p); + let b = limbs_to_projective(&*q); + use core::ops::Add; + projective_to_limbs(a.add(b), &mut *p); + } + + #[no_mangle] + #[allow(clippy::arithmetic_side_effects)] + unsafe extern "C" fn syscall_secp256r1_double(p: *mut [u64; 8]) { + let a = limbs_to_projective(&*p); + projective_to_limbs(a + a, &mut *p); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_algorithm_ids_match() { + use rustls_pki_types::alg_id; + assert_eq!( + ECDSA_P256_ALG_ID.as_ref(), + alg_id::ECDSA_P256.as_ref(), + "P-256 public key AlgorithmIdentifier mismatch" + ); + assert_eq!( + ECDSA_SHA256_ALG_ID.as_ref(), + alg_id::ECDSA_SHA256.as_ref(), + "ECDSA-SHA256 signature AlgorithmIdentifier mismatch" + ); + } + + #[test] + fn test_ecdsa_verify_roundtrip() { + use p256::ecdsa::{signature::Signer, SigningKey}; + + let signing_key = + SigningKey::from_bytes(&[0xAB; 32].into()).expect("valid signing key"); + let verifying_key = p256::ecdsa::VerifyingKey::from(&signing_key); + let message = b"test message for SP1 backend verification"; + + let sig: p256::ecdsa::Signature = signing_key.sign(message); + let der_sig = sig.to_der(); + + let pubkey = verifying_key.to_encoded_point(false); + let pubkey_bytes = pubkey.as_bytes(); + + let result = Sp1EcdsaP256.verify_signature(pubkey_bytes, message, der_sig.as_bytes()); + assert!(result.is_ok(), "SP1 backend verification failed: {:?}", result); + } + + #[test] + fn test_ecdsa_verify_wrong_message() { + use p256::ecdsa::{signature::Signer, SigningKey}; + + let signing_key = + SigningKey::from_bytes(&[0xCD; 32].into()).expect("valid signing key"); + let verifying_key = p256::ecdsa::VerifyingKey::from(&signing_key); + + let sig: p256::ecdsa::Signature = signing_key.sign(b"correct message"); + let der_sig = sig.to_der(); + let pubkey = verifying_key.to_encoded_point(false); + + let result = + Sp1EcdsaP256.verify_signature(pubkey.as_bytes(), b"wrong message", der_sig.as_bytes()); + assert!(result.is_err(), "Should fail with wrong message"); + } + + #[test] + fn test_ecdsa_verify_wrong_key() { + use p256::ecdsa::{signature::Signer, SigningKey}; + + let signing_key = + SigningKey::from_bytes(&[0xEF; 32].into()).expect("valid signing key"); + let wrong_key = + SigningKey::from_bytes(&[0x12; 32].into()).expect("valid signing key"); + let wrong_verifying = p256::ecdsa::VerifyingKey::from(&wrong_key); + + let message = b"test message"; + let sig: p256::ecdsa::Signature = signing_key.sign(message); + let der_sig = sig.to_der(); + let pubkey = wrong_verifying.to_encoded_point(false); + + let result = + Sp1EcdsaP256.verify_signature(pubkey.as_bytes(), message, der_sig.as_bytes()); + assert!(result.is_err(), "Should fail with wrong key"); + } +} diff --git a/src/utils.rs b/src/utils.rs index 9abedef..d9f6c4f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -96,7 +96,7 @@ pub fn get_pce_svn(extension_section: &[u8]) -> Result { } } -pub(crate) fn extract_raw_certs(cert_chain: &[u8]) -> Result>> { +pub fn extract_raw_certs(cert_chain: &[u8]) -> Result>> { Ok(pem::parse_many(cert_chain) .context("Failed to parse certs")? .iter() @@ -159,8 +159,8 @@ pub fn verify_certificate_chain( time: UnixTime, crls: &[CertRevocationList<'_>], trust_anchor: TrustAnchor<'_>, + sig_algs: &[&dyn rustls_pki_types::SignatureVerificationAlgorithm], ) -> Result<()> { - let sig_algs = webpki::ALL_VERIFICATION_ALGS; let crl_slice = crls.iter().collect::>(); diff --git a/src/verify.rs b/src/verify.rs index ec58b71..270a2c3 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -29,6 +29,15 @@ use serde::{Deserialize, Serialize}; pub(crate) use self::ring as default_crypto; #[cfg(all(not(feature = "ring"), feature = "rustcrypto"))] pub(crate) use self::rustcrypto as default_crypto; +#[cfg(all(not(feature = "ring"), not(feature = "rustcrypto"), feature = "sp1"))] +pub(crate) use self::sp1 as default_crypto; +#[cfg(all( + not(feature = "ring"), + not(feature = "rustcrypto"), + not(feature = "sp1"), + feature = "cosmwasm" +))] +pub(crate) use self::cosmwasm as default_crypto; /// Crypto backend configuration for quote verification. /// @@ -244,7 +253,7 @@ fn verify_tcb_info_signature( }; let tcb_leaf_cert = webpki::EndEntityCert::try_from(tcb_leaf) .context("Failed to parse TCB Info leaf certificate")?; - verify_certificate_chain(&tcb_leaf_cert, tcb_chain, now, crls, trust_anchor)?; + verify_certificate_chain(&tcb_leaf_cert, tcb_chain, now, crls, trust_anchor, &[backend.sig_algo])?; // Verify signature let asn1_signature = encode_as_der(&collateral.tcb_info_signature)?; @@ -299,7 +308,7 @@ fn verify_qe_identity_signature( }; let qe_id_leaf_cert = webpki::EndEntityCert::try_from(qe_id_leaf) .context("Failed to parse QE Identity leaf certificate")?; - verify_certificate_chain(&qe_id_leaf_cert, qe_id_chain, now, crls, trust_anchor)?; + verify_certificate_chain(&qe_id_leaf_cert, qe_id_chain, now, crls, trust_anchor, &[backend.sig_algo])?; // Verify signature let qe_id_asn1_signature = encode_as_der(&collateral.qe_identity_signature)?; @@ -331,6 +340,7 @@ fn verify_pck_cert_chain( now: UnixTime, crls: &[webpki::CertRevocationList<'_>], trust_anchor: rustls_pki_types::TrustAnchor, + sig_algs: &[&dyn rustls_pki_types::SignatureVerificationAlgorithm], ) -> Result { // Extract PCK certificate chain - prefer collateral, fall back to quote let certification_certs = if let Some(pem_chain) = &collateral.pck_certificate_chain { @@ -351,7 +361,7 @@ fn verify_pck_cert_chain( // Verify PCK certificate chain let pck_leaf_cert = webpki::EndEntityCert::try_from(pck_leaf).context("Failed to parse PCK certificate")?; - verify_certificate_chain(&pck_leaf_cert, pck_chain, now, crls, trust_anchor)?; + verify_certificate_chain(&pck_leaf_cert, pck_chain, now, crls, trust_anchor, sig_algs)?; // Extract Intel extension data from PCK cert (parsed once) let pck_ext = intel::parse_pck_extension(pck_leaf)?; @@ -374,6 +384,443 @@ struct PckCertChainResult { fmspc: [u8; 6], } +/// Pre-verified outputs from certificate chain validation (steps 1-3). +/// +/// Extracted on the host (where cert chain validation is cheap) and passed to +/// the zkVM guest, which only runs steps 4-10. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PreVerifiedInputs { + pub tcb_info: TcbInfo, + pub qe_identity: QeIdentity, + #[serde(with = "serde_bytes")] + pub pck_leaf_der: Vec, + pub cpu_svn: [u8; 16], + pub pce_svn: u16, + pub fmspc: [u8; 6], + #[serde(with = "serde_bytes")] + pub ppid: Vec, +} + +/// Pre-parsed collateral for efficient full verification in zkVM. +/// +/// Replaces `QuoteCollateralV3` for the full guest path. All PEM cert chains are +/// pre-extracted to DER bytes (skipping base64/PEM parsing in guest), and TCB Info / +/// QE Identity JSON are pre-parsed to structs (skipping serde_json in guest). +/// Raw JSON bytes are retained for signature verification. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PreparedCollateral { + // CRL bytes (DER, unchanged) + #[serde(with = "serde_bytes")] + pub root_ca_crl: Vec, + #[serde(with = "serde_bytes")] + pub pck_crl: Vec, + + // TCB Info: pre-parsed struct + raw JSON bytes for sig verification + pub tcb_info: TcbInfo, + #[serde(with = "serde_bytes")] + pub tcb_info_raw: Vec, + #[serde(with = "serde_bytes")] + pub tcb_info_signature: Vec, + /// TCB Info issuer chain as DER cert bytes (leaf first) + pub tcb_info_certs_der: Vec>, + + // QE Identity: pre-parsed struct + raw JSON bytes for sig verification + pub qe_identity: QeIdentity, + #[serde(with = "serde_bytes")] + pub qe_identity_raw: Vec, + #[serde(with = "serde_bytes")] + pub qe_identity_signature: Vec, + /// QE Identity issuer chain as DER cert bytes (leaf first) + pub qe_identity_certs_der: Vec>, + + /// PCK certificate chain as DER cert bytes (leaf first) + pub pck_certs_der: Vec>, +} + +/// Run certificate chain validation (steps 1-3) and return pre-verified inputs. +/// +/// Call this on the host, then pass the result to [`verify_quote_lite()`] in the zkVM guest. +pub fn extract_pre_verified_inputs( + raw_quote: &[u8], + collateral: &QuoteCollateralV3, + now_secs: u64, + backend: &CryptoBackend, +) -> Result { + let root_ca_der = TRUSTED_ROOT_CA_DER; + let root_ca = CertificateDer::from_slice(root_ca_der); + let trust_anchor = + webpki::anchor_from_trusted_cert(&root_ca).context("Failed to load root ca")?; + let now = UnixTime::since_unix_epoch(Duration::from_secs(now_secs)); + let raw_crls = [&collateral.root_ca_crl[..], &collateral.pck_crl]; + + webpki::check_single_cert_crl(root_ca_der, &raw_crls, now)?; + let crls = parse_crls(&raw_crls)?; + + // Parse quote to get certification_data for step 3 + let mut quote_slice = raw_quote; + let quote = Quote::decode(&mut quote_slice).context("Failed to decode quote")?; + let auth_data = quote.auth_data.clone().into_v3(); + + // Step 1: Verify TCB Info signature + let tcb_info = + verify_tcb_info_signature(collateral, now, &crls, trust_anchor.clone(), backend)?; + + // Step 2: Verify QE Identity signature + let qe_identity = + verify_qe_identity_signature(collateral, now, &crls, trust_anchor.clone(), backend)?; + + // Step 3: Verify PCK certificate chain + let pck_result = verify_pck_cert_chain( + collateral, + &auth_data.certification_data, + now, + &crls, + trust_anchor, + &[backend.sig_algo], + )?; + + Ok(PreVerifiedInputs { + tcb_info, + qe_identity, + pck_leaf_der: pck_result.pck_leaf_der, + cpu_svn: pck_result.cpu_svn, + pce_svn: pck_result.pce_svn, + fmspc: pck_result.fmspc, + ppid: pck_result.ppid, + }) +} + +/// Prepare collateral for efficient zkVM full verification. +/// +/// Pre-extracts PEM cert chains to DER bytes and pre-parses TCB Info / QE Identity +/// JSON to structs. The result can be serialized with bincode for SP1 stdin, avoiding +/// PEM/base64 and JSON parsing overhead inside the zkVM guest. +pub fn prepare_collateral( + collateral: &QuoteCollateralV3, + raw_quote: &[u8], +) -> Result { + use crate::utils::extract_raw_certs; + + // Pre-parse JSON + let tcb_info = serde_json::from_str::(&collateral.tcb_info) + .context("Failed to decode TcbInfo")?; + let qe_identity = serde_json::from_str::(&collateral.qe_identity) + .context("Failed to decode QeIdentity")?; + + // Pre-extract PEM to DER + let tcb_info_certs_der = extract_raw_certs(collateral.tcb_info_issuer_chain.as_bytes())?; + let qe_identity_certs_der = + extract_raw_certs(collateral.qe_identity_issuer_chain.as_bytes())?; + + // PCK certs: prefer collateral, fall back to quote cert data + let pck_certs_der = if let Some(ref pem_chain) = collateral.pck_certificate_chain { + extract_raw_certs(pem_chain.as_bytes())? + } else { + let mut slice = raw_quote; + let quote = Quote::decode(&mut slice).context("Failed to decode quote")?; + let auth_data = quote.auth_data.clone().into_v3(); + extract_raw_certs(&auth_data.certification_data.body.data)? + }; + + Ok(PreparedCollateral { + root_ca_crl: collateral.root_ca_crl.clone(), + pck_crl: collateral.pck_crl.clone(), + tcb_info, + tcb_info_raw: collateral.tcb_info.as_bytes().to_vec(), + tcb_info_signature: collateral.tcb_info_signature.clone(), + tcb_info_certs_der, + qe_identity, + qe_identity_raw: collateral.qe_identity.as_bytes().to_vec(), + qe_identity_signature: collateral.qe_identity_signature.clone(), + qe_identity_certs_der, + pck_certs_der, + }) +} + +/// Full DCAP verification using pre-parsed collateral (steps 1-10). +/// +/// Like [`verify_impl`] but skips PEM parsing and JSON deserialization by using +/// pre-extracted DER certs and pre-parsed structs from [`PreparedCollateral`]. +pub fn verify_with_prepared( + raw_quote: &[u8], + prepared: &PreparedCollateral, + now_secs: u64, + root_ca_der: &[u8], + backend: &CryptoBackend, +) -> Result { + let root_ca = CertificateDer::from_slice(root_ca_der); + let trust_anchor = + webpki::anchor_from_trusted_cert(&root_ca).context("Failed to load root ca")?; + let now = UnixTime::since_unix_epoch(Duration::from_secs(now_secs)); + let raw_crls = [&prepared.root_ca_crl[..], &prepared.pck_crl]; + + webpki::check_single_cert_crl(root_ca_der, &raw_crls, now)?; + let crls = parse_crls(&raw_crls)?; + + // Parse quote + let mut quote_slice = raw_quote; + let quote = Quote::decode(&mut quote_slice).context("Failed to decode quote")?; + if !ALLOWED_QUOTE_VERSIONS.contains("e.header.version) { + bail!("Unsupported DCAP quote version"); + } + let tee_type = TeeType::from_u32(quote.header.tee_type)?; + match tee_type { + TeeType::Sgx => { + if quote.header.version != 3 { + bail!("SGX TEE quote must have version 3"); + } + } + TeeType::Tdx => { + if ![4, 5].contains("e.header.version) { + bail!("TDX TEE quote must have version 4 or 5"); + } + } + } + if quote.header.attestation_key_type != ATTESTATION_KEY_TYPE_ECDSA256_WITH_P256_CURVE { + bail!("Unsupported DCAP attestation key type"); + } + let auth_data = quote.auth_data.clone().into_v3(); + + // Step 1: Verify TCB Info signature (using pre-parsed struct + pre-extracted DER certs) + { + let issue_date = chrono::DateTime::parse_from_rfc3339(&prepared.tcb_info.issue_date) + .ok() + .context("Failed to parse TCB Info issue date")?; + let next_update = chrono::DateTime::parse_from_rfc3339(&prepared.tcb_info.next_update) + .ok() + .context("Failed to parse TCB Info next update")?; + if now.as_secs() < issue_date.timestamp() as u64 { + bail!("TCBInfo issue date is in the future"); + } + if now.as_secs() > next_update.timestamp() as u64 { + bail!("TCBInfo expired"); + } + + let tcb_certs: Vec = prepared + .tcb_info_certs_der + .iter() + .map(|d| CertificateDer::from(d.as_slice())) + .collect(); + let [tcb_leaf, tcb_chain @ ..] = &tcb_certs[..] else { + bail!("Certificate chain is too short for TCB Info"); + }; + let tcb_leaf_cert = webpki::EndEntityCert::try_from(tcb_leaf) + .context("Failed to parse TCB Info leaf certificate")?; + verify_certificate_chain( + &tcb_leaf_cert, + tcb_chain, + now, + &crls, + trust_anchor.clone(), + &[backend.sig_algo], + )?; + + let asn1_signature = encode_as_der(&prepared.tcb_info_signature)?; + if tcb_leaf_cert + .verify_signature(backend.sig_algo, &prepared.tcb_info_raw, &asn1_signature) + .is_err() + { + bail!("Signature is invalid for tcb_info"); + } + } + + // Step 2: Verify QE Identity signature (using pre-parsed struct + pre-extracted DER certs) + { + let issue_date = chrono::DateTime::parse_from_rfc3339(&prepared.qe_identity.issue_date) + .ok() + .context("Failed to parse QE Identity issue date")?; + let next_update = chrono::DateTime::parse_from_rfc3339(&prepared.qe_identity.next_update) + .ok() + .context("Failed to parse QE Identity next update")?; + if now.as_secs() < issue_date.timestamp() as u64 { + bail!("QE Identity issue date is in the future"); + } + if now.as_secs() > next_update.timestamp() as u64 { + bail!("QE Identity expired"); + } + + let qe_certs: Vec = prepared + .qe_identity_certs_der + .iter() + .map(|d| CertificateDer::from(d.as_slice())) + .collect(); + let [qe_leaf, qe_chain @ ..] = &qe_certs[..] else { + bail!("Certificate chain is too short for QE Identity"); + }; + let qe_leaf_cert = webpki::EndEntityCert::try_from(qe_leaf) + .context("Failed to parse QE Identity leaf certificate")?; + verify_certificate_chain( + &qe_leaf_cert, + qe_chain, + now, + &crls, + trust_anchor.clone(), + &[backend.sig_algo], + )?; + + let asn1_signature = encode_as_der(&prepared.qe_identity_signature)?; + if qe_leaf_cert + .verify_signature( + backend.sig_algo, + &prepared.qe_identity_raw, + &asn1_signature, + ) + .is_err() + { + bail!("Signature is invalid for qe_identity"); + } + } + + let (expected_qe_id, allowed_qe_versions): (&str, &[u8]) = match tee_type { + TeeType::Sgx => ("QE", &[2]), + TeeType::Tdx => ("TD_QE", &[2, 3]), + }; + if prepared.qe_identity.id != expected_qe_id + || !allowed_qe_versions.contains(&prepared.qe_identity.version) + { + bail!("Unsupported QE Identity id/version"); + } + + // Step 3: Verify PCK certificate chain (using pre-extracted DER certs) + let pck_certs: Vec = prepared + .pck_certs_der + .iter() + .map(|d| CertificateDer::from(d.as_slice())) + .collect(); + let [pck_leaf, pck_chain @ ..] = &pck_certs[..] else { + bail!("Certificate chain is too short for PCK"); + }; + let pck_leaf_cert = + webpki::EndEntityCert::try_from(pck_leaf).context("Failed to parse PCK certificate")?; + verify_certificate_chain( + &pck_leaf_cert, + pck_chain, + now, + &crls, + trust_anchor, + &[backend.sig_algo], + )?; + let pck_ext = intel::parse_pck_extension(pck_leaf)?; + + // Steps 4-10: same as verify_impl + let qe_report = verify_qe_report_signature(pck_leaf, &auth_data, backend)?; + verify_qe_report_data(&qe_report, &auth_data, backend)?; + let qe_status = verify_qe_identity_policy(&qe_report, &prepared.qe_identity)?; + verify_isv_report_signature(raw_quote, "e, &auth_data, backend)?; + + let platform_status = match_platform_tcb( + &prepared.tcb_info, + "e, + tee_type, + &pck_ext.cpu_svn, + pck_ext.pce_svn, + &pck_ext.fmspc, + )?; + + let final_status = platform_status.clone().merge(&qe_status); + if !final_status.status.is_valid() { + bail!("TCB status is invalid: {:?}", final_status.status); + } + + validate_attrs("e.report)?; + + Ok(VerifiedReport { + status: final_status.status.to_string(), + advisory_ids: final_status.advisory_ids, + report: quote.report, + ppid: pck_ext.ppid, + qe_status, + platform_status, + }) +} + +/// Verify a quote using pre-verified certificate chain results (steps 4-10 only). +/// +/// Skips certificate chain validation (steps 1-3), which must have been done +/// previously via [`extract_pre_verified_inputs()`]. Use this in zkVM guests where +/// cert chain validation is prohibitively expensive. +pub fn verify_quote_lite( + raw_quote: &[u8], + pre: &PreVerifiedInputs, + backend: &CryptoBackend, +) -> Result { + // Parse quote and validate header + let mut quote_slice = raw_quote; + let quote = Quote::decode(&mut quote_slice).context("Failed to decode quote")?; + if !ALLOWED_QUOTE_VERSIONS.contains("e.header.version) { + bail!("Unsupported DCAP quote version"); + } + let tee_type = TeeType::from_u32(quote.header.tee_type)?; + match tee_type { + TeeType::Sgx => { + if quote.header.version != 3 { + bail!("SGX TEE quote must have version 3"); + } + } + TeeType::Tdx => { + if ![4, 5].contains("e.header.version) { + bail!("TDX TEE quote must have version 4 or 5"); + } + } + } + if quote.header.attestation_key_type != ATTESTATION_KEY_TYPE_ECDSA256_WITH_P256_CURVE { + bail!("Unsupported DCAP attestation key type"); + } + let auth_data = quote.auth_data.clone().into_v3(); + + // Validate QE Identity id/version + let (expected_qe_id, allowed_qe_versions): (&str, &[u8]) = match tee_type { + TeeType::Sgx => ("QE", &[2]), + TeeType::Tdx => ("TD_QE", &[2, 3]), + }; + if pre.qe_identity.id != expected_qe_id + || !allowed_qe_versions.contains(&pre.qe_identity.version) + { + bail!("Unsupported QE Identity id/version"); + } + + let pck_leaf = CertificateDer::from(pre.pck_leaf_der.as_slice()); + + // Step 4: Verify QE Report signature + let qe_report = verify_qe_report_signature(&pck_leaf, &auth_data, backend)?; + + // Step 5: Verify QE Report content (hash check) + verify_qe_report_data(&qe_report, &auth_data, backend)?; + + // Step 6: Verify QE Report policy + let qe_status = verify_qe_identity_policy(&qe_report, &pre.qe_identity)?; + + // Step 7: Verify ISV Report signature + verify_isv_report_signature(raw_quote, "e, &auth_data, backend)?; + + // Step 8: Match Platform TCB + let platform_status = match_platform_tcb( + &pre.tcb_info, + "e, + tee_type, + &pre.cpu_svn, + pre.pce_svn, + &pre.fmspc, + )?; + + // Step 9 & 10: Merge TCB statuses + let final_status = platform_status.clone().merge(&qe_status); + if !final_status.status.is_valid() { + bail!("TCB status is invalid: {:?}", final_status.status); + } + + validate_attrs("e.report)?; + + Ok(VerifiedReport { + status: final_status.status.to_string(), + advisory_ids: final_status.advisory_ids, + report: quote.report, + ppid: pre.ppid.clone(), + qe_status, + platform_status, + }) +} + // ============================================================================= // Step 4: Verify QE Report signature (PCK Cert signs QE Report) // ============================================================================= @@ -636,6 +1083,7 @@ fn verify_impl( now, &crls, trust_anchor, + &[backend.sig_algo], )?; let pck_leaf = CertificateDer::from(pck_result.pck_leaf_der.as_slice()); @@ -750,6 +1198,23 @@ pub mod ring { QuoteVerifier::new(TRUSTED_ROOT_CA_DER.to_vec(), backend()) .verify(raw_quote, collateral, now_secs) } + + /// Extract pre-verified inputs (steps 1-3) using ring backend. + pub fn extract_pre_verified( + raw_quote: &[u8], + collateral: &QuoteCollateralV3, + now_secs: u64, + ) -> Result { + extract_pre_verified_inputs(raw_quote, collateral, now_secs, &backend()) + } + + /// Verify a quote using pre-verified inputs (steps 4-10 only, ring backend). + pub fn verify_lite( + raw_quote: &[u8], + pre: &PreVerifiedInputs, + ) -> Result { + verify_quote_lite(raw_quote, pre, &backend()) + } } /// RustCrypto backend module. @@ -781,6 +1246,138 @@ pub mod rustcrypto { QuoteVerifier::new(TRUSTED_ROOT_CA_DER.to_vec(), backend()) .verify(raw_quote, collateral, now_secs) } + + /// Extract pre-verified inputs (steps 1-3) using RustCrypto backend. + pub fn extract_pre_verified( + raw_quote: &[u8], + collateral: &QuoteCollateralV3, + now_secs: u64, + ) -> Result { + extract_pre_verified_inputs(raw_quote, collateral, now_secs, &backend()) + } + + /// Verify a quote using pre-verified inputs (steps 4-10 only, RustCrypto backend). + pub fn verify_lite( + raw_quote: &[u8], + pre: &PreVerifiedInputs, + ) -> Result { + verify_quote_lite(raw_quote, pre, &backend()) + } + + /// Prepare collateral for efficient full verification (pre-parse JSON, pre-extract PEM→DER). + pub fn prepare( + collateral: &QuoteCollateralV3, + raw_quote: &[u8], + ) -> Result { + prepare_collateral(collateral, raw_quote) + } + + /// Full verification using pre-parsed collateral (steps 1-10, RustCrypto backend). + pub fn verify_prepared( + raw_quote: &[u8], + prepared: &PreparedCollateral, + now_secs: u64, + ) -> Result { + verify_with_prepared(raw_quote, prepared, now_secs, TRUSTED_ROOT_CA_DER, &backend()) + } +} + +/// CosmWasm native crypto backend module. +/// +/// Uses the CosmWasm host's native `secp256r1_verify` for ECDSA P-256 verification. +/// Requires wasmd v0.51+ / CosmWasm 2.1+. +#[cfg(feature = "cosmwasm")] +pub mod cosmwasm { + use super::*; + + fn cosmwasm_sha256(data: &[u8]) -> [u8; 32] { + use sha2::Digest; + sha2::Sha256::digest(data).into() + } + + /// Returns a [`CryptoBackend`] using the CosmWasm native host function. + pub fn backend() -> CryptoBackend { + CryptoBackend { + sig_algo: crate::cosmwasm_backend::ECDSA_P256_SHA256, + sha256: cosmwasm_sha256, + } + } + + /// Verify a quote using Intel's trusted root CA and CosmWasm native backend. + pub fn verify( + raw_quote: &[u8], + collateral: &QuoteCollateralV3, + now_secs: u64, + ) -> Result { + QuoteVerifier::new(TRUSTED_ROOT_CA_DER.to_vec(), backend()) + .verify(raw_quote, collateral, now_secs) + } +} + +/// SP1 zkVM crypto backend module. +/// +/// Uses SP1's native secp256r1 precompiles for accelerated ECDSA P-256 verification. +/// Falls back to software implementation when not running inside SP1 zkVM. +#[cfg(feature = "sp1")] +pub mod sp1 { + use super::*; + + fn sp1_sha256(data: &[u8]) -> [u8; 32] { + use sha2::Digest; + sha2::Sha256::digest(data).into() + } + + /// Returns a [`CryptoBackend`] using SP1's secp256r1 precompiles. + pub fn backend() -> CryptoBackend { + CryptoBackend { + sig_algo: crate::sp1_backend::ECDSA_P256_SHA256, + sha256: sp1_sha256, + } + } + + /// Verify a quote using Intel's trusted root CA and SP1 backend. + pub fn verify( + raw_quote: &[u8], + collateral: &QuoteCollateralV3, + now_secs: u64, + ) -> Result { + QuoteVerifier::new(TRUSTED_ROOT_CA_DER.to_vec(), backend()) + .verify(raw_quote, collateral, now_secs) + } + + /// Extract pre-verified inputs (steps 1-3) using SP1 backend. + pub fn extract_pre_verified( + raw_quote: &[u8], + collateral: &QuoteCollateralV3, + now_secs: u64, + ) -> Result { + extract_pre_verified_inputs(raw_quote, collateral, now_secs, &backend()) + } + + /// Verify a quote using pre-verified inputs (steps 4-10 only, SP1 backend). + pub fn verify_lite( + raw_quote: &[u8], + pre: &PreVerifiedInputs, + ) -> Result { + verify_quote_lite(raw_quote, pre, &backend()) + } + + /// Prepare collateral for efficient full verification (pre-parse JSON, pre-extract PEM→DER). + pub fn prepare( + collateral: &QuoteCollateralV3, + raw_quote: &[u8], + ) -> Result { + prepare_collateral(collateral, raw_quote) + } + + /// Full verification using pre-parsed collateral (steps 1-10, SP1 backend). + pub fn verify_prepared( + raw_quote: &[u8], + prepared: &PreparedCollateral, + now_secs: u64, + ) -> Result { + verify_with_prepared(raw_quote, prepared, now_secs, TRUSTED_ROOT_CA_DER, &backend()) + } } /// Verify a quote using Intel's trusted root CA (ring backend).