diff --git a/Cargo.lock b/Cargo.lock index 0ee5fae..6da0a13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,6 +13,21 @@ dependencies = [ "num-traits", ] +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aho-corasick" version = "1.1.4" @@ -22,6 +37,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "anes" version = "0.1.6" @@ -30,9 +51,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anyhow" @@ -46,6 +67,27 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "bincode" version = "1.3.3" @@ -88,6 +130,15 @@ dependencies = [ "wyz", ] +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + [[package]] name = "block-buffer" version = "0.12.0" @@ -103,6 +154,12 @@ version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "cast" version = "0.3.0" @@ -165,18 +222,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstyle", "clap_lex", @@ -184,9 +241,42 @@ dependencies = [ [[package]] name = "clap_lex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "color-eyre" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "cpufeatures" @@ -216,9 +306,9 @@ dependencies = [ "cast", "ciborium", "clap", - "criterion-plot", + "criterion-plot 0.5.0", "is-terminal", - "itertools", + "itertools 0.10.5", "num-traits", "once_cell", "oorandom", @@ -232,6 +322,29 @@ dependencies = [ "walkdir", ] +[[package]] +name = "criterion" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot 0.6.0", + "itertools 0.13.0", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + [[package]] name = "criterion-plot" version = "0.5.0" @@ -239,7 +352,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", + "itertools 0.10.5", +] + +[[package]] +name = "criterion-plot" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" +dependencies = [ + "cast", + "itertools 0.13.0", ] [[package]] @@ -283,18 +406,95 @@ dependencies = [ ] [[package]] -name = "digest" -version = "0.11.1" +name = "curve25519-dalek" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "285743a676ccb6b3e116bc14cc69319b957867930ae9c4822f8e0f54509d7243" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" dependencies = [ - "block-buffer", + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +dependencies = [ + "block-buffer 0.12.0", "crypto-common", ] +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "serde", + "signature", +] + [[package]] name = "ed25519-heea" version = "0.1.0" +dependencies = [ + "bincode", + "color-eyre", + "criterion 0.7.0", + "der", + "ed25519", + "ed25519-zebra", + "hashbrown 0.16.1", + "hex", + "once_cell", + "pkcs8", + "rand 0.8.5", + "rand_core 0.6.4", + "serde", + "sha2 0.11.0-rc.5", + "solana-curve25519", + "subtle", + "zeroize", +] + +[[package]] +name = "ed25519-zebra" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85a1fff1b329c96789427b09d4d5949b2d2f717851fd1e65a18878bee19d1ff" +dependencies = [ + "curve25519-dalek", + "hex", + "rand_core 0.5.1", + "serde", + "sha2 0.9.9", + "thiserror", +] [[package]] name = "either" @@ -318,6 +518,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -342,12 +552,39 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "funty" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -356,7 +593,7 @@ checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] @@ -385,6 +622,12 @@ dependencies = [ "wasip3", ] +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + [[package]] name = "half" version = "2.7.1" @@ -402,7 +645,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -410,6 +653,11 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] [[package]] name = "heck" @@ -453,6 +701,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +[[package]] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + [[package]] name = "indexmap" version = "2.13.0" @@ -486,10 +740,19 @@ dependencies = [ ] [[package]] -name = "itoa" -version = "1.0.17" +name = "itertools" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" @@ -501,6 +764,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "leb128fmt" version = "0.1.0" @@ -537,6 +806,15 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + [[package]] name = "num-bigint" version = "0.3.3" @@ -567,10 +845,19 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.21.3" +name = "object" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "oorandom" @@ -578,6 +865,43 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "owo-colors" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "plotters" version = "0.3.7" @@ -738,6 +1062,15 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + [[package]] name = "rand_core" version = "0.6.4" @@ -820,6 +1153,12 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + [[package]] name = "rustc_version" version = "0.4.1" @@ -969,6 +1308,19 @@ dependencies = [ "zmij", ] +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "sha2" version = "0.11.0-rc.5" @@ -977,7 +1329,16 @@ checksum = "7c5f3b1e2dc8aad28310d8410bd4d7e180eca65fca176c52ab00d364475d0024" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.11.2", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", ] [[package]] @@ -986,6 +1347,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "solana-curve25519" version = "0.1.0" @@ -993,8 +1363,8 @@ dependencies = [ "bincode", "cfg-if", "cpufeatures 0.2.17", - "criterion", - "digest", + "criterion 0.5.1", + "digest 0.11.2", "hex", "proptest", "rand 0.10.0", @@ -1003,7 +1373,7 @@ dependencies = [ "rustcrypto-ff", "rustcrypto-group", "serde", - "sha2", + "sha2 0.11.0-rc.5", "solana-curve25519-derive", "subtle", "zeroize", @@ -1014,7 +1384,7 @@ name = "solana-curve25519-cuda" version = "0.1.0" dependencies = [ "cc", - "criterion", + "criterion 0.5.1", "rand 0.8.5", "rand_chacha 0.3.1", "rand_core 0.6.4", @@ -1032,6 +1402,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "subtle" version = "2.6.1" @@ -1068,9 +1448,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.26.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", "getrandom 0.4.2", @@ -1079,6 +1459,35 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -1089,6 +1498,47 @@ dependencies = [ "serde_json", ] +[[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-error" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + [[package]] name = "typenum" version = "1.19.0" @@ -1113,6 +1563,18 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[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 = "wait-timeout" version = "0.2.1" @@ -1132,6 +1594,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -1453,18 +1921,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.42" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.42" +version = "0.8.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" dependencies = [ "proc-macro2", "quote", @@ -1476,6 +1944,20 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] name = "zmij" diff --git a/curve25519/src/edwards.rs b/curve25519/src/edwards.rs index 6f954b0..ec490f4 100644 --- a/curve25519/src/edwards.rs +++ b/curve25519/src/edwards.rs @@ -106,7 +106,7 @@ use core::ops::{Mul, MulAssign}; #[cfg(feature = "digest")] use digest::{ - FixedOutput, HashMarker, array::typenum::U64, consts::True, crypto_common::BlockSizeUser, + FixedOutput, HashMarker, array::typenum::U64, block_api::BlockSizeUser, consts::True, typenum::IsGreater, }; @@ -2517,9 +2517,12 @@ mod test { #[cfg(all(feature = "alloc", feature = "digest"))] fn hex_str_to_fe(hex_str: &str) -> FieldElement { - let mut bytes = hex::decode(hex_str).unwrap().to_vec(); + let mut bytes = hex::decode(hex_str).expect("RFC test vector hex must decode"); bytes.reverse(); - FieldElement::from_bytes(&bytes.try_into().unwrap()) + let bytes: [u8; 32] = bytes + .try_into() + .expect("RFC test vector field element must be 32 bytes"); + FieldElement::from_bytes(&bytes) } #[test] @@ -2535,7 +2538,7 @@ mod test { AffinePoint { x, y }.to_edwards() }; - let computed = EdwardsPoint::encode_to_curve::(&[&input], &[dst]); + let computed = EdwardsPoint::encode_to_curve::(&[input], &[dst]); assert_eq!(computed, expected_output, "Failed in test {}", index); } } @@ -2594,7 +2597,7 @@ mod test { AffinePoint { x, y }.to_edwards() }; - let computed = EdwardsPoint::hash_to_curve::(&[&input], &[dst]); + let computed = EdwardsPoint::hash_to_curve::(&[input], &[dst]); assert_eq!(expected_output, computed, "Failed in test {}", index); } diff --git a/curve25519/src/field.rs b/curve25519/src/field.rs index ce43b70..2db0c25 100644 --- a/curve25519/src/field.rs +++ b/curve25519/src/field.rs @@ -758,36 +758,46 @@ mod test { fn from_bytes_wide() { // Do the 64-byte input ones first for (input_bytes, expected_reduced) in FROM_BYTES_WIDE_KAT_BIG { - let reduce_fe = FieldElement::from_bytes_wide( - &hex::decode(input_bytes) - .unwrap() - .as_slice() - .try_into() - .unwrap(), - ); + let decoded_input = + hex::decode(input_bytes).expect("64-byte reduction test vector hex must decode"); + let decoded_input: [u8; 64] = decoded_input + .as_slice() + .try_into() + .expect("64-byte reduction test vector must decode to 64 bytes"); + let reduce_fe = FieldElement::from_bytes_wide(&decoded_input); assert_eq!( &reduce_fe.to_bytes(), - hex::decode(expected_reduced).unwrap().as_slice() + hex::decode(expected_reduced) + .expect("reduction result test vector hex must decode") + .as_slice() ); } // Now do the 48-byte inputs for (input_bytes, expected_reduced) in FROM_BYTES_WIDE_KAT_MEDIUM { let mut padded_input_bytes = [0u8; 64]; - padded_input_bytes[..48].copy_from_slice(&hex::decode(input_bytes).unwrap()); + padded_input_bytes[..48].copy_from_slice( + &hex::decode(input_bytes).expect("48-byte reduction test vector hex must decode"), + ); let reduce_fe = FieldElement::from_bytes_wide(&padded_input_bytes); assert_eq!( &reduce_fe.to_bytes(), - hex::decode(expected_reduced).unwrap().as_slice() + hex::decode(expected_reduced) + .expect("reduction result test vector hex must decode") + .as_slice() ); } } #[cfg(feature = "digest")] fn fe_from_test_vector(expected_hex: &str) -> FieldElement { - let mut expected_hash = hex::decode(expected_hex).unwrap(); + let mut expected_hash = + hex::decode(expected_hex).expect("hash-to-field test vector hex must decode"); expected_hash.reverse(); - FieldElement::from_bytes(&expected_hash.try_into().unwrap()) + let expected_hash: [u8; 32] = expected_hash + .try_into() + .expect("hash-to-field test vector must decode to 32 bytes"); + FieldElement::from_bytes(&expected_hash) } /// Hash to field test vectors from diff --git a/ed25519-heea/CHANGELOG.md b/ed25519-heea/CHANGELOG.md new file mode 100644 index 0000000..1d392be --- /dev/null +++ b/ed25519-heea/CHANGELOG.md @@ -0,0 +1,136 @@ +# CHANGELOG + +Entries are listed in reverse chronological order. + +# 4.2.0 + +* Bump MSRV from 1.65 to 1.85. +* Bump `hashbrown` from 0.15 to 0.16. + +# 4.1.0 + +* Implement `PartialEq` and `Eq` in `SigningKey` and `VerificationKey` (#102) +* Add `alloc` feature by @nazar-pc in (#161, #174) +* Add helper methods by @conradoplg in (#179) + +Note: to use Rust older than `1.85`, you will need to downgrade the `base64ct` +crate: + +``` +cargo update base64ct --precise 1.6.0 +``` + +# 4.0.3 + +* Update `curve25519` to `4.1.0` + +# 4.0.2 + +* Update `curve25519` to `4.0.0` + +# 4.0.1 + +* Fix no-std build with serde activated (#87) +* Update `curve25519` to `4.0.0-rc.3` + +# 4.0.0 + +* `Signature` is now an alias for `ed25519::Signature` + * `impl From for [u8; 64]` no longer exists; use `to_bytes()` instead. +* `signature::{Signer, Verifier} is now implemented for `SigningKey` and `VerificationKey`. +* Updates `sha2` version to `0.10` and `curve25519` version to `4.0.0-rc.2`. +* Add DER & PEM support for SigningKeySeed and VerificationKeyBytes (RFC 8410) #46 https://github.com/ZcashFoundation/ed25519-zebra/pull/46 + * This is under the non-default `pem` and `pkcs8` features + +MSRV increased to `1.65.0`. + +# 3.1.0 + +* Add no_std support by @pvdrz in https://github.com/ZcashFoundation/ed25519-zebra/pull/57 + +# 3.0.0 + +* Fix typo by @rex4539 in https://github.com/ZcashFoundation/ed25519-zebra/pull/32 +* Add Zeroize impl for SigningKey by @kim in https://github.com/ZcashFoundation/ed25519-zebra/pull/34 +* Add JNI code for ed25519-zebra by @droark in https://github.com/ZcashFoundation/ed25519-zebra/pull/37 +* Update rand_core to 0.6 and rand to 0.8 by @dconnolly in https://github.com/ZcashFoundation/ed25519-zebra/pull/44 +* dependencies: update zeroize to 1.2 by @FintanH in https://github.com/ZcashFoundation/ed25519-zebra/pull/52 + +# 2.2.0 + +* Add `PartialOrd`, `Ord` implementations for `VerificationKeyBytes`. While + the derived ordering is not cryptographically meaningful, deriving these + traits is useful because it allows, e.g., using `VerificationKeyBytes` as the + key to a `BTreeMap` (contributed by @cloudhead). + +# 2.1.2 + +* Updates `sha2` version to `0.9` and `curve25519` version to `3`. + +# 2.1.1 + +* Add a missing multiplication by the cofactor in batch verification and test + that individual and batch verification agree. This corrects an omission that + should have been included in `2.0.0`. + +# 2.1.0 + +* Implements `Clone + Debug` for `batch::Item` and provides + `batch::Item::verify_single` to perform fallback verification in case + of batch failure. + +# 2.0.0 + +* Implements ZIP 215, so that batched and individual verification + agree on whether signatures are valid. + +# 1.0.0 + +* Adds `impl TryFrom<&[u8]>` for all types. + +# 1.0.0-pre.0 + +* Add a note about versioning to handle ZIP 215. + +# 0.4.1 + +* Change `docs.rs` configuration in `Cargo.toml` to not refer to the removed + `batch` feature so that the docs render correctly on `docs.rs`. + +# 0.4.0 + +* The sync batch verification api is changed to remove a dependence on the + message lifetime that made it difficult to use in async contexts. + +# 0.3.0 + +* Change terminology from secret and public keys to signing and verification + keys. +* Remove async batch verification in favor of a sync api; the async approach is + to be developed in another crate. + +# 0.2.3 + +* The previous implementation exactly matched the behavior of `libsodium` + `1.0.15` with the `ED25519_COMPAT` configuration, but this configuration + wasn't used by `zcashd`. This commit changes the validation rules to exactly + match without `ED25519_COMPAT`, and highlights the remaining inconsistencies + with the Zcash specification that were not addressed in the previous spec + fix. + +# 0.2.2 + +* Adds `impl AsRef<[u8]> for PublicKey`. +* Adds `impl AsRef<[u8]> for SecretKey`. + +# 0.2.1 + +* Adds `impl AsRef<[u8]> for PublicKeyBytes`. + +# 0.2.0 + +* Adds experimental futures-based batch verification API, gated by the `batch` feature. + +# 0.1.0 + +Initial release, attempting to match the actual `zcashd` behavior. diff --git a/ed25519-heea/Cargo.lock b/ed25519-heea/Cargo.lock new file mode 100644 index 0000000..9960aa1 --- /dev/null +++ b/ed25519-heea/Cargo.lock @@ -0,0 +1,1190 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96eb4cdd6cf1b31d671e9efe75c5d1ec614776856cefbe109ca373554a6d514f" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.5.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + +[[package]] +name = "color-eyre" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "criterion" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[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.2.0-rc.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "919bd05924682a5480aec713596b9e2aabed3a0a6022fab6847f85a99e5f190a" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "curve25519" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519" +version = "5.0.0-pre.2" +source = "git+https://github.com/zz-sol/curve25519.git?branch=zz%2Fimpl_ches25#9db800b4d9ababbcf7522b74d2b0955cc0ee9bb9" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-derive", + "digest 0.11.0-rc.4", + "ethnum", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.11.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea390c940e465846d64775e55e3115d5dc934acb953de6f6e6360bc232fe2bf7" +dependencies = [ + "block-buffer 0.11.0", + "crypto-common", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "serde", + "signature", +] + +[[package]] +name = "ed25519-zebra" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85a1fff1b329c96789427b09d4d5949b2d2f717851fd1e65a18878bee19d1ff" +dependencies = [ + "curve25519 3.2.0", + "hex", + "rand_core 0.5.1", + "serde", + "sha2 0.9.9", + "thiserror", +] + +[[package]] +name = "ed25519-zebra" +version = "4.2.0" +dependencies = [ + "bincode", + "color-eyre", + "criterion", + "curve25519 5.0.0-pre.2", + "der", + "ed25519", + "ed25519-zebra 1.0.1", + "hashbrown", + "hex", + "once_cell", + "pkcs8", + "rand", + "rand_core 0.6.4", + "serde", + "sha2 0.11.0-rc.3", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "ethnum" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca81e6b4777c89fd810c25a4be2b1bd93ea034fbe58e6a75216a34c6b82c539b" + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fiat-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[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 = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hybrid-array" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f471e0a81b2f90ffc0cb2f951ae04da57de8baa46fa99112b062a5173a5088d0" +dependencies = [ + "typenum", +] + +[[package]] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "owo-colors" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[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.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[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 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.11.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d43dc0354d88b791216bb5c1bfbb60c0814460cc653ae0ebd71f286d0bd927" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.11.0-rc.4", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tracing" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[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 = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/ed25519-heea/Cargo.toml b/ed25519-heea/Cargo.toml index 2c8f014..176e376 100644 --- a/ed25519-heea/Cargo.toml +++ b/ed25519-heea/Cargo.toml @@ -1,6 +1,79 @@ [package] name = "ed25519-heea" version = "0.1.0" +rust-version = "1.85.0" +authors = [ + "Henry de Valence ", + "Zcash Foundation ", + "Anza Cryptography Team", +] +license = "MIT OR Apache-2.0" edition = "2024" +description = "ZIP-215-compliant Ed25519 with HEEA-accelerated verification (forked from ed25519-zebra)" +resolver = "2" +include = [ + "/src", + "/README.md", + "/CHANGELOG.md", + "/LICENSE-APACHE", + "/LICENSE-MIT", + "/tests", + "/benches", +] + +[package.metadata.docs.rs] +features = ["nightly"] [dependencies] +# Keep the dependency key as "curve25519" so that all existing `use curve25519::…` +# references in source files remain valid; the actual package is curve25519-sol. +curve25519 = { path = "../curve25519", package = "solana-curve25519", default-features = false, features = ["digest", "zeroize", "precomputed-tables"] } +der = { version = "0.7.9", optional = true } +ed25519 = { version = "2.2.3", default-features = false } +hashbrown = { version = "0.16", optional = true } +pkcs8 = { version = "0.10.1", optional = true, features = ["pem"] } +rand_core = "0.6" +serde = { version = "1", default-features = false, optional = true, features = ["derive"] } +sha2 = { version = "0.11.0-rc.3", default-features = false } +subtle = { version = "2.6.1", default-features = false } +zeroize = { version = "1.8", default-features = false, features = ["derive"] } + +[dev-dependencies] +rand = "0.8" +bincode = "1" +criterion = "0.7" +ed25519-zebra-legacy = { package = "ed25519-zebra", version = "1" } +color-eyre = "0.6" +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } +once_cell = "1.21" + +[features] +nightly = [] +default = ["serde", "std"] +alloc = [ + "curve25519/alloc", + "ed25519/alloc", + "hashbrown", + "pkcs8?/alloc", + "zeroize/alloc", +] +pem = ["pkcs8", "dep:der", "ed25519/pem"] +pkcs8 = ["dep:pkcs8", "ed25519/pkcs8", "alloc"] +serde = ["dep:serde", "ed25519/serde"] +std = ["alloc", "ed25519/std", "subtle/std"] + +[[test]] +name = "rfc8032" + +[[test]] +name = "unit_tests" + +[[test]] +name = "batch" + +[[test]] +name = "heea" + +[[bench]] +name = "bench" +harness = false diff --git a/ed25519-heea/DEVELOPERS.md b/ed25519-heea/DEVELOPERS.md new file mode 100644 index 0000000..7a3c1e4 --- /dev/null +++ b/ed25519-heea/DEVELOPERS.md @@ -0,0 +1,44 @@ +## Release Checklist + +- Run `cargo semver-checks` to see if a major version bump is required +- Bump version in Cargo.toml + - If major version was bumped, update README.md +- Update CHANGELOG.md +- Ensure the MSRV in Cargo.toml (`rust-version` key) is equal to the MSRV being + tested (main.yml) +- Update locked dependencies: `cargo update`. Run `cargo test --all-features` + to check if anything breaks. If that happens, see next section. +- Test if it's publishable: `cargo publish --dry-run` +- Open a PR with the version bump and changelog update, wait for review and merge +- Tag a new release in GitHub: https://github.com/ZcashFoundation/ed25519-zebra/releases/new + - Create a tag with the version (e.g. `4.0.3`) + - Name: e.g. `ed25519-zebra 4.0.3` + - Paste the changelog for the version +- Publish: `cargo publish` + +## If something breaks + +If testing broke after running `cargo update`, first determine if it's a +test-only dependency or not. Run `cargo build --all-features`. If that works, +then it's probably a test-only dependency, and you can avoid updating that +specific dependency (leave a old version in the lockfile). Otherwise investigate +why it caused build to fail. + +If the "test on nightly" test failed, then either there is some bug in the code +or some dependency update caused it to fail. Investigate and if it's the latter, +you can either downgrade in the lockfile or try to workaround it. + +If the "build with no_std" test failed, then some change was introduced that +depended on the std-library. You will probably need to fix this by changing +to some no_std dependency, or gating the code so it only compiles when +`std` is enabled. + +If one of the dependencies bumped its MSRV, we might require a MSRV bump too: + +- Double check if the dependency is not a test-only dependency. (The MSRV + test in CI only builds the library but does not test it, to prevent + a test-only dependency MSRV bump from breaking it.) +- If it's not a test-only dependency, check if the main consumers of the + library are OK with a MSRV bump. I usually ask ECC devs. +- If it's OK, bump it in Cargo.toml and main.yml. +- If not, you will need to find some workaround. diff --git a/ed25519-heea/LICENSE-APACHE b/ed25519-heea/LICENSE-APACHE new file mode 100644 index 0000000..bdb0982 --- /dev/null +++ b/ed25519-heea/LICENSE-APACHE @@ -0,0 +1,203 @@ +Copyright (c) 2020 Zcash Foundation + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/ed25519-heea/LICENSE-MIT b/ed25519-heea/LICENSE-MIT new file mode 100644 index 0000000..416b525 --- /dev/null +++ b/ed25519-heea/LICENSE-MIT @@ -0,0 +1,20 @@ +Copyright (c) 2020 Zcash Foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/ed25519-heea/README.md b/ed25519-heea/README.md new file mode 100644 index 0000000..21ac1e1 --- /dev/null +++ b/ed25519-heea/README.md @@ -0,0 +1,139 @@ +# ed25519-heea + +**ZIP-215-compliant Ed25519 signatures with HEEA-accelerated verification, forked from +[ed25519-zebra].** + +> For the original ed25519-zebra documentation see [README_zebra.md](README_zebra.md). + +This crate is part of the [curve25519-sol](../README.md) workspace. + +--- + +## Changes from ed25519-zebra + +### `verify_heea`: fast-path signature verification + +A new method `VerificationKey::verify_heea` has been added alongside the existing `verify`. +Both methods accept the same arguments and produce identical results — `verify_heea` is a +**drop-in accelerated replacement** for `verify`. + +The HEEA method (from the TCHES 2025 paper _"Accelerating EdDSA Signature Verification with +Faster Scalar Size Halving"_) transforms the standard 2-point MSM: + +```text +[8][s]B = [8]R + [8][h]A (standard) +``` + +into a 4-point MSM over half-size (~128-bit) scalars: + +```text +τs_lo·B + τs_hi·(2¹²⁸·B) = τ·R + ρ·A (HEEA) +``` + +where `ρ ≡ ±τ·h (mod ℓ)` and `τs = τs_hi·2¹²⁸ + τs_lo`. All four scalars are ≤128 bits, +and the two basepoints (`B` and `2¹²⁸B`) use precomputed lookup tables, giving approximately +**~15% faster** verification compared to the standard path. + +### Dependencies + +`ed25519-zebra` was updated to depend on this fork's `curve25519` crate instead of +`curve25519-dalek`, in order to access `HEEADecomposition` and `vartime_triple_scalar_mul_basepoint`. + +--- + +## ZIP 215 + +ZIP-215-compliant Ed25519 validation rules are fully preserved from ed25519-zebra: + +- Non-canonical point encodings are accepted for `A` and `R`. +- `s` must be a canonical integer less than the group order `ℓ`. +- The cofactor-cleared equation `[8][s]B = [8]R + [8][h]A` is used (not the RFC 8032 variant). + +See [README_zebra.md](README_zebra.md) and [ZIP 215] for full details. + +--- + +## Usage + +```toml +[dependencies] +ed25519-heea = { git = "https://github.com/zz-sol/ed25519-sol", package = "ed25519-heea" } +``` + +### Example + +```rust,no_run +use core::convert::TryFrom; +use rand::thread_rng; +use ed25519_heea::{SigningKey, VerificationKey}; + +let msg = b"curve25519-sol"; + +// Generate key and sign +let sk = SigningKey::new(thread_rng()); +let sig = sk.sign(msg); +let vk = VerificationKey::from(&sk); + +// Standard ZIP-215 verification (from ed25519-zebra) +vk.verify(&sig, msg).expect("valid signature"); + +// HEEA-accelerated verification (same result, ~15% faster) +vk.verify_heea(&sig, msg).expect("valid signature"); +``` + +### Batch verification + +Batch verification is unchanged from ed25519-zebra and uses a randomised linear combination to +check multiple signatures in one pass: + +```rust,ignore +#[cfg(feature = "alloc")] +{ + use ed25519_heea::batch; + + let mut verifier = batch::Verifier::new(); + for (vk_bytes, sig, msg) in items { + verifier.queue((vk_bytes, sig, msg)); + } + verifier.verify(thread_rng()).expect("all valid"); +} +``` + +--- + +## Features + +| Feature | Default? | Description | +|---|:---:|---| +| `std` | ✓ | Enables `std`; without it the crate is `no_std` + `alloc`. | +| `alloc` | ✓ | Enables batch verification. | +| `serde` | | Serialization for key and signature types. | +| `pkcs8` | | PKCS#8 DER encoding/decoding for `VerificationKey`. | + +--- + +## MSRV + +Rust **1.85.0** (Edition 2024). + +--- + +## References + +- [TCHES 2025 paper] – _Accelerating EdDSA Signature Verification with Faster Scalar Size Halving_ +- [ed25519-zebra] – upstream library (Zcash Foundation) +- [ZIP 215] – Ed25519 validation rules for Zcash +- [Original ed25519-zebra README](README_zebra.md) + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE)) +- MIT license ([LICENSE-MIT](LICENSE-MIT)) + +at your option. + +[TCHES 2025 paper]: https://tches.iacr.org/index.php/TCHES/article/view/11971 +[ed25519-zebra]: https://github.com/ZcashFoundation/ed25519-zebra +[ZIP 215]: https://zips.z.cash/zip-0215 diff --git a/ed25519-heea/README_zebra.md b/ed25519-heea/README_zebra.md new file mode 100644 index 0000000..f984ec8 --- /dev/null +++ b/ed25519-heea/README_zebra.md @@ -0,0 +1,93 @@ +[![Build status](https://github.com/ZcashFoundation/ed25519-zebra/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/ZcashFoundation/ed25519-zebra/actions/workflows/main.yml?query=branch%3Amain) +[![dependency status](https://deps.rs/repo/github/ZcashFoundation/ed25519-zebra/status.svg)](https://deps.rs/repo/github/ZcashFoundation/ed25519-zebra) + +Zcash-flavored Ed25519 for use in [Zebra][zebra]. + +Zcash uses Ed25519 for [JoinSplit signatures][zcash_protocol_jssig] with +particular validation rules around edge cases in Ed25519 signatures. Ed25519, +as specified in [RFC8032], does not specify behaviour around these edge cases +and so does not require conformant implementations to agree on whether a +signature is valid. For most applications, these edge cases are irrelevant, +but in Zcash, nodes must be able to reach consensus on which signatures would +be valid, so these validation behaviors are *consensus-critical*. + +Because the Ed25519 validation rules are consensus-critical for Zcash, Zebra +requires an Ed25519 library that implements the Zcash-flavored validation rules +specifically, and since it is unreasonable to expect an upstream dependency to +maintain Zcash-specific behavior, this crate provides an Ed25519 implementation +matching the Zcash consensus rules exactly. + +However, this library may be of independent interest, as it implements +ZIP215, a set of precisely specified validation rules for Ed25519 that make +individual verification consistent with batch verification and are +backwards-compatible with all existing Ed25519 signatures. Any non-Zcash users +should use the ZIP215 rules: +```toml +ed25519-zebra = "4" +``` + +## ZIP 215 and changes to Zcash-flavored Ed25519 + +[Zcash Improvement Proposal 215][ZIP215] changes validation criteria for +Ed25519 signatures in Zcash after its activation (which occurred in the +Canopy network upgrade at mainnet block height 1046400). These changes remove +the dependence on validation rules inherited from a specific point release of +`libsodium` and make individual verification consistent with batch +verification. More details and motivation are available in the text of [ZIP215]. + +The `1.x` series of this crate implements the legacy, pre-ZIP-215 validation +criteria; the `2.x+` series of this crate implements the post-ZIP-215 +validation criteria. Users (like Zebra or zcashd) who need to handle the +upgrade can use both versions simultaneously using cargo renaming, e.g., +```toml +ed25519-zebra-legacy = { package = "ed25519-zebra", version = "1" } +ed25519-zebra-zip215 = { package = "ed25519-zebra", version = "4" } +``` + +## Example + +``` +use std::convert::TryFrom; +use rand::thread_rng; +use ed25519_zebra::*; + +let msg = b"Zcash"; + +// Signer's context +let (vk_bytes, sig_bytes) = { + // Generate a signing key and sign the message + let sk = SigningKey::new(thread_rng()); + let sig = sk.sign(msg); + + // Types can be converted to raw byte arrays with From/Into + let sig_bytes: [u8; 64] = sig.into(); + let vk_bytes: [u8; 32] = VerificationKey::from(&sk).into(); + + (vk_bytes, sig_bytes) +}; + +// Verify the signature +assert!( + VerificationKey::try_from(vk_bytes) + .and_then(|vk| vk.verify(&sig_bytes.into(), msg)) + .is_ok() +); +``` + +## Developers guide + + +See [DEVELOPERS.md](DEVELOPERS.md). + +## License + +ed25519-zebra is distributed under the terms of both the MIT license +and the Apache License (Version 2.0). + +See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT). + + +[zcash_protocol_jssig]: https://zips.z.cash/protocol/protocol.pdf#concretejssig +[RFC8032]: https://tools.ietf.org/html/rfc8032 +[zebra]: https://github.com/ZcashFoundation/zebra +[ZIP215]: https://zips.z.cash/zip-0215 diff --git a/ed25519-heea/benches/bench.rs b/ed25519-heea/benches/bench.rs new file mode 100644 index 0000000..5fb3dc8 --- /dev/null +++ b/ed25519-heea/benches/bench.rs @@ -0,0 +1,101 @@ +use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; + +use core::convert::TryFrom; +use ed25519_heea::*; +use rand::thread_rng; + +fn sigs_with_distinct_pubkeys() -> impl Iterator { + std::iter::repeat_with(|| { + let sk = SigningKey::new(thread_rng()); + let pk_bytes = VerificationKeyBytes::from(&sk); + let sig = sk.sign(b""); + (pk_bytes, sig) + }) +} + +fn sigs_with_same_pubkey() -> impl Iterator { + let sk = SigningKey::new(thread_rng()); + let pk_bytes = VerificationKeyBytes::from(&sk); + std::iter::repeat_with(move || { + let sig = sk.sign(b""); + (pk_bytes, sig) + }) +} + +fn bench_batch_verify(c: &mut Criterion) { + let mut group = c.benchmark_group("Batch Verification"); + for n in [8usize, 16, 24, 32, 40, 48, 56, 64].iter() { + group.throughput(Throughput::Elements(*n as u64)); + let sigs = sigs_with_distinct_pubkeys().take(*n).collect::>(); + group.bench_with_input( + BenchmarkId::new("Unbatched verification", n), + &sigs, + |b, sigs| { + b.iter(|| { + for (vk_bytes, sig) in sigs.iter() { + let _ = + VerificationKey::try_from(*vk_bytes).and_then(|vk| vk.verify(sig, b"")); + } + }) + }, + ); + #[cfg(feature = "alloc")] + group.bench_with_input( + BenchmarkId::new("Signatures with Distinct Pubkeys", n), + &sigs, + |b, sigs| { + b.iter(|| { + let mut batch = batch::Verifier::new(); + for (vk_bytes, sig) in sigs.iter().cloned() { + batch.queue((vk_bytes, sig, b"")); + } + batch.verify(thread_rng()) + }) + }, + ); + #[cfg(feature = "alloc")] + let sigs = sigs_with_same_pubkey().take(*n).collect::>(); + #[cfg(feature = "alloc")] + group.bench_with_input( + BenchmarkId::new("Signatures with the Same Pubkey", n), + &sigs, + |b, sigs| { + b.iter(|| { + let mut batch = batch::Verifier::new(); + for (vk_bytes, sig) in sigs.iter().cloned() { + batch.queue((vk_bytes, sig, b"")); + } + batch.verify(thread_rng()) + }) + }, + ); + } + group.finish(); +} + +fn bench_single_verify(c: &mut Criterion) { + let mut group = c.benchmark_group("Single Verification"); + + group.bench_function("ed25519", |b| { + let sk = SigningKey::new(thread_rng()); + let vk = VerificationKey::from(&sk); + let sig = sk.sign(b""); + b.iter(|| { + let _ = vk.verify(&sig, b""); + }) + }); + + group.bench_function("ed25519_hEEA", |b| { + let sk = SigningKey::new(thread_rng()); + let vk = VerificationKey::from(&sk); + let sig = sk.sign(b""); + b.iter(|| { + let _ = vk.verify_heea(&sig, b""); + }) + }); + + group.finish(); +} + +criterion_group!(benches, bench_single_verify, bench_batch_verify,); +criterion_main!(benches); diff --git a/ed25519-heea/src/batch.rs b/ed25519-heea/src/batch.rs new file mode 100644 index 0000000..fd73d4e --- /dev/null +++ b/ed25519-heea/src/batch.rs @@ -0,0 +1,215 @@ +//! Performs batch Ed25519 signature verification. +//! +//! Batch verification asks whether *all* signatures in some set are valid, +//! rather than asking whether *each* of them is valid. This allows sharing +//! computations among all signature verifications, performing less work overall +//! at the cost of higher latency (the entire batch must complete), complexity of +//! caller code (which must assemble a batch of signatures across work-items), +//! and loss of the ability to easily pinpoint failing signatures. +//! +//! In addition to these general tradeoffs, design flaws in Ed25519 specifically +//! mean that batched verification may not agree with individual verification. +//! Some signatures may verify as part of a batch but not on their own. +//! This problem is fixed by [ZIP215], a precise specification for edge cases +//! in Ed25519 signature validation that ensures that batch verification agrees +//! with individual verification in all cases. +//! +//! This crate implements ZIP215, so batch verification always agrees with +//! individual verification, but this is not guaranteed by other implementations. +//! **Be extremely careful when using Ed25519 in a consensus-critical context +//! like a blockchain.** +//! +//! This batch verification implementation is adaptive in the sense that it +//! detects multiple signatures created with the same verification key and +//! automatically coalesces terms in the final verification equation. In the +//! limiting case where all signatures in the batch are made with the same +//! verification key, coalesced batch verification runs twice as fast as ordinary +//! batch verification. +//! +//! ![benchmark](https://www.zfnd.org/images/coalesced-batch-graph.png) +//! +//! This optimization doesn't help much with Zcash, where public keys are random, +//! but could be useful in proof-of-stake systems where signatures come from a +//! set of validators (provided that system uses the ZIP215 rules). +//! +//! # Example +//! ``` +//! # use ed25519_heea::*; +//! let mut batch = batch::Verifier::new(); +//! for _ in 0..32 { +//! let sk = SigningKey::new(rand::thread_rng()); +//! let vk_bytes = VerificationKeyBytes::from(&sk); +//! let msg = b"BatchVerifyTest"; +//! let sig = sk.sign(&msg[..]); +//! batch.queue((vk_bytes, sig, &msg[..])); +//! } +//! assert!(batch.verify(rand::thread_rng()).is_ok()); +//! ``` +//! +//! [ZIP215]: https://zips.z.cash/zip-0215 + +use alloc::vec::Vec; +use core::convert::TryFrom; + +use curve25519::{ + edwards::{CompressedEdwardsY, EdwardsPoint}, + scalar::Scalar, + traits::{IsIdentity, VartimeMultiscalarMul}, +}; +use hashbrown::HashMap; +use rand_core::{CryptoRng, RngCore}; +use sha2::{Sha512, digest::Update}; + +use crate::{Error, VerificationKey, VerificationKeyBytes}; +use ed25519::Signature; + +// Shim to generate a u128 without importing `rand`. +fn gen_u128(mut rng: R) -> u128 { + let mut bytes = [0u8; 16]; + rng.fill_bytes(&mut bytes[..]); + u128::from_le_bytes(bytes) +} + +/// A batch verification item. +/// +/// This struct exists to allow batch processing to be decoupled from the +/// lifetime of the message. This is useful when using the batch verification API +/// in an async context. +#[derive(Clone, Debug)] +pub struct Item { + vk_bytes: VerificationKeyBytes, + sig: Signature, + k: Scalar, +} + +impl<'msg, M: AsRef<[u8]> + ?Sized> From<(VerificationKeyBytes, Signature, &'msg M)> for Item { + fn from(tup: (VerificationKeyBytes, Signature, &'msg M)) -> Self { + let (vk_bytes, sig, msg) = tup; + // Compute k now to avoid dependency on the msg lifetime. + let k = Scalar::from_hash( + Sha512::default() + .chain(&sig.r_bytes()[..]) + .chain(&vk_bytes.0[..]) + .chain(msg), + ); + Self { vk_bytes, sig, k } + } +} + +impl Item { + /// Perform non-batched verification of this `Item`. + /// + /// This is useful (in combination with `Item::clone`) for implementing fallback + /// logic when batch verification fails. In contrast to + /// [`VerificationKey::verify`](crate::VerificationKey::verify), which requires + /// borrowing the message data, the `Item` type is unlinked from the lifetime of + /// the message. + pub fn verify_single(self) -> Result<(), Error> { + VerificationKey::try_from(self.vk_bytes) + .and_then(|vk| vk.verify_prehashed(&self.sig, self.k)) + } +} + +/// A batch verification context. +#[derive(Default)] +pub struct Verifier { + /// Signature data queued for verification. + signatures: HashMap>, + /// Caching this count avoids a hash traversal to figure out + /// how much to preallocate. + batch_size: usize, +} + +impl Verifier { + /// Construct a new batch verifier. + pub fn new() -> Verifier { + Verifier::default() + } + + /// Queue a (key, signature, message) tuple for verification. + pub fn queue>(&mut self, item: I) { + let Item { vk_bytes, sig, k } = item.into(); + + self.signatures + .entry(vk_bytes) + // The common case is 1 signature per public key. + // We could also consider using a smallvec here. + .or_insert_with(|| Vec::with_capacity(1)) + .push((k, sig)); + self.batch_size += 1; + } + + /// Perform batch verification, returning `Ok(())` if all signatures were + /// valid and `Err` otherwise. + #[allow(non_snake_case)] + pub fn verify(self, mut rng: R) -> Result<(), Error> { + // The batch verification equation is + // + // 8*[-sum(z_i * s_i)]B + 8*sum([z_i]R_i) + 8*sum([z_i * k_i]A_i) = 0. + // + // where for each signature i, + // - A_i is the verification key; + // - R_i is the signature's R value; + // - s_i is the signature's s value; + // - k_i is the hash of the message and other data; + // - z_i is a random 128-bit Scalar. + // + // Normally n signatures would require a multiscalar multiplication of + // size 2*n + 1, together with 2*n point decompressions (to obtain A_i + // and R_i). However, because we store batch entries in a HashMap + // indexed by the verification key, we can "coalesce" all z_i * k_i + // terms for each distinct verification key into a single coefficient. + // + // For n signatures from m verification keys, this approach instead + // requires a multiscalar multiplication of size n + m + 1 together with + // n + m point decompressions. When m = n, so all signatures are from + // distinct verification keys, this is as efficient as the usual method. + // However, when m = 1 and all signatures are from a single verification + // key, this is nearly twice as fast. + + let m = self.signatures.keys().count(); + + let mut A_coeffs = Vec::with_capacity(m); + let mut As = Vec::with_capacity(m); + let mut R_coeffs = Vec::with_capacity(self.batch_size); + let mut Rs = Vec::with_capacity(self.batch_size); + let mut B_coeff = Scalar::ZERO; + + for (vk_bytes, sigs) in self.signatures.iter() { + let A = CompressedEdwardsY(vk_bytes.0) + .decompress() + .ok_or(Error::InvalidSignature)?; + + let mut A_coeff = Scalar::ZERO; + + for (k, sig) in sigs.iter() { + let R = CompressedEdwardsY(*sig.r_bytes()) + .decompress() + .ok_or(Error::InvalidSignature)?; + let s = Option::::from(Scalar::from_canonical_bytes(*sig.s_bytes())) + .ok_or(Error::InvalidSignature)?; + let z = Scalar::from(gen_u128(&mut rng)); + B_coeff -= z * s; + Rs.push(R); + R_coeffs.push(z); + A_coeff += z * k; + } + + As.push(A); + A_coeffs.push(A_coeff); + } + + use core::iter::once; + use curve25519::constants::ED25519_BASEPOINT_POINT as B; + let check = EdwardsPoint::vartime_multiscalar_mul( + once(&B_coeff).chain(A_coeffs.iter()).chain(R_coeffs.iter()), + once(&B).chain(As.iter()).chain(Rs.iter()), + ); + + if check.mul_by_cofactor().is_identity() { + Ok(()) + } else { + Err(Error::InvalidSignature) + } + } +} diff --git a/ed25519-heea/src/error.rs b/ed25519-heea/src/error.rs new file mode 100644 index 0000000..adc1da3 --- /dev/null +++ b/ed25519-heea/src/error.rs @@ -0,0 +1,30 @@ +use core::fmt; + +/// An error related to Ed25519 signatures. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum Error { + /// The encoding of a secret key was malformed. + MalformedSecretKey, + /// The encoding of a public key was malformed. + MalformedPublicKey, + /// Signature verification failed. + InvalidSignature, + /// A byte slice of the wrong length was supplied during parsing. + InvalidSliceLength, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let msg = match self { + Self::MalformedSecretKey => "Malformed secret key encoding.", + Self::MalformedPublicKey => "Malformed public key encoding.", + Self::InvalidSignature => "Invalid signature.", + Self::InvalidSliceLength => "Invalid length when parsing byte slice.", + }; + + msg.fmt(f) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error {} diff --git a/ed25519-heea/src/lib.rs b/ed25519-heea/src/lib.rs index b93cf3f..f4c312e 100644 --- a/ed25519-heea/src/lib.rs +++ b/ed25519-heea/src/lib.rs @@ -1,14 +1,21 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} +#![deny(missing_docs)] +#![doc = include_str!("../README.md")] +#![cfg_attr(not(feature = "std"), no_std)] -#[cfg(test)] -mod tests { - use super::*; +//! Docs require the `nightly` feature until RFC 1990 lands. - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +#[cfg(feature = "alloc")] +extern crate alloc; + +#[cfg(feature = "alloc")] +pub mod batch; +mod error; +mod signing_key; +mod verification_key; + +// Allows importing traits used by `Signature`. +pub use ed25519; +pub use ed25519::Signature; +pub use error::Error; +pub use signing_key::SigningKey; +pub use verification_key::{VerificationKey, VerificationKeyBytes}; diff --git a/ed25519-heea/src/signing_key.rs b/ed25519-heea/src/signing_key.rs new file mode 100644 index 0000000..d3a8d34 --- /dev/null +++ b/ed25519-heea/src/signing_key.rs @@ -0,0 +1,383 @@ +#[cfg(feature = "pkcs8")] +const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.101.112"); // RFC 8410 +#[cfg(feature = "pkcs8")] +const ALGORITHM_ID: AlgorithmIdentifierRef = AlgorithmIdentifierRef { + oid: OID, + parameters: None, +}; + +use crate::Error; +#[cfg(all(feature = "pem", feature = "pkcs8"))] +use alloc::string::String; +use core::convert::TryFrom; +#[cfg(feature = "pkcs8")] +use core::convert::TryInto; +use curve25519::{constants, scalar::Scalar}; +use rand_core::{CryptoRng, RngCore}; +use sha2::{Digest, Sha512, digest::Update}; +use subtle::ConstantTimeEq; +use zeroize::Zeroize; + +use ed25519::{Signature, signature::Signer}; + +#[cfg(feature = "pkcs8")] +use ed25519::KeypairBytes; +#[cfg(feature = "pem")] +use ed25519::PublicKeyBytes; + +#[cfg(all(feature = "pem", feature = "pkcs8"))] +use der::pem::LineEnding; +#[cfg(feature = "pkcs8")] +use pkcs8::der::SecretDocument; +#[cfg(feature = "pkcs8")] +use pkcs8::{ + DecodePrivateKey, DecodePublicKey, Document, EncodePrivateKey, EncodePublicKey, + ObjectIdentifier, PrivateKeyInfo, spki::AlgorithmIdentifierRef, +}; +#[cfg(all(feature = "pem", feature = "pkcs8"))] +use zeroize::Zeroizing; + +#[cfg(all(feature = "pem", feature = "pkcs8"))] +use pkcs8::der::pem::PemLabel; + +use crate::{VerificationKey, VerificationKeyBytes}; + +/// The length of a ed25519 `SecretKey`, in bytes. +pub const SECRET_KEY_LENGTH: usize = 32; + +/// ed25519 secret key as defined in [RFC8032 § 5.1.5]: +/// +/// > The private key is 32 octets (256 bits, corresponding to b) of +/// > cryptographically secure random data. +/// +/// [RFC8032 § 5.1.5]: https://www.rfc-editor.org/rfc/rfc8032#section-5.1.5 +pub type SecretKey = [u8; SECRET_KEY_LENGTH]; + +/// An Ed25519 signing key. +/// +/// This is also called a secret key by other implementations. +#[derive(Copy, Clone, Zeroize)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(from = "SerdeHelper"))] +#[cfg_attr(feature = "serde", serde(into = "SerdeHelper"))] +pub struct SigningKey { + seed: SecretKey, + s: Scalar, + prefix: [u8; 32], + vk: VerificationKey, +} + +impl core::fmt::Debug for SigningKey { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + fmt.debug_struct("SigningKey") + .field("vk", &self.vk) + .finish() + } +} + +impl<'a> From<&'a SigningKey> for VerificationKey { + fn from(sk: &'a SigningKey) -> VerificationKey { + sk.vk + } +} + +impl<'a> From<&'a SigningKey> for VerificationKeyBytes { + fn from(sk: &'a SigningKey) -> VerificationKeyBytes { + sk.vk.into() + } +} + +impl AsRef<[u8]> for SigningKey { + fn as_ref(&self) -> &[u8] { + &self.seed[..] + } +} + +impl From for SecretKey { + fn from(sk: SigningKey) -> SecretKey { + sk.seed + } +} + +impl TryFrom<&[u8]> for SigningKey { + type Error = Error; + fn try_from(slice: &[u8]) -> Result { + if slice.len() == 32 { + let mut bytes = [0u8; 32]; + bytes[..].copy_from_slice(slice); + Ok(bytes.into()) + } else { + Err(Self::Error::InvalidSliceLength) + } + } +} + +impl From for SigningKey { + #[allow(non_snake_case)] + fn from(seed: [u8; 32]) -> SigningKey { + // Expand the seed to a 64-byte array with SHA512. + let h = Sha512::digest(&seed[..]); + + // Convert the low half to a scalar with Ed25519 "clamping" + let s = { + let mut scalar_bytes = [0u8; 32]; + scalar_bytes[..].copy_from_slice(&h[0..32]); + scalar_bytes[0] &= 248; + scalar_bytes[31] &= 127; + scalar_bytes[31] |= 64; + Scalar::from_bytes_mod_order(scalar_bytes) + }; + + // Extract and cache the high half. + let prefix = { + let mut prefix = [0u8; 32]; + prefix[..].copy_from_slice(&h[32..64]); + prefix + }; + + // Compute the public key as A = [s]B. + let A = &s * constants::ED25519_BASEPOINT_TABLE; + + SigningKey { + seed, + s, + prefix, + vk: VerificationKey { + minus_A: -A, + A_bytes: VerificationKeyBytes(A.compress().to_bytes()), + }, + } + } +} + +impl ConstantTimeEq for SigningKey { + fn ct_eq(&self, other: &Self) -> subtle::Choice { + self.seed.ct_eq(&other.seed) + } +} + +impl PartialEq for SigningKey { + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} + +impl Eq for SigningKey {} + +#[cfg(feature = "pkcs8")] +impl<'a> TryFrom> for SigningKey { + type Error = Error; + fn try_from(pki: PrivateKeyInfo) -> Result { + if pki.algorithm == ALGORITHM_ID { + SigningKey::try_from(pki.private_key) + } else { + Err(Self::Error::MalformedSecretKey) + } + } +} + +#[cfg(feature = "pkcs8")] +impl EncodePublicKey for SigningKey { + /// Serialize the public key for a [`SigningKey`] to an ASN.1 DER-encoded document. + fn to_public_key_der(&self) -> pkcs8::spki::Result { + self.vk.to_public_key_der() + } +} + +impl Signer for SigningKey { + /// Generate a [`Signature`] using a given [`SigningKey`]. + fn try_sign(&self, message: &[u8]) -> Result { + Ok(self.sign(message)) + } +} + +#[cfg(feature = "pkcs8")] +impl TryFrom for SigningKey { + type Error = pkcs8::Error; + + fn try_from(pkcs8_key: KeypairBytes) -> pkcs8::Result { + SigningKey::try_from(&pkcs8_key) + } +} + +#[cfg(feature = "pkcs8")] +impl TryFrom<&KeypairBytes> for SigningKey { + type Error = pkcs8::Error; + + fn try_from(pkcs8_key: &KeypairBytes) -> pkcs8::Result { + let signing_key = SigningKey::from_der(&pkcs8_key.secret_key); + + // Validate the public key in the PKCS#8 document if present + if let Some(public_bytes) = &pkcs8_key.public_key { + let expected_verifying_key = + VerificationKey::from_public_key_der(public_bytes.as_ref()) + .map_err(|_| pkcs8::Error::KeyMalformed)?; + + if VerificationKey::from(&signing_key.unwrap()).A_bytes != expected_verifying_key.into() + { + return Err(pkcs8::Error::KeyMalformed); + } + } + + signing_key + } +} + +#[cfg(feature = "pem")] +impl From for KeypairBytes { + fn from(signing_key: SigningKey) -> KeypairBytes { + KeypairBytes::from(&signing_key) + } +} + +#[cfg(feature = "pem")] +impl From<&SigningKey> for KeypairBytes { + fn from(signing_key: &SigningKey) -> KeypairBytes { + KeypairBytes { + secret_key: signing_key.seed, + public_key: Some(PublicKeyBytes(signing_key.vk.into())), + } + } +} + +#[cfg(feature = "pkcs8")] +impl EncodePrivateKey for SigningKey { + /// Serialize [`SigningKey`] to an ASN.1 DER-encoded secret document. Note that this + /// will generate a v2 (RFC 5958) DER encoding with a public key. + fn to_pkcs8_der(&self) -> pkcs8::Result { + // In RFC 8410, the octet string containing the private key is encapsulated by + // another octet string. Just add octet string bytes to the key when building + // the document. + let mut final_key = [0u8; 34]; + final_key[..2].copy_from_slice(&[0x04, 0x20]); + final_key[2..].copy_from_slice(&self.seed); + SecretDocument::try_from(PrivateKeyInfo { + algorithm: ALGORITHM_ID, + private_key: &final_key, + public_key: Some(self.vk.A_bytes.0.as_slice()), + }) + } +} + +#[cfg(feature = "pkcs8")] +impl DecodePrivateKey for SigningKey { + /// Create a [`SigningKey`] from an ASN.1 DER-encoded bytes. The bytes may include an + /// accompanying public key, as defined in RFC 5958 (v1 and v2), but the call will + /// fail if the public key doesn't match the private key's true accompanying public + /// key. + fn from_pkcs8_der(bytes: &[u8]) -> pkcs8::Result { + let keypair = KeypairBytes::from_pkcs8_der(bytes).unwrap(); + let sk = SigningKey::from(keypair.secret_key); + match keypair.public_key { + Some(vk2) => { + if sk.vk.A_bytes.0 == vk2.to_bytes() { + Ok(sk) + } else { + Err(pkcs8::Error::KeyMalformed) + } + } + None => Ok(sk), + } + } +} + +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +struct SerdeHelper([u8; 32]); + +impl From for SigningKey { + fn from(helper: SerdeHelper) -> SigningKey { + helper.0.into() + } +} + +impl From for SerdeHelper { + fn from(sk: SigningKey) -> Self { + Self(sk.into()) + } +} + +impl SigningKey { + /// Construct a [`SigningKey`] from a `SecretKey` + /// + #[inline] + pub fn from_bytes(secret_key: &SecretKey) -> Self { + (*secret_key).into() + } + + /// Convert this [`SigningKey`] into a `SecretKey` + #[inline] + pub fn to_bytes(&self) -> SecretKey { + (*self).into() + } + + /// Convert this [`SigningKey`] into a `SecretKey` reference + #[inline] + pub fn as_bytes(&self) -> &SecretKey { + &self.seed + } + + /// Generate a new signing key. + pub fn new(mut rng: R) -> SigningKey { + let mut bytes = [0u8; 32]; + rng.fill_bytes(&mut bytes[..]); + bytes.into() + } + + /// Get the [`VerificationKey`] for this [`SigningKey`]. + pub fn verification_key(&self) -> VerificationKey { + self.into() + } + + /// Create a signature on `msg` using this key. + #[allow(non_snake_case)] + pub fn sign(&self, msg: &[u8]) -> Signature { + let r = Scalar::from_hash(Sha512::default().chain(&self.prefix[..]).chain(msg)); + + let R_bytes = (&r * constants::ED25519_BASEPOINT_TABLE) + .compress() + .to_bytes(); + + let k = Scalar::from_hash( + Sha512::default() + .chain(&R_bytes[..]) + .chain(&self.vk.A_bytes.0[..]) + .chain(msg), + ); + + let s_bytes = (r + k * self.s).to_bytes(); + + Signature::from_components(R_bytes, s_bytes) + } + + /// Parse [`SigningKey`] from ASN.1 DER bytes. + #[cfg(feature = "pkcs8")] + pub fn from_der(bytes: &[u8]) -> pkcs8::Result { + bytes + .try_into() + .map_err(|_| pkcs8::Error::ParametersMalformed) + } + + /// Serialize [`SigningKey`] to an ASN.1 DER-encoded secret document. Note that this + /// will generate a v1 (RFC 5958) DER encoding without a public key. + #[cfg(feature = "pkcs8")] + pub fn to_pkcs8_der_v1(&self) -> pkcs8::Result { + // In RFC 8410, the octet string containing the private key is encapsulated by + // another octet string. Just add octet string bytes to the key when building + // the document. + let mut final_key = [0u8; 34]; + final_key[..2].copy_from_slice(&[0x04, 0x20]); + final_key[2..].copy_from_slice(&self.seed); + SecretDocument::try_from(PrivateKeyInfo::new(ALGORITHM_ID, &final_key)) + } + + /// Serialize [`SigningKey`] as a PEM-encoded PKCS#8 string. Note that this + /// will generate a v1 (RFC 5958) PEM encoding without a public key. + #[cfg(all(feature = "pem", feature = "pkcs8"))] + pub fn to_pkcs8_pem_v1( + &self, + line_ending: LineEnding, + ) -> Result, pkcs8::Error> { + let doc = self.to_pkcs8_der_v1()?; + Ok(doc.to_pem(PrivateKeyInfo::PEM_LABEL, line_ending)?) + } +} diff --git a/ed25519-heea/src/verification_key.rs b/ed25519-heea/src/verification_key.rs new file mode 100644 index 0000000..9b37e21 --- /dev/null +++ b/ed25519-heea/src/verification_key.rs @@ -0,0 +1,360 @@ +// -*- mode: rust; -*- +// +// This file is part of ed25519-heea, a fork of ed25519-zebra. +// Original ed25519-zebra code: Copyright (c) Zcash Foundation contributors +// Modifications for HEEA: Copyright (c) 2025 curve25519-sol contributors +// See LICENSE-APACHE and LICENSE-MIT for licensing information. +// +// Modifications from ed25519-zebra: +// - Added `verify_heea`, an accelerated verification path using the HEEA +// scalar decomposition from curve25519-sol's `HEEADecomposition` trait. +// See "Accelerating EdDSA Signature Verification with Faster Scalar Size +// Halving" (TCHES 2025) for the algorithm. +// - `verify` and all ZIP-215 consensus logic are unchanged from ed25519-zebra. + +use core::convert::{TryFrom, TryInto}; +use curve25519::{ + edwards::{CompressedEdwardsY, EdwardsPoint}, + scalar::Scalar, + traits::{HEEADecomposition, IsIdentity}, +}; +use sha2::{Sha512, digest::Update}; +use zeroize::DefaultIsZeroes; + +use ed25519::{Signature, signature::Verifier}; + +#[cfg(feature = "pkcs8")] +use pkcs8::der::asn1::BitStringRef; +#[cfg(feature = "pkcs8")] +use pkcs8::spki::{ + AlgorithmIdentifierRef, DecodePublicKey, EncodePublicKey, SubjectPublicKeyInfoRef, +}; +#[cfg(feature = "pkcs8")] +use pkcs8::{Document, ObjectIdentifier}; + +use crate::Error; + +/// The length of an ed25519 `VerificationKey`, in bytes. +pub const VERIFICATION_KEY_LENGTH: usize = 32; + +/// A refinement type for `[u8; 32]` indicating that the bytes represent an +/// encoding of an Ed25519 verification key. +/// +/// This is useful for representing an encoded verification key, while the +/// [`VerificationKey`] type in this library caches other decoded state used in +/// signature verification. +/// +/// A `VerificationKeyBytes` can be used to verify a single signature using the +/// following idiom: +/// ``` +/// use core::convert::TryFrom; +/// # use rand::thread_rng; +/// # use ed25519_heea::*; +/// # let msg = b"Zcash"; +/// # let sk = SigningKey::new(thread_rng()); +/// # let sig = sk.sign(msg); +/// # let vk_bytes = VerificationKeyBytes::from(&sk); +/// VerificationKey::try_from(vk_bytes) +/// .and_then(|vk| vk.verify(&sig, msg)); +/// ``` +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct VerificationKeyBytes(pub(crate) [u8; VERIFICATION_KEY_LENGTH]); + +impl core::fmt::Debug for VerificationKeyBytes { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + fmt.debug_tuple("VerificationKeyBytes") + .field(&self.0) + .finish() + } +} + +impl AsRef<[u8]> for VerificationKeyBytes { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } +} + +impl TryFrom<&[u8]> for VerificationKeyBytes { + type Error = Error; + fn try_from(slice: &[u8]) -> Result { + if slice.len() == 32 { + let mut bytes = [0u8; 32]; + bytes[..].copy_from_slice(slice); + Ok(bytes.into()) + } else { + Err(Error::InvalidSliceLength) + } + } +} + +impl From<[u8; 32]> for VerificationKeyBytes { + fn from(bytes: [u8; 32]) -> VerificationKeyBytes { + VerificationKeyBytes(bytes) + } +} + +impl From for [u8; 32] { + fn from(refined: VerificationKeyBytes) -> [u8; 32] { + refined.0 + } +} + +#[cfg(feature = "pkcs8")] +impl<'a> TryFrom> for VerificationKeyBytes { + type Error = Error; + + fn try_from(spki: SubjectPublicKeyInfoRef) -> Result { + Ok(VerificationKeyBytes::try_from(spki.subject_public_key.as_bytes().unwrap()).unwrap()) + } +} + +/// A valid Ed25519 verification key. +/// +/// This is also called a public key by other implementations. +/// +/// This type holds decompressed state used in signature verification; if the +/// verification key may not be used immediately, it is probably better to use +/// [`VerificationKeyBytes`], which is a refinement type for `[u8; 32]`. +/// +/// ## Zcash-specific consensus properties +/// +/// Ed25519 checks are described in [§5.4.5][ps] of the Zcash protocol specification and in +/// [ZIP 215]. The verification criteria for an (encoded) verification key `A_bytes` are: +/// +/// * `A_bytes` MUST be an encoding of a point `A` on the twisted Edwards form of +/// Curve25519, and non-canonical encodings MUST be accepted; +/// +/// [ps]: https://zips.z.cash/protocol/protocol.pdf#concreteed25519 +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(try_from = "VerificationKeyBytes"))] +#[cfg_attr(feature = "serde", serde(into = "VerificationKeyBytes"))] +#[allow(non_snake_case)] +pub struct VerificationKey { + pub(crate) A_bytes: VerificationKeyBytes, + pub(crate) minus_A: EdwardsPoint, +} + +impl From for VerificationKeyBytes { + fn from(vk: VerificationKey) -> VerificationKeyBytes { + vk.A_bytes + } +} + +impl AsRef<[u8]> for VerificationKey { + fn as_ref(&self) -> &[u8] { + &self.A_bytes.0[..] + } +} + +impl Default for VerificationKey { + fn default() -> VerificationKey { + let identity: EdwardsPoint = Default::default(); + let identity_bytes = identity.compress().to_bytes(); + + VerificationKey { + A_bytes: VerificationKeyBytes::from(identity_bytes), + minus_A: -identity, + } + } +} + +impl DefaultIsZeroes for VerificationKey {} + +impl From for [u8; 32] { + fn from(vk: VerificationKey) -> [u8; 32] { + vk.A_bytes.0 + } +} + +impl TryFrom for VerificationKey { + type Error = Error; + #[allow(non_snake_case)] + fn try_from(bytes: VerificationKeyBytes) -> Result { + // * `A_bytes` and `R_bytes` MUST be encodings of points `A` and `R` respectively on the + // twisted Edwards form of Curve25519, and non-canonical encodings MUST be accepted; + let A = CompressedEdwardsY(bytes.0) + .decompress() + .ok_or(Error::MalformedPublicKey)?; + + Ok(VerificationKey { + A_bytes: bytes, + minus_A: -A, + }) + } +} + +impl TryFrom<&[u8]> for VerificationKey { + type Error = Error; + fn try_from(slice: &[u8]) -> Result { + VerificationKeyBytes::try_from(slice).and_then(|vkb| vkb.try_into()) + } +} + +impl TryFrom<[u8; 32]> for VerificationKey { + type Error = Error; + fn try_from(bytes: [u8; 32]) -> Result { + VerificationKeyBytes::from(bytes).try_into() + } +} + +#[cfg(feature = "pkcs8")] +impl EncodePublicKey for VerificationKey { + /// Serialize [`VerificationKey`] to an ASN.1 DER-encoded document. + fn to_public_key_der(&self) -> pkcs8::spki::Result { + let alg_info = AlgorithmIdentifierRef { + oid: ObjectIdentifier::new_unwrap("1.3.101.112"), // RFC 8410 + parameters: None, + }; + SubjectPublicKeyInfoRef { + algorithm: alg_info, + subject_public_key: BitStringRef::from_bytes(&self.A_bytes.0[..])?, + } + .try_into() + } +} + +#[cfg(feature = "pkcs8")] +impl DecodePublicKey for VerificationKey { + /// Deserialize [`VerificationKey`] from ASN.1 DER bytes (32 bytes). + fn from_public_key_der(bytes: &[u8]) -> Result { + let spki = SubjectPublicKeyInfoRef::try_from(bytes).unwrap(); + let pk_bytes = spki.subject_public_key.as_bytes().unwrap(); + Ok(Self::try_from(pk_bytes).unwrap()) + } +} + +impl Verifier for VerificationKey { + /// Verify a [`Signature`] object against a given [`VerificationKey`]. + fn verify( + &self, + message: &[u8], + signature: &Signature, + ) -> Result<(), ed25519::signature::Error> { + self.verify(signature, message) + .map_err(|_| ed25519::signature::Error::new()) + } +} + +impl VerificationKey { + /// Verify a purported `signature` on the given `msg`. + /// + /// ## Zcash-specific consensus properties + /// + /// Ed25519 checks are described in [§5.4.5][ps] of the Zcash protocol specification and in + /// [ZIP215]. The verification criteria for an (encoded) signature `(R_bytes, s_bytes)` with + /// (encoded) verification key `A_bytes` are: + /// + /// * `A_bytes` and `R_bytes` MUST be encodings of points `A` and `R` respectively on the + /// twisted Edwards form of Curve25519, and non-canonical encodings MUST be accepted; + /// + /// * `s_bytes` MUST represent an integer `s` less than `l`, the order of the prime-order + /// subgroup of Curve25519; + /// + /// * the verification equation `[8][s]B = [8]R + [8][k]A` MUST be satisfied; + /// + /// * the alternate verification equation `[s]B = R + [k]A`, allowed by RFC 8032, MUST NOT be + /// used. + /// + /// [ps]: https://zips.z.cash/protocol/protocol.pdf#concreteed25519 + /// [ZIP215]: https://zips.z.cash/zip-0215 + pub fn verify(&self, signature: &Signature, msg: &[u8]) -> Result<(), Error> { + let k = Scalar::from_hash( + Sha512::default() + .chain(&signature.r_bytes()[..]) + .chain(&self.A_bytes.0[..]) + .chain(msg), + ); + self.verify_prehashed(signature, k) + } + + /// Verify a signature using the heea half-size scalar optimization. + /// + /// This implements the algorithm from "Accelerating EdDSA Signature Verification + /// with Faster Scalar Size Halving" (TCHES 2025). + /// + /// The standard verification equation sB = R + hA is transformed to: + /// τsB = τR + ρA where ρ ≡ τh (mod ℓ) + /// + /// Both ρ and τ are approximately half the size of h. + /// + /// We then decompose τs into two 128-bit scalars: + /// τs = τs_hi * 2^128 + τs_lo + /// + /// The verification equation becomes: + /// τs_lo B + τs_hi (2^128 B) = τR + ρA + /// which can be done via 4-variable MSM with half-size scalars. + #[allow(non_snake_case)] + pub fn verify_heea(&self, signature: &Signature, msg: &[u8]) -> Result<(), Error> { + // Compute the hash scalar h (called k in the standard implementation) + let h = Scalar::from_hash( + Sha512::default() + .chain(&signature.r_bytes()[..]) + .chain(&self.A_bytes.0[..]) + .chain(msg), + ); + + // Generate half-size scalars ρ and τ such that ρ ≡ τh (mod ℓ) + // in order to have rho and tau approximately half the size of h + // it is possible that we compute ρ ≡ -τh (mod ℓ) + // this is indicated by `flip_h` flag being true, + // in which case we will need to negate A later + // let (rho, tau, flip_h) = crate::heea::generate_half_size_scalars(&h); + let (rho, tau, flip_h) = h.heea_decompose(); + + // Extract s from the signature + let s = Option::::from(Scalar::from_canonical_bytes(*signature.s_bytes())) + .ok_or(Error::InvalidSignature)?; + + // Decode R from the signature + let neg_R = -CompressedEdwardsY(*signature.r_bytes()) + .decompress() + .ok_or(Error::InvalidSignature)?; + + // Standard verification checks: sB = R + hA + // Transformed verification: -τsB + τR + ρA == 0 + // + // We verify: + // [8] τs B + [8] τ (-R) + [8] ρ (-A) == 0 + + // Compute τs + let ts = tau * s; + let A = if flip_h { -self.minus_A } else { self.minus_A }; + // Compute the multi-scalar multiplication + let result = EdwardsPoint::vartime_triple_scalar_mul_basepoint(&tau, &neg_R, &rho, &A, &ts); + + // Check if [8] τs B + [8] τ (-R) + [8] ρ (-A) == 0 + if result.mul_by_cofactor().is_identity() { + Ok(()) + } else { + Err(Error::InvalidSignature) + } + } + + /// Verify a signature with a prehashed `k` value. Note that this is not the + /// same as "prehashing" in RFC8032. + #[allow(non_snake_case)] + pub(crate) fn verify_prehashed(&self, signature: &Signature, k: Scalar) -> Result<(), Error> { + // `s_bytes` MUST represent an integer less than the prime `l`. + let s = Option::::from(Scalar::from_canonical_bytes(*signature.s_bytes())) + .ok_or(Error::InvalidSignature)?; + // `R_bytes` MUST be an encoding of a point on the twisted Edwards form of Curve25519. + let R = CompressedEdwardsY(*signature.r_bytes()) + .decompress() + .ok_or(Error::InvalidSignature)?; + // We checked the encoding of A_bytes when constructing `self`. + + // [8][s]B = [8]R + [8][k]A + // <=> [8]R = [8][s]B - [8][k]A + // <=> 0 = [8](R - ([s]B - [k]A)) + // <=> 0 = [8](R - R') where R' = [s]B - [k]A + let R_prime = EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &self.minus_A, &s); + + if (R - R_prime).mul_by_cofactor().is_identity() { + Ok(()) + } else { + Err(Error::InvalidSignature) + } + } +} diff --git a/ed25519-heea/tests/batch.rs b/ed25519-heea/tests/batch.rs new file mode 100644 index 0000000..eb06840 --- /dev/null +++ b/ed25519-heea/tests/batch.rs @@ -0,0 +1,46 @@ +#![cfg(feature = "alloc")] + +use rand::thread_rng; + +use ed25519_heea::*; + +#[test] +fn batch_verify() { + let mut batch = batch::Verifier::new(); + for _ in 0..32 { + let sk = SigningKey::new(thread_rng()); + let pk_bytes = VerificationKeyBytes::from(&sk); + let msg = b"BatchVerifyTest"; + let sig = sk.sign(&msg[..]); + batch.queue((pk_bytes, sig, msg)); + } + assert!(batch.verify(thread_rng()).is_ok()); +} + +#[test] +fn batch_verify_with_one_bad_sig() { + let bad_index = 10; + let mut batch = batch::Verifier::new(); + let mut items = Vec::new(); + for i in 0..32 { + let sk = SigningKey::new(thread_rng()); + let pk_bytes = VerificationKeyBytes::from(&sk); + let msg = b"BatchVerifyTest"; + let sig = if i != bad_index { + sk.sign(&msg[..]) + } else { + sk.sign(b"badmsg") + }; + let item: batch::Item = (pk_bytes, sig, msg).into(); + items.push(item.clone()); + batch.queue(item); + } + assert!(batch.verify(thread_rng()).is_err()); + for (i, item) in items.drain(..).enumerate() { + if i != bad_index { + assert!(item.verify_single().is_ok()); + } else { + assert!(item.verify_single().is_err()); + } + } +} diff --git a/ed25519-heea/tests/decoding.rs b/ed25519-heea/tests/decoding.rs new file mode 100644 index 0000000..acf23c3 --- /dev/null +++ b/ed25519-heea/tests/decoding.rs @@ -0,0 +1,87 @@ +#[cfg(any(feature = "pem", feature = "pkcs8"))] +use ed25519_heea::*; + +#[cfg(feature = "pkcs8")] +use pkcs8::{DecodePrivateKey, DecodePublicKey}; + +/// Ed25519 PKCS#8 v1 private key encoded as ASN.1 DER. +#[cfg(feature = "pkcs8")] +const PKCS8_V1_DER: &[u8] = include_bytes!("examples/pkcs8-v1.der"); + +/// Ed25519 PKCS#8 v1 private key encoded as PEM. +#[cfg(feature = "pem")] +const PKCS8_V1_PEM: &str = include_str!("examples/pkcs8-v1.pem"); + +/// Ed25519 PKCS#8 v2 private key + public key encoded as ASN.1 DER. +#[cfg(feature = "pkcs8")] +const PKCS8_V2_DER: &[u8] = include_bytes!("examples/pkcs8-v2.der"); + +/// Ed25519 PKCS#8 v1 private key encoded as PEM. +#[cfg(feature = "pem")] +const PKCS8_V2_PEM: &str = include_str!("examples/pkcs8-v2.pem"); + +/// Ed25519 PKCS#8 v2 private key + mismatched public key encoded as ASN.1 DER. +#[cfg(feature = "pkcs8")] +const PKCS8_V2_DER_BAD: &[u8] = include_bytes!("examples/pkcs8-v2-bad-ver-key.der"); + +/// Ed25519 PKCS#8 v2 private key + mismatched public key encoded as PEM. +#[cfg(feature = "pem")] +const PKCS8_V2_PEM_BAD: &str = include_str!("examples/pkcs8-v2-bad-ver-key.pem"); + +/// Ed25519 SubjectPublicKeyInfo encoded as ASN.1 DER. +#[cfg(feature = "pkcs8")] +const PUBLIC_KEY_DER: &[u8] = include_bytes!("examples/pubkey.der"); + +/// Ed25519 SubjectPublicKeyInfo encoded as PEM. +#[cfg(feature = "pem")] +const PUBLIC_KEY_PEM: &str = include_str!("examples/pubkey.pem"); + +#[test] +#[cfg(feature = "pkcs8")] +fn decode_der_to_signing_key() { + // Test against a v1 DER key. + let sk1 = SigningKey::from_pkcs8_der(PKCS8_V1_DER).unwrap(); + let sk_bytes_string_1 = "D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F44755842"; + assert_eq!(hex::decode(sk_bytes_string_1).unwrap(), sk1.as_ref()); + + // Test against a v2 DER key. + let sk2 = SigningKey::from_pkcs8_der(PKCS8_V2_DER).unwrap(); + let sk_bytes_string_2 = "D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F44755842"; + assert_eq!(hex::decode(sk_bytes_string_2).unwrap(), sk2.as_ref()); + + // Test against a v2 DER key with a mismatched public key. + assert!(SigningKey::from_pkcs8_der(PKCS8_V2_DER_BAD).is_err()); +} + +#[test] +#[cfg(feature = "pem")] +fn decode_doc_to_signing_key() { + // Test against a v1 PEM key. + let sk1 = SigningKey::from_pkcs8_pem(PKCS8_V1_PEM).unwrap(); + let sk_bytes_string_1 = "D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F44755842"; + assert_eq!(hex::decode(sk_bytes_string_1).unwrap(), sk1.as_ref()); + + // Test against a valid v2 PEM key. + let sk2 = SigningKey::from_pkcs8_pem(PKCS8_V2_PEM).unwrap(); + let sk_bytes_string_2 = "D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F44755842"; + assert_eq!(hex::decode(sk_bytes_string_2).unwrap(), sk2.as_ref()); + + // Test against a v2 DER key with a mismatched public key. + assert!(SigningKey::from_pkcs8_pem(PKCS8_V2_PEM_BAD).is_err()); +} + +#[test] +#[cfg(feature = "pkcs8")] +fn decode_der_to_verification_key() { + let vk = VerificationKey::from_public_key_der(PUBLIC_KEY_DER).unwrap(); + let vk_bytes_string = "19bf44096984cdfe8541bac167dc3b96c85086aa30b6b6cb0c5c38ad703166e1"; + assert_eq!(hex::decode(vk_bytes_string).unwrap(), vk.as_ref()); +} + +#[test] +#[cfg(feature = "pem")] +fn decode_doc_to_verification_key() { + let vk = VerificationKey::from_public_key_pem(PUBLIC_KEY_PEM).unwrap(); + let vk_bytes_string = "19bf44096984cdfe8541bac167dc3b96c85086aa30b6b6cb0c5c38ad703166e1"; + assert_eq!(hex::decode(vk_bytes_string).unwrap(), vk.as_ref()); +} diff --git a/ed25519-heea/tests/encoding.rs b/ed25519-heea/tests/encoding.rs new file mode 100644 index 0000000..b1acb4a --- /dev/null +++ b/ed25519-heea/tests/encoding.rs @@ -0,0 +1,95 @@ +#[cfg(feature = "pem")] +use der::pem::LineEnding; +#[cfg(any(feature = "pem", feature = "pkcs8"))] +use ed25519_heea::*; +#[cfg(feature = "pkcs8")] +pub use pkcs8::{ + EncodePrivateKey, EncodePublicKey, ObjectIdentifier, PrivateKeyInfo, + spki::AlgorithmIdentifierRef, +}; +#[cfg(any(feature = "pem", feature = "pkcs8"))] +use std::convert::TryFrom; + +/// Ed25519 PKCS#8 v1 private key encoded as ASN.1 DER. +#[cfg(feature = "pkcs8")] +const PKCS8_V1_DER: &[u8] = include_bytes!("examples/pkcs8-v1.der"); + +/// Ed25519 PKCS#8 v1 private key encoded as PEM. +#[cfg(feature = "pem")] +const PKCS8_V1_PEM: &str = include_str!("examples/pkcs8-v1.pem"); + +/// Ed25519 PKCS#8 v2 private key + public key encoded as ASN.1 DER. +#[cfg(feature = "pkcs8")] +const PKCS8_V2_DER: &[u8] = include_bytes!("examples/pkcs8-v2.der"); + +/// Ed25519 PKCS#8 v1 private key encoded as PEM. +#[cfg(feature = "pem")] +const PKCS8_V2_PEM: &str = include_str!("examples/pkcs8-v2.pem"); + +/// Ed25519 SubjectPublicKeyInfo encoded as ASN.1 DER. +#[cfg(feature = "pkcs8")] +const PUBLIC_KEY_DER: &[u8] = include_bytes!("examples/pubkey.der"); + +/// Ed25519 SubjectPublicKeyInfo encoded as PEM. +#[cfg(feature = "pem")] +const PUBLIC_KEY_PEM: &str = include_str!("examples/pubkey.pem"); + +#[test] +#[cfg(feature = "pkcs8")] +fn encode_signing_key_to_der() { + let sk_bytes_string = "D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F44755842"; + let mut sk_array = [0u8; 32]; + hex::decode_to_slice(sk_bytes_string, &mut sk_array as &mut [u8]).ok(); + + let sk = SigningKey::from(sk_array); + let vk = sk.to_public_key_der().unwrap(); + assert_eq!(sk.to_pkcs8_der_v1().unwrap().as_bytes(), PKCS8_V1_DER); + assert_eq!(sk.to_pkcs8_der().unwrap().as_bytes(), PKCS8_V2_DER); + assert_eq!(vk.as_bytes(), PUBLIC_KEY_DER); +} + +#[test] +#[cfg(feature = "pem")] +fn encode_signing_key_to_pem() { + let sk_bytes_string = "D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F44755842"; + let mut sk_array = [0u8; 32]; + hex::decode_to_slice(sk_bytes_string, &mut sk_array as &mut [u8]).ok(); + + let sk = SigningKey::from(sk_array); + let vk = sk.to_public_key_pem(LineEnding::default()).unwrap(); + assert_eq!( + sk.to_pkcs8_pem_v1(LineEnding::default()) + .unwrap() + .as_bytes(), + PKCS8_V1_PEM.as_bytes() + ); + assert_eq!( + sk.to_pkcs8_pem(LineEnding::default()).unwrap().as_bytes(), + PKCS8_V2_PEM.as_bytes() + ); + assert_eq!(vk, PUBLIC_KEY_PEM); +} + +#[test] +#[cfg(feature = "pkcs8")] +fn encode_verification_key_to_der() { + let vk_bytes_string = "19bf44096984cdfe8541bac167dc3b96c85086aa30b6b6cb0c5c38ad703166e1"; + let mut vk_array = [0u8; 32]; + hex::decode_to_slice(vk_bytes_string, &mut vk_array as &mut [u8]).ok(); + + let vk = VerificationKey::try_from(vk_array).unwrap(); + let pkd = vk.to_public_key_der().unwrap(); + assert_eq!(pkd.as_ref(), PUBLIC_KEY_DER); +} + +#[test] +#[cfg(feature = "pem")] +fn encode_verification_key_to_pem() { + let vk_bytes_string = "19bf44096984cdfe8541bac167dc3b96c85086aa30b6b6cb0c5c38ad703166e1"; + let mut vk_array = [0u8; 32]; + hex::decode_to_slice(vk_bytes_string, &mut vk_array as &mut [u8]).ok(); + + let vk = VerificationKey::try_from(vk_array).unwrap(); + let pem = vk.to_public_key_pem(LineEnding::default()).unwrap(); + assert_eq!(pem, PUBLIC_KEY_PEM); +} diff --git a/ed25519-heea/tests/examples/pkcs8-v1.der b/ed25519-heea/tests/examples/pkcs8-v1.der new file mode 100644 index 0000000..cb780b3 Binary files /dev/null and b/ed25519-heea/tests/examples/pkcs8-v1.der differ diff --git a/ed25519-heea/tests/examples/pkcs8-v1.pem b/ed25519-heea/tests/examples/pkcs8-v1.pem new file mode 100644 index 0000000..e447080 --- /dev/null +++ b/ed25519-heea/tests/examples/pkcs8-v1.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC +-----END PRIVATE KEY----- diff --git a/ed25519-heea/tests/examples/pkcs8-v2-bad-ver-key.der b/ed25519-heea/tests/examples/pkcs8-v2-bad-ver-key.der new file mode 100644 index 0000000..6bba2fa Binary files /dev/null and b/ed25519-heea/tests/examples/pkcs8-v2-bad-ver-key.der differ diff --git a/ed25519-heea/tests/examples/pkcs8-v2-bad-ver-key.pem b/ed25519-heea/tests/examples/pkcs8-v2-bad-ver-key.pem new file mode 100644 index 0000000..088e0a0 --- /dev/null +++ b/ed25519-heea/tests/examples/pkcs8-v2-bad-ver-key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MHICAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC +oB8wHQYKKoZIhvcNAQkJFDEPDA1DdXJkbGUgQ2hhaXJzgSEAGb9ECWmEzf6FQbrB +Z9xLlshQhqowtrbLDFw4rXAxZuE= +-----END PRIVATE KEY----- diff --git a/ed25519-heea/tests/examples/pkcs8-v2.der b/ed25519-heea/tests/examples/pkcs8-v2.der new file mode 100644 index 0000000..da17cc0 Binary files /dev/null and b/ed25519-heea/tests/examples/pkcs8-v2.der differ diff --git a/ed25519-heea/tests/examples/pkcs8-v2.pem b/ed25519-heea/tests/examples/pkcs8-v2.pem new file mode 100644 index 0000000..fa15603 --- /dev/null +++ b/ed25519-heea/tests/examples/pkcs8-v2.pem @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MFECAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC +gSEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE= +-----END PRIVATE KEY----- diff --git a/ed25519-heea/tests/examples/pubkey.der b/ed25519-heea/tests/examples/pubkey.der new file mode 100644 index 0000000..d1002c4 Binary files /dev/null and b/ed25519-heea/tests/examples/pubkey.der differ diff --git a/ed25519-heea/tests/examples/pubkey.pem b/ed25519-heea/tests/examples/pubkey.pem new file mode 100644 index 0000000..41b0218 --- /dev/null +++ b/ed25519-heea/tests/examples/pubkey.pem @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE= +-----END PUBLIC KEY----- diff --git a/ed25519-heea/tests/heea.rs b/ed25519-heea/tests/heea.rs new file mode 100644 index 0000000..e87e8f9 --- /dev/null +++ b/ed25519-heea/tests/heea.rs @@ -0,0 +1,57 @@ +use ed25519_heea::SigningKey; +use ed25519_heea::VerificationKey; + +use rand::thread_rng; + +#[test] +fn test_verify_heea_invalid_signature() { + let mut rng = thread_rng(); + let signing_key = SigningKey::new(&mut rng); + let verification_key = VerificationKey::from(&signing_key); + + let msg = b"Original message"; + let signature = signing_key.sign(msg); + + // Try to verify with different message + let wrong_msg = b"Different message"; + + let result_standard = verification_key.verify(&signature, wrong_msg); + let result_heea = verification_key.verify_heea(&signature, wrong_msg); + + // Both should fail + assert!( + result_standard.is_err(), + "Standard verification should fail for wrong message" + ); + assert!( + result_heea.is_err(), + "heea verification should fail for wrong message" + ); +} + +#[test] +fn test_verify_heea_multiple_signatures() { + let mut rng = thread_rng(); + + for i in 0..100 { + let signing_key = SigningKey::new(&mut rng); + let verification_key = VerificationKey::from(&signing_key); + + let msg = format!("Message number {}", i); + let signature = signing_key.sign(msg.as_bytes()); + + let result_standard = verification_key.verify(&signature, msg.as_bytes()); + let result_heea = verification_key.verify_heea(&signature, msg.as_bytes()); + + assert!( + result_standard.is_ok(), + "Standard verification should succeed for signature {}", + i + ); + assert!( + result_heea.is_ok(), + "heea verification should succeed for signature {}", + i + ); + } +} diff --git a/ed25519-heea/tests/rfc8032.rs b/ed25519-heea/tests/rfc8032.rs new file mode 100644 index 0000000..d96a61b --- /dev/null +++ b/ed25519-heea/tests/rfc8032.rs @@ -0,0 +1,73 @@ +//! RFC 8032 test vectors. +//! +//! Note that RFC 8032 does not actually specify validation criteria for Ed25519, +//! so these are basic sanity checks, rather than the more detailed test vectors +//! in consensus.rs. +#![cfg(feature = "serde")] + +use ed25519::Signature; +use ed25519_heea::*; + +fn rfc8032_test_case(sk_bytes: Vec, pk_bytes: Vec, sig_bytes: Vec, msg: Vec) { + let sk: SigningKey = bincode::deserialize(&sk_bytes).expect("sk should deserialize"); + let pk: VerificationKey = bincode::deserialize(&pk_bytes).expect("pk should deserialize"); + let sig: Signature = + Signature::from_slice(sig_bytes.as_slice()).expect("sig should deserialize"); + + assert!(pk.verify(&sig, &msg).is_ok(), "verification failed"); + + let pk_from_sk = VerificationKey::from(&sk); + assert_eq!( + VerificationKeyBytes::from(pk), + VerificationKeyBytes::from(pk_from_sk), + "regenerated pubkey did not match test vector pubkey" + ); + + let sig_from_sk = sk.sign(&msg); + assert_eq!( + sig, sig_from_sk, + "regenerated signature did not match test vector" + ); +} + +#[test] +fn rfc8032_test_1() { + rfc8032_test_case( + hex::decode("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60") + .expect("hex should decode"), + hex::decode("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a") + .expect("hex should decode"), + hex::decode("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b") + .expect("hex should decode"), + hex::decode("") + .expect("hex should decode"), + ); +} + +#[test] +fn rfc8032_test_2() { + rfc8032_test_case( + hex::decode("4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb") + .expect("hex should decode"), + hex::decode("3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c") + .expect("hex should decode"), + hex::decode("92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00") + .expect("hex should decode"), + hex::decode("72") + .expect("hex should decode"), + ); +} + +#[test] +fn rfc8032_test_3() { + rfc8032_test_case( + hex::decode("c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7") + .expect("hex should decode"), + hex::decode("fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025") + .expect("hex should decode"), + hex::decode("6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a") + .expect("hex should decode"), + hex::decode("af82") + .expect("hex should decode"), + ); +} diff --git a/ed25519-heea/tests/small_order.rs b/ed25519-heea/tests/small_order.rs new file mode 100644 index 0000000..ed2c56f --- /dev/null +++ b/ed25519-heea/tests/small_order.rs @@ -0,0 +1,105 @@ +#![cfg(feature = "std")] + +use color_eyre::Report; +use curve25519::{ + constants::EIGHT_TORSION, digest::Update, edwards::CompressedEdwardsY, scalar::Scalar, + traits::IsIdentity, +}; +use once_cell::sync::Lazy; +use sha2::Sha512; + +mod util; +use util::TestCase; + +#[allow(non_snake_case)] +pub static SMALL_ORDER_SIGS: Lazy> = Lazy::new(|| { + let mut tests = Vec::new(); + let s = Scalar::ZERO; + + // Use all the canonical encodings of the 8-torsion points, + // and the low-order non-canonical encodings. + let encodings = EIGHT_TORSION + .iter() + .map(|point| point.compress().to_bytes()) + .chain(util::non_canonical_point_encodings().into_iter().take(6)) + .collect::>(); + + /* + for (i, e) in encodings.iter().enumerate() { + println!("{}: {}", i, hex::encode(e)); + } + */ + + for A_bytes in &encodings { + let A = CompressedEdwardsY(*A_bytes).decompress().unwrap(); + for R_bytes in &encodings { + let R = CompressedEdwardsY(*R_bytes).decompress().unwrap(); + let sig_bytes = { + let mut bytes = [0u8; 64]; + bytes[0..32].copy_from_slice(&R_bytes[..]); + bytes[32..64].copy_from_slice(s.as_bytes()); + bytes + }; + let vk_bytes = *A_bytes; + // The verification equation is [8][s]B = [8]R + [8][k]A. + // If R, A are torsion points the LHS is 0, setting s = 0 makes RHS 0. + let valid_zip215 = true; + // In the legacy equation the RHS is 0 and the LHS is R + [k]A. + // This will be valid only if: + // * A is not all zeros. + // * R is not an excluded point + // * R + [k]A = 0 + // * R is canonically encoded (because the check recomputes R) + let k = Scalar::from_hash( + Sha512::default() + .chain(&sig_bytes[0..32]) + .chain(vk_bytes) + .chain(b"Zcash"), + ); + let check = R + k * A; + let non_canonical_R = R.compress().as_bytes() != R_bytes; + let valid_legacy = !(vk_bytes == [0; 32] + || util::EXCLUDED_POINT_ENCODINGS.contains(R.compress().as_bytes()) + || !check.is_identity() + || non_canonical_R); + + tests.push(TestCase { + vk_bytes, + sig_bytes, + valid_legacy, + valid_zip215, + }) + } + } + tests +}); + +#[test] +fn conformance() -> Result<(), Report> { + for case in SMALL_ORDER_SIGS.iter() { + case.check()?; + } + println!("{:#?}", *SMALL_ORDER_SIGS); + Ok(()) +} + +#[cfg(feature = "alloc")] +#[test] +fn individual_matches_batch_verification() -> Result<(), Report> { + use core::convert::TryFrom; + use ed25519::Signature; + use ed25519_heea::{VerificationKey, VerificationKeyBytes, batch}; + + for case in SMALL_ORDER_SIGS.iter() { + let msg = b"Zcash"; + let sig = Signature::from(case.sig_bytes); + let vkb = VerificationKeyBytes::from(case.vk_bytes); + let individual_verification = + VerificationKey::try_from(vkb).and_then(|vk| vk.verify(&sig, msg)); + let mut bv = batch::Verifier::new(); + bv.queue((vkb, sig, msg)); + let batch_verification = bv.verify(rand::thread_rng()); + assert_eq!(individual_verification.is_ok(), batch_verification.is_ok()); + } + Ok(()) +} diff --git a/ed25519-heea/tests/unit_tests.rs b/ed25519-heea/tests/unit_tests.rs new file mode 100644 index 0000000..d1e8d7a --- /dev/null +++ b/ed25519-heea/tests/unit_tests.rs @@ -0,0 +1,50 @@ +#![cfg(feature = "serde")] + +use core::convert::TryFrom; + +use rand::thread_rng; + +use ed25519_heea::{Signature, SigningKey, VerificationKey, VerificationKeyBytes}; + +#[test] +fn parsing() { + let sk = SigningKey::new(thread_rng()); + let pk = VerificationKey::from(&sk); + let pkb = VerificationKeyBytes::from(&sk); + let sig = sk.sign(b"test"); + + let sk_array: [u8; 32] = sk.into(); + let pk_array: [u8; 32] = pk.into(); + let pkb_array: [u8; 32] = pkb.into(); + let sig_array: [u8; 64] = sig.into(); + + let sk2 = SigningKey::from(sk_array); + let pk2 = VerificationKey::try_from(pk_array).unwrap(); + let pkb2 = VerificationKeyBytes::from(pkb_array); + let sig2 = Signature::from(sig_array); + + assert_eq!(sk, sk2); + assert_eq!(pk, pk2); + assert_eq!(pkb, pkb2); + assert_eq!(sig, sig2); + + let sk3: SigningKey = bincode::deserialize(sk.as_ref()).unwrap(); + let pk3: VerificationKey = bincode::deserialize(pk.as_ref()).unwrap(); + let pkb3: VerificationKeyBytes = bincode::deserialize(pkb.as_ref()).unwrap(); + + assert_eq!(sk, sk3); + assert_eq!(pk, pk3); + assert_eq!(pkb, pkb3); +} + +#[test] +fn sign_and_verify() { + let sk = SigningKey::new(thread_rng()); + let pk = VerificationKey::from(&sk); + + let msg = b"ed25519-zebra test message"; + + let sig = sk.sign(&msg[..]); + + assert_eq!(pk.verify(&sig, &msg[..]), Ok(())) +} diff --git a/ed25519-heea/tests/util/mod.rs b/ed25519-heea/tests/util/mod.rs new file mode 100644 index 0000000..29842a9 --- /dev/null +++ b/ed25519-heea/tests/util/mod.rs @@ -0,0 +1,265 @@ +// functions are used in small_order but not recognized as such? +#![allow(dead_code)] +#![cfg(feature = "std")] + +use color_eyre::{Report, eyre::eyre}; +use curve25519::edwards::{CompressedEdwardsY, EdwardsPoint}; +use ed25519_heea as ed25519_heea_zip215; + +use core::convert::TryFrom; + +pub struct TestCase { + pub vk_bytes: [u8; 32], + pub sig_bytes: [u8; 64], + pub valid_legacy: bool, + pub valid_zip215: bool, +} + +impl core::fmt::Debug for TestCase { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + fmt.debug_struct("TestCase") + .field("vk_bytes", &hex::encode(&self.vk_bytes[..])) + .field("sig_bytes", &hex::encode(&self.sig_bytes[..])) + .field("valid_legacy", &self.valid_legacy) + .field("valid_zip215", &self.valid_zip215) + .finish() + } +} + +impl TestCase { + pub fn check(&self) -> Result<(), Report> { + match (self.valid_legacy, self.check_legacy()) { + (false, Err(_)) => Ok(()), + (true, Ok(())) => Ok(()), + (false, Ok(())) => Err(eyre!( + "legacy-invalid signature case validated under legacy rules" + )), + (true, Err(e)) => { + Err(e.wrap_err("legacy-valid signature case was rejected under legacy rules")) + } + }?; + match (self.valid_zip215, self.check_zip215()) { + (false, Err(_)) => Ok(()), + (true, Ok(())) => Ok(()), + (false, Ok(())) => Err(eyre!( + "zip215-invalid signature case validated under zip215 rules" + )), + (true, Err(e)) => { + Err(e.wrap_err("zip215-valid signature case was rejected under zip215 rules")) + } + } + } + + fn check_legacy(&self) -> Result<(), Report> { + use ed25519_zebra_legacy::{Signature, VerificationKey}; + let sig = Signature::from(self.sig_bytes); + VerificationKey::try_from(self.vk_bytes).and_then(|vk| vk.verify(&sig, b"Zcash"))?; + Ok(()) + } + + fn check_zip215(&self) -> Result<(), Report> { + use ed25519_heea_zip215::{Signature, VerificationKey}; + let sig = Signature::from(self.sig_bytes); + VerificationKey::try_from(self.vk_bytes).and_then(|vk| vk.verify(&sig, b"Zcash"))?; + Ok(()) + } +} + +pub fn non_canonical_field_encodings() -> Vec<[u8; 32]> { + // There are 19 finite field elements which can be represented + // non-canonically as x + p with x + p fitting in 255 bits: + let mut bytes = [ + 237, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 127, + ]; + let mut encodings = Vec::new(); + for i in 0..19u8 { + bytes[0] = 237 + i; + encodings.push(bytes); + } + encodings +} + +// Compute all 25 non-canonical point encodings. The first 5 are low order. +pub fn non_canonical_point_encodings() -> Vec<[u8; 32]> { + // Points are encoded by the y-coordinate and a bit indicating the + // sign of the x-coordinate. There are two ways to construct a + // non-canonical point encoding: + // + // (1) by using a non-canonical encoding of y (cf RFC8032§5.1.3.1) + // (2) by selecting y so that both sign choices give the same x. + // + // Condition (1) can occur only for 19 field elements that can be encoded + // non-canonically as y + p with y + p fitting in 255 bits. + // + // Condition (2) occurs if and only if x = -x, i.e., x = 0. + // The curve equation is ax^2 + y^2 = 1 + dx^2 + y^2 so x = 0 => y^2 = 1. + // This means y = 1 or y = -1. + // + // When y = -1, y can only be canonically encoded, so the encodings of (0,-1) are: + // * enc(-1) || 0 [canonical] + // * enc(-1) || 1 [non-canonical] + // + // When y = 1, y can be non-canonically encoded, so the encodings of (0,1) are: + // * enc(1) || 0 [canonical] + // * enc(1) || 1 [non-canonical] + // * enc(2^255 - 18) || 0 [non-canonical] + // * enc(2^255 - 18) || 1 [non-canonical] + // + // We pick up the latter two in generation of non-canonically encoded field elements, + // and construct the first two explicitly. + // + // RFC8032§5.1.3.4 requires implementations to perform a field element equality check + // on the x value computed inside the decompression routine and abort if x = 0 and + // the sign bit was set. However, no implementations do this, and any implementation + // that did would then be subtly incompatible with others in a new and different way. + // + // (This taxonomy was created with pointers from Sean Bowe and NCC Group). + let mut encodings = Vec::new(); + + // Canonical y with non-canonical sign bits. + let y1_noncanonical_sign_bit = [ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 128, + ]; + encodings.push(y1_noncanonical_sign_bit); + let ym1_noncanonical_sign_bit = [ + 236, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + ]; + encodings.push(ym1_noncanonical_sign_bit); + + // Run through non-canonical field elements. + // Not all field elements are x-coordinates of curve points, so check: + for mut x in non_canonical_field_encodings().into_iter() { + if CompressedEdwardsY(x).decompress().is_some() { + encodings.push(x); + } + x[31] |= 128; + if CompressedEdwardsY(x).decompress().is_some() { + encodings.push(x); + } + } + + // Check that all of the non-canonical points are really non-canonical + for &e in &encodings { + assert_ne!( + e, + CompressedEdwardsY(e) + .decompress() + .unwrap() + .compress() + .to_bytes() + ); + } + + encodings +} + +// Running this reveals that only the first 6 entries on the list have low order. +#[test] +fn print_non_canonical_points() { + for encoding in non_canonical_point_encodings().into_iter() { + let point = CompressedEdwardsY(encoding).decompress().unwrap(); + println!( + "encoding {} has order {}", + hex::encode(&encoding[..]), + order(point) + ); + } +} + +pub fn order(point: EdwardsPoint) -> &'static str { + use curve25519::traits::IsIdentity; + if point.is_small_order() { + let point2 = point + point; + let point4 = point2 + point2; + if point.is_identity() { + "1" + } else if point2.is_identity() { + "2" + } else if point4.is_identity() { + "4" + } else { + "8" + } + } else if point.is_torsion_free() { + "p" + } else { + ">p" + } +} + +#[test] +fn find_valid_excluded_encodings() { + for (i, encoding) in EXCLUDED_POINT_ENCODINGS.iter().enumerate() { + if let Some(point) = CompressedEdwardsY(*encoding).decompress() { + println!("index {} is valid point of order {}", i, order(point)); + } else { + println!("index {} is not a valid encoding", i); + } + } +} + +/// These point encodings were specifically blacklisted by libsodium 1.0.15, in +/// an apparent (and unsuccessful) attempt to exclude points of low order. +/// +/// To maintain exact compatibility with this version of libsodium, we encode +/// them here, following the Zcash protocol specification. +pub static EXCLUDED_POINT_ENCODINGS: [[u8; 32]; 11] = [ + [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + ], + [ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + ], + [ + 0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, + 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, + 0xfc, 0x05, + ], + [ + 0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, + 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, + 0x03, 0x7a, + ], + [ + 0x13, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, + 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, + 0xfc, 0x85, + ], + [ + 0xb4, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, + 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, + 0x03, 0xfa, + ], + [ + 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x7f, + ], + [ + 0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x7f, + ], + [ + 0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x7f, + ], + [ + 0xd9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, + ], + [ + 0xda, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, + ], +];