integrate heea (#4)

* Initial commit

* skeleton

* integrate heea

* ci

* fix nits

* Update signing_key.rs
This commit is contained in:
zz-sol 2026-03-23 08:18:33 -04:00 committed by GitHub
parent 5e70ebe267
commit e56e48a473
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 4345 additions and 63 deletions

550
Cargo.lock generated
View file

@ -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"

View file

@ -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::<sha2::Sha512>(&[&input], &[dst]);
let computed = EdwardsPoint::encode_to_curve::<sha2::Sha512>(&[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::<sha2::Sha512>(&[&input], &[dst]);
let computed = EdwardsPoint::hash_to_curve::<sha2::Sha512>(&[input], &[dst]);
assert_eq!(expected_output, computed, "Failed in test {}", index);
}

View file

@ -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()
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()
.unwrap(),
);
.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

136
ed25519-heea/CHANGELOG.md Normal file
View file

@ -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<Signature> 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.

1190
ed25519-heea/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,79 @@
[package]
name = "ed25519-heea"
version = "0.1.0"
rust-version = "1.85.0"
authors = [
"Henry de Valence <hdevalence@hdevalence.ca>",
"Zcash Foundation <zebra@zfnd.org>",
"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

View file

@ -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.

203
ed25519-heea/LICENSE-APACHE Normal file
View file

@ -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.

20
ed25519-heea/LICENSE-MIT Normal file
View file

@ -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.

139
ed25519-heea/README.md Normal file
View file

@ -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

View file

@ -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

View file

@ -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<Item = (VerificationKeyBytes, Signature)> {
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<Item = (VerificationKeyBytes, Signature)> {
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::<Vec<_>>();
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::<Vec<_>>();
#[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);

215
ed25519-heea/src/batch.rs Normal file
View file

@ -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<R: RngCore + CryptoRng>(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<VerificationKeyBytes, Vec<(Scalar, Signature)>>,
/// 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<I: Into<Item>>(&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<R: RngCore + CryptoRng>(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::<Scalar>::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)
}
}
}

30
ed25519-heea/src/error.rs Normal file
View file

@ -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 {}

View file

@ -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};

View file

@ -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<SigningKey> for SecretKey {
fn from(sk: SigningKey) -> SecretKey {
sk.seed
}
}
impl TryFrom<&[u8]> for SigningKey {
type Error = Error;
fn try_from(slice: &[u8]) -> Result<SigningKey, Self::Error> {
if slice.len() == 32 {
let mut bytes = [0u8; 32];
bytes[..].copy_from_slice(slice);
Ok(bytes.into())
} else {
Err(Self::Error::InvalidSliceLength)
}
}
}
impl From<SecretKey> 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<PrivateKeyInfo<'a>> for SigningKey {
type Error = Error;
fn try_from(pki: PrivateKeyInfo) -> Result<Self, Self::Error> {
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<Document> {
self.vk.to_public_key_der()
}
}
impl Signer<Signature> for SigningKey {
/// Generate a [`Signature`] using a given [`SigningKey`].
fn try_sign(&self, message: &[u8]) -> Result<Signature, ed25519::signature::Error> {
Ok(self.sign(message))
}
}
#[cfg(feature = "pkcs8")]
impl TryFrom<KeypairBytes> for SigningKey {
type Error = pkcs8::Error;
fn try_from(pkcs8_key: KeypairBytes) -> pkcs8::Result<Self> {
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<Self> {
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<SigningKey> 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<SecretDocument> {
// 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<Self> {
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<SerdeHelper> for SigningKey {
fn from(helper: SerdeHelper) -> SigningKey {
helper.0.into()
}
}
impl From<SigningKey> 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<R: RngCore + CryptoRng>(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<Self> {
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<SecretDocument> {
// 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<Zeroizing<String>, pkcs8::Error> {
let doc = self.to_pkcs8_der_v1()?;
Ok(doc.to_pem(PrivateKeyInfo::PEM_LABEL, line_ending)?)
}
}

View file

@ -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<VerificationKeyBytes, Self::Error> {
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<VerificationKeyBytes> for [u8; 32] {
fn from(refined: VerificationKeyBytes) -> [u8; 32] {
refined.0
}
}
#[cfg(feature = "pkcs8")]
impl<'a> TryFrom<SubjectPublicKeyInfoRef<'a>> for VerificationKeyBytes {
type Error = Error;
fn try_from(spki: SubjectPublicKeyInfoRef) -> Result<VerificationKeyBytes, Error> {
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<VerificationKey> 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<VerificationKey> for [u8; 32] {
fn from(vk: VerificationKey) -> [u8; 32] {
vk.A_bytes.0
}
}
impl TryFrom<VerificationKeyBytes> for VerificationKey {
type Error = Error;
#[allow(non_snake_case)]
fn try_from(bytes: VerificationKeyBytes) -> Result<Self, Self::Error> {
// * `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<VerificationKey, Error> {
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<Self, Self::Error> {
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<Document> {
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<Self, pkcs8::spki::Error> {
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<Signature> 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::<Scalar>::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::<Scalar>::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)
}
}
}

View file

@ -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());
}
}
}

View file

@ -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());
}

View file

@ -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);
}

Binary file not shown.

View file

@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
-----END PRIVATE KEY-----

Binary file not shown.

View file

@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MHICAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
oB8wHQYKKoZIhvcNAQkJFDEPDA1DdXJkbGUgQ2hhaXJzgSEAGb9ECWmEzf6FQbrB
Z9xLlshQhqowtrbLDFw4rXAxZuE=
-----END PRIVATE KEY-----

Binary file not shown.

View file

@ -0,0 +1,4 @@
-----BEGIN PRIVATE KEY-----
MFECAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
gSEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE=
-----END PRIVATE KEY-----

Binary file not shown.

View file

@ -0,0 +1,3 @@
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE=
-----END PUBLIC KEY-----

View file

@ -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
);
}
}

View file

@ -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<u8>, pk_bytes: Vec<u8>, sig_bytes: Vec<u8>, msg: Vec<u8>) {
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"),
);
}

View file

@ -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<Vec<TestCase>> = 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::<Vec<_>>();
/*
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(())
}

View file

@ -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(()))
}

View file

@ -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,
],
];