this repo has no description

feat: add rsky (blacksky) stack benchmarks for decode and sig-verify

adds rust benchmarks using the same crate stack as blacksky's rsky-relay:
ciborium + serde_ipld_dagcbor + rs-car-sync (with CID verification) for
decode, RustCrypto k256/p256 for sig-verify. three-way comparison now
covers zig, rust, and go across both decode and signature verification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+1739 -22
+2
.gitignore
··· 5 5 # rust 6 6 rust/target/ 7 7 rust-raw/target/ 8 + rust-rsky/target/ 9 + rust-sigs/target/ 8 10 9 11 # go 10 12 go/atproto-bench
+28 -22
README.md
··· 20 20 21 21 ### production-correct (with CID hash verification) 22 22 23 - only two SDKs verify CID hashes (SHA-256 per block): zat and indigo. this is the correct behavior for untrusted network data — it proves block content matches the content identifier. 23 + three SDKs verify CID hashes (SHA-256 per block): zat, rsky, and indigo. this is the correct behavior for untrusted network data — it proves block content matches the content identifier. 24 24 25 25 | SDK | frames/sec (median) | MB/s | blocks/frame | errors | 26 26 |-----|--------:|-----:|-----:|-----:| 27 - | zig ([zat](https://tangled.sh/@zzstoatzz.io/zat), arena reuse) | 235,049 | 1,133.6 | 9.98 | 0 | 28 - | go ([indigo](https://github.com/bluesky-social/indigo)) | 15,587 | 75.6 | 9.98 | 0 | 27 + | zig ([zat](https://tangled.sh/@zzstoatzz.io/zat), arena reuse) | 290,461 | 1,408.5 | 9.98 | 0 | 28 + | rust ([rsky](https://github.com/blacksky-algorithms/rsky) stack) | 38,905 | 186.5 | 9.98 | 0 | 29 + | go ([indigo](https://github.com/bluesky-social/indigo)) | 15,074 | 73.3 | 9.98 | 0 | 29 30 30 31 ### decode-only (no CID hash verification) 31 32 ··· 53 54 | SDK | verifies CID hashes? | notes | 54 55 |-----|---------------------|-------| 55 56 | zig (zat) | yes (v0.2.1+) | `car.read()` verifies by default; `readWithOptions(.{ .verify_block_hashes = false })` to skip | 57 + | rust (rsky, rs-car-sync) | yes | `CarReader::new(&mut cursor, true)` — second arg enables verification | 56 58 | go (indigo, go-car v1) | yes (always) | no option to disable in v1 | 57 59 | rust (jacquard, iroh-car) | no | not implemented | 58 60 | rust (raw) | no | not implemented | ··· 66 68 | SDK | decode path | 67 69 |-----|-------------| 68 70 | zig | `cbor.decode` header → `cbor.decodeAll` payload → `car.read` (+ SHA-256 verify) → `cbor.decodeAll` per block | 71 + | rust (rsky stack) | `ciborium` header → `serde_ipld_dagcbor` payload → `rs-car-sync` CAR (+ SHA-256 verify) → `serde_ipld_dagcbor` per block | 69 72 | rust (raw) | `minicbor::Decoder` header → payload → hand-rolled sync CAR → `minicbor` + `bumpalo` per block | 70 73 | rust (jacquard) | `SubscribeReposMessage::decode_framed` → typed `Commit`, `jacquard_repo::car::parse_car_bytes` → blocks, `serde_ipld_dagcbor` per block | 71 74 | go (raw) | `fxamacker/cbor` struct decode → hand-rolled sync CAR → `fxamacker/cbor` Unmarshal per block | ··· 76 79 77 80 we traced the full decode path of every SDK to verify that no SDK is winning by skipping correctness work. 78 81 79 - **what both zat and indigo do per frame:** 82 + **what zat, rsky, and indigo all do per frame:** 80 83 - decode full CBOR payload (all commit fields — repo, rev, ops, timestamp, etc.) 81 84 - parse CAR header and all block sections 82 85 - parse CID structure (version, codec, multihash) for each block 83 86 - SHA-256 hash each block and compare against CID digest 84 87 - decode every block as DAG-CBOR 85 88 86 - **what both do that isn't obvious:** 89 + **what zat and indigo do that isn't obvious:** 87 90 - enforce size limits (2MB max on blocks field, max block count) — zat matches indigo's limits as of v0.2.2 88 91 89 - **what neither side does:** 92 + **what none of them do:** 90 93 - DAG-CBOR deterministic encoding validation (sorted keys, minimal integers) — indigo's refmt doesn't check this either 91 94 - signature verification — separate from decode, not measured here 92 95 - MST validation — separate from decode, not measured here 93 96 94 - there are no correctness differences between the two decode paths. the ~15x gap is entirely implementation cost. 97 + there are no correctness differences between the verified decode paths. the performance gaps are entirely implementation cost. 95 98 96 99 ## where the ~15x comes from 97 100 ··· 111 114 112 115 ## fairness notes 113 116 114 - - **CID verification**: only zat and indigo verify block hashes. this is ~2x overhead for zat (235k vs 529k fps). the decode-only table exists for architectural comparison, but the production-correct table is the one that matters for real-world use 117 + - **CID verification**: zat, rsky, and indigo all verify block hashes. this is ~2x overhead for zat (290k vs 595k fps). the decode-only table exists for architectural comparison, but the production-correct table is the one that matters for real-world use 118 + - **rust (rsky stack)** uses the same crates as [blacksky's rsky-relay](https://github.com/blacksky-algorithms/rsky): ciborium for CBOR header, serde_ipld_dagcbor for DAG-CBOR body/blocks, rs-car-sync for CAR with CID verification, and RustCrypto k256/p256 for signatures 115 119 - **zig** and **rust (raw)** both use arena allocation + zero-copy string/byte decoding. the "alloc per frame" variants are the fair cross-language comparison; "arena reuse" shows the production pattern 116 120 - **rust (jacquard)** is the real AT Protocol SDK that rust developers use. it pays for serde-based owned deserialization (`String`, `BTreeMap`), async CAR parsing (tokio poll/wake per block via iroh-car), and per-object heap allocation 117 121 - **go (raw)** uses fxamacker/cbor (no reflection for known struct types), a hand-rolled sync CAR parser (no CID hash verification), and no indigo dependency. GC pressure remains the fundamental constraint — Go's experimental arena package (`GOEXPERIMENT=arenas`) is on hold and not recommended for production ··· 147 151 148 152 | SDK | variant | verifies/sec (median) | entries | P-256 | secp256k1 | errors | 149 153 |-----|---------|--------:|-----:|-----:|-----:|-----:| 150 - | zig ([zat](https://tangled.sh/@zzstoatzz.io/zat) + [k256](https://tangled.sh/@zzstoatzz.io/k256)) | full pipeline | 16,251 | 3,072 | 0 | 3,072 | 0 | 151 - | zig (zat + k256) | crypto-only | 16,688 | 3,072 | 0 | 3,072 | 0 | 152 - | zig (zat + k256) | preparsed-key | 19,263 | 3,072 | 0 | 3,072 | 0 | 153 - | go ([indigo](https://github.com/bluesky-social/indigo)) | full pipeline | 15,462 | 3,072 | 0 | 3,072 | 0 | 154 - | go (indigo) | crypto-only | 15,315 | 3,072 | 0 | 3,072 | 0 | 155 - | go (indigo) | preparsed-key | 18,347 | 3,072 | 0 | 3,072 | 0 | 154 + | rust ([rsky](https://github.com/blacksky-algorithms/rsky) stack) | full pipeline | 18,974 | 3,072 | 0 | 3,072 | 0 | 155 + | rust (rsky stack) | crypto-only | 19,310 | 3,072 | 0 | 3,072 | 0 | 156 + | rust (rsky stack) | preparsed-key | 20,631 | 3,072 | 0 | 3,072 | 0 | 157 + | zig ([zat](https://tangled.sh/@zzstoatzz.io/zat) + [k256](https://tangled.sh/@zzstoatzz.io/k256)) | full pipeline | 15,385 | 3,072 | 0 | 3,072 | 0 | 158 + | zig (zat + k256) | crypto-only | 16,338 | 3,072 | 0 | 3,072 | 0 | 159 + | zig (zat + k256) | preparsed-key | 19,148 | 3,072 | 0 | 3,072 | 0 | 160 + | go ([indigo](https://github.com/bluesky-social/indigo)) | full pipeline | 14,768 | 3,072 | 0 | 3,072 | 0 | 161 + | go (indigo) | crypto-only | 15,399 | 3,072 | 0 | 3,072 | 0 | 162 + | go (indigo) | preparsed-key | 18,227 | 3,072 | 0 | 3,072 | 0 | 156 163 157 - roughly even. both use optimized secp256k1 implementations with GLV endomorphism and precomputed tables — k256 ports [libsecp256k1](https://github.com/bitcoin-core/secp256k1)'s field arithmetic, indigo uses [decred/dcrd](https://github.com/decred/dcrd/tree/master/dcrec/secp256k1). 164 + all three are competitive. all use optimized secp256k1 with GLV endomorphism — RustCrypto [k256](https://crates.io/crates/k256) (complete addition formulas, pure Rust), zat's k256 ports [libsecp256k1](https://github.com/bitcoin-core/secp256k1)'s field arithmetic, indigo uses [decred/dcrd](https://github.com/decred/dcrd/tree/master/dcrec/secp256k1). 158 165 159 166 the crypto-only vs full-pipeline numbers being nearly identical confirms ECDSA is the bottleneck, not CBOR re-encoding overhead. the preparsed-key tier shows key parsing is a small but measurable cost — relevant for relay implementations that cache public keys per-DID. 160 167 161 - ### why only zig + go 162 - 163 - only zat and indigo have production-grade signature verification built in. the raw/jacquard/python implementations don't include commit signing — adding it would mean hand-rolling crypto, which isn't what those SDKs represent. 164 - 165 168 ### sig-verify corpus format 166 169 167 170 ``` ··· 196 199 - **relays at scale** — routing events to many downstream consumers. every microsecond of decode + verify overhead compounds across fan-out. 197 200 - **memory** — smaller value types mean less memory per in-flight frame. 198 201 199 - **the overall picture:** decode throughput varies ~15x across SDKs (dominated by CBOR/memory architecture choices). signature verification is roughly even between the two SDKs that implement it — both use optimized secp256k1 libraries and the math is the same. for relay workloads, decode is the differentiator; sig-verify is table stakes. 202 + **the overall picture:** decode throughput varies ~19x across the production-correct SDKs (dominated by CBOR/memory architecture choices). signature verification is competitive across all three — zig, rust, and go all land within ~40% of each other using optimized secp256k1 libraries. for relay workloads, decode is the differentiator; sig-verify is table stakes. 200 203 201 204 ## SDKs tested 202 205 203 206 | lang | SDK | version | CBOR engine | CAR engine | 204 207 |------|-----|---------|-------------|------------| 205 - | zig | [zat](https://tangled.sh/@zzstoatzz.io/zat) v0.2.2 + [k256](https://tangled.sh/@zzstoatzz.io/k256) v0.0.3 | — | hand-rolled | hand-rolled (+ SHA-256 CID verify, size limits) | 208 + | zig | [zat](https://tangled.sh/@zzstoatzz.io/zat) v0.2.2 + [k256](https://tangled.sh/@zzstoatzz.io/k256) v0.0.4 | — | hand-rolled | hand-rolled (+ SHA-256 CID verify, size limits) | 209 + | rust | [rsky](https://github.com/blacksky-algorithms/rsky) stack | — | [ciborium](https://crates.io/crates/ciborium) (header) + [serde_ipld_dagcbor](https://crates.io/crates/serde_ipld_dagcbor) (body) | [rs-car-sync](https://crates.io/crates/rs-car-sync) (+ SHA-256 CID verify) | 206 210 | rust | raw (minicbor + bumpalo) | — | [minicbor](https://crates.io/crates/minicbor) (zero-copy) | hand-rolled (sync) | 207 211 | rust | [jacquard](https://github.com/rsform/jacquard) | 0.9 | [ciborium](https://crates.io/crates/ciborium) (header) + [serde_ipld_dagcbor](https://crates.io/crates/serde_ipld_dagcbor) (body) | [iroh-car](https://crates.io/crates/iroh-car) (async) | 208 212 | go | raw (fxamacker/cbor) | — | [fxamacker/cbor](https://github.com/fxamacker/cbor) | hand-rolled (sync, no CID verify) | ··· 216 220 just capture # capture ~10s of firehose traffic 217 221 just bench # run all decode benchmarks 218 222 just bench-zig # run a single language 223 + just bench-rust-rsky 219 224 220 225 # sig verify benchmarks 221 226 just capture-sigs # capture signed commits + resolve public keys (~10s + DID resolution) 222 - just bench-sigs # run all sig verify benchmarks (zig + go) 227 + just bench-sigs # run all sig verify benchmarks (zig + rust + go) 223 228 just bench-sigs-zig 229 + just bench-sigs-rust 224 230 just bench-sigs-go 225 231 ``` 226 232
+14
justfile
··· 15 15 @echo "--------------------------------------------" 16 16 cd zig && zig build run-bench -Doptimize=ReleaseFast 17 17 @echo "--------------------------------------------" 18 + cd rust-rsky && cargo run --release 2>&1 19 + @echo "--------------------------------------------" 18 20 cd rust-raw && cargo run --release 2>&1 19 21 @echo "--------------------------------------------" 20 22 cd rust && cargo run --release 2>&1 ··· 32 34 33 35 bench-rust: _ensure-fixtures 34 36 cd rust && cargo run --release 37 + 38 + bench-rust-rsky: _ensure-fixtures 39 + cd rust-rsky && cargo run --release 35 40 36 41 bench-rust-raw: _ensure-fixtures 37 42 cd rust-raw && cargo run --release ··· 61 66 @echo "" 62 67 @echo "--------------------------------------------" 63 68 cd zig && zig build run-bench-sigs -Doptimize=ReleaseFast 69 + @echo "--------------------------------------------" 70 + cd rust-sigs && cargo run --release 2>&1 64 71 @echo "--------------------------------------------" 65 72 cd go-sigs && go run . 66 73 @echo "============================================" ··· 68 75 bench-sigs-zig: _ensure-sigs-fixtures 69 76 cd zig && zig build run-bench-sigs -Doptimize=ReleaseFast 70 77 78 + bench-sigs-rust: _ensure-sigs-fixtures 79 + cd rust-sigs && cargo run --release 80 + 71 81 bench-sigs-go: _ensure-sigs-fixtures 72 82 cd go-sigs && go run . 73 83 ··· 78 88 cd zig && zig build 79 89 cd rust && cargo build --release 80 90 cd rust-raw && cargo build --release 91 + cd rust-rsky && cargo build --release 92 + cd rust-sigs && cargo build --release 81 93 cd go && go build . 82 94 cd go-sigs && go build . 83 95 ··· 86 98 rm -rf zig/.zig-cache zig/zig-out 87 99 cd rust && cargo clean 88 100 cd rust-raw && cargo clean 101 + cd rust-rsky && cargo clean 102 + cd rust-sigs && cargo clean 89 103 rm -f go/atproto-bench 90 104 rm -f go-sigs/atproto-bench-sigs 91 105 rm -rf python/.venv
+433
rust-rsky/Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 4 4 + 5 + [[package]] 6 + name = "arrayref" 7 + version = "0.3.9" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" 10 + 11 + [[package]] 12 + name = "arrayvec" 13 + version = "0.7.6" 14 + source = "registry+https://github.com/rust-lang/crates.io-index" 15 + checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" 16 + 17 + [[package]] 18 + name = "atproto-bench-rsky" 19 + version = "0.1.0" 20 + dependencies = [ 21 + "ciborium", 22 + "ipld-core", 23 + "rs-car-sync", 24 + "serde", 25 + "serde_bytes", 26 + "serde_ipld_dagcbor", 27 + ] 28 + 29 + [[package]] 30 + name = "base-x" 31 + version = "0.2.11" 32 + source = "registry+https://github.com/rust-lang/crates.io-index" 33 + checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" 34 + 35 + [[package]] 36 + name = "base256emoji" 37 + version = "1.0.2" 38 + source = "registry+https://github.com/rust-lang/crates.io-index" 39 + checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" 40 + dependencies = [ 41 + "const-str", 42 + "match-lookup", 43 + ] 44 + 45 + [[package]] 46 + name = "blake2b_simd" 47 + version = "1.0.4" 48 + source = "registry+https://github.com/rust-lang/crates.io-index" 49 + checksum = "b79834656f71332577234b50bfc009996f7449e0c056884e6a02492ded0ca2f3" 50 + dependencies = [ 51 + "arrayref", 52 + "arrayvec", 53 + "constant_time_eq", 54 + ] 55 + 56 + [[package]] 57 + name = "block-buffer" 58 + version = "0.10.4" 59 + source = "registry+https://github.com/rust-lang/crates.io-index" 60 + checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 61 + dependencies = [ 62 + "generic-array", 63 + ] 64 + 65 + [[package]] 66 + name = "cbor4ii" 67 + version = "0.2.14" 68 + source = "registry+https://github.com/rust-lang/crates.io-index" 69 + checksum = "b544cf8c89359205f4f990d0e6f3828db42df85b5dac95d09157a250eb0749c4" 70 + dependencies = [ 71 + "serde", 72 + ] 73 + 74 + [[package]] 75 + name = "cfg-if" 76 + version = "1.0.4" 77 + source = "registry+https://github.com/rust-lang/crates.io-index" 78 + checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 79 + 80 + [[package]] 81 + name = "ciborium" 82 + version = "0.2.2" 83 + source = "registry+https://github.com/rust-lang/crates.io-index" 84 + checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" 85 + dependencies = [ 86 + "ciborium-io", 87 + "ciborium-ll", 88 + "serde", 89 + ] 90 + 91 + [[package]] 92 + name = "ciborium-io" 93 + version = "0.2.2" 94 + source = "registry+https://github.com/rust-lang/crates.io-index" 95 + checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" 96 + 97 + [[package]] 98 + name = "ciborium-ll" 99 + version = "0.2.2" 100 + source = "registry+https://github.com/rust-lang/crates.io-index" 101 + checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" 102 + dependencies = [ 103 + "ciborium-io", 104 + "half", 105 + ] 106 + 107 + [[package]] 108 + name = "cid" 109 + version = "0.11.1" 110 + source = "registry+https://github.com/rust-lang/crates.io-index" 111 + checksum = "3147d8272e8fa0ccd29ce51194dd98f79ddfb8191ba9e3409884e751798acf3a" 112 + dependencies = [ 113 + "core2", 114 + "multibase", 115 + "multihash", 116 + "serde", 117 + "serde_bytes", 118 + "unsigned-varint", 119 + ] 120 + 121 + [[package]] 122 + name = "const-str" 123 + version = "0.4.3" 124 + source = "registry+https://github.com/rust-lang/crates.io-index" 125 + checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" 126 + 127 + [[package]] 128 + name = "constant_time_eq" 129 + version = "0.4.2" 130 + source = "registry+https://github.com/rust-lang/crates.io-index" 131 + checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" 132 + 133 + [[package]] 134 + name = "core2" 135 + version = "0.4.0" 136 + source = "registry+https://github.com/rust-lang/crates.io-index" 137 + checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" 138 + dependencies = [ 139 + "memchr", 140 + ] 141 + 142 + [[package]] 143 + name = "cpufeatures" 144 + version = "0.2.17" 145 + source = "registry+https://github.com/rust-lang/crates.io-index" 146 + checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 147 + dependencies = [ 148 + "libc", 149 + ] 150 + 151 + [[package]] 152 + name = "crunchy" 153 + version = "0.2.4" 154 + source = "registry+https://github.com/rust-lang/crates.io-index" 155 + checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" 156 + 157 + [[package]] 158 + name = "crypto-common" 159 + version = "0.1.7" 160 + source = "registry+https://github.com/rust-lang/crates.io-index" 161 + checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" 162 + dependencies = [ 163 + "generic-array", 164 + "typenum", 165 + ] 166 + 167 + [[package]] 168 + name = "data-encoding" 169 + version = "2.10.0" 170 + source = "registry+https://github.com/rust-lang/crates.io-index" 171 + checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" 172 + 173 + [[package]] 174 + name = "data-encoding-macro" 175 + version = "0.1.19" 176 + source = "registry+https://github.com/rust-lang/crates.io-index" 177 + checksum = "8142a83c17aa9461d637e649271eae18bf2edd00e91f2e105df36c3c16355bdb" 178 + dependencies = [ 179 + "data-encoding", 180 + "data-encoding-macro-internal", 181 + ] 182 + 183 + [[package]] 184 + name = "data-encoding-macro-internal" 185 + version = "0.1.17" 186 + source = "registry+https://github.com/rust-lang/crates.io-index" 187 + checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" 188 + dependencies = [ 189 + "data-encoding", 190 + "syn", 191 + ] 192 + 193 + [[package]] 194 + name = "digest" 195 + version = "0.10.7" 196 + source = "registry+https://github.com/rust-lang/crates.io-index" 197 + checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 198 + dependencies = [ 199 + "block-buffer", 200 + "crypto-common", 201 + ] 202 + 203 + [[package]] 204 + name = "generic-array" 205 + version = "0.14.7" 206 + source = "registry+https://github.com/rust-lang/crates.io-index" 207 + checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 208 + dependencies = [ 209 + "typenum", 210 + "version_check", 211 + ] 212 + 213 + [[package]] 214 + name = "half" 215 + version = "2.7.1" 216 + source = "registry+https://github.com/rust-lang/crates.io-index" 217 + checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" 218 + dependencies = [ 219 + "cfg-if", 220 + "crunchy", 221 + "zerocopy", 222 + ] 223 + 224 + [[package]] 225 + name = "ipld-core" 226 + version = "0.4.3" 227 + source = "registry+https://github.com/rust-lang/crates.io-index" 228 + checksum = "090f624976d72f0b0bb71b86d58dc16c15e069193067cb3a3a09d655246cbbda" 229 + dependencies = [ 230 + "cid", 231 + "serde", 232 + "serde_bytes", 233 + ] 234 + 235 + [[package]] 236 + name = "libc" 237 + version = "0.2.182" 238 + source = "registry+https://github.com/rust-lang/crates.io-index" 239 + checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" 240 + 241 + [[package]] 242 + name = "match-lookup" 243 + version = "0.1.2" 244 + source = "registry+https://github.com/rust-lang/crates.io-index" 245 + checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" 246 + dependencies = [ 247 + "proc-macro2", 248 + "quote", 249 + "syn", 250 + ] 251 + 252 + [[package]] 253 + name = "memchr" 254 + version = "2.8.0" 255 + source = "registry+https://github.com/rust-lang/crates.io-index" 256 + checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" 257 + 258 + [[package]] 259 + name = "multibase" 260 + version = "0.9.2" 261 + source = "registry+https://github.com/rust-lang/crates.io-index" 262 + checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" 263 + dependencies = [ 264 + "base-x", 265 + "base256emoji", 266 + "data-encoding", 267 + "data-encoding-macro", 268 + ] 269 + 270 + [[package]] 271 + name = "multihash" 272 + version = "0.19.3" 273 + source = "registry+https://github.com/rust-lang/crates.io-index" 274 + checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" 275 + dependencies = [ 276 + "core2", 277 + "serde", 278 + "unsigned-varint", 279 + ] 280 + 281 + [[package]] 282 + name = "proc-macro2" 283 + version = "1.0.106" 284 + source = "registry+https://github.com/rust-lang/crates.io-index" 285 + checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" 286 + dependencies = [ 287 + "unicode-ident", 288 + ] 289 + 290 + [[package]] 291 + name = "quote" 292 + version = "1.0.44" 293 + source = "registry+https://github.com/rust-lang/crates.io-index" 294 + checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" 295 + dependencies = [ 296 + "proc-macro2", 297 + ] 298 + 299 + [[package]] 300 + name = "rs-car-sync" 301 + version = "0.5.0" 302 + source = "registry+https://github.com/rust-lang/crates.io-index" 303 + checksum = "4606956ed24295501de0ef964d9dbccd1b1907fd56b05affa387bc74f44e9539" 304 + dependencies = [ 305 + "blake2b_simd", 306 + "ipld-core", 307 + "serde_ipld_dagcbor", 308 + "sha2", 309 + ] 310 + 311 + [[package]] 312 + name = "scopeguard" 313 + version = "1.2.0" 314 + source = "registry+https://github.com/rust-lang/crates.io-index" 315 + checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 316 + 317 + [[package]] 318 + name = "serde" 319 + version = "1.0.228" 320 + source = "registry+https://github.com/rust-lang/crates.io-index" 321 + checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 322 + dependencies = [ 323 + "serde_core", 324 + "serde_derive", 325 + ] 326 + 327 + [[package]] 328 + name = "serde_bytes" 329 + version = "0.11.19" 330 + source = "registry+https://github.com/rust-lang/crates.io-index" 331 + checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" 332 + dependencies = [ 333 + "serde", 334 + "serde_core", 335 + ] 336 + 337 + [[package]] 338 + name = "serde_core" 339 + version = "1.0.228" 340 + source = "registry+https://github.com/rust-lang/crates.io-index" 341 + checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 342 + dependencies = [ 343 + "serde_derive", 344 + ] 345 + 346 + [[package]] 347 + name = "serde_derive" 348 + version = "1.0.228" 349 + source = "registry+https://github.com/rust-lang/crates.io-index" 350 + checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 351 + dependencies = [ 352 + "proc-macro2", 353 + "quote", 354 + "syn", 355 + ] 356 + 357 + [[package]] 358 + name = "serde_ipld_dagcbor" 359 + version = "0.6.4" 360 + source = "registry+https://github.com/rust-lang/crates.io-index" 361 + checksum = "46182f4f08349a02b45c998ba3215d3f9de826246ba02bb9dddfe9a2a2100778" 362 + dependencies = [ 363 + "cbor4ii", 364 + "ipld-core", 365 + "scopeguard", 366 + "serde", 367 + ] 368 + 369 + [[package]] 370 + name = "sha2" 371 + version = "0.10.9" 372 + source = "registry+https://github.com/rust-lang/crates.io-index" 373 + checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 374 + dependencies = [ 375 + "cfg-if", 376 + "cpufeatures", 377 + "digest", 378 + ] 379 + 380 + [[package]] 381 + name = "syn" 382 + version = "2.0.117" 383 + source = "registry+https://github.com/rust-lang/crates.io-index" 384 + checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" 385 + dependencies = [ 386 + "proc-macro2", 387 + "quote", 388 + "unicode-ident", 389 + ] 390 + 391 + [[package]] 392 + name = "typenum" 393 + version = "1.19.0" 394 + source = "registry+https://github.com/rust-lang/crates.io-index" 395 + checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" 396 + 397 + [[package]] 398 + name = "unicode-ident" 399 + version = "1.0.24" 400 + source = "registry+https://github.com/rust-lang/crates.io-index" 401 + checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" 402 + 403 + [[package]] 404 + name = "unsigned-varint" 405 + version = "0.8.0" 406 + source = "registry+https://github.com/rust-lang/crates.io-index" 407 + checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" 408 + 409 + [[package]] 410 + name = "version_check" 411 + version = "0.9.5" 412 + source = "registry+https://github.com/rust-lang/crates.io-index" 413 + checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 414 + 415 + [[package]] 416 + name = "zerocopy" 417 + version = "0.8.40" 418 + source = "registry+https://github.com/rust-lang/crates.io-index" 419 + checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" 420 + dependencies = [ 421 + "zerocopy-derive", 422 + ] 423 + 424 + [[package]] 425 + name = "zerocopy-derive" 426 + version = "0.8.40" 427 + source = "registry+https://github.com/rust-lang/crates.io-index" 428 + checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" 429 + dependencies = [ 430 + "proc-macro2", 431 + "quote", 432 + "syn", 433 + ]
+18
rust-rsky/Cargo.toml
··· 1 + [package] 2 + name = "atproto-bench-rsky" 3 + version = "0.1.0" 4 + edition = "2021" 5 + 6 + [dependencies] 7 + ciborium = "0.2" 8 + serde = { version = "1", features = ["derive"] } 9 + serde_bytes = "0.11" 10 + serde_ipld_dagcbor = "0.6" 11 + ipld-core = { version = "0.4", features = ["serde"] } 12 + rs-car-sync = "0.5" 13 + 14 + [profile.release] 15 + opt-level = 3 16 + lto = true 17 + codegen-units = 1 18 + panic = "abort"
+247
rust-rsky/src/main.rs
··· 1 + //! atproto firehose benchmarks — rust (rsky stack) 2 + //! 3 + //! uses the same crate stack as blacksky's rsky-relay: 4 + //! ciborium for CBOR header, serde_ipld_dagcbor for DAG-CBOR body/blocks, 5 + //! rs-car-sync for CAR parsing with CID hash verification. 6 + 7 + use ipld_core::ipld::Ipld; 8 + use std::io::Cursor; 9 + use std::time::Instant; 10 + 11 + const WARMUP_PASSES: usize = 2; 12 + const MEASURED_PASSES: usize = 5; 13 + const FIXTURES_DIR: &str = "../fixtures"; 14 + 15 + #[derive(serde::Deserialize)] 16 + struct Header { 17 + t: Option<String>, 18 + } 19 + 20 + #[derive(serde::Deserialize)] 21 + struct CommitBody { 22 + #[serde(default, with = "serde_bytes")] 23 + blocks: Vec<u8>, 24 + } 25 + 26 + struct DecodeResult { 27 + blocks: usize, 28 + errors: usize, 29 + } 30 + 31 + struct PassResult { 32 + frames: usize, 33 + blocks: usize, 34 + errors: usize, 35 + elapsed: std::time::Duration, 36 + } 37 + 38 + fn decode_full(data: &[u8]) -> DecodeResult { 39 + // 1. decode header with ciborium 40 + let mut cursor = Cursor::new(data); 41 + let header: Header = match ciborium::de::from_reader(&mut cursor) { 42 + Ok(h) => h, 43 + Err(_) => return DecodeResult { blocks: 0, errors: 1 }, 44 + }; 45 + 46 + if header.t.as_deref() != Some("#commit") { 47 + return DecodeResult { blocks: 0, errors: 0 }; 48 + } 49 + 50 + // 2. decode body with serde_ipld_dagcbor 51 + let body_start = cursor.position() as usize; 52 + let body: CommitBody = match serde_ipld_dagcbor::from_slice(&data[body_start..]) { 53 + Ok(b) => b, 54 + Err(_) => return DecodeResult { blocks: 0, errors: 1 }, 55 + }; 56 + 57 + if body.blocks.is_empty() { 58 + return DecodeResult { blocks: 0, errors: 0 }; 59 + } 60 + 61 + // 3. parse CAR with rs-car-sync (CID hash verification enabled) 62 + let mut blocks_cursor = Cursor::new(body.blocks.as_slice()); 63 + let reader = match rs_car_sync::CarReader::new(&mut blocks_cursor, true) { 64 + Ok(r) => r, 65 + Err(_) => return DecodeResult { blocks: 0, errors: 1 }, 66 + }; 67 + 68 + let mut blocks = 0usize; 69 + let mut errors = 0usize; 70 + 71 + // 4. decode each block as DAG-CBOR 72 + for item in reader { 73 + match item { 74 + Ok((_cid, block_data)) => { 75 + match serde_ipld_dagcbor::from_slice::<Ipld>(&block_data) { 76 + Ok(_) => blocks += 1, 77 + Err(_) => errors += 1, 78 + } 79 + } 80 + Err(_) => errors += 1, 81 + } 82 + } 83 + 84 + DecodeResult { blocks, errors } 85 + } 86 + 87 + // --- corpus loading --- 88 + 89 + struct CorpusInfo { 90 + raw: Vec<u8>, 91 + frame_ranges: Vec<(usize, usize)>, 92 + total_bytes: usize, 93 + min_frame: usize, 94 + max_frame: usize, 95 + } 96 + 97 + impl CorpusInfo { 98 + fn frames(&self) -> impl Iterator<Item = &[u8]> { 99 + self.frame_ranges 100 + .iter() 101 + .map(move |&(start, end)| &self.raw[start..end]) 102 + } 103 + } 104 + 105 + fn load_corpus(name: &str) -> Result<CorpusInfo, Box<dyn std::error::Error>> { 106 + let path = format!("{FIXTURES_DIR}/{name}"); 107 + let raw = std::fs::read(&path).map_err(|e| { 108 + eprintln!("cannot open {path}: {e}"); 109 + eprintln!("run `just capture` first to generate fixtures"); 110 + e 111 + })?; 112 + 113 + if raw.len() < 4 { 114 + return Err("corpus file too small".into()); 115 + } 116 + 117 + let frame_count = u32::from_be_bytes(raw[0..4].try_into().unwrap()) as usize; 118 + let mut frame_ranges = Vec::with_capacity(frame_count); 119 + let mut pos = 4usize; 120 + let mut total_bytes = 0usize; 121 + let mut min_frame = usize::MAX; 122 + let mut max_frame = 0usize; 123 + 124 + for _ in 0..frame_count { 125 + if pos + 4 > raw.len() { 126 + return Err("truncated corpus".into()); 127 + } 128 + let frame_len = u32::from_be_bytes(raw[pos..pos + 4].try_into().unwrap()) as usize; 129 + pos += 4; 130 + if pos + frame_len > raw.len() { 131 + return Err("truncated corpus".into()); 132 + } 133 + frame_ranges.push((pos, pos + frame_len)); 134 + pos += frame_len; 135 + total_bytes += frame_len; 136 + min_frame = min_frame.min(frame_len); 137 + max_frame = max_frame.max(frame_len); 138 + } 139 + 140 + Ok(CorpusInfo { 141 + raw, 142 + frame_ranges, 143 + total_bytes, 144 + min_frame, 145 + max_frame, 146 + }) 147 + } 148 + 149 + // --- benchmark --- 150 + 151 + fn main() { 152 + println!("\n=== rust (rsky stack) benchmarks ===\n"); 153 + 154 + let corpus = match load_corpus("firehose-frames.bin") { 155 + Ok(c) => c, 156 + Err(e) => { 157 + println!("firehose-frames.bin: SKIP ({e})"); 158 + return; 159 + } 160 + }; 161 + 162 + println!( 163 + "corpus: {} frames, {} bytes total", 164 + corpus.frame_ranges.len(), 165 + corpus.total_bytes 166 + ); 167 + println!( 168 + " frame sizes: {}..{} bytes", 169 + corpus.min_frame, corpus.max_frame 170 + ); 171 + println!( 172 + " passes: {} warmup, {} measured\n", 173 + WARMUP_PASSES, MEASURED_PASSES 174 + ); 175 + 176 + if let Some(first) = corpus.frames().next() { 177 + let result = decode_full(first); 178 + println!( 179 + "first frame: blocks={} errors={}", 180 + result.blocks, result.errors, 181 + ); 182 + } 183 + println!(); 184 + 185 + bench_decode(&corpus); 186 + println!(); 187 + } 188 + 189 + fn bench_decode(corpus: &CorpusInfo) { 190 + for _ in 0..WARMUP_PASSES { 191 + for frame in corpus.frames() { 192 + decode_full(frame); 193 + } 194 + } 195 + 196 + let mut pass_results = Vec::with_capacity(MEASURED_PASSES); 197 + 198 + for _ in 0..MEASURED_PASSES { 199 + let mut pass_blocks = 0usize; 200 + let mut pass_errors = 0usize; 201 + let start = Instant::now(); 202 + for frame in corpus.frames() { 203 + let result = decode_full(frame); 204 + pass_blocks += result.blocks; 205 + pass_errors += result.errors; 206 + } 207 + let elapsed = start.elapsed(); 208 + pass_results.push(PassResult { 209 + frames: corpus.frame_ranges.len(), 210 + blocks: pass_blocks, 211 + errors: pass_errors, 212 + elapsed, 213 + }); 214 + } 215 + 216 + report_result("decode+v (rsky)", corpus, &pass_results); 217 + } 218 + 219 + fn report_result(name: &str, corpus: &CorpusInfo, pass_results: &[PassResult]) { 220 + let mut fps_values: Vec<f64> = pass_results 221 + .iter() 222 + .map(|r| r.frames as f64 / r.elapsed.as_secs_f64()) 223 + .collect(); 224 + fps_values.sort_by(|a, b| a.partial_cmp(b).unwrap()); 225 + 226 + let total_frames: usize = pass_results.iter().map(|r| r.frames).sum(); 227 + let total_blocks: usize = pass_results.iter().map(|r| r.blocks).sum(); 228 + let total_errors: usize = pass_results.iter().map(|r| r.errors).sum(); 229 + let total_elapsed: f64 = pass_results.iter().map(|r| r.elapsed.as_secs_f64()).sum(); 230 + 231 + let total_bytes = corpus.total_bytes as f64 * MEASURED_PASSES as f64; 232 + let throughput_mb = total_bytes / (1024.0 * 1024.0) / total_elapsed; 233 + let blocks_per_frame = total_blocks as f64 / total_frames as f64; 234 + 235 + let min_fps = fps_values[0]; 236 + let median_fps = fps_values[MEASURED_PASSES / 2]; 237 + let max_fps = fps_values[MEASURED_PASSES - 1]; 238 + 239 + println!( 240 + "{:<20} {:>10.0} frames/sec {:>8.1} MB/s blocks={} ({:.2}/frame) errors={}", 241 + name, median_fps, throughput_mb, total_blocks, blocks_per_frame, total_errors, 242 + ); 243 + println!( 244 + "{:<20} variance: min={:.0} median={:.0} max={:.0} frames/sec", 245 + "", min_fps, median_fps, max_fps, 246 + ); 247 + }
+576
rust-sigs/Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 4 4 + 5 + [[package]] 6 + name = "atproto-bench-sigs-rust" 7 + version = "0.1.0" 8 + dependencies = [ 9 + "ecdsa", 10 + "ipld-core", 11 + "k256", 12 + "p256", 13 + "serde_ipld_dagcbor", 14 + ] 15 + 16 + [[package]] 17 + name = "base-x" 18 + version = "0.2.11" 19 + source = "registry+https://github.com/rust-lang/crates.io-index" 20 + checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" 21 + 22 + [[package]] 23 + name = "base16ct" 24 + version = "0.2.0" 25 + source = "registry+https://github.com/rust-lang/crates.io-index" 26 + checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" 27 + 28 + [[package]] 29 + name = "base256emoji" 30 + version = "1.0.2" 31 + source = "registry+https://github.com/rust-lang/crates.io-index" 32 + checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" 33 + dependencies = [ 34 + "const-str", 35 + "match-lookup", 36 + ] 37 + 38 + [[package]] 39 + name = "base64ct" 40 + version = "1.8.3" 41 + source = "registry+https://github.com/rust-lang/crates.io-index" 42 + checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" 43 + 44 + [[package]] 45 + name = "block-buffer" 46 + version = "0.10.4" 47 + source = "registry+https://github.com/rust-lang/crates.io-index" 48 + checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 49 + dependencies = [ 50 + "generic-array", 51 + ] 52 + 53 + [[package]] 54 + name = "cbor4ii" 55 + version = "0.2.14" 56 + source = "registry+https://github.com/rust-lang/crates.io-index" 57 + checksum = "b544cf8c89359205f4f990d0e6f3828db42df85b5dac95d09157a250eb0749c4" 58 + dependencies = [ 59 + "serde", 60 + ] 61 + 62 + [[package]] 63 + name = "cfg-if" 64 + version = "1.0.4" 65 + source = "registry+https://github.com/rust-lang/crates.io-index" 66 + checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 67 + 68 + [[package]] 69 + name = "cid" 70 + version = "0.11.1" 71 + source = "registry+https://github.com/rust-lang/crates.io-index" 72 + checksum = "3147d8272e8fa0ccd29ce51194dd98f79ddfb8191ba9e3409884e751798acf3a" 73 + dependencies = [ 74 + "core2", 75 + "multibase", 76 + "multihash", 77 + "serde", 78 + "serde_bytes", 79 + "unsigned-varint", 80 + ] 81 + 82 + [[package]] 83 + name = "const-oid" 84 + version = "0.9.6" 85 + source = "registry+https://github.com/rust-lang/crates.io-index" 86 + checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" 87 + 88 + [[package]] 89 + name = "const-str" 90 + version = "0.4.3" 91 + source = "registry+https://github.com/rust-lang/crates.io-index" 92 + checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" 93 + 94 + [[package]] 95 + name = "core2" 96 + version = "0.4.0" 97 + source = "registry+https://github.com/rust-lang/crates.io-index" 98 + checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" 99 + dependencies = [ 100 + "memchr", 101 + ] 102 + 103 + [[package]] 104 + name = "cpufeatures" 105 + version = "0.2.17" 106 + source = "registry+https://github.com/rust-lang/crates.io-index" 107 + checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 108 + dependencies = [ 109 + "libc", 110 + ] 111 + 112 + [[package]] 113 + name = "crypto-bigint" 114 + version = "0.5.5" 115 + source = "registry+https://github.com/rust-lang/crates.io-index" 116 + checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" 117 + dependencies = [ 118 + "generic-array", 119 + "rand_core", 120 + "subtle", 121 + "zeroize", 122 + ] 123 + 124 + [[package]] 125 + name = "crypto-common" 126 + version = "0.1.6" 127 + source = "registry+https://github.com/rust-lang/crates.io-index" 128 + checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 129 + dependencies = [ 130 + "generic-array", 131 + "typenum", 132 + ] 133 + 134 + [[package]] 135 + name = "data-encoding" 136 + version = "2.10.0" 137 + source = "registry+https://github.com/rust-lang/crates.io-index" 138 + checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" 139 + 140 + [[package]] 141 + name = "data-encoding-macro" 142 + version = "0.1.19" 143 + source = "registry+https://github.com/rust-lang/crates.io-index" 144 + checksum = "8142a83c17aa9461d637e649271eae18bf2edd00e91f2e105df36c3c16355bdb" 145 + dependencies = [ 146 + "data-encoding", 147 + "data-encoding-macro-internal", 148 + ] 149 + 150 + [[package]] 151 + name = "data-encoding-macro-internal" 152 + version = "0.1.17" 153 + source = "registry+https://github.com/rust-lang/crates.io-index" 154 + checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" 155 + dependencies = [ 156 + "data-encoding", 157 + "syn", 158 + ] 159 + 160 + [[package]] 161 + name = "der" 162 + version = "0.7.10" 163 + source = "registry+https://github.com/rust-lang/crates.io-index" 164 + checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" 165 + dependencies = [ 166 + "const-oid", 167 + "pem-rfc7468", 168 + "zeroize", 169 + ] 170 + 171 + [[package]] 172 + name = "digest" 173 + version = "0.10.7" 174 + source = "registry+https://github.com/rust-lang/crates.io-index" 175 + checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 176 + dependencies = [ 177 + "block-buffer", 178 + "const-oid", 179 + "crypto-common", 180 + "subtle", 181 + ] 182 + 183 + [[package]] 184 + name = "ecdsa" 185 + version = "0.16.9" 186 + source = "registry+https://github.com/rust-lang/crates.io-index" 187 + checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" 188 + dependencies = [ 189 + "der", 190 + "digest", 191 + "elliptic-curve", 192 + "rfc6979", 193 + "signature", 194 + "spki", 195 + ] 196 + 197 + [[package]] 198 + name = "elliptic-curve" 199 + version = "0.13.8" 200 + source = "registry+https://github.com/rust-lang/crates.io-index" 201 + checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" 202 + dependencies = [ 203 + "base16ct", 204 + "crypto-bigint", 205 + "digest", 206 + "ff", 207 + "generic-array", 208 + "group", 209 + "pem-rfc7468", 210 + "pkcs8", 211 + "rand_core", 212 + "sec1", 213 + "subtle", 214 + "zeroize", 215 + ] 216 + 217 + [[package]] 218 + name = "ff" 219 + version = "0.13.1" 220 + source = "registry+https://github.com/rust-lang/crates.io-index" 221 + checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" 222 + dependencies = [ 223 + "rand_core", 224 + "subtle", 225 + ] 226 + 227 + [[package]] 228 + name = "generic-array" 229 + version = "0.14.9" 230 + source = "registry+https://github.com/rust-lang/crates.io-index" 231 + checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" 232 + dependencies = [ 233 + "typenum", 234 + "version_check", 235 + "zeroize", 236 + ] 237 + 238 + [[package]] 239 + name = "getrandom" 240 + version = "0.2.17" 241 + source = "registry+https://github.com/rust-lang/crates.io-index" 242 + checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" 243 + dependencies = [ 244 + "cfg-if", 245 + "libc", 246 + "wasi", 247 + ] 248 + 249 + [[package]] 250 + name = "group" 251 + version = "0.13.0" 252 + source = "registry+https://github.com/rust-lang/crates.io-index" 253 + checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" 254 + dependencies = [ 255 + "ff", 256 + "rand_core", 257 + "subtle", 258 + ] 259 + 260 + [[package]] 261 + name = "hmac" 262 + version = "0.12.1" 263 + source = "registry+https://github.com/rust-lang/crates.io-index" 264 + checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 265 + dependencies = [ 266 + "digest", 267 + ] 268 + 269 + [[package]] 270 + name = "ipld-core" 271 + version = "0.4.3" 272 + source = "registry+https://github.com/rust-lang/crates.io-index" 273 + checksum = "090f624976d72f0b0bb71b86d58dc16c15e069193067cb3a3a09d655246cbbda" 274 + dependencies = [ 275 + "cid", 276 + "serde", 277 + "serde_bytes", 278 + ] 279 + 280 + [[package]] 281 + name = "k256" 282 + version = "0.13.4" 283 + source = "registry+https://github.com/rust-lang/crates.io-index" 284 + checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" 285 + dependencies = [ 286 + "cfg-if", 287 + "ecdsa", 288 + "elliptic-curve", 289 + "once_cell", 290 + "sha2", 291 + "signature", 292 + ] 293 + 294 + [[package]] 295 + name = "libc" 296 + version = "0.2.182" 297 + source = "registry+https://github.com/rust-lang/crates.io-index" 298 + checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" 299 + 300 + [[package]] 301 + name = "match-lookup" 302 + version = "0.1.2" 303 + source = "registry+https://github.com/rust-lang/crates.io-index" 304 + checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" 305 + dependencies = [ 306 + "proc-macro2", 307 + "quote", 308 + "syn", 309 + ] 310 + 311 + [[package]] 312 + name = "memchr" 313 + version = "2.8.0" 314 + source = "registry+https://github.com/rust-lang/crates.io-index" 315 + checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" 316 + 317 + [[package]] 318 + name = "multibase" 319 + version = "0.9.2" 320 + source = "registry+https://github.com/rust-lang/crates.io-index" 321 + checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" 322 + dependencies = [ 323 + "base-x", 324 + "base256emoji", 325 + "data-encoding", 326 + "data-encoding-macro", 327 + ] 328 + 329 + [[package]] 330 + name = "multihash" 331 + version = "0.19.3" 332 + source = "registry+https://github.com/rust-lang/crates.io-index" 333 + checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" 334 + dependencies = [ 335 + "core2", 336 + "serde", 337 + "unsigned-varint", 338 + ] 339 + 340 + [[package]] 341 + name = "once_cell" 342 + version = "1.21.3" 343 + source = "registry+https://github.com/rust-lang/crates.io-index" 344 + checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 345 + 346 + [[package]] 347 + name = "p256" 348 + version = "0.13.2" 349 + source = "registry+https://github.com/rust-lang/crates.io-index" 350 + checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" 351 + dependencies = [ 352 + "ecdsa", 353 + "elliptic-curve", 354 + "primeorder", 355 + "sha2", 356 + ] 357 + 358 + [[package]] 359 + name = "pem-rfc7468" 360 + version = "0.7.0" 361 + source = "registry+https://github.com/rust-lang/crates.io-index" 362 + checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" 363 + dependencies = [ 364 + "base64ct", 365 + ] 366 + 367 + [[package]] 368 + name = "pkcs8" 369 + version = "0.10.2" 370 + source = "registry+https://github.com/rust-lang/crates.io-index" 371 + checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" 372 + dependencies = [ 373 + "der", 374 + "spki", 375 + ] 376 + 377 + [[package]] 378 + name = "primeorder" 379 + version = "0.13.6" 380 + source = "registry+https://github.com/rust-lang/crates.io-index" 381 + checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" 382 + dependencies = [ 383 + "elliptic-curve", 384 + ] 385 + 386 + [[package]] 387 + name = "proc-macro2" 388 + version = "1.0.106" 389 + source = "registry+https://github.com/rust-lang/crates.io-index" 390 + checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" 391 + dependencies = [ 392 + "unicode-ident", 393 + ] 394 + 395 + [[package]] 396 + name = "quote" 397 + version = "1.0.44" 398 + source = "registry+https://github.com/rust-lang/crates.io-index" 399 + checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" 400 + dependencies = [ 401 + "proc-macro2", 402 + ] 403 + 404 + [[package]] 405 + name = "rand_core" 406 + version = "0.6.4" 407 + source = "registry+https://github.com/rust-lang/crates.io-index" 408 + checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 409 + dependencies = [ 410 + "getrandom", 411 + ] 412 + 413 + [[package]] 414 + name = "rfc6979" 415 + version = "0.4.0" 416 + source = "registry+https://github.com/rust-lang/crates.io-index" 417 + checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" 418 + dependencies = [ 419 + "hmac", 420 + "subtle", 421 + ] 422 + 423 + [[package]] 424 + name = "scopeguard" 425 + version = "1.2.0" 426 + source = "registry+https://github.com/rust-lang/crates.io-index" 427 + checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 428 + 429 + [[package]] 430 + name = "sec1" 431 + version = "0.7.3" 432 + source = "registry+https://github.com/rust-lang/crates.io-index" 433 + checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" 434 + dependencies = [ 435 + "base16ct", 436 + "der", 437 + "generic-array", 438 + "pkcs8", 439 + "subtle", 440 + "zeroize", 441 + ] 442 + 443 + [[package]] 444 + name = "serde" 445 + version = "1.0.228" 446 + source = "registry+https://github.com/rust-lang/crates.io-index" 447 + checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 448 + dependencies = [ 449 + "serde_core", 450 + ] 451 + 452 + [[package]] 453 + name = "serde_bytes" 454 + version = "0.11.19" 455 + source = "registry+https://github.com/rust-lang/crates.io-index" 456 + checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" 457 + dependencies = [ 458 + "serde", 459 + "serde_core", 460 + ] 461 + 462 + [[package]] 463 + name = "serde_core" 464 + version = "1.0.228" 465 + source = "registry+https://github.com/rust-lang/crates.io-index" 466 + checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 467 + dependencies = [ 468 + "serde_derive", 469 + ] 470 + 471 + [[package]] 472 + name = "serde_derive" 473 + version = "1.0.228" 474 + source = "registry+https://github.com/rust-lang/crates.io-index" 475 + checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 476 + dependencies = [ 477 + "proc-macro2", 478 + "quote", 479 + "syn", 480 + ] 481 + 482 + [[package]] 483 + name = "serde_ipld_dagcbor" 484 + version = "0.6.4" 485 + source = "registry+https://github.com/rust-lang/crates.io-index" 486 + checksum = "46182f4f08349a02b45c998ba3215d3f9de826246ba02bb9dddfe9a2a2100778" 487 + dependencies = [ 488 + "cbor4ii", 489 + "ipld-core", 490 + "scopeguard", 491 + "serde", 492 + ] 493 + 494 + [[package]] 495 + name = "sha2" 496 + version = "0.10.9" 497 + source = "registry+https://github.com/rust-lang/crates.io-index" 498 + checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 499 + dependencies = [ 500 + "cfg-if", 501 + "cpufeatures", 502 + "digest", 503 + ] 504 + 505 + [[package]] 506 + name = "signature" 507 + version = "2.2.0" 508 + source = "registry+https://github.com/rust-lang/crates.io-index" 509 + checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" 510 + dependencies = [ 511 + "digest", 512 + "rand_core", 513 + ] 514 + 515 + [[package]] 516 + name = "spki" 517 + version = "0.7.3" 518 + source = "registry+https://github.com/rust-lang/crates.io-index" 519 + checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" 520 + dependencies = [ 521 + "base64ct", 522 + "der", 523 + ] 524 + 525 + [[package]] 526 + name = "subtle" 527 + version = "2.6.1" 528 + source = "registry+https://github.com/rust-lang/crates.io-index" 529 + checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 530 + 531 + [[package]] 532 + name = "syn" 533 + version = "2.0.117" 534 + source = "registry+https://github.com/rust-lang/crates.io-index" 535 + checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" 536 + dependencies = [ 537 + "proc-macro2", 538 + "quote", 539 + "unicode-ident", 540 + ] 541 + 542 + [[package]] 543 + name = "typenum" 544 + version = "1.19.0" 545 + source = "registry+https://github.com/rust-lang/crates.io-index" 546 + checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" 547 + 548 + [[package]] 549 + name = "unicode-ident" 550 + version = "1.0.24" 551 + source = "registry+https://github.com/rust-lang/crates.io-index" 552 + checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" 553 + 554 + [[package]] 555 + name = "unsigned-varint" 556 + version = "0.8.0" 557 + source = "registry+https://github.com/rust-lang/crates.io-index" 558 + checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" 559 + 560 + [[package]] 561 + name = "version_check" 562 + version = "0.9.5" 563 + source = "registry+https://github.com/rust-lang/crates.io-index" 564 + checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 565 + 566 + [[package]] 567 + name = "wasi" 568 + version = "0.11.1+wasi-snapshot-preview1" 569 + source = "registry+https://github.com/rust-lang/crates.io-index" 570 + checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 571 + 572 + [[package]] 573 + name = "zeroize" 574 + version = "1.8.2" 575 + source = "registry+https://github.com/rust-lang/crates.io-index" 576 + checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
+17
rust-sigs/Cargo.toml
··· 1 + [package] 2 + name = "atproto-bench-sigs-rust" 3 + version = "0.1.0" 4 + edition = "2021" 5 + 6 + [dependencies] 7 + k256 = { version = "0.13", features = ["ecdsa"] } 8 + p256 = { version = "0.13", features = ["ecdsa"] } 9 + ecdsa = "0.16" 10 + serde_ipld_dagcbor = "0.6" 11 + ipld-core = { version = "0.4", features = ["serde"] } 12 + 13 + [profile.release] 14 + opt-level = 3 15 + lto = true 16 + codegen-units = 1 17 + panic = "abort"
+404
rust-sigs/src/main.rs
··· 1 + //! atproto sig-verify benchmarks — rust (rsky stack) 2 + //! 3 + //! uses the same crypto crates as blacksky's rsky-relay: 4 + //! k256 (RustCrypto) for secp256k1, p256 for P-256, 5 + //! serde_ipld_dagcbor for commit encode/decode. 6 + 7 + use ecdsa::signature::Verifier; 8 + use ipld_core::ipld::Ipld; 9 + use std::time::Instant; 10 + 11 + const WARMUP_PASSES: usize = 2; 12 + const MEASURED_PASSES: usize = 5; 13 + const FIXTURES_DIR: &str = "../fixtures"; 14 + 15 + struct CorpusEntry { 16 + curve_type: u8, // 0 = P-256, 1 = secp256k1 17 + signed_bytes: Vec<u8>, 18 + pubkey_bytes: Vec<u8>, 19 + } 20 + 21 + struct CorpusInfo { 22 + entries: Vec<CorpusEntry>, 23 + total_bytes: usize, 24 + p256_count: usize, 25 + k256_count: usize, 26 + } 27 + 28 + struct PrecomputedEntry { 29 + curve_type: u8, 30 + unsigned_bytes: Vec<u8>, 31 + sig_bytes: Vec<u8>, 32 + pubkey_bytes: Vec<u8>, 33 + } 34 + 35 + enum ParsedKey { 36 + P256(p256::ecdsa::VerifyingKey), 37 + K256(k256::ecdsa::VerifyingKey), 38 + } 39 + 40 + struct PreparsedEntry { 41 + unsigned_bytes: Vec<u8>, 42 + sig_bytes: Vec<u8>, 43 + key: ParsedKey, 44 + } 45 + 46 + struct PassResult { 47 + verifies: usize, 48 + errors: usize, 49 + elapsed: std::time::Duration, 50 + } 51 + 52 + // --- verify functions --- 53 + 54 + /// extract unsigned bytes + sig from a signed commit. 55 + /// deserializes as generic Ipld to preserve all fields (typed structs drop unknowns). 56 + fn split_commit(signed_bytes: &[u8]) -> Option<(Vec<u8>, Vec<u8>)> { 57 + let ipld: Ipld = serde_ipld_dagcbor::from_slice(signed_bytes).ok()?; 58 + let mut map = match ipld { 59 + Ipld::Map(m) => m, 60 + _ => return None, 61 + }; 62 + let sig_bytes = match map.remove("sig")? { 63 + Ipld::Bytes(b) => b, 64 + _ => return None, 65 + }; 66 + let unsigned_bytes = serde_ipld_dagcbor::to_vec(&Ipld::Map(map)).ok()?; 67 + Some((unsigned_bytes, sig_bytes)) 68 + } 69 + 70 + fn verify_full_pipeline(entry: &CorpusEntry) -> bool { 71 + // 1. decode commit, strip sig, re-encode 72 + let (unsigned_bytes, sig_bytes) = match split_commit(&entry.signed_bytes) { 73 + Some(pair) => pair, 74 + None => return false, 75 + }; 76 + 77 + // 2. ECDSA verify (verify() hashes internally) 78 + verify_raw(entry.curve_type, &unsigned_bytes, &sig_bytes, &entry.pubkey_bytes) 79 + } 80 + 81 + fn verify_crypto_only(entry: &PrecomputedEntry) -> bool { 82 + verify_raw( 83 + entry.curve_type, 84 + &entry.unsigned_bytes, 85 + &entry.sig_bytes, 86 + &entry.pubkey_bytes, 87 + ) 88 + } 89 + 90 + fn verify_preparsed(entry: &PreparsedEntry) -> bool { 91 + match &entry.key { 92 + ParsedKey::P256(key) => { 93 + let sig = match p256::ecdsa::Signature::from_slice(&entry.sig_bytes) { 94 + Ok(s) => s, 95 + Err(_) => return false, 96 + }; 97 + key.verify(&entry.unsigned_bytes, &sig).is_ok() 98 + } 99 + ParsedKey::K256(key) => { 100 + let sig = match k256::ecdsa::Signature::from_slice(&entry.sig_bytes) { 101 + Ok(s) => s, 102 + Err(_) => return false, 103 + }; 104 + key.verify(&entry.unsigned_bytes, &sig).is_ok() 105 + } 106 + } 107 + } 108 + 109 + /// parse key + sig from raw bytes, verify against unsigned message. 110 + /// Verifier::verify() hashes internally (SHA-256 for ECDSA). 111 + fn verify_raw(curve_type: u8, msg: &[u8], sig_bytes: &[u8], pubkey_bytes: &[u8]) -> bool { 112 + match curve_type { 113 + 0 => { 114 + // P-256 115 + let key = match p256::ecdsa::VerifyingKey::from_sec1_bytes(pubkey_bytes) { 116 + Ok(k) => k, 117 + Err(_) => return false, 118 + }; 119 + let sig = match p256::ecdsa::Signature::from_slice(sig_bytes) { 120 + Ok(s) => s, 121 + Err(_) => return false, 122 + }; 123 + key.verify(msg, &sig).is_ok() 124 + } 125 + 1 => { 126 + // secp256k1 127 + let key = match k256::ecdsa::VerifyingKey::from_sec1_bytes(pubkey_bytes) { 128 + Ok(k) => k, 129 + Err(_) => return false, 130 + }; 131 + let sig = match k256::ecdsa::Signature::from_slice(sig_bytes) { 132 + Ok(s) => s, 133 + Err(_) => return false, 134 + }; 135 + key.verify(msg, &sig).is_ok() 136 + } 137 + _ => false, 138 + } 139 + } 140 + 141 + fn parse_key(curve_type: u8, pubkey_bytes: &[u8]) -> Option<ParsedKey> { 142 + match curve_type { 143 + 0 => p256::ecdsa::VerifyingKey::from_sec1_bytes(pubkey_bytes) 144 + .ok() 145 + .map(ParsedKey::P256), 146 + 1 => k256::ecdsa::VerifyingKey::from_sec1_bytes(pubkey_bytes) 147 + .ok() 148 + .map(ParsedKey::K256), 149 + _ => None, 150 + } 151 + } 152 + 153 + // --- benchmark tiers --- 154 + 155 + fn bench_full_pipeline(corpus: &CorpusInfo) { 156 + for _ in 0..WARMUP_PASSES { 157 + for entry in &corpus.entries { 158 + let _ = verify_full_pipeline(entry); 159 + } 160 + } 161 + 162 + let mut results = Vec::with_capacity(MEASURED_PASSES); 163 + for _ in 0..MEASURED_PASSES { 164 + let (mut verifies, mut errors) = (0usize, 0usize); 165 + let start = Instant::now(); 166 + for entry in &corpus.entries { 167 + if verify_full_pipeline(entry) { 168 + verifies += 1; 169 + } else { 170 + errors += 1; 171 + } 172 + } 173 + results.push(PassResult { 174 + verifies, 175 + errors, 176 + elapsed: start.elapsed(), 177 + }); 178 + } 179 + report_result("sig-verify", corpus, &results); 180 + } 181 + 182 + fn bench_crypto_only(corpus: &CorpusInfo) { 183 + // precompute unsigned bytes 184 + let mut precomputed = Vec::with_capacity(corpus.entries.len()); 185 + for entry in &corpus.entries { 186 + let (unsigned_bytes, sig_bytes) = match split_commit(&entry.signed_bytes) { 187 + Some(pair) => pair, 188 + None => continue, 189 + }; 190 + precomputed.push(PrecomputedEntry { 191 + curve_type: entry.curve_type, 192 + unsigned_bytes, 193 + sig_bytes, 194 + pubkey_bytes: entry.pubkey_bytes.clone(), 195 + }); 196 + } 197 + 198 + for _ in 0..WARMUP_PASSES { 199 + for entry in &precomputed { 200 + let _ = verify_crypto_only(entry); 201 + } 202 + } 203 + 204 + let mut results = Vec::with_capacity(MEASURED_PASSES); 205 + for _ in 0..MEASURED_PASSES { 206 + let (mut verifies, mut errors) = (0usize, 0usize); 207 + let start = Instant::now(); 208 + for entry in &precomputed { 209 + if verify_crypto_only(entry) { 210 + verifies += 1; 211 + } else { 212 + errors += 1; 213 + } 214 + } 215 + results.push(PassResult { 216 + verifies, 217 + errors, 218 + elapsed: start.elapsed(), 219 + }); 220 + } 221 + report_result("crypto-only", corpus, &results); 222 + } 223 + 224 + fn bench_preparsed_key(corpus: &CorpusInfo) { 225 + let mut preparsed = Vec::with_capacity(corpus.entries.len()); 226 + for entry in &corpus.entries { 227 + let (unsigned_bytes, sig_bytes) = match split_commit(&entry.signed_bytes) { 228 + Some(pair) => pair, 229 + None => continue, 230 + }; 231 + let key = match parse_key(entry.curve_type, &entry.pubkey_bytes) { 232 + Some(k) => k, 233 + None => continue, 234 + }; 235 + preparsed.push(PreparsedEntry { 236 + unsigned_bytes, 237 + sig_bytes, 238 + key, 239 + }); 240 + } 241 + 242 + for _ in 0..WARMUP_PASSES { 243 + for entry in &preparsed { 244 + let _ = verify_preparsed(entry); 245 + } 246 + } 247 + 248 + let mut results = Vec::with_capacity(MEASURED_PASSES); 249 + for _ in 0..MEASURED_PASSES { 250 + let (mut verifies, mut errors) = (0usize, 0usize); 251 + let start = Instant::now(); 252 + for entry in &preparsed { 253 + if verify_preparsed(entry) { 254 + verifies += 1; 255 + } else { 256 + errors += 1; 257 + } 258 + } 259 + results.push(PassResult { 260 + verifies, 261 + errors, 262 + elapsed: start.elapsed(), 263 + }); 264 + } 265 + report_result("preparsed-key", corpus, &results); 266 + } 267 + 268 + // --- reporting --- 269 + 270 + fn report_result(name: &str, corpus: &CorpusInfo, results: &[PassResult]) { 271 + let mut vps_values: Vec<f64> = results 272 + .iter() 273 + .map(|r| r.verifies as f64 / r.elapsed.as_secs_f64()) 274 + .collect(); 275 + vps_values.sort_by(|a, b| a.partial_cmp(b).unwrap()); 276 + 277 + let total_errors: usize = results.iter().map(|r| r.errors).sum(); 278 + let min_vps = vps_values[0]; 279 + let median_vps = vps_values[MEASURED_PASSES / 2]; 280 + let max_vps = vps_values[MEASURED_PASSES - 1]; 281 + 282 + println!( 283 + "{:<14} {:>10.0} verifies/sec entries={} P-256={} secp256k1={} errors={}", 284 + name, 285 + median_vps, 286 + corpus.entries.len(), 287 + corpus.p256_count, 288 + corpus.k256_count, 289 + total_errors, 290 + ); 291 + println!( 292 + "{:<14} variance: min={:.0} median={:.0} max={:.0} verifies/sec", 293 + "", min_vps, median_vps, max_vps, 294 + ); 295 + } 296 + 297 + // --- corpus loading --- 298 + 299 + fn load_corpus(name: &str) -> Result<CorpusInfo, Box<dyn std::error::Error>> { 300 + let path = format!("{FIXTURES_DIR}/{name}"); 301 + let data = std::fs::read(&path).map_err(|e| { 302 + eprintln!("cannot open {path}: {e}"); 303 + eprintln!("run `just capture-sigs` first to generate corpus"); 304 + e 305 + })?; 306 + 307 + if data.len() < 4 { 308 + return Err("corpus too small".into()); 309 + } 310 + 311 + let entry_count = u32::from_be_bytes(data[0..4].try_into().unwrap()) as usize; 312 + let mut entries = Vec::with_capacity(entry_count); 313 + let mut pos = 4usize; 314 + let mut total_bytes = 0usize; 315 + let mut p256_count = 0usize; 316 + let mut k256_count = 0usize; 317 + 318 + for _ in 0..entry_count { 319 + if pos + 1 > data.len() { 320 + return Err("truncated".into()); 321 + } 322 + let curve_type = data[pos]; 323 + pos += 1; 324 + 325 + if pos + 2 > data.len() { 326 + return Err("truncated".into()); 327 + } 328 + let signed_len = u16::from_be_bytes(data[pos..pos + 2].try_into().unwrap()) as usize; 329 + pos += 2; 330 + if pos + signed_len > data.len() { 331 + return Err("truncated".into()); 332 + } 333 + let signed_bytes = data[pos..pos + signed_len].to_vec(); 334 + pos += signed_len; 335 + 336 + if pos + 2 > data.len() { 337 + return Err("truncated".into()); 338 + } 339 + let pubkey_len = u16::from_be_bytes(data[pos..pos + 2].try_into().unwrap()) as usize; 340 + pos += 2; 341 + if pos + pubkey_len > data.len() { 342 + return Err("truncated".into()); 343 + } 344 + let pubkey_bytes = data[pos..pos + pubkey_len].to_vec(); 345 + pos += pubkey_len; 346 + 347 + total_bytes += 1 + 2 + signed_len + 2 + pubkey_len; 348 + if curve_type == 0 { 349 + p256_count += 1; 350 + } else { 351 + k256_count += 1; 352 + } 353 + 354 + entries.push(CorpusEntry { 355 + curve_type, 356 + signed_bytes, 357 + pubkey_bytes, 358 + }); 359 + } 360 + 361 + Ok(CorpusInfo { 362 + entries, 363 + total_bytes, 364 + p256_count, 365 + k256_count, 366 + }) 367 + } 368 + 369 + fn main() { 370 + println!("\n=== rust (rsky stack) sig-verify benchmarks ===\n"); 371 + 372 + let corpus = match load_corpus("sig-verify-corpus.bin") { 373 + Ok(c) => c, 374 + Err(e) => { 375 + println!("sig-verify-corpus.bin: SKIP ({e})"); 376 + return; 377 + } 378 + }; 379 + 380 + println!( 381 + "corpus: {} entries, {} bytes", 382 + corpus.entries.len(), 383 + corpus.total_bytes 384 + ); 385 + println!( 386 + " P-256: {}, secp256k1: {}", 387 + corpus.p256_count, corpus.k256_count 388 + ); 389 + println!( 390 + " passes: {} warmup, {} measured\n", 391 + WARMUP_PASSES, MEASURED_PASSES 392 + ); 393 + 394 + // sanity check 395 + if !corpus.entries.is_empty() { 396 + let ok = verify_full_pipeline(&corpus.entries[0]); 397 + println!("first entry: {}\n", if ok { "OK" } else { "FAIL" }); 398 + } 399 + 400 + bench_full_pipeline(&corpus); 401 + bench_crypto_only(&corpus); 402 + bench_preparsed_key(&corpus); 403 + println!(); 404 + }