igneum-light: the finality object (version 1, 832 bytes), its verifier and the test vectors
light/ is its own workspace: igneum-light (no_std core: the object container, the statement offsets of BlockOutput and FinExt, the MMR history check, the SP1 6.8.1 Groth16 wrap verified with arkworks under the compiled-in key, the final-at answer), igneum-light-wasm (a C ABI for the tab, no bindings generator) and igneum-light-cli (vectors, encodings, native timing). The wrap has not landed, so the chain fixture (the fin-proof lane's proof 81053, public values 504 bytes) is a kind-0 stub; a kind-1 object with generator points exercises the pairing and is refused. Native on this Mac: the pairing 0.78 ms in arkworks, 155 ms in sp1-verifier's substrate-bn (kept as the reference check). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
3f0091f2e2
commit
59d2177fd3
33 changed files with 2432 additions and 0 deletions
2
light/.gitignore
vendored
Normal file
2
light/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
target/
|
||||
*/target-remote/
|
||||
880
light/Cargo.lock
generated
Normal file
880
light/Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,880 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "ark-bn254"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc"
|
||||
dependencies = [
|
||||
"ark-ec",
|
||||
"ark-ff",
|
||||
"ark-std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ark-crypto-primitives"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e0c292754729c8a190e50414fd1a37093c786c709899f29c9f7daccecfa855e"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"ark-crypto-primitives-macros",
|
||||
"ark-ec",
|
||||
"ark-ff",
|
||||
"ark-relations",
|
||||
"ark-serialize",
|
||||
"ark-snark",
|
||||
"ark-std",
|
||||
"blake2",
|
||||
"derivative",
|
||||
"digest",
|
||||
"fnv",
|
||||
"merlin",
|
||||
"rayon",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ark-crypto-primitives-macros"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7e89fe77d1f0f4fe5b96dfc940923d88d17b6a773808124f21e764dfb063c6a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ark-ec"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"ark-ff",
|
||||
"ark-poly",
|
||||
"ark-serialize",
|
||||
"ark-std",
|
||||
"educe",
|
||||
"fnv",
|
||||
"hashbrown",
|
||||
"itertools",
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"rayon",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ark-ff"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70"
|
||||
dependencies = [
|
||||
"ark-ff-asm",
|
||||
"ark-ff-macros",
|
||||
"ark-serialize",
|
||||
"ark-std",
|
||||
"arrayvec",
|
||||
"digest",
|
||||
"educe",
|
||||
"itertools",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"paste",
|
||||
"rayon",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ark-ff-asm"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ark-ff-macros"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ark-groth16"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88f1d0f3a534bb54188b8dcc104307db6c56cdae574ddc3212aec0625740fc7e"
|
||||
dependencies = [
|
||||
"ark-crypto-primitives",
|
||||
"ark-ec",
|
||||
"ark-ff",
|
||||
"ark-poly",
|
||||
"ark-relations",
|
||||
"ark-serialize",
|
||||
"ark-std",
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ark-poly"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"ark-ff",
|
||||
"ark-serialize",
|
||||
"ark-std",
|
||||
"educe",
|
||||
"fnv",
|
||||
"hashbrown",
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ark-relations"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec46ddc93e7af44bcab5230937635b06fb5744464dd6a7e7b083e80ebd274384"
|
||||
dependencies = [
|
||||
"ark-ff",
|
||||
"ark-std",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ark-serialize"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7"
|
||||
dependencies = [
|
||||
"ark-serialize-derive",
|
||||
"ark-std",
|
||||
"arrayvec",
|
||||
"digest",
|
||||
"num-bigint",
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ark-serialize-derive"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ark-snark"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d368e2848c2d4c129ce7679a7d0d2d612b6a274d3ea6a13bad4445d61b381b88"
|
||||
dependencies = [
|
||||
"ark-ff",
|
||||
"ark-relations",
|
||||
"ark-serialize",
|
||||
"ark-std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ark-std"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"rand",
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "blake2"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake3"
|
||||
version = "1.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"constant_time_eq",
|
||||
"cpufeatures 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
|
||||
dependencies = [
|
||||
"bytemuck_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck_derive"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f74872d07caf508b30a21f6836e7d7016a2eaf7d9ff4f48deaa58cd8a0407630"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "educe"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417"
|
||||
dependencies = [
|
||||
"enum-ordinalize",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be"
|
||||
|
||||
[[package]]
|
||||
name = "enum-ordinalize"
|
||||
version = "4.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89dd01549b09589510cf0647475075d12071456586d70f5c75c98ae2a5537677"
|
||||
dependencies = [
|
||||
"enum-ordinalize-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum-ordinalize-derive"
|
||||
version = "4.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a65863d15a4ce2888bd2f0f543cc963d3879c3a022c8ee43f6141d479a3ac815"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "igneum-light"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ark-bn254",
|
||||
"ark-ec",
|
||||
"ark-ff",
|
||||
"ark-groth16",
|
||||
"sha2",
|
||||
"sp1-verifier",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "igneum-light-cli"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"igneum-light",
|
||||
"sha2",
|
||||
"substrate-bn-succinct-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "igneum-light-wasm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"igneum-light",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keccak"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653"
|
||||
dependencies = [
|
||||
"cpufeatures 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb"
|
||||
dependencies = [
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.190"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
|
||||
|
||||
[[package]]
|
||||
name = "merlin"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"keccak",
|
||||
"rand_core",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
|
||||
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 = "rustc-hex"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "sp1-verifier"
|
||||
version = "6.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aca7223cc7a77e42536c7229c8016672737aab07f6149097d9c7a26324b29814"
|
||||
dependencies = [
|
||||
"ark-bn254",
|
||||
"ark-ec",
|
||||
"ark-ff",
|
||||
"ark-groth16",
|
||||
"ark-serialize",
|
||||
"blake3",
|
||||
"cfg-if",
|
||||
"hex",
|
||||
"lazy_static",
|
||||
"sha2",
|
||||
"substrate-bn-succinct-rs",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
|
||||
|
||||
[[package]]
|
||||
name = "substrate-bn-succinct-rs"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a241fd7c1016fb8ad30fcf5a20986c0c4538e8f15a1b41a1761516299e377ec1"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder",
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
"lazy_static",
|
||||
"num-bigint",
|
||||
"rand",
|
||||
"rustc-hex",
|
||||
]
|
||||
|
||||
[[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.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71"
|
||||
dependencies = [
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879fb705ce98c32e41ebdb970fbe1204f8492423b314c6ab0354c3e7b5542866"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "708882a28301d604fa039cc7727607a98d04c4b86dc76ec9cc683f805d709759"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
25
light/Cargo.toml
Normal file
25
light/Cargo.toml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# The light client in the pocket (docs/design/finality-object.md, docs/design/phone-app.md "igneum-light"): one Rust
|
||||
# crate that parses the finality object, verifies its SP1 Groth16 wrap over bn254 and answers "final at checkpoint N"
|
||||
# from the object and a history path alone; a WebAssembly wrapper for the tab and a CLI for test vectors and timing.
|
||||
# Its own workspace: nothing here depends on the node or the prover, so a phone or a tab builds it alone.
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["igneum-light", "igneum-light-wasm", "igneum-light-cli"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[workspace.dependencies]
|
||||
# The SP1 Groth16 verifier, the version the aggregator guest is proven with (proving/igneum-prove/Cargo.toml pins
|
||||
# sp1-sdk =6.8.1); `full` off: no compressed-proof verifier, no recursion machine, so it compiles to wasm32.
|
||||
sp1-verifier = { version = "=6.8.1", default-features = false }
|
||||
sha2 = { version = "0.10.8", default-features = false }
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
strip = true
|
||||
15
light/igneum-light-cli/Cargo.toml
Normal file
15
light/igneum-light-cli/Cargo.toml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "igneum-light-cli"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
description = "Test vectors, encodings and the native timing of igneum-light"
|
||||
|
||||
[[bin]]
|
||||
name = "igneum-light"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
igneum-light = { path = "../igneum-light", features = ["std"] }
|
||||
bn = { package = "substrate-bn-succinct-rs", version = "=0.6.0" }
|
||||
sha2 = { workspace = true }
|
||||
284
light/igneum-light-cli/src/main.rs
Normal file
284
light/igneum-light-cli/src/main.rs
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
//! igneum-light on the command line: the test vectors of docs/design/finality-object.md, the encodings, and the
|
||||
//! native timing of the verifier (the phone path's proxy on a Mac until a phone build exists).
|
||||
//!
|
||||
//! igneum-light make-stub --pv <504 or 340 bytes> --key-id <hex32> --out <file>
|
||||
//! igneum-light make-timing --pv <file> --key-id <hex32> --out <file> kind 1, curve points that are on the
|
||||
//! curve and prove nothing: the pairing runs
|
||||
//! igneum-light verify --object <file> [--query <file>] --key-id <hex32> --chain-id <n> [--genesis <unix s>]
|
||||
//! igneum-light time --object <file> --key-id <hex32> --chain-id <n> [--rounds 20]
|
||||
//! igneum-light url --object <file> [--query <file>] [--base https://igneum.network/pocket/]
|
||||
//! igneum-light vectors --pv <file> --key-id <hex32> --out-dir <dir> the vector set with its manifest
|
||||
|
||||
use igneum_light::object::{base64url_encode, PV_LEN_FIN};
|
||||
use igneum_light::{json::verdict_json, mmr::Mmr, Answer, Clock, FinalityObject, HistoryLeaf, Pinned, ProofVerdict, Query, Statement, Verifier, KIND_SP1_GROTH16};
|
||||
use bn::Group;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::collections::HashMap;
|
||||
use std::time::Instant;
|
||||
|
||||
fn args() -> (String, HashMap<String, String>) {
|
||||
let mut it = std::env::args().skip(1);
|
||||
let cmd = it.next().unwrap_or_default();
|
||||
let mut m = HashMap::new();
|
||||
let mut key: Option<String> = None;
|
||||
for a in it {
|
||||
if let Some(k) = a.strip_prefix("--") {
|
||||
if let Some(prev) = key.take() {
|
||||
m.insert(prev, String::from("1"));
|
||||
}
|
||||
key = Some(k.to_string());
|
||||
} else if let Some(k) = key.take() {
|
||||
m.insert(k, a);
|
||||
}
|
||||
}
|
||||
if let Some(prev) = key.take() {
|
||||
m.insert(prev, String::from("1"));
|
||||
}
|
||||
(cmd, m)
|
||||
}
|
||||
|
||||
fn hex32(s: &str) -> [u8; 32] {
|
||||
let s = s.trim().trim_start_matches("0x");
|
||||
let b = (0..32).map(|i| u8::from_str_radix(&s[2 * i..2 * i + 2], 16).expect("hex")).collect::<Vec<_>>();
|
||||
b.try_into().unwrap()
|
||||
}
|
||||
|
||||
fn hex(b: &[u8]) -> String {
|
||||
b.iter().map(|x| format!("{x:02x}")).collect()
|
||||
}
|
||||
|
||||
fn fq_be(f: bn::Fq) -> [u8; 32] {
|
||||
let mut b = [0u8; 32];
|
||||
f.to_big_endian(&mut b).unwrap();
|
||||
b
|
||||
}
|
||||
|
||||
/// A proof whose three points are the curve generators: on the curve, so the verifier loads them and runs the
|
||||
/// pairing, and wrong, so it refuses. The accept path costs the same pairing.
|
||||
fn timing_proof() -> [u8; 256] {
|
||||
let g1 = bn::AffineG1::from_jacobian(bn::G1::one()).unwrap();
|
||||
let g2 = bn::AffineG2::from_jacobian(bn::G2::one()).unwrap();
|
||||
let mut p = [0u8; 256];
|
||||
p[0..32].copy_from_slice(&fq_be(g1.x()));
|
||||
p[32..64].copy_from_slice(&fq_be(g1.y()));
|
||||
p[64..96].copy_from_slice(&fq_be(g2.x().imaginary()));
|
||||
p[96..128].copy_from_slice(&fq_be(g2.x().real()));
|
||||
p[128..160].copy_from_slice(&fq_be(g2.y().imaginary()));
|
||||
p[160..192].copy_from_slice(&fq_be(g2.y().real()));
|
||||
p[192..224].copy_from_slice(&fq_be(g1.x()));
|
||||
p[224..256].copy_from_slice(&fq_be(g1.y()));
|
||||
p
|
||||
}
|
||||
|
||||
fn timing_object(key_id: [u8; 32], pv: Vec<u8>) -> FinalityObject {
|
||||
FinalityObject { kind: KIND_SP1_GROTH16, key_id, public_values: pv, nonce: [0u8; 32], proof: timing_proof() }
|
||||
}
|
||||
|
||||
fn read(p: &str) -> Vec<u8> {
|
||||
std::fs::read(p).unwrap_or_else(|e| panic!("read {p}: {e}"))
|
||||
}
|
||||
|
||||
fn now_s() -> i64 {
|
||||
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as i64
|
||||
}
|
||||
|
||||
/// A synthetic 504-byte statement over a synthetic history, for the vectors that need a history the CLI can prove
|
||||
/// paths in: `n` chain blocks from `first`, lock at `lock_number`, the tail block `first + n - 1`.
|
||||
fn synthetic(chain_id: u64, key_id: [u8; 32], first: u64, n: u64, lock_number: u64, stale: bool) -> (Vec<u8>, Vec<HistoryLeaf>, Mmr) {
|
||||
let mut leaves = Vec::new();
|
||||
let mut mmr = Mmr::default();
|
||||
for i in 0..n {
|
||||
let number = first + i;
|
||||
let leaf = HistoryLeaf {
|
||||
number,
|
||||
block_hash: Sha256::digest(format!("igneum-pocket-vector-block-{number}").as_bytes()).into(),
|
||||
daa: 100_000 + 3 * i,
|
||||
table_root: Sha256::digest(format!("table-{number}").as_bytes()).into(),
|
||||
keys_hash: Sha256::digest(format!("keys-{number}").as_bytes()).into(),
|
||||
total: 4008,
|
||||
};
|
||||
mmr.push(&leaf);
|
||||
leaves.push(leaf);
|
||||
}
|
||||
let last = &leaves[leaves.len() - 1];
|
||||
let lock = &leaves[(lock_number - first) as usize];
|
||||
let mut pv = Vec::with_capacity(PV_LEN_FIN);
|
||||
pv.extend_from_slice(&chain_id.to_be_bytes());
|
||||
pv.extend_from_slice(&last.number.to_be_bytes());
|
||||
pv.extend_from_slice(&last.block_hash);
|
||||
pv.extend_from_slice(&leaves[leaves.len() - 2].block_hash);
|
||||
pv.extend_from_slice(&1u32.to_be_bytes());
|
||||
for tag in ["tx", "pre", "post", "receipts"] {
|
||||
pv.extend_from_slice(&Sha256::digest(format!("{tag}-{}", last.number).as_bytes()));
|
||||
}
|
||||
pv.extend_from_slice(&21_000u64.to_be_bytes());
|
||||
pv.extend_from_slice(&0u64.to_be_bytes());
|
||||
pv.extend_from_slice(&1u32.to_be_bytes());
|
||||
pv.extend_from_slice(&0u32.to_be_bytes());
|
||||
pv.extend_from_slice(&[0u8; 32]);
|
||||
pv.extend_from_slice(&[0x39u8; 32]);
|
||||
pv.extend_from_slice(&key_id);
|
||||
pv.extend_from_slice(&n.to_be_bytes());
|
||||
assert_eq!(pv.len(), 340);
|
||||
pv.extend_from_slice(&1u16.to_be_bytes());
|
||||
pv.extend_from_slice(&last.table_root);
|
||||
pv.extend_from_slice(&mmr.root());
|
||||
pv.extend_from_slice(&first.to_be_bytes());
|
||||
pv.extend_from_slice(&2702u64.to_be_bytes());
|
||||
pv.extend_from_slice(&lock.block_hash);
|
||||
pv.extend_from_slice(&lock.number.to_be_bytes());
|
||||
pv.extend_from_slice(&2808u64.to_be_bytes());
|
||||
pv.extend_from_slice(&4008u64.to_be_bytes());
|
||||
pv.extend_from_slice(&2808u64.to_be_bytes());
|
||||
pv.extend_from_slice(&4008u64.to_be_bytes());
|
||||
pv.extend_from_slice(&lock.daa.to_be_bytes());
|
||||
pv.extend_from_slice(&(if stale { 1u16 } else { 0 }).to_be_bytes());
|
||||
assert_eq!(pv.len(), PV_LEN_FIN);
|
||||
(pv, leaves, mmr)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let (cmd, a) = args();
|
||||
let get = |k: &str| a.get(k).cloned();
|
||||
let need = |k: &str| a.get(k).cloned().unwrap_or_else(|| panic!("--{k} is required"));
|
||||
match cmd.as_str() {
|
||||
"make-stub" | "make-timing" => {
|
||||
let pv = read(&need("pv"));
|
||||
let key = hex32(&need("key-id"));
|
||||
let obj = if cmd == "make-stub" { FinalityObject::stub(key, pv) } else { timing_object(key, pv) };
|
||||
let b = obj.encode();
|
||||
std::fs::write(need("out"), &b).unwrap();
|
||||
println!("{} bytes, kind {}, sha256 {}", b.len(), obj.kind, hex(&Sha256::digest(&b)));
|
||||
}
|
||||
"verify" => {
|
||||
let object = read(&need("object"));
|
||||
let q = get("query").map(|p| Query::decode(&read(&p)).expect("query").0);
|
||||
let pinned = Pinned { key_id: hex32(&need("key-id")), chain_id: need("chain-id").parse().unwrap() };
|
||||
let clock = get("genesis").map(|g| Clock { now_unix_s: now_s(), genesis_unix_s: g.parse().unwrap(), seconds_per_daa: get("seconds-per-daa").map(|s| s.parse().unwrap()).unwrap_or(1.0) });
|
||||
let t = Instant::now();
|
||||
let v = igneum_light::verify(&object, q.as_ref(), &pinned, clock);
|
||||
let ms = t.elapsed().as_secs_f64() * 1000.0;
|
||||
println!("{}", verdict_json(&v));
|
||||
eprintln!("{ms:.3} ms");
|
||||
}
|
||||
"time" => {
|
||||
let object = read(&need("object"));
|
||||
let pinned = Pinned { key_id: hex32(&need("key-id")), chain_id: need("chain-id").parse().unwrap() };
|
||||
let rounds: usize = get("rounds").map(|s| s.parse().unwrap()).unwrap_or(20);
|
||||
let obj = FinalityObject::decode(&object).unwrap();
|
||||
let t = Instant::now();
|
||||
let verifier = Verifier::new();
|
||||
let prepare_ms = t.elapsed().as_secs_f64() * 1000.0;
|
||||
let (mut full, mut wrap, mut reference) = (Vec::new(), Vec::new(), Vec::new());
|
||||
for _ in 0..rounds {
|
||||
let t = Instant::now();
|
||||
let v = verifier.verify(&object, None, &pinned, None);
|
||||
full.push(t.elapsed().as_secs_f64() * 1000.0);
|
||||
assert!(matches!(v.proof, ProofVerdict::Refused(_) | ProofVerdict::Verified | ProofVerdict::Stub));
|
||||
let t = Instant::now();
|
||||
let _ = verifier.verify_wrap(&obj);
|
||||
wrap.push(t.elapsed().as_secs_f64() * 1000.0);
|
||||
let t = Instant::now();
|
||||
let _ = igneum_light::verify_sp1_groth16_reference_one_pairing(&obj);
|
||||
reference.push(t.elapsed().as_secs_f64() * 1000.0);
|
||||
}
|
||||
let stats = |v: &mut Vec<f64>| {
|
||||
v.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
format!("min {:.2} median {:.2} max {:.2} ms", v[0], v[v.len() / 2], v[v.len() - 1])
|
||||
};
|
||||
println!("rounds {rounds}; prepare the key once {prepare_ms:.2} ms; verify (parse, pin, one pairing in arkworks) {}; the pairing alone {}; the reference pairing in substrate-bn {}", stats(&mut full), stats(&mut wrap), stats(&mut reference));
|
||||
}
|
||||
"url" => {
|
||||
let object = read(&need("object"));
|
||||
let base = get("base").unwrap_or_else(|| String::from("https://igneum.network/pocket/"));
|
||||
let mut u = format!("{base}#o={}", base64url_encode(&object));
|
||||
if let Some(q) = get("query") {
|
||||
u.push_str(&format!("&q={}", base64url_encode(&read(&q))));
|
||||
}
|
||||
println!("{u}");
|
||||
eprintln!("{} characters", u.len());
|
||||
}
|
||||
"vectors" => {
|
||||
let pv = read(&need("pv"));
|
||||
let key = hex32(&need("key-id"));
|
||||
let dir = need("out-dir");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let st = Statement::parse(&pv).expect("the public values parse");
|
||||
let chain_id = st.chain_id;
|
||||
let pinned = Pinned { key_id: key, chain_id };
|
||||
let verifier = Verifier::new();
|
||||
let mut manifest = Vec::new();
|
||||
let mut put = |name: &str, bytes: &[u8], query: Option<&[u8]>, expect_proof: &str, expect_answer: Option<Answer>| {
|
||||
std::fs::write(format!("{dir}/{name}.bin"), bytes).unwrap();
|
||||
if let Some(q) = query {
|
||||
std::fs::write(format!("{dir}/{name}.query.bin"), q).unwrap();
|
||||
}
|
||||
let qd = query.map(|q| Query::decode(q).unwrap().0);
|
||||
let v = verifier.verify(bytes, qd.as_ref(), &pinned, None);
|
||||
let proof = match &v.proof { ProofVerdict::Verified => "verified", ProofVerdict::Stub => "stub", ProofVerdict::Refused(_) => "refused" };
|
||||
assert_eq!(proof, expect_proof, "{name}: {}", verdict_json(&v));
|
||||
assert_eq!(v.answer, expect_answer, "{name}: {}", verdict_json(&v));
|
||||
manifest.push(format!(
|
||||
"{{\"name\":\"{name}\",\"bytes\":{},\"sha256\":\"{}\",\"query\":{},\"expect\":{{\"proof\":\"{proof}\",\"answer\":{}}},\"verdict\":{}}}",
|
||||
bytes.len(),
|
||||
hex(&Sha256::digest(bytes)),
|
||||
query.map(|q| format!("{{\"bytes\":{},\"sha256\":\"{}\"}}", q.len(), hex(&Sha256::digest(q)))).unwrap_or_else(|| String::from("null")),
|
||||
expect_answer.map(|x| format!("\"{}\"", x.as_str())).unwrap_or_else(|| String::from("null")),
|
||||
verdict_json(&v)
|
||||
));
|
||||
};
|
||||
// 1. the fixture from the chain: the fin-proof lane's proof 81053 as a stub object (the wrap pending); its
|
||||
// own block 81053 lies above the lock at 81049, so the proof's own block is not final
|
||||
let stub = FinalityObject::stub(key, pv.clone()).encode();
|
||||
put("fixture-81053-stub", &stub, None, "stub", Some(Answer::NotFinal));
|
||||
// 2. the timing object: kind 1 with generator points; the pairing runs and refuses
|
||||
let timing = timing_object(key, pv.clone()).encode();
|
||||
put("fixture-81053-timing", &timing, None, "refused", None);
|
||||
// 3. one byte changed in the public values of the stub (the lock number's low byte): a stub parses it
|
||||
// like any other, which is why a stub never says "verified"; a kind-1 object refuses the same change
|
||||
let mut flipped = stub.clone();
|
||||
flipped[40 + 461] ^= 0x01;
|
||||
put("fixture-81053-stub-flipped-lock-number", &flipped, None, "stub", Some(Answer::NotFinal));
|
||||
let mut tflipped = timing.clone();
|
||||
tflipped[40 + 461] ^= 0x01;
|
||||
put("fixture-81053-timing-flipped-lock-number", &tflipped, None, "refused", None);
|
||||
// 4. one byte changed in the proof of the timing object: refused
|
||||
let mut tf = timing.clone();
|
||||
let last = tf.len() - 1;
|
||||
tf[last] ^= 0x01;
|
||||
put("fixture-81053-timing-flipped-proof", &tf, None, "refused", None);
|
||||
// 5. the key id changed: refused before anything is read
|
||||
let mut kf = stub.clone();
|
||||
kf[8] ^= 0x01;
|
||||
put("fixture-81053-stub-wrong-key", &kf, None, "refused", None);
|
||||
// 6. the header: version 2
|
||||
let mut vf = stub.clone();
|
||||
vf[4] = 2;
|
||||
put("fixture-81053-stub-version-2", &vf, None, "refused", None);
|
||||
// 7. a synthetic history of 300 blocks from 79046 with the lock at 79300: a block below the lock is
|
||||
// final, a block above is not, a block with a wrong leaf is not in this chain, a stale flag is stale
|
||||
let (spv, leaves, mmr) = synthetic(chain_id, key, 79046, 300, 79300, false);
|
||||
let sobj = FinalityObject::stub(key, spv.clone()).encode();
|
||||
let q_final = Query { leaf: leaves[100].clone(), proof: mmr.proof(100).unwrap() }.encode();
|
||||
put("synthetic-300-block-79146-final", &sobj, Some(&q_final), "stub", Some(Answer::Final));
|
||||
let q_last = Query { leaf: leaves[299].clone(), proof: mmr.proof(299).unwrap() }.encode();
|
||||
put("synthetic-300-block-79345-not-final", &sobj, Some(&q_last), "stub", Some(Answer::NotFinal));
|
||||
let mut bad_leaf = leaves[100].clone();
|
||||
bad_leaf.daa += 1;
|
||||
let q_bad = Query { leaf: bad_leaf, proof: mmr.proof(100).unwrap() }.encode();
|
||||
put("synthetic-300-block-79146-wrong-leaf-not-in-chain", &sobj, Some(&q_bad), "stub", Some(Answer::NotInChain));
|
||||
let (stale_pv, _, _) = synthetic(chain_id, key, 79046, 300, 79300, true);
|
||||
put("synthetic-300-stale", &FinalityObject::stub(key, stale_pv).encode(), None, "stub", Some(Answer::Stale));
|
||||
let (other, _, _) = synthetic(chain_id + 1, key, 79046, 300, 79300, false);
|
||||
put("synthetic-300-other-chain", &FinalityObject::stub(key, other).encode(), None, "refused", None);
|
||||
// 8. the timing object over the synthetic statement, with the final query: proof refused, no answer
|
||||
put("synthetic-300-timing", &timing_object(key, spv).encode(), Some(&q_final), "refused", None);
|
||||
std::fs::write(format!("{dir}/manifest.json"), format!("{{\"format\":\"igneum-finality-object-vectors-1\",\"object_version\":1,\"sp1_version\":\"{}\",\"key_id\":\"{}\",\"chain_id\":{chain_id},\"vectors\":[\n{}\n]}}\n", igneum_light::SP1_VERSION, hex(&key), manifest.join(",\n"))).unwrap();
|
||||
println!("{} vectors in {dir}", manifest.len());
|
||||
}
|
||||
_ => {
|
||||
eprintln!("igneum-light make-stub|make-timing|verify|time|url|vectors (see the file head)");
|
||||
std::process::exit(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
light/igneum-light-wasm/Cargo.toml
Normal file
12
light/igneum-light-wasm/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "igneum-light-wasm"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
description = "igneum-light for the browser tab: a C ABI over WebAssembly, no bindings generator"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
igneum-light = { path = "../igneum-light" }
|
||||
98
light/igneum-light-wasm/src/lib.rs
Normal file
98
light/igneum-light-wasm/src/lib.rs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
//! igneum-light for the tab: a C ABI the page calls through `WebAssembly.instantiate`, no bindings generator, no
|
||||
//! JavaScript glue beyond `site/pocket/light.js`. Memory travels through `pf_alloc`/`pf_free`; the verdict comes
|
||||
//! back as JSON (`igneum_light::json::verdict_json`) in a caller-supplied buffer.
|
||||
//!
|
||||
//! pf_abi_version() -> 1
|
||||
//! pf_warm() prepares the verifying key once (one pairing); idempotent
|
||||
//! pf_alloc(len) -> ptr, pf_free(ptr, len)
|
||||
//! pf_verify(obj, obj_len, query, query_len, key_id (32 bytes), chain_id (u64), has_clock (0/1), now_unix_s,
|
||||
//! genesis_unix_s, seconds_per_daa, out, out_cap) -> bytes written, or -(bytes needed) when out_cap is short
|
||||
//!
|
||||
//! `query_len` 0 means "the proof's own block". A query that fails to decode refuses the call (written as a refused
|
||||
//! verdict), never a silent fall-through to the proof's own block.
|
||||
|
||||
use igneum_light::{json::verdict_json, Clock, Pinned, ProofVerdict, Query, Verdict, Verifier, TRUST_ROW};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
static VERIFIER: OnceLock<Verifier> = OnceLock::new();
|
||||
|
||||
fn verifier() -> &'static Verifier {
|
||||
VERIFIER.get_or_init(Verifier::new)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn pf_abi_version() -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
/// Prepares the Groth16 verifying key (one pairing) ahead of the first verify; the page calls it at load so the
|
||||
/// measured verify is the per-object cost alone. Idempotent.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn pf_warm() {
|
||||
let _ = verifier();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn pf_alloc(len: usize) -> *mut u8 {
|
||||
let mut v = Vec::<u8>::with_capacity(len.max(1));
|
||||
let p = v.as_mut_ptr();
|
||||
std::mem::forget(v);
|
||||
p
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `ptr` came from `pf_alloc(len)` and is freed once.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn pf_free(ptr: *mut u8, len: usize) {
|
||||
if !ptr.is_null() {
|
||||
drop(Vec::from_raw_parts(ptr, 0, len.max(1)));
|
||||
}
|
||||
}
|
||||
|
||||
fn write_out(json: &str, out: *mut u8, out_cap: usize) -> i32 {
|
||||
let b = json.as_bytes();
|
||||
if b.len() > out_cap {
|
||||
return -(b.len() as i32);
|
||||
}
|
||||
// SAFETY: the caller allocated `out_cap` bytes at `out` through pf_alloc
|
||||
unsafe { core::ptr::copy_nonoverlapping(b.as_ptr(), out, b.len()) };
|
||||
b.len() as i32
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// Every pointer names `len` readable bytes allocated through `pf_alloc`; `key_id` names 32; `out` names `out_cap`.
|
||||
#[no_mangle]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub unsafe extern "C" fn pf_verify(
|
||||
obj: *const u8,
|
||||
obj_len: usize,
|
||||
query: *const u8,
|
||||
query_len: usize,
|
||||
key_id: *const u8,
|
||||
chain_id: u64,
|
||||
has_clock: u32,
|
||||
now_unix_s: f64,
|
||||
genesis_unix_s: f64,
|
||||
seconds_per_daa: f64,
|
||||
out: *mut u8,
|
||||
out_cap: usize,
|
||||
) -> i32 {
|
||||
let object = core::slice::from_raw_parts(obj, obj_len);
|
||||
let mut key = [0u8; 32];
|
||||
key.copy_from_slice(core::slice::from_raw_parts(key_id, 32));
|
||||
let pinned = Pinned { key_id: key, chain_id };
|
||||
let q = if query_len == 0 {
|
||||
None
|
||||
} else {
|
||||
match Query::decode(core::slice::from_raw_parts(query, query_len)) {
|
||||
Ok((q, _)) => Some(q),
|
||||
Err(why) => {
|
||||
let v = Verdict { proof: ProofVerdict::Refused(format!("block query: {why}")), answer: None, block_number: None, statement: None, age_s: None, trust_row: TRUST_ROW };
|
||||
return write_out(&verdict_json(&v), out, out_cap);
|
||||
}
|
||||
}
|
||||
};
|
||||
let clock = (has_clock != 0).then_some(Clock { now_unix_s: now_unix_s as i64, genesis_unix_s: genesis_unix_s as i64, seconds_per_daa });
|
||||
let v = verifier().verify(object, q.as_ref(), &pinned, clock);
|
||||
write_out(&verdict_json(&v), out, out_cap)
|
||||
}
|
||||
20
light/igneum-light/Cargo.toml
Normal file
20
light/igneum-light/Cargo.toml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "igneum-light"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
description = "Igneum light client core: the finality object, its SP1 Groth16 verification and the final-at answer"
|
||||
|
||||
[dependencies]
|
||||
# `ark` gives the gnark-to-arkworks converters; the pairing itself runs in ark-groth16 (the measured path: one
|
||||
# multi-Miller loop in arkworks against sp1-verifier's own substrate-bn, kept as the reference check)
|
||||
sp1-verifier = { workspace = true, features = ["ark"] }
|
||||
sha2 = { workspace = true }
|
||||
ark-groth16 = { version = "0.5.0", default-features = false }
|
||||
ark-bn254 = { version = "0.5.0", default-features = false, features = ["curve"] }
|
||||
ark-ec = { version = "0.5.0", default-features = false }
|
||||
ark-ff = { version = "0.5.0", default-features = false }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
std = []
|
||||
87
light/igneum-light/src/json.rs
Normal file
87
light/igneum-light/src/json.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
//! The verdict as JSON for the tab and the CLI, written by hand so the wasm carries no serializer.
|
||||
|
||||
use crate::{ProofVerdict, Verdict};
|
||||
use alloc::string::String;
|
||||
|
||||
fn push_str(out: &mut String, s: &str) {
|
||||
out.push('"');
|
||||
for c in s.chars() {
|
||||
match c {
|
||||
'"' => out.push_str("\\\""),
|
||||
'\\' => out.push_str("\\\\"),
|
||||
'\n' => out.push_str("\\n"),
|
||||
c if (c as u32) < 0x20 => out.push_str(&alloc::format!("\\u{:04x}", c as u32)),
|
||||
c => out.push(c),
|
||||
}
|
||||
}
|
||||
out.push('"');
|
||||
}
|
||||
|
||||
fn push_hex(out: &mut String, b: &[u8]) {
|
||||
out.push('"');
|
||||
for x in b {
|
||||
out.push_str(&alloc::format!("{x:02x}"));
|
||||
}
|
||||
out.push('"');
|
||||
}
|
||||
|
||||
pub fn verdict_json(v: &Verdict) -> String {
|
||||
let mut o = String::with_capacity(1024);
|
||||
o.push_str("{\"proof\":");
|
||||
match &v.proof {
|
||||
ProofVerdict::Verified => o.push_str("\"verified\",\"reason\":null"),
|
||||
ProofVerdict::Stub => o.push_str("\"stub\",\"reason\":null"),
|
||||
ProofVerdict::Refused(why) => {
|
||||
o.push_str("\"refused\",\"reason\":");
|
||||
push_str(&mut o, why);
|
||||
}
|
||||
}
|
||||
o.push_str(",\"answer\":");
|
||||
match v.answer {
|
||||
Some(a) => push_str(&mut o, a.as_str()),
|
||||
None => o.push_str("null"),
|
||||
}
|
||||
o.push_str(",\"block_number\":");
|
||||
match v.block_number {
|
||||
Some(n) => o.push_str(&alloc::format!("{n}")),
|
||||
None => o.push_str("null"),
|
||||
}
|
||||
o.push_str(",\"age_s\":");
|
||||
match v.age_s {
|
||||
Some(n) => o.push_str(&alloc::format!("{n}")),
|
||||
None => o.push_str("null"),
|
||||
}
|
||||
o.push_str(",\"trust_row\":");
|
||||
push_str(&mut o, v.trust_row);
|
||||
o.push_str(",\"statement\":");
|
||||
match &v.statement {
|
||||
None => o.push_str("null"),
|
||||
Some(s) => {
|
||||
o.push_str(&alloc::format!("{{\"chain_id\":{},\"number\":{},\"chain_len\":{},\"block_hash\":", s.chain_id, s.number, s.chain_len));
|
||||
push_hex(&mut o, &s.block_hash);
|
||||
o.push_str(",\"post_root\":");
|
||||
push_hex(&mut o, &s.post_root);
|
||||
o.push_str(",\"agg_vk\":");
|
||||
push_hex(&mut o, &s.agg_vk);
|
||||
o.push_str(",\"fin\":");
|
||||
match &s.fin {
|
||||
None => o.push_str("null"),
|
||||
Some(f) => {
|
||||
o.push_str(&alloc::format!("{{\"history_first\":{},\"stale\":{},\"history_root\":", f.history_first, f.stale()));
|
||||
push_hex(&mut o, &f.history_root);
|
||||
o.push_str(",\"table_root\":");
|
||||
push_hex(&mut o, &f.table_root);
|
||||
o.push_str(&alloc::format!(
|
||||
",\"lock\":{{\"index\":{},\"number\":{},\"signed\":{},\"total\":{},\"frozen_signed\":{},\"frozen_total\":{},\"daa\":{},\"hash\":",
|
||||
f.lock.index, f.lock.number, f.lock.signed, f.lock.total, f.lock.frozen_signed, f.lock.frozen_total, f.lock.daa
|
||||
));
|
||||
push_hex(&mut o, &f.lock.hash);
|
||||
o.push_str("}}");
|
||||
}
|
||||
}
|
||||
o.push('}');
|
||||
}
|
||||
}
|
||||
o.push('}');
|
||||
o
|
||||
}
|
||||
250
light/igneum-light/src/lib.rs
Normal file
250
light/igneum-light/src/lib.rs
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
//! Igneum light client, the core (docs/design/finality-object.md; docs/design/finality-in-proof.md section 4).
|
||||
//!
|
||||
//! One function answers the pocket question: `verify(object, query, pinned, now)` takes the finality object's bytes,
|
||||
//! an optional block query (a history leaf and its MMR path), the pinned aggregator id and the reader's clock, and
|
||||
//! returns a `Verdict`: whether the proof verified (the SP1 Groth16 wrap over bn254 under the pinned key), what the
|
||||
//! statement says (chain id, block, lock, weight), and the answer for the block asked about: final, not final, stale,
|
||||
//! not in this chain, or no claim. It asks nothing of any node and holds no state. `no_std` with `alloc`, so the same
|
||||
//! code runs natively, in WebAssembly in a tab (`igneum-light-wasm`) and behind a foreign-function layer on a phone.
|
||||
//!
|
||||
//! Byte offsets inside the public values are those of `BlockOutput::to_bytes` (proving/igneum-prove/core/src/agg.rs)
|
||||
//! and `FinExt::to_bytes` (proving/igneum-prove/fin/src/lib.rs), big-endian, confirmed by the fin-proof lane on
|
||||
//! 7 October 2026; the MMR and leaf hashing follow `igneum_fin_core::mmr` as `site/verify/finproof.js` does.
|
||||
|
||||
#![cfg_attr(not(any(feature = "std", test)), no_std)]
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
pub mod json;
|
||||
pub mod mmr;
|
||||
pub mod object;
|
||||
pub mod statement;
|
||||
|
||||
pub use mmr::{HistoryLeaf, MmrProof, Query};
|
||||
pub use object::{FinalityObject, ObjectError, KIND_SP1_GROTH16, KIND_STUB};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
pub use statement::{FinExt, Lock, Statement};
|
||||
|
||||
/// The SP1 version whose Groth16 verifying key and recursion root are compiled in (sp1-verifier 6.8.1).
|
||||
pub const SP1_VERSION: &str = "6.8.1";
|
||||
|
||||
/// What the reader pins before it trusts an object: the aggregator program id (the SP1 verifying-key hash of the
|
||||
/// pinned aggregator guest, `vk.bytes32()`) and the chain id. An object under another key or for another chain is
|
||||
/// refused before anything else is read from it.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Pinned {
|
||||
pub key_id: [u8; 32],
|
||||
pub chain_id: u64,
|
||||
}
|
||||
|
||||
/// The proof half of the verdict.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ProofVerdict {
|
||||
/// The Groth16 wrap verified under the pinned key: the statement is proven.
|
||||
Verified,
|
||||
/// The object carries the stub kind: the wrap has not landed, the statement is unverified. Never "final".
|
||||
Stub,
|
||||
/// The object was refused; the reason names the first check that failed.
|
||||
Refused(String),
|
||||
}
|
||||
|
||||
/// The block half of the verdict.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Answer {
|
||||
/// The block is on the chain at or below the latest lock the proof carries.
|
||||
Final,
|
||||
/// The block is on the chain but above the lock, or the proof carries no lock yet.
|
||||
NotFinal,
|
||||
/// The proof chain paused for more than a weight window; the object needs a fresh root (design 4.4).
|
||||
Stale,
|
||||
/// The query's leaf and path do not reach the proof's history root.
|
||||
NotInChain,
|
||||
/// The public values carry no finality extension (a proof made before the activation).
|
||||
NoClaim,
|
||||
}
|
||||
|
||||
impl Answer {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Answer::Final => "final",
|
||||
Answer::NotFinal => "not final",
|
||||
Answer::Stale => "stale",
|
||||
Answer::NotInChain => "not in this chain",
|
||||
Answer::NoClaim => "no claim",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The whole verdict. `answer` is `None` when the object was refused outright (nothing in it is believed).
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Verdict {
|
||||
pub proof: ProofVerdict,
|
||||
pub answer: Option<Answer>,
|
||||
/// The block the answer is about: the query's leaf, or the proof's own last block.
|
||||
pub block_number: Option<u64>,
|
||||
pub statement: Option<Statement>,
|
||||
/// The age of the lock by the reader's clock in seconds, when the reader gave a clock and a genesis time.
|
||||
pub age_s: Option<i64>,
|
||||
pub trust_row: &'static str,
|
||||
}
|
||||
|
||||
/// The trust row every surface shows beside a proof-carried lock (design section 4.4, level 0 of 5.2).
|
||||
pub const TRUST_ROW: &str = "voter set: verified in the proof; blue set: from the prover, vetoed by full nodes (level 0)";
|
||||
/// The trust row for a stub object.
|
||||
pub const TRUST_ROW_STUB: &str = "wrap pending: the public values are read, nothing is proven";
|
||||
|
||||
/// The reader's clock, for the age line: the current unix time and the chain's genesis time, both in seconds. The
|
||||
/// DAA score counts seconds of expected block time since genesis at one block a second, so the lock's age by the
|
||||
/// reader's clock is `now - (genesis + lock_daa)`; the devnet at a different block rate scales it (`seconds_per_daa`).
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Clock {
|
||||
pub now_unix_s: i64,
|
||||
pub genesis_unix_s: i64,
|
||||
pub seconds_per_daa: f64,
|
||||
}
|
||||
|
||||
/// The verifier: the Groth16 verifying key of the pinned SP1 version, prepared once (the preparation is itself one
|
||||
/// pairing, `alpha_g1 x beta_g2`, so a reader keeps one `Verifier` for its lifetime and the per-object cost is one
|
||||
/// multi-Miller loop and one final exponentiation).
|
||||
pub struct Verifier {
|
||||
pvk: ark_groth16::PreparedVerifyingKey<ark_bn254::Bn254>,
|
||||
}
|
||||
|
||||
impl Default for Verifier {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Verifier {
|
||||
pub fn new() -> Self {
|
||||
let vk = sp1_verifier::load_ark_groth16_verifying_key_from_bytes(*sp1_verifier::GROTH16_VK_BYTES).expect("the compiled-in Groth16 verifying key parses");
|
||||
Self { pvk: ark_groth16::prepare_verifying_key(&vk) }
|
||||
}
|
||||
|
||||
/// The pocket question. Order of checks: the container (magic, version, kind, lengths), the pinned key, the
|
||||
/// chain id, the proof (kind 1 only), then the query against the history root, then the lock. The first failure
|
||||
/// refuses the object and nothing later is read.
|
||||
pub fn verify(&self, object: &[u8], query: Option<&Query>, pinned: &Pinned, clock: Option<Clock>) -> Verdict {
|
||||
let refused = |why: String| Verdict { proof: ProofVerdict::Refused(why), answer: None, block_number: None, statement: None, age_s: None, trust_row: TRUST_ROW };
|
||||
let obj = match FinalityObject::decode(object) {
|
||||
Ok(o) => o,
|
||||
Err(e) => return refused(e.to_string()),
|
||||
};
|
||||
if obj.key_id != pinned.key_id {
|
||||
return refused(String::from("key id is not the pinned aggregator id"));
|
||||
}
|
||||
let st = match Statement::parse(&obj.public_values) {
|
||||
Some(s) => s,
|
||||
None => return refused(String::from("public values do not parse as a block statement")),
|
||||
};
|
||||
if st.chain_id != pinned.chain_id {
|
||||
return refused(alloc::format!("chain id {} is not the pinned chain {}", st.chain_id, pinned.chain_id));
|
||||
}
|
||||
if st.agg_vk != [0u8; 32] && st.agg_vk != pinned.key_id {
|
||||
// a continuing proof names the aggregator key it verified; it must be the same pinned program
|
||||
return refused(String::from("the statement's agg_vk is not the pinned aggregator id"));
|
||||
}
|
||||
let proof = match obj.kind {
|
||||
KIND_STUB => ProofVerdict::Stub,
|
||||
KIND_SP1_GROTH16 => match self.verify_wrap(&obj) {
|
||||
Ok(()) => ProofVerdict::Verified,
|
||||
Err(why) => return refused(why),
|
||||
},
|
||||
k => return refused(alloc::format!("unknown proof kind {k}")),
|
||||
};
|
||||
let trust_row = if proof == ProofVerdict::Stub { TRUST_ROW_STUB } else { TRUST_ROW };
|
||||
let Some(fin) = &st.fin else {
|
||||
return Verdict { proof, answer: Some(Answer::NoClaim), block_number: Some(st.number), statement: Some(st), age_s: None, trust_row };
|
||||
};
|
||||
let mut number = st.number;
|
||||
if let Some(q) = query {
|
||||
let leaves = st.number - fin.history_first + 1;
|
||||
if !mmr::verify_history(&q.leaf, &q.proof, &fin.history_root, leaves) {
|
||||
return Verdict { proof, answer: Some(Answer::NotInChain), block_number: Some(q.leaf.number), statement: Some(st), age_s: None, trust_row };
|
||||
}
|
||||
number = q.leaf.number;
|
||||
}
|
||||
let age_s = clock.map(|c| c.now_unix_s - c.genesis_unix_s - ((fin.lock.daa as f64) * c.seconds_per_daa) as i64);
|
||||
let answer = if fin.stale() {
|
||||
Answer::Stale
|
||||
} else if fin.lock.index > 0 && number <= fin.lock.number {
|
||||
Answer::Final
|
||||
} else {
|
||||
Answer::NotFinal
|
||||
};
|
||||
Verdict { proof, answer: Some(answer), block_number: Some(number), statement: Some(st), age_s, trust_row }
|
||||
}
|
||||
|
||||
/// The wrap check as `sp1_verifier::Groth16Verifier::verify_with_exit_code` does it at 6.8.1 (exit code zero,
|
||||
/// the recursion root of the version, the public values hashed with SHA-256 and masked to 253 bits), with the
|
||||
/// pairing in arkworks: public inputs `[key_id, sha256(public_values) & mask, 0, vk_root, nonce]`.
|
||||
pub fn verify_wrap(&self, obj: &FinalityObject) -> Result<(), String> {
|
||||
let proof = sp1_verifier::load_ark_proof_from_bytes(&obj.proof).map_err(|e| alloc::format!("the wrap's curve points do not load: {e:?}"))?;
|
||||
let inputs = sp1_verifier::load_ark_public_inputs_from_bytes(&obj.key_id, &sp1_verifier::hash_public_inputs(&obj.public_values), &[0u8; 32], &sp1_verifier::VK_ROOT_BYTES, &obj.nonce);
|
||||
match ark_groth16::Groth16::<ark_bn254::Bn254>::verify_proof(&self.pvk, &proof, &inputs) {
|
||||
Ok(true) => Ok(()),
|
||||
Ok(false) => Err(String::from("the Groth16 wrap does not verify under the pinned key")),
|
||||
Err(e) => Err(alloc::format!("the Groth16 wrap does not verify under the pinned key: {e:?}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The pocket question with a fresh `Verifier` (one extra pairing to prepare the key; a long-lived reader keeps one).
|
||||
pub fn verify(object: &[u8], query: Option<&Query>, pinned: &Pinned, clock: Option<Clock>) -> Verdict {
|
||||
Verifier::new().verify(object, query, pinned, clock)
|
||||
}
|
||||
|
||||
/// The SP1 Groth16 proof as the SDK emits it at 6.8.1, rebuilt from the object's 288 carried bytes: the 4-byte
|
||||
/// prefix (sha256 of the Groth16 verifying key), the exit code (zero: the guest never exits otherwise), the recursion
|
||||
/// verifying-key root (a constant of the SP1 version), the proof nonce and the 256-byte gnark proof. The prefix and
|
||||
/// the root are not carried because the verifier pins them; carrying them would let an object name a key the
|
||||
/// verifier does not hold, which is a refusal either way.
|
||||
pub fn sp1_proof_bytes(obj: &FinalityObject) -> Vec<u8> {
|
||||
let vk_hash = Sha256::digest(*sp1_verifier::GROTH16_VK_BYTES);
|
||||
let mut v = Vec::with_capacity(4 + 96 + 256);
|
||||
v.extend_from_slice(&vk_hash[..4]);
|
||||
v.extend_from_slice(&[0u8; 32]);
|
||||
v.extend_from_slice(&*sp1_verifier::VK_ROOT_BYTES);
|
||||
v.extend_from_slice(&obj.nonce);
|
||||
v.extend_from_slice(&obj.proof);
|
||||
v
|
||||
}
|
||||
|
||||
fn hex32(b: &[u8; 32]) -> String {
|
||||
let mut s = String::with_capacity(66);
|
||||
s.push_str("0x");
|
||||
for x in b {
|
||||
s.push_str(&alloc::format!("{x:02x}"));
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
/// The reference check: sp1-verifier's own `Groth16Verifier::verify` (substrate-bn). It hashes the public values
|
||||
/// with SHA-256 and, on failure, once more with BLAKE3, so a refusal costs two pairings and an acceptance one. The
|
||||
/// tests hold the arkworks path to this one.
|
||||
pub fn verify_sp1_groth16_reference(obj: &FinalityObject) -> Result<(), String> {
|
||||
let full = sp1_proof_bytes(obj);
|
||||
sp1_verifier::Groth16Verifier::verify(&full, &obj.public_values, &hex32(&obj.key_id), *sp1_verifier::GROTH16_VK_BYTES)
|
||||
.map_err(|e| alloc::format!("the Groth16 wrap does not verify under the pinned key: {e:?}"))
|
||||
}
|
||||
|
||||
/// The reference check's accept-path cost alone (one pairing in substrate-bn), for timing.
|
||||
pub fn verify_sp1_groth16_reference_one_pairing(obj: &FinalityObject) -> Result<(), String> {
|
||||
let pv_hash = sp1_verifier::hash_public_inputs(&obj.public_values);
|
||||
sp1_verifier::Groth16Verifier::verify_gnark_proof(&obj.proof, &[obj.key_id, pv_hash, [0u8; 32], *sp1_verifier::VK_ROOT_BYTES, obj.nonce], *sp1_verifier::GROTH16_VK_BYTES)
|
||||
.map_err(|e| alloc::format!("{e:?}"))
|
||||
}
|
||||
|
||||
pub fn sha256(parts: &[&[u8]]) -> [u8; 32] {
|
||||
let mut h = Sha256::new();
|
||||
for p in parts {
|
||||
h.update(p);
|
||||
}
|
||||
h.finalize().into()
|
||||
}
|
||||
293
light/igneum-light/src/mmr.rs
Normal file
293
light/igneum-light/src/mmr.rs
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
//! The history check (`igneum_fin_core::mmr`, mirrored by `site/verify/finproof.js`): a chain block's leaf and its
|
||||
//! Merkle mountain range path against the `history_root` the proof carries. Leaf `sha256(0x04 ‖ number_le ‖
|
||||
//! block_hash ‖ daa_le ‖ table_root ‖ keys_hash ‖ total_le)`, node `sha256(0x02 ‖ left ‖ right)`, peaks bagged
|
||||
//! from the right with `sha256(0x03 ‖ peak ‖ acc)`.
|
||||
//!
|
||||
//! The query's byte form (section 4 of the object spec), appended to an object in a QR bundle or carried as `q=`
|
||||
//! in the URL:
|
||||
//!
|
||||
//! | offset | bytes | field |
|
||||
//! |---|---|---|
|
||||
//! | 0 | 4 | magic `IGFQ` |
|
||||
//! | 4 | 1 | version, 1 |
|
||||
//! | 5 | 8 | leaf number |
|
||||
//! | 13 | 32 | leaf block hash |
|
||||
//! | 45 | 8 | leaf daa |
|
||||
//! | 53 | 32 | leaf table root |
|
||||
//! | 85 | 32 | leaf keys hash |
|
||||
//! | 117 | 8 | leaf total |
|
||||
//! | 125 | 8 | position (leaf index in the history, zero based) |
|
||||
//! | 133 | 1 | peak index |
|
||||
//! | 134 | 1 | peak count p |
|
||||
//! | 135 | 33 p | peaks, each height (1) then hash (32), heights strictly decreasing |
|
||||
//! | 135 + 33 p | 1 | sibling count s (equals the chosen peak's height) |
|
||||
//! | 136 + 33 p | 33 s | siblings, each side (1: 1 = the sibling is on the left) then hash (32) |
|
||||
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
pub const QUERY_MAGIC: [u8; 4] = *b"IGFQ";
|
||||
pub const QUERY_VERSION: u8 = 1;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct HistoryLeaf {
|
||||
pub number: u64,
|
||||
pub block_hash: [u8; 32],
|
||||
pub daa: u64,
|
||||
pub table_root: [u8; 32],
|
||||
pub keys_hash: [u8; 32],
|
||||
pub total: u64,
|
||||
}
|
||||
|
||||
impl HistoryLeaf {
|
||||
pub fn hash(&self) -> [u8; 32] {
|
||||
crate::sha256(&[&[4u8], &self.number.to_le_bytes(), &self.block_hash, &self.daa.to_le_bytes(), &self.table_root, &self.keys_hash, &self.total.to_le_bytes()])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct MmrProof {
|
||||
pub position: u64,
|
||||
pub peak_index: usize,
|
||||
/// (height, hash), left to right, heights strictly decreasing
|
||||
pub peaks: Vec<(u8, [u8; 32])>,
|
||||
/// (sibling is on the left, hash), leaf upwards
|
||||
pub siblings: Vec<(bool, [u8; 32])>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Query {
|
||||
pub leaf: HistoryLeaf,
|
||||
pub proof: MmrProof,
|
||||
}
|
||||
|
||||
/// `MmrProof::verify`: the leaf sits at `proof.position` in a history of `leaves` leaves whose peaks bag to `root`.
|
||||
pub fn verify_history(leaf: &HistoryLeaf, proof: &MmrProof, root: &[u8; 32], leaves: u64) -> bool {
|
||||
let peaks = &proof.peaks;
|
||||
if proof.position >= leaves || proof.peak_index >= peaks.len() {
|
||||
return false;
|
||||
}
|
||||
let mut count: u128 = 0;
|
||||
let mut last: Option<u8> = None;
|
||||
for (h, _) in peaks {
|
||||
if let Some(l) = last {
|
||||
if l <= *h {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if *h >= 64 {
|
||||
return false;
|
||||
}
|
||||
last = Some(*h);
|
||||
count += 1u128 << *h;
|
||||
}
|
||||
if count != leaves as u128 {
|
||||
return false;
|
||||
}
|
||||
let before: u128 = peaks[..proof.peak_index].iter().map(|(h, _)| 1u128 << *h).sum();
|
||||
let height = peaks[proof.peak_index].0;
|
||||
if (proof.position as u128) < before {
|
||||
return false;
|
||||
}
|
||||
let mut idx = proof.position as u128 - before;
|
||||
if idx >= (1u128 << height) || proof.siblings.len() != height as usize {
|
||||
return false;
|
||||
}
|
||||
let mut node = leaf.hash();
|
||||
for (left, sib) in &proof.siblings {
|
||||
if *left != (idx & 1 == 1) {
|
||||
return false;
|
||||
}
|
||||
node = if *left { crate::sha256(&[&[2u8], sib, &node]) } else { crate::sha256(&[&[2u8], &node, sib]) };
|
||||
idx >>= 1;
|
||||
}
|
||||
if node != peaks[proof.peak_index].1 {
|
||||
return false;
|
||||
}
|
||||
let mut acc = peaks[peaks.len() - 1].1;
|
||||
for i in (0..peaks.len() - 1).rev() {
|
||||
acc = crate::sha256(&[&[3u8], &peaks[i].1, &acc]);
|
||||
}
|
||||
acc == *root
|
||||
}
|
||||
|
||||
impl Query {
|
||||
pub fn encode(&self) -> Vec<u8> {
|
||||
let mut v = Vec::with_capacity(136 + 33 * (self.proof.peaks.len() + self.proof.siblings.len()));
|
||||
v.extend_from_slice(&QUERY_MAGIC);
|
||||
v.push(QUERY_VERSION);
|
||||
v.extend_from_slice(&self.leaf.number.to_be_bytes());
|
||||
v.extend_from_slice(&self.leaf.block_hash);
|
||||
v.extend_from_slice(&self.leaf.daa.to_be_bytes());
|
||||
v.extend_from_slice(&self.leaf.table_root);
|
||||
v.extend_from_slice(&self.leaf.keys_hash);
|
||||
v.extend_from_slice(&self.leaf.total.to_be_bytes());
|
||||
v.extend_from_slice(&self.proof.position.to_be_bytes());
|
||||
v.push(self.proof.peak_index as u8);
|
||||
v.push(self.proof.peaks.len() as u8);
|
||||
for (h, p) in &self.proof.peaks {
|
||||
v.push(*h);
|
||||
v.extend_from_slice(p);
|
||||
}
|
||||
v.push(self.proof.siblings.len() as u8);
|
||||
for (left, s) in &self.proof.siblings {
|
||||
v.push(*left as u8);
|
||||
v.extend_from_slice(s);
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
/// Decodes a query from the front of `b`; returns it with the bytes it used.
|
||||
pub fn decode(b: &[u8]) -> Result<(Self, usize), String> {
|
||||
if b.len() < 135 {
|
||||
return Err(String::from("query is shorter than its fixed part"));
|
||||
}
|
||||
if b[0..4] != QUERY_MAGIC {
|
||||
return Err(String::from("not a block query (magic)"));
|
||||
}
|
||||
if b[4] != QUERY_VERSION {
|
||||
return Err(alloc::format!("query version {} is not 1", b[4]));
|
||||
}
|
||||
let u64_at = |i: usize| u64::from_be_bytes(b[i..i + 8].try_into().unwrap());
|
||||
let h_at = |i: usize| -> [u8; 32] { b[i..i + 32].try_into().unwrap() };
|
||||
let leaf = HistoryLeaf { number: u64_at(5), block_hash: h_at(13), daa: u64_at(45), table_root: h_at(53), keys_hash: h_at(85), total: u64_at(117) };
|
||||
let position = u64_at(125);
|
||||
let peak_index = b[133] as usize;
|
||||
let np = b[134] as usize;
|
||||
let mut o = 135;
|
||||
if b.len() < o + 33 * np + 1 {
|
||||
return Err(String::from("query is shorter than its peaks"));
|
||||
}
|
||||
let mut peaks = Vec::with_capacity(np);
|
||||
for _ in 0..np {
|
||||
peaks.push((b[o], h_at(o + 1)));
|
||||
o += 33;
|
||||
}
|
||||
let ns = b[o] as usize;
|
||||
o += 1;
|
||||
if b.len() < o + 33 * ns {
|
||||
return Err(String::from("query is shorter than its siblings"));
|
||||
}
|
||||
let mut siblings = Vec::with_capacity(ns);
|
||||
for _ in 0..ns {
|
||||
if b[o] > 1 {
|
||||
return Err(String::from("a sibling side byte is not 0 or 1"));
|
||||
}
|
||||
siblings.push((b[o] == 1, h_at(o + 1)));
|
||||
o += 33;
|
||||
}
|
||||
Ok((Query { leaf, proof: MmrProof { position, peak_index, peaks, siblings } }, o))
|
||||
}
|
||||
}
|
||||
|
||||
/// A small in-memory MMR, for tests and for the CLI's vectors: the same construction as `igneum_fin_core::mmr::Mmr`
|
||||
/// (append leaves; peaks are perfect binary trees by height; a proof is the path inside the leaf's peak).
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Mmr {
|
||||
leaves: Vec<[u8; 32]>,
|
||||
}
|
||||
|
||||
impl Mmr {
|
||||
pub fn push(&mut self, leaf: &HistoryLeaf) {
|
||||
self.leaves.push(leaf.hash());
|
||||
}
|
||||
|
||||
pub fn leaves(&self) -> u64 {
|
||||
self.leaves.len() as u64
|
||||
}
|
||||
|
||||
/// The peaks are the set bits of the leaf count, highest first: one perfect tree per bit.
|
||||
fn peak_ranges(&self) -> Vec<(u8, usize, usize)> {
|
||||
let n = self.leaves.len();
|
||||
let mut out = Vec::new();
|
||||
let mut start = 0;
|
||||
for h in (0..63u8).rev() {
|
||||
if n & (1usize << h) != 0 {
|
||||
out.push((h, start, start + (1 << h)));
|
||||
start += 1 << h;
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn subtree(&self, lo: usize, hi: usize) -> [u8; 32] {
|
||||
if hi - lo == 1 {
|
||||
return self.leaves[lo];
|
||||
}
|
||||
let mid = lo + (hi - lo) / 2;
|
||||
crate::sha256(&[&[2u8], &self.subtree(lo, mid), &self.subtree(mid, hi)])
|
||||
}
|
||||
|
||||
pub fn peaks(&self) -> Vec<(u8, [u8; 32])> {
|
||||
self.peak_ranges().iter().map(|(h, lo, hi)| (*h, self.subtree(*lo, *hi))).collect()
|
||||
}
|
||||
|
||||
pub fn root(&self) -> [u8; 32] {
|
||||
let peaks = self.peaks();
|
||||
let mut acc = peaks[peaks.len() - 1].1;
|
||||
for i in (0..peaks.len() - 1).rev() {
|
||||
acc = crate::sha256(&[&[3u8], &peaks[i].1, &acc]);
|
||||
}
|
||||
acc
|
||||
}
|
||||
|
||||
pub fn proof(&self, position: u64) -> Option<MmrProof> {
|
||||
let ranges = self.peak_ranges();
|
||||
let pos = position as usize;
|
||||
let (peak_index, &(h, lo, hi)) = ranges.iter().enumerate().find(|(_, (_, lo, hi))| pos >= *lo && pos < *hi)?;
|
||||
let mut siblings = Vec::with_capacity(h as usize);
|
||||
let (mut l, mut r) = (lo, hi);
|
||||
let mut path = Vec::new();
|
||||
while r - l > 1 {
|
||||
let mid = l + (r - l) / 2;
|
||||
if pos < mid {
|
||||
path.push((false, mid, r));
|
||||
r = mid;
|
||||
} else {
|
||||
path.push((true, l, mid));
|
||||
l = mid;
|
||||
}
|
||||
}
|
||||
for (left, a, b) in path.into_iter().rev() {
|
||||
siblings.push((left, self.subtree(a, b)));
|
||||
}
|
||||
Some(MmrProof { position, peak_index, peaks: self.peaks(), siblings })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn leaf(n: u64) -> HistoryLeaf {
|
||||
HistoryLeaf { number: 1000 + n, block_hash: [n as u8; 32], daa: 5000 + n, table_root: [1; 32], keys_hash: [2; 32], total: 99 }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_leaf_of_every_size_verifies_and_a_flip_fails() {
|
||||
for n in 1..40u64 {
|
||||
let mut m = Mmr::default();
|
||||
for i in 0..n {
|
||||
m.push(&leaf(i));
|
||||
}
|
||||
let root = m.root();
|
||||
for i in 0..n {
|
||||
let p = m.proof(i).unwrap();
|
||||
assert!(verify_history(&leaf(i), &p, &root, n), "n {n} i {i}");
|
||||
let mut bad = leaf(i);
|
||||
bad.daa += 1;
|
||||
assert!(!verify_history(&bad, &p, &root, n));
|
||||
assert!(!verify_history(&leaf(i), &p, &root, n + 1));
|
||||
let q = Query { leaf: leaf(i), proof: p.clone() };
|
||||
let (back, used) = Query::decode(&q.encode()).unwrap();
|
||||
assert_eq!(back, q);
|
||||
assert_eq!(used, q.encode().len());
|
||||
}
|
||||
let mut p = m.proof(0).unwrap();
|
||||
if let Some(s) = p.siblings.first_mut() {
|
||||
s.1[0] ^= 1;
|
||||
assert!(!verify_history(&leaf(0), &p, &root, n));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
225
light/igneum-light/src/object.rs
Normal file
225
light/igneum-light/src/object.rs
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
//! The finality object, version 1 (docs/design/finality-object.md section 2): a fixed header, the pinned key id,
|
||||
//! the aggregator's public values and the 288 carried bytes of the SP1 Groth16 wrap.
|
||||
//!
|
||||
//! | offset | bytes | field |
|
||||
//! |---|---|---|
|
||||
//! | 0 | 4 | magic `IGFO` |
|
||||
//! | 4 | 1 | version, 1 |
|
||||
//! | 5 | 1 | kind: 0 stub (no wrap), 1 SP1 6.8.1 Groth16 over bn254 |
|
||||
//! | 6 | 2 | public values length, big-endian: 340 (no finality claim) or 504 (with the extension) |
|
||||
//! | 8 | 32 | key id: the aggregator program's SP1 verifying-key hash |
|
||||
//! | 40 | n | public values, `BlockOutput::to_bytes` |
|
||||
//! | 40 + n | 32 | proof nonce (bound as a public input by the wrap circuit) |
|
||||
//! | 72 + n | 256 | gnark Groth16 proof: A (64), B (128), C (64), uncompressed big-endian |
|
||||
//!
|
||||
//! 832 bytes at n = 504, 668 at n = 340. Every byte is bound: the header by the parser, the key id and the public
|
||||
//! values as public inputs of the pairing, the nonce likewise, the proof by the pairing itself.
|
||||
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
pub const MAGIC: [u8; 4] = *b"IGFO";
|
||||
pub const VERSION: u8 = 1;
|
||||
pub const KIND_STUB: u8 = 0;
|
||||
pub const KIND_SP1_GROTH16: u8 = 1;
|
||||
pub const HEADER_LEN: usize = 8;
|
||||
pub const KEY_ID_LEN: usize = 32;
|
||||
pub const NONCE_LEN: usize = 32;
|
||||
pub const PROOF_LEN: usize = 256;
|
||||
/// The two public-values lengths the aggregator emits (agg.rs `BlockOutput::LEN` and `LEN2`).
|
||||
pub const PV_LEN_PLAIN: usize = 340;
|
||||
pub const PV_LEN_FIN: usize = 504;
|
||||
/// The object's length with the finality extension: 832.
|
||||
pub const LEN_FIN: usize = HEADER_LEN + KEY_ID_LEN + PV_LEN_FIN + NONCE_LEN + PROOF_LEN;
|
||||
pub const LEN_PLAIN: usize = HEADER_LEN + KEY_ID_LEN + PV_LEN_PLAIN + NONCE_LEN + PROOF_LEN;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct FinalityObject {
|
||||
pub kind: u8,
|
||||
pub key_id: [u8; 32],
|
||||
pub public_values: Vec<u8>,
|
||||
pub nonce: [u8; 32],
|
||||
pub proof: [u8; 256],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ObjectError {
|
||||
TooShort(usize),
|
||||
BadMagic,
|
||||
BadVersion(u8),
|
||||
BadKind(u8),
|
||||
BadPublicValuesLength(usize),
|
||||
BadLength { got: usize, want: usize },
|
||||
}
|
||||
|
||||
impl ObjectError {
|
||||
pub fn to_string(&self) -> String {
|
||||
match self {
|
||||
ObjectError::TooShort(n) => alloc::format!("object is {n} bytes, shorter than the header"),
|
||||
ObjectError::BadMagic => String::from("not a finality object (magic)"),
|
||||
ObjectError::BadVersion(v) => alloc::format!("object version {v} is not 1"),
|
||||
ObjectError::BadKind(k) => alloc::format!("object kind {k} is not 0 (stub) or 1 (SP1 Groth16)"),
|
||||
ObjectError::BadPublicValuesLength(n) => alloc::format!("public values length {n} is not 340 or 504"),
|
||||
ObjectError::BadLength { got, want } => alloc::format!("object is {got} bytes, the header says {want}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FinalityObject {
|
||||
pub fn len(&self) -> usize {
|
||||
HEADER_LEN + KEY_ID_LEN + self.public_values.len() + NONCE_LEN + PROOF_LEN
|
||||
}
|
||||
|
||||
pub fn encode(&self) -> Vec<u8> {
|
||||
let mut v = Vec::with_capacity(self.len());
|
||||
v.extend_from_slice(&MAGIC);
|
||||
v.push(VERSION);
|
||||
v.push(self.kind);
|
||||
v.extend_from_slice(&(self.public_values.len() as u16).to_be_bytes());
|
||||
v.extend_from_slice(&self.key_id);
|
||||
v.extend_from_slice(&self.public_values);
|
||||
v.extend_from_slice(&self.nonce);
|
||||
v.extend_from_slice(&self.proof);
|
||||
v
|
||||
}
|
||||
|
||||
pub fn decode(b: &[u8]) -> Result<Self, ObjectError> {
|
||||
if b.len() < HEADER_LEN {
|
||||
return Err(ObjectError::TooShort(b.len()));
|
||||
}
|
||||
if b[0..4] != MAGIC {
|
||||
return Err(ObjectError::BadMagic);
|
||||
}
|
||||
if b[4] != VERSION {
|
||||
return Err(ObjectError::BadVersion(b[4]));
|
||||
}
|
||||
let kind = b[5];
|
||||
if kind != KIND_STUB && kind != KIND_SP1_GROTH16 {
|
||||
return Err(ObjectError::BadKind(kind));
|
||||
}
|
||||
let n = u16::from_be_bytes([b[6], b[7]]) as usize;
|
||||
if n != PV_LEN_PLAIN && n != PV_LEN_FIN {
|
||||
return Err(ObjectError::BadPublicValuesLength(n));
|
||||
}
|
||||
let want = HEADER_LEN + KEY_ID_LEN + n + NONCE_LEN + PROOF_LEN;
|
||||
if b.len() != want {
|
||||
return Err(ObjectError::BadLength { got: b.len(), want });
|
||||
}
|
||||
let mut key_id = [0u8; 32];
|
||||
key_id.copy_from_slice(&b[8..40]);
|
||||
let public_values = b[40..40 + n].to_vec();
|
||||
let mut nonce = [0u8; 32];
|
||||
nonce.copy_from_slice(&b[40 + n..72 + n]);
|
||||
let mut proof = [0u8; 256];
|
||||
proof.copy_from_slice(&b[72 + n..72 + n + 256]);
|
||||
Ok(Self { kind, key_id, public_values, nonce, proof })
|
||||
}
|
||||
|
||||
/// The stub object the node serves until the wrap lands: the public values the chain carried, the wrap bytes
|
||||
/// zero, the kind marked.
|
||||
pub fn stub(key_id: [u8; 32], public_values: Vec<u8>) -> Self {
|
||||
Self { kind: KIND_STUB, key_id, public_values, nonce: [0u8; 32], proof: [0u8; 256] }
|
||||
}
|
||||
}
|
||||
|
||||
/// The URL form: the object's bytes in base64url with no padding, in a fragment so no server sees them.
|
||||
pub fn base64url_encode(b: &[u8]) -> String {
|
||||
const T: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||
let mut s = String::with_capacity((b.len() + 2) / 3 * 4);
|
||||
for chunk in b.chunks(3) {
|
||||
let n = chunk.len();
|
||||
let v = (chunk[0] as u32) << 16 | (if n > 1 { chunk[1] as u32 } else { 0 }) << 8 | (if n > 2 { chunk[2] as u32 } else { 0 });
|
||||
s.push(T[(v >> 18) as usize & 63] as char);
|
||||
s.push(T[(v >> 12) as usize & 63] as char);
|
||||
if n > 1 {
|
||||
s.push(T[(v >> 6) as usize & 63] as char);
|
||||
}
|
||||
if n > 2 {
|
||||
s.push(T[v as usize & 63] as char);
|
||||
}
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
pub fn base64url_decode(s: &str) -> Option<Vec<u8>> {
|
||||
let val = |c: u8| -> Option<u32> {
|
||||
Some(match c {
|
||||
b'A'..=b'Z' => (c - b'A') as u32,
|
||||
b'a'..=b'z' => (c - b'a') as u32 + 26,
|
||||
b'0'..=b'9' => (c - b'0') as u32 + 52,
|
||||
b'-' => 62,
|
||||
b'_' => 63,
|
||||
_ => return None,
|
||||
})
|
||||
};
|
||||
let bytes = s.trim_end_matches('=').as_bytes();
|
||||
let mut out = Vec::with_capacity(bytes.len() * 3 / 4);
|
||||
for chunk in bytes.chunks(4) {
|
||||
let n = chunk.len();
|
||||
if n == 1 {
|
||||
return None;
|
||||
}
|
||||
let mut v = 0u32;
|
||||
for (i, c) in chunk.iter().enumerate() {
|
||||
v |= val(*c)? << (18 - 6 * i);
|
||||
}
|
||||
out.push((v >> 16) as u8);
|
||||
if n > 2 {
|
||||
out.push((v >> 8) as u8);
|
||||
}
|
||||
if n > 3 {
|
||||
out.push(v as u8);
|
||||
}
|
||||
}
|
||||
Some(out)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn round_trip_and_lengths() {
|
||||
let o = FinalityObject::stub([7u8; 32], alloc::vec![1u8; PV_LEN_FIN]);
|
||||
let b = o.encode();
|
||||
assert_eq!(b.len(), LEN_FIN);
|
||||
assert_eq!(LEN_FIN, 832);
|
||||
assert_eq!(LEN_PLAIN, 668);
|
||||
assert_eq!(FinalityObject::decode(&b).unwrap(), o);
|
||||
let p = FinalityObject::stub([7u8; 32], alloc::vec![1u8; PV_LEN_PLAIN]);
|
||||
assert_eq!(FinalityObject::decode(&p.encode()).unwrap(), p);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_header_byte_is_checked() {
|
||||
let o = FinalityObject::stub([7u8; 32], alloc::vec![1u8; PV_LEN_FIN]).encode();
|
||||
let mut b = o.clone();
|
||||
b[0] = b'X';
|
||||
assert_eq!(FinalityObject::decode(&b), Err(ObjectError::BadMagic));
|
||||
let mut b = o.clone();
|
||||
b[4] = 2;
|
||||
assert_eq!(FinalityObject::decode(&b), Err(ObjectError::BadVersion(2)));
|
||||
let mut b = o.clone();
|
||||
b[5] = 9;
|
||||
assert_eq!(FinalityObject::decode(&b), Err(ObjectError::BadKind(9)));
|
||||
let mut b = o.clone();
|
||||
b[7] = 0xf7;
|
||||
assert_eq!(FinalityObject::decode(&b), Err(ObjectError::BadPublicValuesLength(0x01f7)));
|
||||
let mut b = o.clone();
|
||||
b.push(0);
|
||||
assert_eq!(FinalityObject::decode(&b), Err(ObjectError::BadLength { got: 833, want: 832 }));
|
||||
assert_eq!(FinalityObject::decode(&b[..5]), Err(ObjectError::TooShort(5)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base64url_round_trip() {
|
||||
for n in 0..70usize {
|
||||
let v: Vec<u8> = (0..n).map(|i| (i * 37 + 11) as u8).collect();
|
||||
let s = base64url_encode(&v);
|
||||
assert!(!s.contains('='));
|
||||
assert_eq!(base64url_decode(&s).unwrap(), v, "{n}");
|
||||
}
|
||||
assert_eq!(base64url_decode("A"), None);
|
||||
assert_eq!(base64url_decode("A!"), None);
|
||||
}
|
||||
}
|
||||
137
light/igneum-light/src/statement.rs
Normal file
137
light/igneum-light/src/statement.rs
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
//! The aggregator's public values: `BlockOutput` (340 bytes) and the finality extension `FinExt` (164 bytes), the
|
||||
//! byte order of `BlockOutput::to_bytes` and `FinExt::to_bytes`, every integer big-endian.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
pub const BLOCK_STATEMENT_LEN: usize = 340;
|
||||
pub const FIN_EXT_LEN: usize = 164;
|
||||
pub const FLAG_STALE: u16 = 1;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Lock {
|
||||
pub index: u64,
|
||||
pub hash: [u8; 32],
|
||||
pub number: u64,
|
||||
pub signed: u64,
|
||||
pub total: u64,
|
||||
pub frozen_signed: u64,
|
||||
pub frozen_total: u64,
|
||||
pub daa: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct FinExt {
|
||||
pub fin_version: u16,
|
||||
pub table_root: [u8; 32],
|
||||
pub history_root: [u8; 32],
|
||||
pub history_first: u64,
|
||||
pub lock: Lock,
|
||||
pub flags: u16,
|
||||
}
|
||||
|
||||
impl FinExt {
|
||||
pub fn stale(&self) -> bool {
|
||||
self.flags & FLAG_STALE != 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Statement {
|
||||
pub chain_id: u64,
|
||||
pub number: u64,
|
||||
pub block_hash: [u8; 32],
|
||||
pub parent_hash: [u8; 32],
|
||||
pub post_root: [u8; 32],
|
||||
pub shard_vk: [u8; 32],
|
||||
pub agg_vk: [u8; 32],
|
||||
pub chain_len: u64,
|
||||
pub fin: Option<FinExt>,
|
||||
}
|
||||
|
||||
fn u64_at(b: &[u8], i: usize) -> u64 {
|
||||
u64::from_be_bytes(b[i..i + 8].try_into().unwrap())
|
||||
}
|
||||
fn u16_at(b: &[u8], i: usize) -> u16 {
|
||||
u16::from_be_bytes([b[i], b[i + 1]])
|
||||
}
|
||||
fn h_at(b: &[u8], i: usize) -> [u8; 32] {
|
||||
b[i..i + 32].try_into().unwrap()
|
||||
}
|
||||
|
||||
impl Statement {
|
||||
/// `None` unless the bytes are exactly 340 or 504 long and, at 504, carry `fin_version` 1.
|
||||
pub fn parse(b: &[u8]) -> Option<Self> {
|
||||
let fin = match b.len() {
|
||||
BLOCK_STATEMENT_LEN => None,
|
||||
n if n == BLOCK_STATEMENT_LEN + FIN_EXT_LEN => {
|
||||
let e = &b[BLOCK_STATEMENT_LEN..];
|
||||
let fin_version = u16_at(e, 0);
|
||||
if fin_version != 1 {
|
||||
return None;
|
||||
}
|
||||
Some(FinExt {
|
||||
fin_version,
|
||||
table_root: h_at(e, 2),
|
||||
history_root: h_at(e, 34),
|
||||
history_first: u64_at(e, 66),
|
||||
lock: Lock {
|
||||
index: u64_at(e, 74),
|
||||
hash: h_at(e, 82),
|
||||
number: u64_at(e, 114),
|
||||
signed: u64_at(e, 122),
|
||||
total: u64_at(e, 130),
|
||||
frozen_signed: u64_at(e, 138),
|
||||
frozen_total: u64_at(e, 146),
|
||||
daa: u64_at(e, 154),
|
||||
},
|
||||
flags: u16_at(e, 162),
|
||||
})
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
let st = Self {
|
||||
chain_id: u64_at(b, 0),
|
||||
number: u64_at(b, 8),
|
||||
block_hash: h_at(b, 16),
|
||||
parent_hash: h_at(b, 48),
|
||||
post_root: h_at(b, 148),
|
||||
shard_vk: h_at(b, 268),
|
||||
agg_vk: h_at(b, 300),
|
||||
chain_len: u64_at(b, 332),
|
||||
fin,
|
||||
};
|
||||
if let Some(f) = &st.fin {
|
||||
// the history counts from history_first to this block; a first above the block is malformed
|
||||
if f.history_first > st.number {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(st)
|
||||
}
|
||||
|
||||
/// The history's leaf count: one per chain block from `history_first` to `number`.
|
||||
pub fn history_leaves(&self) -> Option<u64> {
|
||||
self.fin.as_ref().map(|f| self.number - f.history_first + 1)
|
||||
}
|
||||
|
||||
pub fn to_bytes_len(&self) -> usize {
|
||||
if self.fin.is_some() { BLOCK_STATEMENT_LEN + FIN_EXT_LEN } else { BLOCK_STATEMENT_LEN }
|
||||
}
|
||||
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub fn describe(&self) -> Vec<(&'static str, alloc::string::String)> {
|
||||
let mut v = alloc::vec![
|
||||
("chain_id", alloc::format!("{}", self.chain_id)),
|
||||
("number", alloc::format!("{}", self.number)),
|
||||
("chain_len", alloc::format!("{}", self.chain_len)),
|
||||
];
|
||||
if let Some(f) = &self.fin {
|
||||
v.push(("lock_index", alloc::format!("{}", f.lock.index)));
|
||||
v.push(("lock_number", alloc::format!("{}", f.lock.number)));
|
||||
v.push(("lock_signed", alloc::format!("{}", f.lock.signed)));
|
||||
v.push(("lock_total", alloc::format!("{}", f.lock.total)));
|
||||
v.push(("stale", alloc::format!("{}", f.stale())));
|
||||
}
|
||||
v
|
||||
}
|
||||
}
|
||||
89
light/igneum-light/src/tests.rs
Normal file
89
light/igneum-light/src/tests.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
//! The two pairing paths agree on the one fixture the repository can make before the wrap lands: generator points
|
||||
//! that load and prove nothing (refused by both), and every refusal that must come before the pairing.
|
||||
|
||||
use crate::object::PV_LEN_FIN;
|
||||
use crate::*;
|
||||
use alloc::vec;
|
||||
|
||||
fn pv(chain_id: u64, key: [u8; 32]) -> Vec<u8> {
|
||||
let mut v = vec![0u8; PV_LEN_FIN];
|
||||
v[0..8].copy_from_slice(&chain_id.to_be_bytes());
|
||||
v[8..16].copy_from_slice(&81053u64.to_be_bytes());
|
||||
v[300..332].copy_from_slice(&key);
|
||||
v[332..340].copy_from_slice(&8u64.to_be_bytes());
|
||||
v[340..342].copy_from_slice(&1u16.to_be_bytes());
|
||||
v[406..414].copy_from_slice(&79046u64.to_be_bytes());
|
||||
v[414..422].copy_from_slice(&2702u64.to_be_bytes());
|
||||
v[454..462].copy_from_slice(&81049u64.to_be_bytes());
|
||||
v
|
||||
}
|
||||
|
||||
/// bn254's generators in gnark's uncompressed big-endian order (x1 ‖ x0 ‖ y1 ‖ y0 for G2), from EIP-197.
|
||||
fn generator_proof() -> [u8; 256] {
|
||||
fn be(dec: &str) -> [u8; 32] {
|
||||
// decimal to 32-byte big-endian without a bignum crate
|
||||
let mut out = [0u8; 32];
|
||||
for d in dec.bytes() {
|
||||
let mut carry = (d - b'0') as u32;
|
||||
for b in out.iter_mut().rev() {
|
||||
let v = (*b as u32) * 10 + carry;
|
||||
*b = (v & 0xff) as u8;
|
||||
carry = v >> 8;
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
let mut p = [0u8; 256];
|
||||
p[0..32].copy_from_slice(&be("1"));
|
||||
p[32..64].copy_from_slice(&be("2"));
|
||||
p[64..96].copy_from_slice(&be("11559732032986387107991004021392285783925812861821192530917403151452391805634"));
|
||||
p[96..128].copy_from_slice(&be("10857046999023057135944570762232829481370756359578518086990519993285655852781"));
|
||||
p[128..160].copy_from_slice(&be("4082367875863433681332203403145435568316851327593401208105741076214120093531"));
|
||||
p[160..192].copy_from_slice(&be("8495653923123431417604973247489272438418190587263600148770280649306958101930"));
|
||||
p[192..224].copy_from_slice(&be("1"));
|
||||
p[224..256].copy_from_slice(&be("2"));
|
||||
p
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn both_pairing_paths_refuse_the_generator_proof_and_agree() {
|
||||
let key = [0x12u8; 32];
|
||||
let obj = FinalityObject { kind: KIND_SP1_GROTH16, key_id: key, public_values: pv(4463, key), nonce: [0u8; 32], proof: generator_proof() };
|
||||
let v = Verifier::new();
|
||||
assert!(v.verify_wrap(&obj).is_err(), "arkworks must refuse generator points");
|
||||
assert!(verify_sp1_groth16_reference(&obj).is_err(), "substrate-bn must refuse generator points");
|
||||
assert!(verify_sp1_groth16_reference_one_pairing(&obj).is_err());
|
||||
let verdict = v.verify(&obj.encode(), None, &Pinned { key_id: key, chain_id: 4463 }, None);
|
||||
assert!(matches!(verdict.proof, ProofVerdict::Refused(_)), "{verdict:?}");
|
||||
assert_eq!(verdict.answer, None);
|
||||
// a point off the curve is refused before any pairing
|
||||
let mut off = obj.clone();
|
||||
off.proof[1] ^= 1;
|
||||
let why = v.verify_wrap(&off).unwrap_err();
|
||||
assert!(why.contains("curve points"), "{why}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refusals_before_the_pairing_and_the_stub_answers() {
|
||||
let key = [0x12u8; 32];
|
||||
let pinned = Pinned { key_id: key, chain_id: 4463 };
|
||||
let v = Verifier::new();
|
||||
let stub = FinalityObject::stub(key, pv(4463, key));
|
||||
let r = v.verify(&stub.encode(), None, &pinned, None);
|
||||
assert_eq!(r.proof, ProofVerdict::Stub);
|
||||
assert_eq!(r.answer, Some(Answer::NotFinal), "81053 lies above the lock at 81049");
|
||||
assert_eq!(r.trust_row, TRUST_ROW_STUB);
|
||||
let other = FinalityObject::stub(key, pv(4464, key));
|
||||
assert!(matches!(v.verify(&other.encode(), None, &pinned, None).proof, ProofVerdict::Refused(ref w) if w.contains("chain id")));
|
||||
let wrong_key = Pinned { key_id: [9u8; 32], chain_id: 4463 };
|
||||
assert!(matches!(v.verify(&stub.encode(), None, &wrong_key, None).proof, ProofVerdict::Refused(ref w) if w.contains("pinned aggregator")));
|
||||
let mut plain = FinalityObject::stub(key, pv(4463, key));
|
||||
plain.public_values.truncate(340);
|
||||
assert_eq!(v.verify(&plain.encode(), None, &pinned, None).answer, Some(Answer::NoClaim));
|
||||
let mut stale = FinalityObject::stub(key, pv(4463, key));
|
||||
stale.public_values[503] = 1;
|
||||
assert_eq!(v.verify(&stale.encode(), None, &pinned, None).answer, Some(Answer::Stale));
|
||||
let clock = Clock { now_unix_s: 1_000_000, genesis_unix_s: 800_000, seconds_per_daa: 1.0 };
|
||||
let aged = v.verify(&stub.encode(), None, &pinned, Some(clock));
|
||||
assert_eq!(aged.age_s, Some(200_000), "lock daa 0 in this synthetic statement");
|
||||
}
|
||||
BIN
light/vectors/fixture-81053-stub-flipped-lock-number.bin
Normal file
BIN
light/vectors/fixture-81053-stub-flipped-lock-number.bin
Normal file
Binary file not shown.
BIN
light/vectors/fixture-81053-stub-version-2.bin
Normal file
BIN
light/vectors/fixture-81053-stub-version-2.bin
Normal file
Binary file not shown.
BIN
light/vectors/fixture-81053-stub-wrong-key.bin
Normal file
BIN
light/vectors/fixture-81053-stub-wrong-key.bin
Normal file
Binary file not shown.
BIN
light/vectors/fixture-81053-stub.bin
Normal file
BIN
light/vectors/fixture-81053-stub.bin
Normal file
Binary file not shown.
BIN
light/vectors/fixture-81053-timing-flipped-lock-number.bin
Normal file
BIN
light/vectors/fixture-81053-timing-flipped-lock-number.bin
Normal file
Binary file not shown.
BIN
light/vectors/fixture-81053-timing-flipped-proof.bin
Normal file
BIN
light/vectors/fixture-81053-timing-flipped-proof.bin
Normal file
Binary file not shown.
BIN
light/vectors/fixture-81053-timing.bin
Normal file
BIN
light/vectors/fixture-81053-timing.bin
Normal file
Binary file not shown.
15
light/vectors/manifest.json
Normal file
15
light/vectors/manifest.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{"format":"igneum-finality-object-vectors-1","object_version":1,"sp1_version":"6.8.1","key_id":"12bff5be5699cc5e2b677d837559517033870903248c2761726523096d5f3822","chain_id":4463,"vectors":[
|
||||
{"name":"fixture-81053-stub","bytes":832,"sha256":"5174955671d86375ae7f96417673ca55dbc77498a4e825a585f03b72a0eab371","query":null,"expect":{"proof":"stub","answer":"not final"},"verdict":{"proof":"stub","reason":null,"answer":"not final","block_number":81053,"age_s":null,"trust_row":"wrap pending: the public values are read, nothing is proven","statement":{"chain_id":4463,"number":81053,"chain_len":8,"block_hash":"040acfe3dc3ca820cfe2565433d200cf9b2399ed3bcd1e04598fc05bcb1379c5","post_root":"be1ac38932436b515a6ef939e28dc50b4299c26094563fffb3e3fc710439c874","agg_vk":"12bff5be5699cc5e2b677d837559517033870903248c2761726523096d5f3822","fin":{"history_first":79046,"stale":false,"history_root":"71ea9db64404391ea74cd595c0395d0cc82b36620140f0b5b5c41bf4e39d2d33","table_root":"10c2458b19e20cbbbf68c4bfc01d7576201f7cd26ee916f393a720812e1b577d","lock":{"index":2702,"number":81049,"signed":2808,"total":4008,"frozen_signed":0,"frozen_total":0,"daa":100003,"hash":"dba699a61f7e4b3b8356a7494afbb73237f00385d7ad279a77002bfd451b2643"}}}}},
|
||||
{"name":"fixture-81053-timing","bytes":832,"sha256":"40ee84e88112a25ef796dbc693495896a9e92ccad50ebda4c17ac248db9b0854","query":null,"expect":{"proof":"refused","answer":null},"verdict":{"proof":"refused","reason":"the Groth16 wrap does not verify under the pinned key","answer":null,"block_number":null,"age_s":null,"trust_row":"voter set: verified in the proof; blue set: from the prover, vetoed by full nodes (level 0)","statement":null}},
|
||||
{"name":"fixture-81053-stub-flipped-lock-number","bytes":832,"sha256":"d7adc96d66690fcf8b8eaf311ce3b7929fda50e1c122b5a035e6c6f005234f2a","query":null,"expect":{"proof":"stub","answer":"not final"},"verdict":{"proof":"stub","reason":null,"answer":"not final","block_number":81053,"age_s":null,"trust_row":"wrap pending: the public values are read, nothing is proven","statement":{"chain_id":4463,"number":81053,"chain_len":8,"block_hash":"040acfe3dc3ca820cfe2565433d200cf9b2399ed3bcd1e04598fc05bcb1379c5","post_root":"be1ac38932436b515a6ef939e28dc50b4299c26094563fffb3e3fc710439c874","agg_vk":"12bff5be5699cc5e2b677d837559517033870903248c2761726523096d5f3822","fin":{"history_first":79046,"stale":false,"history_root":"71ea9db64404391ea74cd595c0395d0cc82b36620140f0b5b5c41bf4e39d2d33","table_root":"10c2458b19e20cbbbf68c4bfc01d7576201f7cd26ee916f393a720812e1b577d","lock":{"index":2702,"number":81048,"signed":2808,"total":4008,"frozen_signed":0,"frozen_total":0,"daa":100003,"hash":"dba699a61f7e4b3b8356a7494afbb73237f00385d7ad279a77002bfd451b2643"}}}}},
|
||||
{"name":"fixture-81053-timing-flipped-lock-number","bytes":832,"sha256":"0a8242bddd23f2bf8834aa175bcff8e3906612c558a3b8357dd1b3658f88c0e8","query":null,"expect":{"proof":"refused","answer":null},"verdict":{"proof":"refused","reason":"the Groth16 wrap does not verify under the pinned key","answer":null,"block_number":null,"age_s":null,"trust_row":"voter set: verified in the proof; blue set: from the prover, vetoed by full nodes (level 0)","statement":null}},
|
||||
{"name":"fixture-81053-timing-flipped-proof","bytes":832,"sha256":"439c74ffe46ac8285c8916d91ba026f7a460a15fc1b3624be3b1cff6c27ca176","query":null,"expect":{"proof":"refused","answer":null},"verdict":{"proof":"refused","reason":"the wrap's curve points do not load: G1CompressionError","answer":null,"block_number":null,"age_s":null,"trust_row":"voter set: verified in the proof; blue set: from the prover, vetoed by full nodes (level 0)","statement":null}},
|
||||
{"name":"fixture-81053-stub-wrong-key","bytes":832,"sha256":"8def8ed165ce36e377bbdde684710433ea98444748b4de0e452e4374345e8ffd","query":null,"expect":{"proof":"refused","answer":null},"verdict":{"proof":"refused","reason":"key id is not the pinned aggregator id","answer":null,"block_number":null,"age_s":null,"trust_row":"voter set: verified in the proof; blue set: from the prover, vetoed by full nodes (level 0)","statement":null}},
|
||||
{"name":"fixture-81053-stub-version-2","bytes":832,"sha256":"8d297ff5fdbe60249062a72fe456e1be70745fe01a70e2298b54867fe517fd52","query":null,"expect":{"proof":"refused","answer":null},"verdict":{"proof":"refused","reason":"object version 2 is not 1","answer":null,"block_number":null,"age_s":null,"trust_row":"voter set: verified in the proof; blue set: from the prover, vetoed by full nodes (level 0)","statement":null}},
|
||||
{"name":"synthetic-300-block-79146-final","bytes":832,"sha256":"0bc7a808c5a2447e679c2d26b048f18d646911c8c57e75d256058f59ea81b19b","query":{"bytes":532,"sha256":"56a131b1756b004e5db771753960d71afc5a29317236c803aa9af6f366c1e4f7"},"expect":{"proof":"stub","answer":"final"},"verdict":{"proof":"stub","reason":null,"answer":"final","block_number":79146,"age_s":null,"trust_row":"wrap pending: the public values are read, nothing is proven","statement":{"chain_id":4463,"number":79345,"chain_len":300,"block_hash":"e69504449c14a7d1927d745631b42ef667a083bc1d9b1748b616f7e36c52ee56","post_root":"5ad709f448c45ab7524f09206de6a2fbc35fbcacd5044516e773069debc81648","agg_vk":"12bff5be5699cc5e2b677d837559517033870903248c2761726523096d5f3822","fin":{"history_first":79046,"stale":false,"history_root":"8ae98c7320e592c33ae8658197836eed62bcc50d8e99f1bf3f8b2a5ac2327af5","table_root":"a23728c84a7d1cb5593d16f40c301752db2032ca6b62f797ec31d943a4c3cc45","lock":{"index":2702,"number":79300,"signed":2808,"total":4008,"frozen_signed":2808,"frozen_total":4008,"daa":100762,"hash":"97f6c6d7e0d0890710a027fb994397582409b61559753cb46adfab318b448b18"}}}}},
|
||||
{"name":"synthetic-300-block-79345-not-final","bytes":832,"sha256":"0bc7a808c5a2447e679c2d26b048f18d646911c8c57e75d256058f59ea81b19b","query":{"bytes":334,"sha256":"f89d1b1f0713cb2c741292281dfa4866c239f9d226b42571c0eda6fc8407e266"},"expect":{"proof":"stub","answer":"not final"},"verdict":{"proof":"stub","reason":null,"answer":"not final","block_number":79345,"age_s":null,"trust_row":"wrap pending: the public values are read, nothing is proven","statement":{"chain_id":4463,"number":79345,"chain_len":300,"block_hash":"e69504449c14a7d1927d745631b42ef667a083bc1d9b1748b616f7e36c52ee56","post_root":"5ad709f448c45ab7524f09206de6a2fbc35fbcacd5044516e773069debc81648","agg_vk":"12bff5be5699cc5e2b677d837559517033870903248c2761726523096d5f3822","fin":{"history_first":79046,"stale":false,"history_root":"8ae98c7320e592c33ae8658197836eed62bcc50d8e99f1bf3f8b2a5ac2327af5","table_root":"a23728c84a7d1cb5593d16f40c301752db2032ca6b62f797ec31d943a4c3cc45","lock":{"index":2702,"number":79300,"signed":2808,"total":4008,"frozen_signed":2808,"frozen_total":4008,"daa":100762,"hash":"97f6c6d7e0d0890710a027fb994397582409b61559753cb46adfab318b448b18"}}}}},
|
||||
{"name":"synthetic-300-block-79146-wrong-leaf-not-in-chain","bytes":832,"sha256":"0bc7a808c5a2447e679c2d26b048f18d646911c8c57e75d256058f59ea81b19b","query":{"bytes":532,"sha256":"fded005f2fc69c85217fd389ea1e4252c6bff9fc097e647e1ce28940a3c175a0"},"expect":{"proof":"stub","answer":"not in this chain"},"verdict":{"proof":"stub","reason":null,"answer":"not in this chain","block_number":79146,"age_s":null,"trust_row":"wrap pending: the public values are read, nothing is proven","statement":{"chain_id":4463,"number":79345,"chain_len":300,"block_hash":"e69504449c14a7d1927d745631b42ef667a083bc1d9b1748b616f7e36c52ee56","post_root":"5ad709f448c45ab7524f09206de6a2fbc35fbcacd5044516e773069debc81648","agg_vk":"12bff5be5699cc5e2b677d837559517033870903248c2761726523096d5f3822","fin":{"history_first":79046,"stale":false,"history_root":"8ae98c7320e592c33ae8658197836eed62bcc50d8e99f1bf3f8b2a5ac2327af5","table_root":"a23728c84a7d1cb5593d16f40c301752db2032ca6b62f797ec31d943a4c3cc45","lock":{"index":2702,"number":79300,"signed":2808,"total":4008,"frozen_signed":2808,"frozen_total":4008,"daa":100762,"hash":"97f6c6d7e0d0890710a027fb994397582409b61559753cb46adfab318b448b18"}}}}},
|
||||
{"name":"synthetic-300-stale","bytes":832,"sha256":"2887b2b7dc2f21f5d780c8747eaae495f7af4a8507a76eff5c4d0cf2284ad468","query":null,"expect":{"proof":"stub","answer":"stale"},"verdict":{"proof":"stub","reason":null,"answer":"stale","block_number":79345,"age_s":null,"trust_row":"wrap pending: the public values are read, nothing is proven","statement":{"chain_id":4463,"number":79345,"chain_len":300,"block_hash":"e69504449c14a7d1927d745631b42ef667a083bc1d9b1748b616f7e36c52ee56","post_root":"5ad709f448c45ab7524f09206de6a2fbc35fbcacd5044516e773069debc81648","agg_vk":"12bff5be5699cc5e2b677d837559517033870903248c2761726523096d5f3822","fin":{"history_first":79046,"stale":true,"history_root":"8ae98c7320e592c33ae8658197836eed62bcc50d8e99f1bf3f8b2a5ac2327af5","table_root":"a23728c84a7d1cb5593d16f40c301752db2032ca6b62f797ec31d943a4c3cc45","lock":{"index":2702,"number":79300,"signed":2808,"total":4008,"frozen_signed":2808,"frozen_total":4008,"daa":100762,"hash":"97f6c6d7e0d0890710a027fb994397582409b61559753cb46adfab318b448b18"}}}}},
|
||||
{"name":"synthetic-300-other-chain","bytes":832,"sha256":"70d94f4573ae9e8d186c6adb18e93df44718e42f0d2dba1c46f848f3f070a9d4","query":null,"expect":{"proof":"refused","answer":null},"verdict":{"proof":"refused","reason":"chain id 4464 is not the pinned chain 4463","answer":null,"block_number":null,"age_s":null,"trust_row":"voter set: verified in the proof; blue set: from the prover, vetoed by full nodes (level 0)","statement":null}},
|
||||
{"name":"synthetic-300-timing","bytes":832,"sha256":"65fce1a7c0113208806e9543c4a1ca0d40c8d31380a4d589564adc658d0d71f6","query":{"bytes":532,"sha256":"56a131b1756b004e5db771753960d71afc5a29317236c803aa9af6f366c1e4f7"},"expect":{"proof":"refused","answer":null},"verdict":{"proof":"refused","reason":"the Groth16 wrap does not verify under the pinned key","answer":null,"block_number":null,"age_s":null,"trust_row":"voter set: verified in the proof; blue set: from the prover, vetoed by full nodes (level 0)","statement":null}}
|
||||
]}
|
||||
BIN
light/vectors/public-values-81053.bin
Normal file
BIN
light/vectors/public-values-81053.bin
Normal file
Binary file not shown.
BIN
light/vectors/synthetic-300-block-79146-final.bin
Normal file
BIN
light/vectors/synthetic-300-block-79146-final.bin
Normal file
Binary file not shown.
BIN
light/vectors/synthetic-300-block-79146-final.query.bin
Normal file
BIN
light/vectors/synthetic-300-block-79146-final.query.bin
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
light/vectors/synthetic-300-block-79345-not-final.bin
Normal file
BIN
light/vectors/synthetic-300-block-79345-not-final.bin
Normal file
Binary file not shown.
BIN
light/vectors/synthetic-300-block-79345-not-final.query.bin
Normal file
BIN
light/vectors/synthetic-300-block-79345-not-final.query.bin
Normal file
Binary file not shown.
BIN
light/vectors/synthetic-300-other-chain.bin
Normal file
BIN
light/vectors/synthetic-300-other-chain.bin
Normal file
Binary file not shown.
BIN
light/vectors/synthetic-300-stale.bin
Normal file
BIN
light/vectors/synthetic-300-stale.bin
Normal file
Binary file not shown.
BIN
light/vectors/synthetic-300-timing.bin
Normal file
BIN
light/vectors/synthetic-300-timing.bin
Normal file
Binary file not shown.
BIN
light/vectors/synthetic-300-timing.query.bin
Normal file
BIN
light/vectors/synthetic-300-timing.query.bin
Normal file
Binary file not shown.
Loading…
Reference in a new issue