feat(xmss): Add XMSS (#1228)

This commit is contained in:
Michael Lodder
2026-03-03 17:42:27 +00:00
committed by GitHub
parent ee1f61db97
commit c2a39b803b
22 changed files with 5330 additions and 108 deletions
+41
View File
@@ -0,0 +1,41 @@
name: xmss
on:
pull_request:
paths:
- ".github/workflows/xmss.yml"
- "xmss/**"
- "Cargo.*"
push:
branches: master
defaults:
run:
working-directory: xmss
env:
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-Dwarnings"
# Cancels CI jobs when new commits are pushed to a PR branch
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- 1.85.0 # MSRV
- stable
steps:
- uses: actions/checkout@v6.0.2
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
- run: cargo build --benches
- run: cargo build --benches --all-features
- run: cargo test --release
- run: cargo test --all-features --release
- run: cargo test --no-default-features --release
Generated
+394 -108
View File
@@ -36,9 +36,18 @@ checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]]
name = "anyhow"
version = "1.0.100"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "atomic-polyfill"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
dependencies = [
"critical-section",
]
[[package]]
name = "autocfg"
@@ -75,9 +84,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "bitflags"
version = "2.10.0"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "blobby"
@@ -85,20 +94,35 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89af0b093cc13baa4e51e64e65ec2422f7e73aea0e612e5ad3872986671622f1"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "block-buffer"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be"
dependencies = [
"hybrid-array",
"hybrid-array 0.4.7",
]
[[package]]
name = "bumpalo"
version = "3.19.1"
version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cast"
@@ -157,25 +181,25 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea"
dependencies = [
"block-buffer",
"crypto-common",
"block-buffer 0.12.0",
"crypto-common 0.2.1",
"inout",
]
[[package]]
name = "clap"
version = "4.5.54"
version = "4.5.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394"
checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.54"
version = "4.5.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00"
checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
dependencies = [
"anstyle",
"clap_lex",
@@ -183,9 +207,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.7.7"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
[[package]]
name = "cmov"
@@ -193,6 +217,21 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de0758edba32d61d1fd9f4d69491b47604b91ee2f7e6b33de7e54ca4ebe55dc3"
[[package]]
name = "cobs"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
dependencies = [
"thiserror",
]
[[package]]
name = "const-oid"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "const-oid"
version = "0.10.2"
@@ -256,6 +295,12 @@ dependencies = [
"itertools",
]
[[package]]
name = "critical-section"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
@@ -296,7 +341,7 @@ dependencies = [
"cpubits",
"ctutils",
"getrandom 0.4.1",
"hybrid-array",
"hybrid-array 0.4.7",
"num-traits",
"rand_core 0.10.0",
"serdect",
@@ -304,6 +349,16 @@ dependencies = [
"zeroize",
]
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "crypto-common"
version = "0.2.1"
@@ -311,7 +366,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710"
dependencies = [
"getrandom 0.4.1",
"hybrid-array",
"hybrid-array 0.4.7",
"rand_core 0.10.0",
]
@@ -345,13 +400,23 @@ dependencies = [
"subtle",
]
[[package]]
name = "der"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
"const-oid 0.9.6",
"zeroize",
]
[[package]]
name = "der"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b"
dependencies = [
"const-oid",
"const-oid 0.10.2",
"der_derive",
"pem-rfc7468",
"zeroize",
@@ -368,6 +433,16 @@ dependencies = [
"syn",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer 0.10.4",
"crypto-common 0.1.7",
]
[[package]]
name = "digest"
version = "0.11.1"
@@ -375,9 +450,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "285743a676ccb6b3e116bc14cc69319b957867930ae9c4822f8e0f54509d7243"
dependencies = [
"blobby",
"block-buffer",
"const-oid",
"crypto-common",
"block-buffer 0.12.0",
"const-oid 0.10.2",
"crypto-common 0.2.1",
"ctutils",
]
@@ -388,18 +463,18 @@ dependencies = [
"chacha20",
"crypto-bigint",
"crypto-primes",
"der",
"digest",
"der 0.8.0",
"digest 0.11.1",
"getrandom 0.4.1",
"hex",
"hex-literal",
"pkcs8",
"pkcs8 0.11.0-rc.11",
"proptest",
"rand_core 0.10.0",
"rfc6979",
"sha1",
"sha2",
"signature",
"sha2 0.11.0-rc.5",
"signature 3.0.0-rc.10",
"zeroize",
]
@@ -407,15 +482,15 @@ dependencies = [
name = "ecdsa"
version = "0.17.0-rc.16"
dependencies = [
"der",
"digest",
"der 0.8.0",
"digest 0.11.1",
"elliptic-curve",
"hex-literal",
"rfc6979",
"serdect",
"sha2",
"signature",
"spki",
"sha2 0.11.0-rc.5",
"signature 3.0.0-rc.10",
"spki 0.8.0-rc.4",
"zeroize",
]
@@ -424,11 +499,11 @@ name = "ed25519"
version = "3.0.0-rc.4"
dependencies = [
"hex-literal",
"pkcs8",
"pkcs8 0.11.0-rc.11",
"rand_core 0.9.5",
"serde",
"serde_bytes",
"signature",
"signature 3.0.0-rc.10",
"zerocopy",
"zeroize",
]
@@ -438,10 +513,10 @@ name = "ed448"
version = "0.5.0-rc.5"
dependencies = [
"hex-literal",
"pkcs8",
"pkcs8 0.11.0-rc.11",
"serde",
"serde_bytes",
"signature",
"signature 3.0.0-rc.10",
"zeroize",
]
@@ -459,13 +534,13 @@ checksum = "bde7860544606d222fd6bd6d9f9a0773321bf78072a637e1d560a058c0031978"
dependencies = [
"base16ct",
"crypto-bigint",
"crypto-common",
"digest",
"crypto-common 0.2.1",
"digest 0.11.1",
"hex-literal",
"hybrid-array",
"hybrid-array 0.4.7",
"once_cell",
"pem-rfc7468",
"pkcs8",
"pkcs8 0.11.0-rc.11",
"rand_core 0.10.0",
"rustcrypto-ff",
"rustcrypto-group",
@@ -475,6 +550,18 @@ dependencies = [
"zeroize",
]
[[package]]
name = "embedded-io"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
[[package]]
name = "embedded-io"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -509,6 +596,16 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.3.4"
@@ -546,6 +643,15 @@ dependencies = [
"zerocopy",
]
[[package]]
name = "hash32"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
dependencies = [
"byteorder",
]
[[package]]
name = "hashbrown"
version = "0.15.5"
@@ -561,6 +667,20 @@ version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "heapless"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
dependencies = [
"atomic-polyfill",
"hash32",
"rustc_version",
"serde",
"spin",
"stable_deref_trait",
]
[[package]]
name = "heck"
version = "0.5.0"
@@ -588,7 +708,17 @@ version = "0.13.0-rc.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef451d73f36d8a3f93ad32c332ea01146c9650e1ec821a9b0e46c01277d544f8"
dependencies = [
"digest",
"digest 0.11.1",
]
[[package]]
name = "hybrid-array"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2d35805454dc9f8662a98d6d61886ffe26bd465f5960e0e55345c70d5c0d2a9"
dependencies = [
"typenum",
"zeroize",
]
[[package]]
@@ -626,7 +756,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
dependencies = [
"hybrid-array",
"hybrid-array 0.4.7",
]
[[package]]
@@ -646,14 +776,23 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]]
name = "js-sys"
version = "0.3.85"
version = "0.3.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "keccak"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653"
dependencies = [
"cpufeatures 0.2.17",
]
[[package]]
name = "keccak"
version = "0.2.0-rc.2"
@@ -671,9 +810,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "libc"
version = "0.2.180"
version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "libm"
@@ -683,27 +822,36 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]]
name = "linux-raw-sys"
version = "0.11.0"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "lms-signature"
version = "0.1.0-rc.2"
dependencies = [
"digest",
"digest 0.11.1",
"getrandom 0.4.1",
"hex",
"hex-literal",
"hybrid-array",
"hybrid-array 0.4.7",
"rand_core 0.10.0",
"sha2",
"signature",
"sha2 0.11.0-rc.5",
"signature 3.0.0-rc.10",
"static_assertions",
"typenum",
"zeroize",
]
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.29"
@@ -712,28 +860,28 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
version = "2.7.6"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "ml-dsa"
version = "0.1.0-rc.7"
dependencies = [
"const-oid",
"const-oid 0.10.2",
"criterion",
"getrandom 0.4.1",
"hex",
"hex-literal",
"hybrid-array",
"hybrid-array 0.4.7",
"module-lattice",
"pkcs8",
"pkcs8 0.11.0-rc.11",
"proptest",
"rand_core 0.10.0",
"serde",
"serde_json",
"sha3",
"signature",
"sha3 0.11.0-rc.8",
"signature 3.0.0-rc.10",
"zeroize",
]
@@ -743,7 +891,7 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dfecc750073acc09af2f8899b2342d520d570392ba1c3aed53eeb0d84ca4103"
dependencies = [
"hybrid-array",
"hybrid-array 0.4.7",
"num-traits",
"zeroize",
]
@@ -803,14 +951,24 @@ dependencies = [
"base64ct",
]
[[package]]
name = "pkcs8"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der 0.7.10",
"spki 0.7.3",
]
[[package]]
name = "pkcs8"
version = "0.11.0-rc.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12922b6296c06eb741b02d7b5161e3aaa22864af38dfa025a1a3ba3f68c84577"
dependencies = [
"der",
"spki",
"der 0.8.0",
"spki 0.8.0-rc.4",
]
[[package]]
@@ -841,6 +999,19 @@ dependencies = [
"plotters-backend",
]
[[package]]
name = "postcard"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
dependencies = [
"cobs",
"embedded-io 0.4.0",
"embedded-io 0.6.1",
"heapless",
"serde",
]
[[package]]
name = "ppv-lite86"
version = "0.2.21"
@@ -986,9 +1157,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.12.2"
version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
dependencies = [
"aho-corasick",
"memchr",
@@ -998,9 +1169,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.13"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
@@ -1009,9 +1180,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.8"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
name = "rfc6979"
@@ -1019,10 +1190,19 @@ version = "0.5.0-rc.5"
dependencies = [
"hex-literal",
"hmac",
"sha2",
"sha2 0.11.0-rc.5",
"subtle",
]
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "rustcrypto-ff"
version = "0.14.0-rc.0"
@@ -1046,9 +1226,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "1.1.3"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags",
"errno",
@@ -1084,6 +1264,12 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sec1"
version = "0.8.0-rc.13"
@@ -1092,8 +1278,8 @@ checksum = "7a2400ed44a13193820aa528a19f376c3843141a8ce96ff34b11104cc79763f2"
dependencies = [
"base16ct",
"ctutils",
"der",
"hybrid-array",
"der 0.8.0",
"hybrid-array 0.4.7",
"serdect",
"subtle",
"zeroize",
@@ -1176,7 +1362,18 @@ checksum = "3b167252f3c126be0d8926639c4c4706950f01445900c4b3db0fd7e89fcb750a"
dependencies = [
"cfg-if",
"cpufeatures 0.2.17",
"digest",
"digest 0.11.1",
]
[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures 0.2.17",
"digest 0.10.7",
]
[[package]]
@@ -1187,7 +1384,17 @@ checksum = "7c5f3b1e2dc8aad28310d8410bd4d7e180eca65fca176c52ab00d364475d0024"
dependencies = [
"cfg-if",
"cpufeatures 0.2.17",
"digest",
"digest 0.11.1",
]
[[package]]
name = "sha3"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
dependencies = [
"digest 0.10.7",
"keccak 0.1.6",
]
[[package]]
@@ -1196,17 +1403,23 @@ version = "0.11.0-rc.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95f78cd62cc39ece5aefbeb6caaa2ea44f70b4815d4b85f7e150ac685ada2bb5"
dependencies = [
"digest",
"keccak",
"digest 0.11.1",
"keccak 0.2.0-rc.2",
]
[[package]]
name = "signature"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
[[package]]
name = "signature"
version = "3.0.0-rc.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f1880df446116126965eeec169136b2e0251dba37c6223bcc819569550edea3"
dependencies = [
"digest",
"digest 0.11.1",
"rand_core 0.10.0",
]
@@ -1216,30 +1429,49 @@ version = "0.2.0-rc.4"
dependencies = [
"aes",
"cipher",
"const-oid",
"const-oid 0.10.2",
"criterion",
"ctr",
"digest",
"digest 0.11.1",
"hex",
"hex-literal",
"hmac",
"hybrid-array",
"hybrid-array 0.4.7",
"num-bigint",
"paste",
"pkcs8",
"pkcs8 0.11.0-rc.11",
"proptest",
"rand 0.10.0",
"rand_core 0.10.0",
"serde",
"serde_json",
"sha2",
"sha3",
"signature",
"sha2 0.11.0-rc.5",
"sha3 0.11.0-rc.8",
"signature 3.0.0-rc.10",
"typenum",
"zerocopy",
"zeroize",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "spki"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
"der 0.7.10",
]
[[package]]
name = "spki"
version = "0.8.0-rc.4"
@@ -1247,9 +1479,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8baeff88f34ed0691978ec34440140e1572b68c7dd4a495fd14a3dc1944daa80"
dependencies = [
"base64ct",
"der",
"der 0.8.0",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "static_assertions"
version = "1.1.0"
@@ -1264,9 +1502,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.114"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
@@ -1275,17 +1513,37 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.24.0"
version = "3.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0"
dependencies = [
"fastrand",
"getrandom 0.3.4",
"getrandom 0.4.1",
"once_cell",
"rustix",
"windows-sys",
]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
@@ -1310,9 +1568,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
[[package]]
name = "unicode-ident"
version = "1.0.22"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-xid"
@@ -1320,6 +1578,12 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[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"
@@ -1359,9 +1623,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
version = "0.2.108"
version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
dependencies = [
"cfg-if",
"once_cell",
@@ -1372,9 +1636,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.108"
version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1382,9 +1646,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.108"
version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
dependencies = [
"bumpalo",
"proc-macro2",
@@ -1395,9 +1659,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.108"
version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
dependencies = [
"unicode-ident",
]
@@ -1438,9 +1702,9 @@ dependencies = [
[[package]]
name = "web-sys"
version = "0.3.85"
version = "0.3.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598"
checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -1558,20 +1822,42 @@ dependencies = [
"wasmparser",
]
[[package]]
name = "xmss-signatures"
version = "0.1.0"
dependencies = [
"const-oid 0.9.6",
"der 0.7.10",
"digest 0.10.7",
"hybrid-array 0.2.3",
"pkcs8 0.10.2",
"postcard",
"rand 0.10.0",
"serde_json",
"serdect",
"sha2 0.10.9",
"sha3 0.10.8",
"signature 2.2.0",
"spki 0.7.3",
"subtle",
"thiserror",
"zeroize",
]
[[package]]
name = "zerocopy"
version = "0.8.34"
version = "0.8.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d"
checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.34"
version = "0.8.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d"
checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953"
dependencies = [
"proc-macro2",
"quote",
@@ -1586,6 +1872,6 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]]
name = "zmij"
version = "1.0.17"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
+2
View File
@@ -9,6 +9,7 @@ members = [
"ml-dsa",
"rfc6979",
"slh-dsa",
"xmss",
]
[profile.dev]
@@ -25,3 +26,4 @@ lms-signature = { path = "./lms" }
ml-dsa = { path = "./ml-dsa" }
rfc6979 = { path = "./rfc6979" }
slh-dsa = { path = "./slh-dsa" }
xmss-signatures = { path = "./xmss" }
+2
View File
@@ -21,6 +21,7 @@ and can be easily used for bare-metal or lightweight WebAssembly programming.
| [`ml-dsa`] | [Module Lattice DSA](https://csrc.nist.gov/pubs/fips/204/final) | [![crates.io](https://img.shields.io/crates/v/ml-dsa.svg)](https://crates.io/crates/ml-dsa) | [![Documentation](https://docs.rs/ml-dsa/badge.svg)](https://docs.rs/ml-dsa) | [![lms build](https://github.com/RustCrypto/signatures/actions/workflows/ml-dsa.yml/badge.svg)](https://github.com/RustCrypto/signatures/actions/workflows/ml-dsa.yml)
| [`rfc6979`] | [Deterministic (EC)DSA Signatures](https://datatracker.ietf.org/doc/html/rfc6979) | [![crates.io](https://img.shields.io/crates/v/rfc6979.svg)](https://crates.io/crates/rfc6979) | [![Documentation](https://docs.rs/rfc6979/badge.svg)](https://docs.rs/rfc6979) | [![rfc6979 build](https://github.com/RustCrypto/signatures/actions/workflows/rfc6979.yml/badge.svg)](https://github.com/RustCrypto/signatures/actions/workflows/rfc6979.yml)
| [`slh-dsa`] | [Stateless Hash-Based Signature](https://csrc.nist.gov/pubs/fips/205/final) | [![crates.io](https://img.shields.io/crates/v/slh-dsa.svg)](https://crates.io/crates/slh-dsa) | [![Documentation](https://docs.rs/slh-dsa/badge.svg)](https://docs.rs/slh-dsa) | [![slh-dsa build](https://github.com/RustCrypto/signatures/actions/workflows/slh-dsa.yml/badge.svg)](https://github.com/RustCrypto/signatures/actions/workflows/slh-dsa.yml)
| [`xmss`] | [eXtended Merkle Signature Scheme](https://datatracker.ietf.org/doc/html/rfc8391) | [![crates.io](https://img.shields.io/crates/v/xmss-signatures.svg)](https://crates.io/crates/xmss-signatures) | [![Documentation](https://docs.rs/xmss-signatures/badge.svg)](https://docs.rs/xmss-signatures) | [![xmss build](https://github.com/RustCrypto/signatures/actions/workflows/xmss.yml/badge.svg)](https://github.com/RustCrypto/signatures/actions/workflows/xmss.yml)
NOTE: for RSA signatures see <https://github.com/RustCrypto/RSA>
@@ -61,6 +62,7 @@ dual licensed as above, without any additional terms or conditions.
[`ml-dsa`]: ./ml-dsa
[`rfc6979`]: ./rfc6979
[`slh-dsa`]: ./slh-dsa
[`xmss`]: ./xmss
[//]: # (general links)
+1
View File
@@ -0,0 +1 @@
/target
+9
View File
@@ -0,0 +1,9 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 0.1.0 (2026-02-20)
- Initial release
+740
View File
@@ -0,0 +1,740 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "anyhow"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
[[package]]
name = "atomic-polyfill"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
dependencies = [
"critical-section",
]
[[package]]
name = "base16ct"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6"
[[package]]
name = "base64ct"
version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
[[package]]
name = "bitflags"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chacha20"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
dependencies = [
"cfg-if",
"cpufeatures 0.3.0",
"rand_core",
]
[[package]]
name = "cobs"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
dependencies = [
"thiserror",
]
[[package]]
name = "const-oid"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "cpufeatures"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
dependencies = [
"libc",
]
[[package]]
name = "critical-section"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "der"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
"const-oid",
"zeroize",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "embedded-io"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
[[package]]
name = "embedded-io"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"rand_core",
"wasip2",
"wasip3",
]
[[package]]
name = "hash32"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
dependencies = [
"byteorder",
]
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "heapless"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
dependencies = [
"atomic-polyfill",
"hash32",
"rustc_version",
"serde",
"spin",
"stable_deref_trait",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hybrid-array"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2d35805454dc9f8662a98d6d61886ffe26bd465f5960e0e55345c70d5c0d2a9"
dependencies = [
"typenum",
"zeroize",
]
[[package]]
name = "id-arena"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
name = "indexmap"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown 0.16.1",
"serde",
"serde_core",
]
[[package]]
name = "itoa"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]]
name = "keccak"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653"
dependencies = [
"cpufeatures 0.2.17",
]
[[package]]
name = "leb128fmt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "libc"
version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "pkcs8"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der",
"spki",
]
[[package]]
name = "postcard"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
dependencies = [
"cobs",
"embedded-io 0.4.0",
"embedded-io 0.6.1",
"heapless",
"serde",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8"
dependencies = [
"chacha20",
"getrandom",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "serdect"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9af4a3e75ebd5599b30d4de5768e00b5095d518a79fefc3ecbaf77e665d1ec06"
dependencies = [
"base16ct",
"serde",
]
[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures 0.2.17",
"digest",
]
[[package]]
name = "sha3"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
dependencies = [
"digest",
"keccak",
]
[[package]]
name = "signature"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "spki"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
"der",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "typenum"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasip2"
version = "1.0.2+wasi-0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wasip3"
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wasm-encoder"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
dependencies = [
"leb128fmt",
"wasmparser",
]
[[package]]
name = "wasm-metadata"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [
"anyhow",
"indexmap",
"wasm-encoder",
"wasmparser",
]
[[package]]
name = "wasmparser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags",
"hashbrown 0.15.5",
"indexmap",
"semver",
]
[[package]]
name = "wit-bindgen"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
dependencies = [
"wit-bindgen-rust-macro",
]
[[package]]
name = "wit-bindgen-core"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
dependencies = [
"anyhow",
"heck",
"wit-parser",
]
[[package]]
name = "wit-bindgen-rust"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
"heck",
"indexmap",
"prettyplease",
"syn",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
]
[[package]]
name = "wit-bindgen-rust-macro"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
dependencies = [
"anyhow",
"prettyplease",
"proc-macro2",
"quote",
"syn",
"wit-bindgen-core",
"wit-bindgen-rust",
]
[[package]]
name = "wit-component"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"wasm-encoder",
"wasm-metadata",
"wasmparser",
"wit-parser",
]
[[package]]
name = "wit-parser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
"anyhow",
"id-arena",
"indexmap",
"log",
"semver",
"serde",
"serde_derive",
"serde_json",
"unicode-xid",
"wasmparser",
]
[[package]]
name = "xmss-signatures"
version = "0.1.0"
dependencies = [
"const-oid",
"der",
"digest",
"hybrid-array",
"pkcs8",
"postcard",
"rand",
"serde_json",
"serdect",
"sha2",
"sha3",
"signature",
"spki",
"subtle",
"thiserror",
"zeroize",
]
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
+64
View File
@@ -0,0 +1,64 @@
[package]
name = "xmss-signatures"
authors = ["Michael Lodder <redmike7@gmail.com>"]
category = ["cryptography"]
description = """
Pure Rust implementation of XMSS (eXtended Merkle Signature Scheme)
as described in RFC 8391 and SP 800-208
"""
edition = "2024"
homepage = "https://github.com/RustCrypto/signature/tree/master/xmss"
repository = "https://github.com/RustCrypto/signatures"
readme = "README.md"
keywords = ["xmss", "signature", "crypto", "cryptography", "rfc8391"]
license = "MIT/Apache-2.0"
version = "0.1.0"
[features]
default = []
serde = ["dep:serdect"]
pkcs8 = ["dep:pkcs8", "dep:spki", "dep:der", "dep:const-oid"]
[dependencies]
const-oid = { version = "0.9", optional = true }
der = { version = "0.7", optional = true, default-features = false, features = ["alloc"] }
digest = "0.10"
hybrid-array = { version = "0.2", features = ["zeroize"] }
pkcs8 = { version = "0.10", optional = true, default-features = false, features = ["alloc"] }
rand = "0.10"
sha2 = "0.10"
sha3 = "0.10"
serdect = { version = "0.4", features = ["alloc"], optional = true }
signature = "2"
spki = { version = "0.7", optional = true, default-features = false, features = ["alloc"] }
subtle = "2.6"
thiserror = "2"
zeroize = "1"
[dev-dependencies]
postcard = { version = "1", features = ["alloc"] }
serde_json = "1"
[lints.rust]
missing_docs = "deny"
missing_debug_implementations = "deny"
trivial_casts = "deny"
trivial_numeric_casts = "deny"
unsafe_code = "deny"
unstable_features = "deny"
unused_import_braces = "deny"
unused_parens = "deny"
unused_lifetimes = "deny"
unused_qualifications = "deny"
unused_extern_crates = "deny"
[lints.clippy]
unwrap_used = "deny"
cast_precision_loss = "warn"
cast_possible_truncation = "warn"
cast_possible_wrap = "warn"
cast_sign_loss = "warn"
checked_conversions = "warn"
mod_module_files = "warn"
panic = "warn"
panic_in_result_fn = "warn"
+201
View File
@@ -0,0 +1,201 @@
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.
+23
View File
@@ -0,0 +1,23 @@
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.
+142
View File
@@ -0,0 +1,142 @@
# [RustCrypto]: XMSS
[![crate][crate-image]][crate-link]
[![Docs][docs-image]][docs-link]
[![Build Status][build-image]][build-link]
![Apache2/MIT licensed][license-image]
![Rust Version][rustc-image]
[![Project Chat][chat-image]][chat-link]
Pure Rust implementation of the XMSS (eXtended Merkle Signature Scheme)
and XMSS^MT (Multi-Tree) signature schemes as described in [RFC 8391] and
[NIST SP 800-208].
## ⚠️ Security Warning
The implementation contained in this crate has never been independently audited!
USE AT YOUR OWN RISK!
## About
XMSS is a stateful hash-based digital signature scheme that is believed to be
resistant to attacks by quantum computers. It is standardized in [RFC 8391] and
approved by NIST in [SP 800-208].
This crate provides:
- XMSS (single-tree) and XMSS^MT (multi-tree) signature schemes
- SHA-256, SHA-512, SHAKE128, and SHAKE256 hash function support
- 93 parameter sets covering tree heights of 10, 16, 20, 40, and 60
- Hash output sizes of 192, 256, and 512 bits
- Optional `serde` support for serialization/deserialization
- Optional `pkcs8` support for PKCS#8/SPKI key encoding
- `no_unsafe` code — zero `unsafe` blocks
- Constant-time operations for signature verification
- Automatic zeroization of secret key material on drop
## Usage
```rust
use xmss_signatures::{KeyPair, XmssSha2_10_256};
// Generate a key pair
let mut kp = KeyPair::<XmssSha2_10_256>::generate(&mut rand::rng()).unwrap();
// Sign a message
let message = b"test message";
let signature = kp.signing_key().sign(message).unwrap();
// Verify the signature and recover the message
let recovered = kp.verifying_key().verify(&signature).unwrap();
assert_eq!(recovered, message);
// Detached signatures are also supported
let signature = kp.signing_key().sign_detached(message).unwrap();
kp.verifying_key().verify_detached(&signature, message).unwrap();
```
## Supported Parameter Sets
### XMSS (Single-Tree)
| Parameter Set | Hash | n (bytes) | Tree Height | Max Signatures |
|---|---|---|---|---|
| `XmssSha2_10_256` | SHA-256 | 32 | 10 | 1,024 |
| `XmssSha2_16_256` | SHA-256 | 32 | 16 | 65,536 |
| `XmssSha2_20_256` | SHA-256 | 32 | 20 | 1,048,576 |
| `XmssSha2_10_512` | SHA-512 | 64 | 10 | 1,024 |
| `XmssSha2_16_512` | SHA-512 | 64 | 16 | 65,536 |
| `XmssSha2_20_512` | SHA-512 | 64 | 20 | 1,048,576 |
| `XmssSha2_10_192` | SHA-256 | 24 | 10 | 1,024 |
| `XmssSha2_16_192` | SHA-256 | 24 | 16 | 65,536 |
| `XmssSha2_20_192` | SHA-256 | 24 | 20 | 1,048,576 |
| `XmssShake_10_256` | SHAKE128 | 32 | 10 | 1,024 |
| `XmssShake_16_256` | SHAKE128 | 32 | 16 | 65,536 |
| `XmssShake_20_256` | SHAKE128 | 32 | 20 | 1,048,576 |
| `XmssShake_10_512` | SHAKE128 | 64 | 10 | 1,024 |
| `XmssShake_16_512` | SHAKE128 | 64 | 16 | 65,536 |
| `XmssShake_20_512` | SHAKE128 | 64 | 20 | 1,048,576 |
| `XmssShake256_10_256` | SHAKE256 | 32 | 10 | 1,024 |
| `XmssShake256_16_256` | SHAKE256 | 32 | 16 | 65,536 |
| `XmssShake256_20_256` | SHAKE256 | 32 | 20 | 1,048,576 |
| `XmssShake256_10_192` | SHAKE256 | 24 | 10 | 1,024 |
| `XmssShake256_16_192` | SHAKE256 | 24 | 16 | 65,536 |
| `XmssShake256_20_192` | SHAKE256 | 24 | 20 | 1,048,576 |
### XMSS^MT (Multi-Tree)
Multi-tree parameter sets follow the naming convention
`Xmssmt[Hash]_[TotalHeight]_[Depth]_[Bits]`, e.g. `XmssmtSha2_20_2_256`.
Total tree heights of 20, 40, and 60 are supported with depths of 2, 4, 8, 3,
6, and 12 (where applicable), across SHA-256, SHA-512, SHAKE128, and SHAKE256
hash functions.
See the [API documentation][docs-link] for a complete list of all 72 XMSS^MT
parameter sets.
## Features
| Feature | Description |
|---|---|
| `serde` | Enables `serde` serialization/deserialization via `serdect` |
| `pkcs8` | Enables PKCS#8 and SPKI key encoding/decoding |
## Minimum Supported Rust Version
This crate requires **Rust 2024 edition**.
## License
All crates licensed under either of
* [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)
* [MIT license](https://opensource.org/licenses/MIT)
at your option.
## Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.
[//]: # (badges)
[crate-image]: https://img.shields.io/crates/v/xmss-signatures?logo=rust
[crate-link]: https://crates.io/crates/xmss-signatures
[docs-image]: https://docs.rs/xmss-signatures/badge.svg
[docs-link]: https://docs.rs/xmss-signatures/
[build-image]: https://github.com/RustCrypto/signatures/actions/workflows/xmss.yml/badge.svg
[build-link]: https://github.com/RustCrypto/signatures/actions/workflows/xmss.yml
[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
[rustc-image]: https://img.shields.io/badge/rustc-1.85+-blue.svg
[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg
[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260048-signatures
[//]: # (links)
[RustCrypto]: https://github.com/RustCrypto
[RFC 8391]: https://www.rfc-editor.org/rfc/rfc8391
[NIST SP 800-208]: https://csrc.nist.gov/pubs/sp/800/208/final
+54
View File
@@ -0,0 +1,54 @@
/// Errors used throughout this crate.
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// The OID value is not recognized.
#[error("Invalid OID: 0x{0:08x}")]
InvalidOid(u32),
/// The parameter set name is not recognized.
#[error("Invalid parameter set: {0}")]
InvalidParameterSet(String),
/// The Winternitz parameter value is not supported.
#[error("Invalid parameters: unsupported Winternitz parameter w={0}")]
InvalidParams(u32),
/// All one-time signatures have been used.
#[error("Key exhausted: all one-time signatures have been used")]
KeyExhausted,
/// The provided seed has an incorrect length.
#[error("Invalid seed length: expected {expected}, got {got}")]
InvalidSeedLength {
/// Expected seed length in bytes.
expected: usize,
/// Actual seed length in bytes.
got: usize,
},
/// Signature verification failed.
#[error("Signature verification failed")]
VerificationFailed,
/// The hash function configuration is not supported.
#[error("Hash function error: unsupported n={n} with func={func}")]
Hash {
/// The hash output length parameter.
n: u32,
/// The hash function identifier.
func: u32,
},
/// The provided key has an incorrect length.
#[error("Invalid key length: expected {expected}, got {got}")]
InvalidKeyLength {
/// Expected key length in bytes.
expected: usize,
/// Actual key length in bytes.
got: usize,
},
/// The provided signature has an incorrect length.
#[error("Invalid signature length: expected {expected}, got {got}")]
InvalidSignatureLength {
/// Expected signature length in bytes.
expected: usize,
/// Actual signature length in bytes.
got: usize,
},
}
/// Result type used by this crate.
pub type XmssResult<T> = Result<T, Error>;
+210
View File
@@ -0,0 +1,210 @@
use sha2::{Digest, Sha256, Sha512};
use sha3::{
Shake128, Shake256,
digest::{ExtendableOutput, Update, XofReader},
};
use zeroize::Zeroize;
use crate::error::{Error, XmssResult};
use crate::hash_address::set_key_and_mask;
use crate::params::{XMSS_SHA2, XMSS_SHAKE128, XMSS_SHAKE256, XmssParams};
use crate::utils::ull_to_bytes;
const XMSS_HASH_PADDING_F: u64 = 0;
const XMSS_HASH_PADDING_H: u64 = 1;
const XMSS_HASH_PADDING_HASH: u64 = 2;
const XMSS_HASH_PADDING_PRF: u64 = 3;
const XMSS_HASH_PADDING_PRF_KEYGEN: u64 = 4;
pub(crate) fn addr_to_bytes(bytes: &mut [u8], addr: &[u32; 8]) {
for i in 0..8 {
ull_to_bytes(&mut bytes[i * 4..i * 4 + 4], addr[i] as u64);
}
}
fn core_hash(params: &XmssParams, out: &mut [u8], input: &[u8]) -> XmssResult<()> {
if params.n == 24 && params.func == XMSS_SHA2 {
let result = Sha256::digest(input);
out[..24].copy_from_slice(&result[..24]);
} else if params.n == 24 && params.func == XMSS_SHAKE256 {
let mut hasher = Shake256::default();
hasher.update(input);
let mut reader = hasher.finalize_xof();
reader.read(&mut out[..24]);
} else if params.n == 32 && params.func == XMSS_SHA2 {
let result = Sha256::digest(input);
out[..32].copy_from_slice(&result);
} else if params.n == 32 && params.func == XMSS_SHAKE128 {
let mut hasher = Shake128::default();
hasher.update(input);
let mut reader = hasher.finalize_xof();
reader.read(&mut out[..32]);
} else if params.n == 32 && params.func == XMSS_SHAKE256 {
let mut hasher = Shake256::default();
hasher.update(input);
let mut reader = hasher.finalize_xof();
reader.read(&mut out[..32]);
} else if params.n == 64 && params.func == XMSS_SHA2 {
let result = Sha512::digest(input);
out[..64].copy_from_slice(&result);
} else if params.n == 64 && params.func == XMSS_SHAKE256 {
let mut hasher = Shake256::default();
hasher.update(input);
let mut reader = hasher.finalize_xof();
reader.read(&mut out[..64]);
} else {
return Err(Error::Hash {
n: params.n,
func: params.func,
});
}
Ok(())
}
/// Computes PRF(key, in), for a key of params.n bytes, and a 32-byte input.
pub(crate) fn prf(
params: &XmssParams,
out: &mut [u8],
input: &[u8; 32],
key: &[u8],
) -> XmssResult<()> {
let n = params.n as usize;
let padding_len = params.padding_len as usize;
let buf_len = padding_len + n + 32;
let mut buf = vec![0u8; buf_len];
ull_to_bytes(&mut buf[..padding_len], XMSS_HASH_PADDING_PRF);
buf[padding_len..padding_len + n].copy_from_slice(&key[..n]);
buf[padding_len + n..padding_len + n + 32].copy_from_slice(input);
let result = core_hash(params, out, &buf);
buf.zeroize();
result
}
/// Computes PRF_keygen(key, in), for a key of params.n bytes,
/// and an input of 32 + params.n bytes.
pub(crate) fn prf_keygen(
params: &XmssParams,
out: &mut [u8],
input: &[u8],
key: &[u8],
) -> XmssResult<()> {
let n = params.n as usize;
let padding_len = params.padding_len as usize;
let buf_len = padding_len + 2 * n + 32;
let mut buf = vec![0u8; buf_len];
ull_to_bytes(&mut buf[..padding_len], XMSS_HASH_PADDING_PRF_KEYGEN);
buf[padding_len..padding_len + n].copy_from_slice(&key[..n]);
buf[padding_len + n..padding_len + n + n + 32].copy_from_slice(&input[..n + 32]);
let result = core_hash(params, out, &buf);
buf.zeroize();
result
}
/// Computes the message hash using R, the public root, the index of the leaf
/// node, and the message.
pub(crate) fn hash_message(
params: &XmssParams,
out: &mut [u8],
r: &[u8],
root: &[u8],
idx: u64,
m_with_prefix: &mut [u8],
mlen: u64,
) -> XmssResult<()> {
let n = params.n as usize;
let padding_len = params.padding_len as usize;
ull_to_bytes(&mut m_with_prefix[..padding_len], XMSS_HASH_PADDING_HASH);
m_with_prefix[padding_len..padding_len + n].copy_from_slice(&r[..n]);
m_with_prefix[padding_len + n..padding_len + 2 * n].copy_from_slice(&root[..n]);
ull_to_bytes(
&mut m_with_prefix[padding_len + 2 * n..padding_len + 3 * n],
idx,
);
#[allow(clippy::cast_possible_truncation)]
let total_len = mlen as usize + padding_len + 3 * n;
core_hash(params, out, &m_with_prefix[..total_len])
}
/// Tree hash function for internal nodes (two n-byte inputs).
pub(crate) fn thash_h(
params: &XmssParams,
out: &mut [u8],
input: &[u8],
pub_seed: &[u8],
addr: &mut [u32; 8],
) -> XmssResult<()> {
let n = params.n as usize;
let padding_len = params.padding_len as usize;
let buf_len = padding_len + 3 * n;
let mut buf = vec![0u8; buf_len];
let mut bitmask = vec![0u8; 2 * n];
let mut addr_as_bytes = [0u8; 32];
ull_to_bytes(&mut buf[..padding_len], XMSS_HASH_PADDING_H);
set_key_and_mask(addr, 0);
addr_to_bytes(&mut addr_as_bytes, addr);
prf(
params,
&mut buf[padding_len..padding_len + n],
&addr_as_bytes,
pub_seed,
)?;
set_key_and_mask(addr, 1);
addr_to_bytes(&mut addr_as_bytes, addr);
prf(params, &mut bitmask[..n], &addr_as_bytes, pub_seed)?;
set_key_and_mask(addr, 2);
addr_to_bytes(&mut addr_as_bytes, addr);
prf(params, &mut bitmask[n..2 * n], &addr_as_bytes, pub_seed)?;
for i in 0..2 * n {
buf[padding_len + n + i] = input[i] ^ bitmask[i];
}
core_hash(params, out, &buf)
}
/// Tree hash function for WOTS chains (single n-byte input).
pub(crate) fn thash_f(
params: &XmssParams,
out: &mut [u8],
input: &[u8],
pub_seed: &[u8],
addr: &mut [u32; 8],
) -> XmssResult<()> {
let n = params.n as usize;
let padding_len = params.padding_len as usize;
let buf_len = padding_len + 2 * n;
let mut buf = vec![0u8; buf_len];
let mut bitmask = vec![0u8; n];
let mut addr_as_bytes = [0u8; 32];
ull_to_bytes(&mut buf[..padding_len], XMSS_HASH_PADDING_F);
set_key_and_mask(addr, 0);
addr_to_bytes(&mut addr_as_bytes, addr);
prf(
params,
&mut buf[padding_len..padding_len + n],
&addr_as_bytes,
pub_seed,
)?;
set_key_and_mask(addr, 1);
addr_to_bytes(&mut addr_as_bytes, addr);
prf(params, &mut bitmask, &addr_as_bytes, pub_seed)?;
for i in 0..n {
buf[padding_len + n + i] = input[i] ^ bitmask[i];
}
core_hash(params, out, &buf)
}
+65
View File
@@ -0,0 +1,65 @@
pub const XMSS_ADDR_TYPE_OTS: u32 = 0;
pub const XMSS_ADDR_TYPE_LTREE: u32 = 1;
pub const XMSS_ADDR_TYPE_HASHTREE: u32 = 2;
#[inline]
pub fn set_layer_addr(addr: &mut [u32; 8], layer: u32) {
addr[0] = layer;
}
#[inline]
pub fn set_tree_addr(addr: &mut [u32; 8], tree: u64) {
addr[1] = (tree >> 32) as u32;
#[allow(clippy::cast_possible_truncation)]
{
addr[2] = tree as u32;
}
}
#[inline]
pub fn set_type(addr: &mut [u32; 8], type_val: u32) {
addr[3] = type_val;
}
#[inline]
pub fn set_key_and_mask(addr: &mut [u32; 8], key_and_mask: u32) {
addr[7] = key_and_mask;
}
/// Copies the layer and tree part of one address into the other.
#[inline]
pub fn copy_subtree_addr(out: &mut [u32; 8], input: &[u32; 8]) {
out[0] = input[0];
out[1] = input[1];
out[2] = input[2];
}
#[inline]
pub fn set_ots_addr(addr: &mut [u32; 8], ots: u32) {
addr[4] = ots;
}
#[inline]
pub fn set_chain_addr(addr: &mut [u32; 8], chain: u32) {
addr[5] = chain;
}
#[inline]
pub fn set_hash_addr(addr: &mut [u32; 8], hash: u32) {
addr[6] = hash;
}
#[inline]
pub fn set_ltree_addr(addr: &mut [u32; 8], ltree: u32) {
addr[4] = ltree;
}
#[inline]
pub fn set_tree_height(addr: &mut [u32; 8], tree_height: u32) {
addr[5] = tree_height;
}
#[inline]
pub fn set_tree_index(addr: &mut [u32; 8], tree_index: u32) {
addr[6] = tree_index;
}
+441
View File
@@ -0,0 +1,441 @@
//! XMSS (eXtended Merkle Signature Scheme) implementation in Rust.
//!
//! Implements RFC 8391 XMSS and XMSSMT hash-based signature schemes.
mod error;
mod hash;
mod hash_address;
mod params;
#[cfg(feature = "pkcs8")]
mod pkcs8;
mod utils;
mod wots;
mod xmss;
mod xmss_commons;
mod xmss_core;
pub use error::{Error, XmssResult};
pub use params::{
XmssMtSha2_20_2_192,
// XMSSMT multi-tree parameter sets
XmssMtSha2_20_2_256,
XmssMtSha2_20_2_512,
XmssMtSha2_20_4_192,
XmssMtSha2_20_4_256,
XmssMtSha2_20_4_512,
XmssMtSha2_40_2_192,
XmssMtSha2_40_2_256,
XmssMtSha2_40_2_512,
XmssMtSha2_40_4_192,
XmssMtSha2_40_4_256,
XmssMtSha2_40_4_512,
XmssMtSha2_40_8_192,
XmssMtSha2_40_8_256,
XmssMtSha2_40_8_512,
XmssMtSha2_60_3_192,
XmssMtSha2_60_3_256,
XmssMtSha2_60_3_512,
XmssMtSha2_60_6_192,
XmssMtSha2_60_6_256,
XmssMtSha2_60_6_512,
XmssMtSha2_60_12_192,
XmssMtSha2_60_12_256,
XmssMtSha2_60_12_512,
XmssMtShake_20_2_256,
XmssMtShake_20_2_512,
XmssMtShake_20_4_256,
XmssMtShake_20_4_512,
XmssMtShake_40_2_256,
XmssMtShake_40_2_512,
XmssMtShake_40_4_256,
XmssMtShake_40_4_512,
XmssMtShake_40_8_256,
XmssMtShake_40_8_512,
XmssMtShake_60_3_256,
XmssMtShake_60_3_512,
XmssMtShake_60_6_256,
XmssMtShake_60_6_512,
XmssMtShake_60_12_256,
XmssMtShake_60_12_512,
XmssMtShake256_20_2_192,
XmssMtShake256_20_2_256,
XmssMtShake256_20_4_192,
XmssMtShake256_20_4_256,
XmssMtShake256_40_2_192,
XmssMtShake256_40_2_256,
XmssMtShake256_40_4_192,
XmssMtShake256_40_4_256,
XmssMtShake256_40_8_192,
XmssMtShake256_40_8_256,
XmssMtShake256_60_3_192,
XmssMtShake256_60_3_256,
XmssMtShake256_60_6_192,
XmssMtShake256_60_6_256,
XmssMtShake256_60_12_192,
XmssMtShake256_60_12_256,
XmssParameter,
// XMSS single-tree parameter sets
XmssSha2_10_192,
XmssSha2_10_256,
XmssSha2_10_512,
XmssSha2_16_192,
XmssSha2_16_256,
XmssSha2_16_512,
XmssSha2_20_192,
XmssSha2_20_256,
XmssSha2_20_512,
XmssShake_10_256,
XmssShake_10_512,
XmssShake_16_256,
XmssShake_16_512,
XmssShake_20_256,
XmssShake_20_512,
XmssShake256_10_192,
XmssShake256_10_256,
XmssShake256_16_192,
XmssShake256_16_256,
XmssShake256_20_192,
XmssShake256_20_256,
};
pub use xmss::{DetachedSignature, KeyPair, Signature, SigningKey, VerifyingKey};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_xmss_sha2_10_256_sign_verify() {
let mut kp = KeyPair::<XmssSha2_10_256>::generate(&mut rand::rng()).unwrap();
let message = b"test message";
let sig = kp.signing_key().sign(message).unwrap();
let recovered = kp.verifying_key().verify(&sig).unwrap();
assert_eq!(recovered, message);
}
#[test]
fn test_xmss_sha2_10_256_bad_signature() {
let mut kp = KeyPair::<XmssSha2_10_256>::generate(&mut rand::rng()).unwrap();
let message = b"test message";
let sig = kp.signing_key().sign(message).unwrap();
// Corrupt the signature
let mut sig_bytes = sig.as_ref().to_vec();
sig_bytes[10] ^= 0xFF;
let bad_sig = Signature::<XmssSha2_10_256>::try_from(sig_bytes).unwrap();
let result = kp.verifying_key().verify(&bad_sig);
assert!(result.is_err());
}
#[test]
fn test_xmssmt_sha2_20_2_256_sign_verify() {
let mut kp = KeyPair::<XmssMtSha2_20_2_256>::generate(&mut rand::rng()).unwrap();
let message = b"test message for xmssmt";
let sig = kp.signing_key().sign(message).unwrap();
let recovered = kp.verifying_key().verify(&sig).unwrap();
assert_eq!(recovered, message);
}
#[test]
fn test_multiple_signatures() {
let mut kp = KeyPair::<XmssSha2_10_256>::generate(&mut rand::rng()).unwrap();
for i in 0..3 {
let msg = format!("message {}", i);
let sig = kp.signing_key().sign(msg.as_bytes()).unwrap();
let recovered = kp.verifying_key().verify(&sig).unwrap();
assert_eq!(recovered, msg.as_bytes());
}
}
#[test]
fn test_xmss_sign_detached_verify() {
let mut kp = KeyPair::<XmssSha2_10_256>::generate(&mut rand::rng()).unwrap();
let message = b"detached test message";
let sig = kp.signing_key().sign_detached(message).unwrap();
// Detached signature should not contain the message
let full_sig = kp.signing_key().sign(b"another").unwrap();
assert!(sig.as_ref().len() < full_sig.as_ref().len());
kp.verifying_key().verify_detached(&sig, message).unwrap();
// Wrong message should fail
assert!(
kp.verifying_key()
.verify_detached(&sig, b"wrong message")
.is_err()
);
}
#[test]
fn test_xmss_verify_truncated_signature() {
let mut kp = KeyPair::<XmssSha2_10_256>::generate(&mut rand::rng()).unwrap();
let sig = kp.signing_key().sign(b"test message").unwrap();
// Truncate the signature to be too short
let short_bytes = &sig.as_ref()[..sig.as_ref().len() / 2];
let short_sig = Signature::<XmssSha2_10_256>::try_from(short_bytes).unwrap();
assert!(kp.verifying_key().verify(&short_sig).is_err());
}
#[test]
fn test_key_exhaustion() {
let mut kp = KeyPair::<XmssSha2_10_256>::generate(&mut rand::rng()).unwrap();
// Modify the index to be at the last valid position (2^10 - 1 = 1023).
let mut sk_bytes = kp.signing_key().as_ref().to_vec();
// Index is at bytes[4..8] (after OID), big-endian.
sk_bytes[4] = 0x00;
sk_bytes[5] = 0x00;
sk_bytes[6] = 0x03;
sk_bytes[7] = 0xFF; // 1023
let mut last_sk = SigningKey::<XmssSha2_10_256>::try_from(sk_bytes).unwrap();
// Signing at the last index should succeed.
let sig = last_sk.sign(b"last message").unwrap();
let recovered = kp.verifying_key().verify(&sig).unwrap();
assert_eq!(recovered, b"last message");
// Signing again should fail with KeyExhausted.
let result = last_sk.sign(b"one more");
assert!(result.is_err());
}
#[test]
fn test_deterministic_keygen() {
// Sequential seed pattern: SK_SEED || SK_PRF || PUB_SEED
let seed: Vec<u8> = (0u8..96).collect();
let kp1 = KeyPair::<XmssSha2_10_256>::from_seed(&seed).unwrap();
let mut kp2 = KeyPair::<XmssSha2_10_256>::from_seed(&seed).unwrap();
// Same seed must produce identical keys.
assert_eq!(kp1.verifying_key(), kp2.verifying_key());
// Sign with one, verify with the other's public key.
let sig = kp2.signing_key().sign(b"deterministic test").unwrap();
let recovered = kp1.verifying_key().verify(&sig).unwrap();
assert_eq!(recovered, b"deterministic test");
}
#[test]
fn test_verifying_key_from_signing_key() {
let kp = KeyPair::<XmssSha2_10_256>::generate(&mut rand::rng()).unwrap();
// Derive verifying key from signing key.
let derived_pk = VerifyingKey::from(kp.signing_key_ref());
assert_eq!(kp.verifying_key(), &derived_pk);
}
/// Decodes a hex string to bytes. Panics on invalid input (test-only).
fn hex_decode(s: &str) -> Vec<u8> {
assert!(s.len() % 2 == 0, "hex string must have even length");
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).expect("invalid hex"))
.collect()
}
/// KAT verification test using liboqs XMSS-SHA2_10_256 known-answer test vectors.
#[test]
fn test_kat_xmss_sha2_10_256_verify() {
let pk_hex = "00000001B901B8D9332FE458EB6DE87AF74655D0B5AD936A66FDB6AC9D1B8CF25BB6DB8404562AD35E8ECAFAAFDA16981CDAA147606BEEA62801342AF13C8B5535F72F94";
let msg_hex = "B338DD755D5618C464AB331F14DE3DD4A358BBA00D28FB35236741E902F7B248CE";
let sm_hex = concat!(
"00000000404DFF9B9F3931FE6158FFF355A8EE715C9BC6A87FE6627928F3CA10",
"55FA7010C534B0D4C6FFDF4DBFE00E72405EFE83BBCF19AA2030A8CB16380848",
"2B6376FF8CE01FB8090F4842896A1EA5E9282F35CACD245A4B9DE9FE84E93158",
"51D68A72B3ECB9F440937C8BA4AC3F0429246CBC2777E8B92D84F4BA49FAB894",
"65FCB0FC8017E582746F531B4697925154A22E2D6A0F1B81913438000C295153",
"D7ADCA8F852C50D360F65F887479E9631A2CA30FE3AD92E7BF648643835F4F8C",
"C081A6C951B83B77608A08C021821DA61962CFCC8E97D75441921D39C5AD5375",
"43EFBAF0345DC70826E6E950929570C72E51619600C58D932A72657B19AF163E",
"0B8F7AAF2949A5EB26C517909E0E663E36753491182975206009107509DFFC89",
"8D308B903E84A8B29718BF7125397AFF5467D53CF8F36EB945B6B98D48E81C01",
"74A0E03541D24369CF8EDDA4288FFA615D16FBC7355CFC0966BA9256E5B8A44D",
"A95760DFB61301B10FD3E82436E267DB089773E43B984297D1E0D395DCC77FCF",
"ECCEFEBD4B80B3F241872EA251DA466CA6C5324346F4B5E6886654A86592641A",
"8C32AC554261B2D9130462C976B039E593F873AD1712820FF3E723FE57F13775",
"1AB3CA8B5B20D28D1B9384DF1D710AC39FAF699989418B7856C2034C695A693E",
"CC336EB472DE5049C743089529695B028F2F72BE0893E59169E9A2376C64BC5C",
"CAC5482E5A6E9C88D710A3FF8F23C206B09D314BF50568228B1BACF1CE330D52",
"9BD3793D7C7CD9EC770C111D9681D6F1B97D908CBBD436444853FEB47F234D31",
"F5E92B9E0465D67AC0FE48859126BEFA7F7D121A67C2C2970B37B8081B4E73C5",
"A21A41F60160A61FAFBD48649A3D2032C1679A67F348E3E25275FCD9AF650937",
"FEB0A30F25878CEED7D6CA693518B5A2F5418135EA9316EFFDECDB1DFFC9EE3A",
"62EFF0E66F3D05BD9D5F8679B536BB6D39792B28DF2481A6EECB9BEE40B11A10",
"D39A90EA1AAC47BF956FBFE9B0427B599B9BC024F326515E71615419423FEC3F",
"19F621D49B6EED59F129A6B1411B7B1AFCF073095D57B03F25A16F946ED716BF",
"705F567A151BE85B8E8195CC2F070BFD482702182B8A4A43ED942F6BD3CBF9DE",
"7E8AEB17C41E1C009C94FF4A2050E3731088B75474B38DC52BADF53C7DCD3FB9",
"8D023649FC4799CE060ADDACEC7CD4E656074E631C1CB8AEF88EFEE0817C2E3D",
"79E287F4510E48DFB7E23CB49D6FCA39A1E0F471F16A8BB65AF02150D059036D",
"00386DD287BEA4D52FB263B57AE5ADD901CADE838B1D7347D9E47EAF6456148C",
"6C4E44B0FA3DFCF5C9CEC2D80AD509A65AEF0E3E663B7F31BCA437311BA799D",
"4C2ACC138F85D73CB40792FF03F8F20427D951444990CA3976A71368A7DC1455",
"E880722F06F02163BC712E852A914F22E5675EB9B1C6C8B7FD20A8880AD2EEF9",
"7982C065C937BD3639357E4C7450CBDA0B51CCA8E3E078DC760FD99EBF646B82",
"369576539B2BD5B2C866ED5AE94423A5CE18C685352398D01C983F080D7BEB8A",
"9243AAA9AC1DDCC1B058B92BEAD301E8F3B8F5EF71EEE7966302B44D2E26D2A0",
"2393713E5D4D3FEF42196FAA368274C78C2932D22840ECA6018CE7D16B19A072",
"7CB1966EB28B57D137C5264CC2E627F24A3BAD50EA4F75C7BD8998709C01ED5A",
"CFFF0891934E94DA2CACCA212FB48BE3F9EAA310547E73C388D881F36AE21EFE",
"DD23744F6B07C5D6D2776C191ED41E607316F61BBEF7A20E1A03150AE833D189",
"52AE35188FBFDFA55C12A388836717BB2BDD97E89121C56C3B53E8198242315C",
"9E438512E0C8354A3E599CB7217AE688647A72985606BBD0720F6FA5C5B6F70E",
"88234EE54C6DB0A41106C866564650829FE4B232635B06B18240C9F86369C75B",
"2F7D237211A380C43F95D362E0680D9EA2CA47E1DC8C49703E22650B765F847A",
"D86BE25A3B7630D640A0097632DF13F600E8A025DD9A1FC67B0EB09C1CA9FA39",
"23896927DEE1E3CC0C81F4B82E43B89CACC69C9B8ADCA1670F7D4E50DB7BCD94",
"C2115E75F2BFD2336DA5A304D0F3455927360BF5040E95D1454106F2A8A7CD27",
"D5510E7B5BE7B5B9EDEFDC3D4249D655C51F4C1DBA0F359BE4769AB66EDBC802",
"824E9AB866E8EEAA2FEB1CC855F0A745AAC84A610DF0238112C6519F8E7346C4",
"5331A6036F84D5B6250F4B5BC0A2A6A31DAF9C60EB13C20CC649A18E27A6C98B",
"82F08E21706A8BDF338CC69C1679D25ECFF733A721211C1F6DD28091AAA9C93B",
"047EFCD2C8A55F2DA65E616F07DCC0F44081D4E359C1688A00F062EC925D2443",
"2862B547BB70F2AF126A3DABA5C918B224DE444B8733E6FA601B3D349307E945",
"83D0EC976AEDA2B90972324B3ACE8C7B79A67723AEA037E12DA9EFA9CA9668A4",
"F5FDADFB9EEE13398921F5023E354A6894825431DBA7317E6A6F69F0E77294BC",
"D02D7616E75AC31EC528FC070B8C34027C4E9CD0672903412FCA6B723650D56A",
"F562069312FC7EF1891A77E1A3F29D810C205EE212E75863F3B8B1ED216DF888",
"ADD07AFF45F1B5C01196329311414797CD5F67FFC54AAD04C803FF7E83C2E8BA",
"224CE83695BB7916AC42B1861F5CB527FDBCD82DBFA31C5ACF981D841420383750",
"4263C96A0015841FBCC721F96D50A86D6E096AB54AF9980F06CEE6341C78D658",
"3F6BAE8081B3C44B0F10FB7300874B5011FF0F97C52F975A31355884C2F12B6F",
"FEE20E8371D38183C9D04977BFA037C9BD4DD7F7CE203FD7FAD3852B3C2AE9D0",
"78ADEC70DB1A7140EF1114EBB03E8DE03237E0A27FF510015AC76FCEFE4EBD4C",
"3A1B6C67DB2A82FE2B1BF18723DB0F29FE4AD47B2EEF22AC3C6661CFA7DA747",
"6D23B470FA2E0441B6473EBD291791F09B4ADA70A5286EB05167BD59BFD8C464",
"27413D60692382EFB7882F60DC53AAAFDF2014CA7D27F8FA93C187A8371B4179",
"6557AE739912E5991C713532E81FA57F9BA562E1D3026D2D2D7373D99871BC62",
"768AD70D3DB184EABED83E30C11C9BC62F3340923A0082B987EC45CC7BD1DB4B",
"2B15E8AD3EAD74E96D8C20D85617BBEDC0BDAF8ED48B7EE8D7C42990028EC066",
"9AFC0861C22F2E9109F9BB35426BDDB4A69EB8F45CD5B226F92E8026F1E62DE1",
"DE435A4FC0CAEDA91C38A88F0037BDB296CD7B07FF040B1E08F02711E946B307",
"A5A38487F53070985B8E28BE6CCE809F34100F0CA780996CD38E91BA7773BB63",
"2D0BE7978F3AF3A92B961BD3A8759590726D6C1811F9E0BCA87377334E7C1F12",
"FE37401CA0200823938C816ED98981521470F7F2CCDD69D85E7530EBF39E3A59",
"2B1C09BC6C352C3FDB108FB26E7ACD3D5A4FC0442962E2C09651AC0D026E370F",
"1EE1A8219C4833D70793D6E581FD25B0E95FAB1EDA67232C2FA12C4E379A6627",
"E75AD408C1D2526005F2567CED8608E88CF53064FCDC58007198ADFA860F9FED",
"1DF80EFACC768A0A063E1AFEE6DF1BE3483105B1C45EB50BF7863B4278422CEB",
"A9001EA00299AC0415BF28A9C49CC2E92FC15565B547538A027886C6EB0D83B7",
"1138CE1A",
);
let pk_bytes = hex_decode(pk_hex);
let msg_bytes = hex_decode(msg_hex);
let sm_bytes = hex_decode(sm_hex);
assert_eq!(pk_bytes.len(), 68); // 4 OID + 32 root + 32 PUB_SEED
assert_eq!(msg_bytes.len(), 33);
assert_eq!(sm_bytes.len(), 2500);
let pk = VerifyingKey::<XmssSha2_10_256>::try_from(pk_bytes.as_slice())
.expect("failed to parse KAT public key");
let sig = DetachedSignature::<XmssSha2_10_256>::try_from(sm_bytes.as_slice())
.expect("failed to parse KAT signature");
pk.verify_detached(&sig, &msg_bytes)
.expect("KAT verification failed — signature should be valid");
// Also verify that a corrupted message fails.
let mut bad_msg = msg_bytes.clone();
bad_msg[0] ^= 0xFF;
assert!(
pk.verify_detached(&sig, &bad_msg).is_err(),
"KAT verification should fail with corrupted message"
);
}
#[cfg(feature = "serde")]
mod serde_tests {
use super::*;
#[test]
fn test_signing_key_serde_json_roundtrip() {
let mut kp = KeyPair::<XmssSha2_10_256>::generate(&mut rand::rng()).unwrap();
let sk = kp.signing_key();
let json = serde_json::to_string(&*sk).unwrap();
let sk2: SigningKey<XmssSha2_10_256> = serde_json::from_str(&json).unwrap();
assert_eq!(*sk, sk2);
}
#[test]
fn test_verifying_key_serde_json_roundtrip() {
let kp = KeyPair::<XmssSha2_10_256>::generate(&mut rand::rng()).unwrap();
let pk = kp.verifying_key();
let json = serde_json::to_string(pk).unwrap();
let pk2: VerifyingKey<XmssSha2_10_256> = serde_json::from_str(&json).unwrap();
assert_eq!(*pk, pk2);
}
#[test]
fn test_signature_serde_json_roundtrip() {
let mut kp = KeyPair::<XmssSha2_10_256>::generate(&mut rand::rng()).unwrap();
let sig = kp.signing_key().sign(b"test message").unwrap();
let json = serde_json::to_string(&sig).unwrap();
let sig2: Signature<XmssSha2_10_256> = serde_json::from_str(&json).unwrap();
assert_eq!(sig, sig2);
}
#[test]
fn test_signing_key_postcard_roundtrip() {
let mut kp = KeyPair::<XmssSha2_10_256>::generate(&mut rand::rng()).unwrap();
let sk = kp.signing_key();
let bytes = postcard::to_allocvec(&*sk).unwrap();
let sk2: SigningKey<XmssSha2_10_256> = postcard::from_bytes(&bytes).unwrap();
assert_eq!(*sk, sk2);
}
#[test]
fn test_verifying_key_postcard_roundtrip() {
let kp = KeyPair::<XmssSha2_10_256>::generate(&mut rand::rng()).unwrap();
let pk = kp.verifying_key();
let bytes = postcard::to_allocvec(pk).unwrap();
let pk2: VerifyingKey<XmssSha2_10_256> = postcard::from_bytes(&bytes).unwrap();
assert_eq!(*pk, pk2);
}
#[test]
fn test_signature_postcard_roundtrip() {
let mut kp = KeyPair::<XmssSha2_10_256>::generate(&mut rand::rng()).unwrap();
let sig = kp.signing_key().sign(b"test message").unwrap();
let bytes = postcard::to_allocvec(&sig).unwrap();
let sig2: Signature<XmssSha2_10_256> = postcard::from_bytes(&bytes).unwrap();
assert_eq!(sig, sig2);
}
}
#[cfg(feature = "pkcs8")]
mod pkcs8_tests {
use super::*;
use ::pkcs8::EncodePrivateKey;
#[test]
fn test_pkcs8_roundtrip() {
let kp = KeyPair::<XmssSha2_10_256>::generate(&mut rand::rng()).unwrap();
let der = kp.to_pkcs8_der().expect("PKCS#8 encode failed");
let kp2 = KeyPair::<XmssSha2_10_256>::from_pkcs8_der(der.as_bytes())
.expect("PKCS#8 decode failed");
assert_eq!(kp.verifying_key(), kp2.verifying_key());
}
}
}
+1465
View File
File diff suppressed because it is too large Load Diff
+103
View File
@@ -0,0 +1,103 @@
//! PKCS#8 encoding/decoding support for XMSS keys and signatures.
use const_oid::ObjectIdentifier;
use der::asn1::BitStringRef;
use pkcs8::{AlgorithmIdentifierRef, EncodePrivateKey, PrivateKeyInfo};
use spki::{EncodePublicKey, SubjectPublicKeyInfoRef};
use crate::error::Error;
use crate::params::XmssParameter;
use crate::xmss::{KeyPair, SigningKey, VerifyingKey};
/// OID for XMSS hash-based signatures: `id-alg-xmss-hashsig`
/// 0.4.0.127.0.15.1.1.13.0
const XMSS_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("0.4.0.127.0.15.1.1.13.0");
/// OID for XMSSMT hash-based signatures: `id-alg-xmssmt-hashsig`
/// 0.4.0.127.0.15.1.1.14.0
const XMSSMT_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("0.4.0.127.0.15.1.1.14.0");
/// Returns the appropriate ASN.1 OID for the given parameter set.
fn algorithm_oid<P: XmssParameter>() -> ObjectIdentifier {
let oid = P::oid();
if oid.is_xmss() { XMSS_OID } else { XMSSMT_OID }
}
impl<P: XmssParameter> EncodePublicKey for VerifyingKey<P> {
fn to_public_key_der(&self) -> spki::Result<der::Document> {
use der::Encode;
let algorithm = AlgorithmIdentifierRef {
oid: algorithm_oid::<P>(),
parameters: None,
};
let pk_bytes = self.as_ref();
let subject_public_key =
BitStringRef::from_bytes(pk_bytes).map_err(|_| spki::Error::KeyMalformed)?;
let spki_ref = SubjectPublicKeyInfoRef {
algorithm,
subject_public_key,
};
let der_bytes = spki_ref.to_der().map_err(|_| spki::Error::KeyMalformed)?;
der::Document::try_from(der_bytes.as_slice()).map_err(|_| spki::Error::KeyMalformed)
}
}
impl<P: XmssParameter> TryFrom<SubjectPublicKeyInfoRef<'_>> for VerifyingKey<P> {
type Error = spki::Error;
fn try_from(spki: SubjectPublicKeyInfoRef<'_>) -> Result<Self, Self::Error> {
let expected_oid = algorithm_oid::<P>();
if spki.algorithm.oid != expected_oid {
return Err(spki::Error::OidUnknown {
oid: spki.algorithm.oid,
});
}
let pk_bytes = spki
.subject_public_key
.as_bytes()
.ok_or(spki::Error::KeyMalformed)?;
VerifyingKey::<P>::try_from(pk_bytes).map_err(|_| spki::Error::KeyMalformed)
}
}
impl<P: XmssParameter> EncodePrivateKey for KeyPair<P> {
fn to_pkcs8_der(&self) -> pkcs8::Result<der::SecretDocument> {
let algo = AlgorithmIdentifierRef {
oid: algorithm_oid::<P>(),
parameters: None,
};
let sk_bytes = self.signing_key_ref().as_ref();
let pk_bytes = self.verifying_key().as_ref();
let pki = PrivateKeyInfo {
algorithm: algo,
private_key: sk_bytes,
public_key: Some(pk_bytes),
};
pki.try_into()
}
}
impl<P: XmssParameter> KeyPair<P> {
/// Decodes a key pair from PKCS#8 DER bytes.
pub fn from_pkcs8_der(der_bytes: &[u8]) -> crate::error::XmssResult<Self> {
let pk_info = PrivateKeyInfo::try_from(der_bytes).map_err(|_| Error::InvalidKeyLength {
expected: 0,
got: der_bytes.len(),
})?;
let expected_oid = algorithm_oid::<P>();
if pk_info.algorithm.oid != expected_oid {
return Err(Error::InvalidOid(0));
}
let signing_key = SigningKey::<P>::try_from(pk_info.private_key)?;
let verifying_key = if let Some(pk_bytes) = pk_info.public_key {
VerifyingKey::<P>::try_from(pk_bytes)?
} else {
VerifyingKey::from(&signing_key)
};
Ok(KeyPair::new(signing_key, verifying_key))
}
}
+19
View File
@@ -0,0 +1,19 @@
/// Converts the value of `val` to `outlen` bytes in big-endian byte order.
pub(crate) fn ull_to_bytes(out: &mut [u8], val: u64) {
let outlen = out.len();
let mut v = val;
for i in (0..outlen).rev() {
out[i] = (v & 0xff) as u8;
v >>= 8;
}
}
/// Converts the bytes in `input` from big-endian byte order to an integer.
/// If `input` is longer than 8 bytes, only the first 8 bytes are used.
pub(crate) fn bytes_to_ull(input: &[u8]) -> u64 {
let mut retval: u64 = 0;
for &byte in input.iter().take(8) {
retval = (retval << 8) | u64::from(byte);
}
retval
}
+200
View File
@@ -0,0 +1,200 @@
use crate::error::XmssResult;
use crate::hash::{addr_to_bytes, prf_keygen, thash_f};
use crate::hash_address::{set_chain_addr, set_hash_addr, set_key_and_mask};
use crate::params::XmssParams;
use crate::utils::ull_to_bytes;
/// Expands an n-byte seed into a wots_len*n byte array using prf_keygen.
fn expand_seed(
params: &XmssParams,
outseeds: &mut [u8],
inseed: &[u8],
pub_seed: &[u8],
addr: &mut [u32; 8],
) -> XmssResult<()> {
let n = params.n as usize;
let mut buf = vec![0u8; n + 32];
set_hash_addr(addr, 0);
set_key_and_mask(addr, 0);
buf[..n].copy_from_slice(&pub_seed[..n]);
for i in 0..params.wots_len {
set_chain_addr(addr, i);
addr_to_bytes(&mut buf[n..n + 32], addr);
prf_keygen(
params,
&mut outseeds[i as usize * n..(i as usize + 1) * n],
&buf,
inseed,
)?;
}
Ok(())
}
/// Computes the chaining function.
/// Interprets `input` as start-th value of the chain.
fn gen_chain(
params: &XmssParams,
out: &mut [u8],
input: &[u8],
start: u32,
steps: u32,
pub_seed: &[u8],
addr: &mut [u32; 8],
) -> XmssResult<()> {
let n = params.n as usize;
out[..n].copy_from_slice(&input[..n]);
let mut i = start;
while i < start + steps && i < params.wots_w {
set_hash_addr(addr, i);
let mut tmp = vec![0u8; n];
tmp.copy_from_slice(&out[..n]);
thash_f(params, out, &tmp, pub_seed, addr)?;
i += 1;
}
Ok(())
}
/// base_w algorithm as described in draft.
/// Interprets an array of bytes as integers in base w.
fn base_w(params: &XmssParams, output: &mut [u32], input: &[u8]) {
let out_len = output.len();
let mut in_idx = 0;
let mut total: u8 = 0;
let mut bits: u32 = 0;
for out_val in output.iter_mut().take(out_len) {
if bits == 0 {
total = input[in_idx];
in_idx += 1;
bits += 8;
}
bits -= params.wots_log_w;
// wots_w is always a power of 2 <= 256, so (wots_w - 1) fits in u8.
#[allow(clippy::cast_possible_truncation)]
let mask = (params.wots_w - 1) as u8;
*out_val = u32::from((total >> bits) & mask);
}
}
/// Computes the WOTS+ checksum over a message (in base_w).
fn wots_checksum(params: &XmssParams, csum_base_w: &mut [u32], msg_base_w: &[u32]) {
let mut csum: u32 = 0;
for val in msg_base_w.iter().take(params.wots_len1 as usize) {
csum += params.wots_w - 1 - val;
}
csum <<= 8 - ((params.wots_len2 * params.wots_log_w) % 8);
let csum_bytes_len = (params.wots_len2 * params.wots_log_w).div_ceil(8) as usize;
let mut csum_bytes = vec![0u8; csum_bytes_len];
ull_to_bytes(&mut csum_bytes, u64::from(csum));
base_w(params, csum_base_w, &csum_bytes);
}
/// Takes a message and derives the matching chain lengths.
fn chain_lengths(params: &XmssParams, lengths: &mut [u32], msg: &[u8]) {
let len1 = params.wots_len1 as usize;
base_w(params, &mut lengths[..len1], msg);
let (msg_part, csum_part) = lengths.split_at_mut(len1);
wots_checksum(params, csum_part, msg_part);
}
/// WOTS key generation. Takes a 32 byte seed for the private key, expands it to
/// a full WOTS private key and computes the corresponding public key.
pub fn wots_pkgen(
params: &XmssParams,
pk: &mut [u8],
seed: &[u8],
pub_seed: &[u8],
addr: &mut [u32; 8],
) -> XmssResult<()> {
let n = params.n as usize;
expand_seed(params, pk, seed, pub_seed, addr)?;
for i in 0..params.wots_len as usize {
#[allow(clippy::cast_possible_truncation)]
set_chain_addr(addr, i as u32);
let mut tmp = vec![0u8; n];
tmp.copy_from_slice(&pk[i * n..(i + 1) * n]);
gen_chain(
params,
&mut pk[i * n..],
&tmp,
0,
params.wots_w - 1,
pub_seed,
addr,
)?;
}
Ok(())
}
/// Takes a n-byte message and the 32-byte seed for the private key to compute a
/// signature that is placed at 'sig'.
pub fn wots_sign(
params: &XmssParams,
sig: &mut [u8],
msg: &[u8],
seed: &[u8],
pub_seed: &[u8],
addr: &mut [u32; 8],
) -> XmssResult<()> {
let n = params.n as usize;
let mut lengths = vec![0u32; params.wots_len as usize];
chain_lengths(params, &mut lengths, msg);
expand_seed(params, sig, seed, pub_seed, addr)?;
for i in 0..params.wots_len as usize {
#[allow(clippy::cast_possible_truncation)]
set_chain_addr(addr, i as u32);
let mut tmp = vec![0u8; n];
tmp.copy_from_slice(&sig[i * n..(i + 1) * n]);
gen_chain(
params,
&mut sig[i * n..],
&tmp,
0,
lengths[i],
pub_seed,
addr,
)?;
}
Ok(())
}
/// Takes a WOTS signature and an n-byte message, computes a WOTS public key.
pub fn wots_pk_from_sig(
params: &XmssParams,
pk: &mut [u8],
sig: &[u8],
msg: &[u8],
pub_seed: &[u8],
addr: &mut [u32; 8],
) -> XmssResult<()> {
let n = params.n as usize;
let mut lengths = vec![0u32; params.wots_len as usize];
chain_lengths(params, &mut lengths, msg);
for i in 0..params.wots_len as usize {
#[allow(clippy::cast_possible_truncation)]
set_chain_addr(addr, i as u32);
gen_chain(
params,
&mut pk[i * n..],
&sig[i * n..],
lengths[i],
params.wots_w - 1 - lengths[i],
pub_seed,
addr,
)?;
}
Ok(())
}
+662
View File
@@ -0,0 +1,662 @@
use core::marker::PhantomData;
use hybrid_array::Array;
use zeroize::Zeroize;
use crate::error::{Error, XmssResult};
use crate::params::{XMSS_OID_LEN, XmssOid, XmssParameter, XmssParams};
use crate::xmss_commons;
use crate::xmss_core;
/// Parses the OID and initializes params from the first bytes of a key.
/// Tries XMSS OIDs first, then falls back to XMSSMT.
fn parse_oid_and_params(bytes: &[u8]) -> XmssResult<(XmssOid, XmssParams)> {
if bytes.len() < XMSS_OID_LEN {
return Err(Error::InvalidOid(0));
}
let mut raw_oid: u32 = 0;
for i in 0..XMSS_OID_LEN {
raw_oid |= (bytes[XMSS_OID_LEN - i - 1] as u32) << (i * 8);
}
let oid = XmssOid::try_from(raw_oid).or_else(|_| XmssOid::from_xmssmt_raw_oid(raw_oid))?;
let mut params = XmssParams::default();
oid.initialize(&mut params)?;
Ok((oid, params))
}
// ---------------------------------------------------------------------------
// SigningKey<P>
// ---------------------------------------------------------------------------
/// An XMSS signing key (secret key).
#[derive(Clone)]
pub struct SigningKey<P: XmssParameter> {
bytes: Array<u8, P::SkLen>,
params: XmssParams,
_marker: PhantomData<P>,
}
impl<P: XmssParameter> core::fmt::Debug for SigningKey<P> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SigningKey")
.field("parameter_set", &P::NAME)
.field("bytes", &"[REDACTED]")
.finish()
}
}
impl<P: XmssParameter> Drop for SigningKey<P> {
fn drop(&mut self) {
self.zeroize();
}
}
impl<P: XmssParameter> Zeroize for SigningKey<P> {
fn zeroize(&mut self) {
self.bytes.zeroize();
}
}
impl<P: XmssParameter> AsRef<[u8]> for SigningKey<P> {
fn as_ref(&self) -> &[u8] {
self.bytes.as_ref()
}
}
impl<P: XmssParameter> TryFrom<&[u8]> for SigningKey<P> {
type Error = Error;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let (oid, params) = parse_oid_and_params(value)?;
let expected_oid = P::oid();
if oid != expected_oid {
return Err(Error::InvalidOid(oid.raw_oid()));
}
if value.len() != P::SK_LEN {
return Err(Error::InvalidKeyLength {
expected: P::SK_LEN,
got: value.len(),
});
}
let bytes = Array::try_from(value).map_err(|_| Error::InvalidKeyLength {
expected: P::SK_LEN,
got: value.len(),
})?;
Ok(Self {
bytes,
params,
_marker: PhantomData,
})
}
}
impl<P: XmssParameter> TryFrom<Vec<u8>> for SigningKey<P> {
type Error = Error;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
SigningKey::<P>::try_from(value.as_slice())
}
}
impl<P: XmssParameter> TryFrom<&Vec<u8>> for SigningKey<P> {
type Error = Error;
fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {
SigningKey::<P>::try_from(value.as_slice())
}
}
impl<P: XmssParameter> TryFrom<Box<[u8]>> for SigningKey<P> {
type Error = Error;
fn try_from(value: Box<[u8]>) -> Result<Self, Self::Error> {
SigningKey::<P>::try_from(value.as_ref())
}
}
#[cfg(test)]
impl<P: XmssParameter> PartialEq for SigningKey<P> {
fn eq(&self, other: &Self) -> bool {
self.bytes[..] == other.bytes[..]
}
}
#[cfg(test)]
impl<P: XmssParameter> Eq for SigningKey<P> {}
#[cfg(test)]
impl<P: XmssParameter> core::hash::Hash for SigningKey<P> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.bytes[..].hash(state);
}
}
#[cfg(feature = "serde")]
impl<P: XmssParameter> serdect::serde::Serialize for SigningKey<P> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serdect::serde::Serializer,
{
serdect::slice::serialize_hex_lower_or_bin(&self.bytes, serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, P: XmssParameter> serdect::serde::Deserialize<'de> for SigningKey<P> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serdect::serde::Deserializer<'de>,
{
let bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?;
Self::try_from(bytes).map_err(serdect::serde::de::Error::custom)
}
}
impl<P: XmssParameter> SigningKey<P> {
pub(crate) fn new(bytes: Array<u8, P::SkLen>, params: XmssParams) -> Self {
Self {
bytes,
params,
_marker: PhantomData,
}
}
/// Signs a message. Returns the signature followed by the message and updates
/// the internal state (one-time key index is advanced).
pub fn sign(&mut self, m: &[u8]) -> XmssResult<Signature<P>> {
xmss_core::xmssmt_core_sign(&self.params, &mut self.bytes[XMSS_OID_LEN..], m).map(|bytes| {
Signature {
bytes,
_marker: PhantomData,
}
})
}
/// Signs a message, returning only the detached signature (without the message appended).
pub fn sign_detached(&mut self, m: &[u8]) -> XmssResult<DetachedSignature<P>> {
let mut sm = xmss_core::xmssmt_core_sign(&self.params, &mut self.bytes[XMSS_OID_LEN..], m)?;
let sig_bytes = sm.len() - m.len();
sm.truncate(sig_bytes);
Ok(DetachedSignature {
bytes: sm.into_boxed_slice(),
_marker: PhantomData,
})
}
}
impl<P: XmssParameter> signature::SignerMut<DetachedSignature<P>> for SigningKey<P> {
fn try_sign(&mut self, msg: &[u8]) -> Result<DetachedSignature<P>, signature::Error> {
self.sign_detached(msg).map_err(|_| signature::Error::new())
}
}
impl<P: XmssParameter> signature::Keypair for SigningKey<P> {
type VerifyingKey = VerifyingKey<P>;
fn verifying_key(&self) -> Self::VerifyingKey {
VerifyingKey::from(self)
}
}
// ---------------------------------------------------------------------------
// VerifyingKey<P>
// ---------------------------------------------------------------------------
/// An XMSS verifying key (public key).
#[derive(Clone, Debug)]
pub struct VerifyingKey<P: XmssParameter> {
bytes: Array<u8, P::VkLen>,
params: XmssParams,
_marker: PhantomData<P>,
}
impl<P: XmssParameter> AsRef<[u8]> for VerifyingKey<P> {
fn as_ref(&self) -> &[u8] {
self.bytes.as_ref()
}
}
impl<P: XmssParameter> TryFrom<&[u8]> for VerifyingKey<P> {
type Error = Error;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
let (oid, params) = parse_oid_and_params(value)?;
let expected_oid = P::oid();
if oid != expected_oid {
return Err(Error::InvalidOid(oid.raw_oid()));
}
if value.len() != P::VK_LEN {
return Err(Error::InvalidKeyLength {
expected: P::VK_LEN,
got: value.len(),
});
}
let bytes = Array::try_from(value).map_err(|_| Error::InvalidKeyLength {
expected: P::VK_LEN,
got: value.len(),
})?;
Ok(Self {
bytes,
params,
_marker: PhantomData,
})
}
}
impl<P: XmssParameter> TryFrom<Vec<u8>> for VerifyingKey<P> {
type Error = Error;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
VerifyingKey::<P>::try_from(value.as_slice())
}
}
impl<P: XmssParameter> TryFrom<&Vec<u8>> for VerifyingKey<P> {
type Error = Error;
fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {
VerifyingKey::<P>::try_from(value.as_slice())
}
}
impl<P: XmssParameter> TryFrom<Box<[u8]>> for VerifyingKey<P> {
type Error = Error;
fn try_from(value: Box<[u8]>) -> Result<Self, Self::Error> {
VerifyingKey::<P>::try_from(value.as_ref())
}
}
impl<P: XmssParameter> PartialEq for VerifyingKey<P> {
fn eq(&self, other: &Self) -> bool {
self.bytes[..] == other.bytes[..]
}
}
impl<P: XmssParameter> Eq for VerifyingKey<P> {}
impl<P: XmssParameter> core::hash::Hash for VerifyingKey<P> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.bytes[..].hash(state);
}
}
#[cfg(feature = "serde")]
impl<P: XmssParameter> serdect::serde::Serialize for VerifyingKey<P> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serdect::serde::Serializer,
{
serdect::slice::serialize_hex_lower_or_bin(&self.bytes, serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, P: XmssParameter> serdect::serde::Deserialize<'de> for VerifyingKey<P> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serdect::serde::Deserializer<'de>,
{
let bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?;
Self::try_from(bytes).map_err(serdect::serde::de::Error::custom)
}
}
impl<P: XmssParameter> VerifyingKey<P> {
pub(crate) fn new(bytes: Array<u8, P::VkLen>, params: XmssParams) -> Self {
Self {
bytes,
params,
_marker: PhantomData,
}
}
/// Verifies a given message signature pair.
/// Returns the verified message on success.
pub fn verify(&self, signature: &Signature<P>) -> XmssResult<Vec<u8>> {
let mut m = Vec::new();
xmss_commons::xmssmt_core_sign_open(
&self.params,
&mut m,
&signature.bytes,
&self.bytes[XMSS_OID_LEN..],
)?;
Ok(m)
}
/// Verifies a detached signature against the provided message.
pub fn verify_detached(&self, signature: &DetachedSignature<P>, m: &[u8]) -> XmssResult<()> {
let mut sm = Vec::with_capacity(signature.bytes.len() + m.len());
sm.extend_from_slice(&signature.bytes);
sm.extend_from_slice(m);
let mut msg = Vec::new();
xmss_commons::xmssmt_core_sign_open(
&self.params,
&mut msg,
&sm,
&self.bytes[XMSS_OID_LEN..],
)?;
Ok(())
}
}
impl<P: XmssParameter> From<&SigningKey<P>> for VerifyingKey<P> {
fn from(sk: &SigningKey<P>) -> Self {
let n = sk.params.n as usize;
let idx_bytes = sk.params.index_bytes as usize;
// sk after OID: [idx || SK_SEED || SK_PRF || root || PUB_SEED]
let root_start = XMSS_OID_LEN + idx_bytes + 2 * n;
// pk: [OID || root || PUB_SEED]
let mut pk = Array::<u8, P::VkLen>::default();
pk[..XMSS_OID_LEN].copy_from_slice(&sk.bytes[..XMSS_OID_LEN]);
pk[XMSS_OID_LEN..].copy_from_slice(&sk.bytes[root_start..root_start + 2 * n]);
VerifyingKey {
bytes: pk,
params: sk.params,
_marker: PhantomData,
}
}
}
impl<P: XmssParameter> signature::Verifier<DetachedSignature<P>> for VerifyingKey<P> {
fn verify(&self, msg: &[u8], signature: &DetachedSignature<P>) -> Result<(), signature::Error> {
self.verify_detached(signature, msg)
.map_err(|_| signature::Error::new())
}
}
// ---------------------------------------------------------------------------
// Signature<P>
// ---------------------------------------------------------------------------
/// An XMSS signature (signature + message, variable length).
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Signature<P: XmssParameter> {
bytes: Vec<u8>,
_marker: PhantomData<P>,
}
impl<P: XmssParameter> AsRef<[u8]> for Signature<P> {
fn as_ref(&self) -> &[u8] {
&self.bytes
}
}
impl<P: XmssParameter> TryFrom<&[u8]> for Signature<P> {
type Error = Error;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
Ok(Signature {
bytes: value.to_vec(),
_marker: PhantomData,
})
}
}
impl<P: XmssParameter> TryFrom<Vec<u8>> for Signature<P> {
type Error = Error;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
Ok(Signature {
bytes: value,
_marker: PhantomData,
})
}
}
impl<P: XmssParameter> TryFrom<&Vec<u8>> for Signature<P> {
type Error = Error;
fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {
Signature::<P>::try_from(value.as_slice())
}
}
impl<P: XmssParameter> TryFrom<Box<[u8]>> for Signature<P> {
type Error = Error;
fn try_from(value: Box<[u8]>) -> Result<Self, Self::Error> {
Signature::<P>::try_from(value.as_ref())
}
}
impl<P: XmssParameter> From<Signature<P>> for Vec<u8> {
fn from(sig: Signature<P>) -> Vec<u8> {
sig.bytes
}
}
#[cfg(feature = "serde")]
impl<P: XmssParameter> serdect::serde::Serialize for Signature<P> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serdect::serde::Serializer,
{
serdect::slice::serialize_hex_lower_or_bin(&self.bytes, serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, P: XmssParameter> serdect::serde::Deserialize<'de> for Signature<P> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serdect::serde::Deserializer<'de>,
{
let bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?;
Self::try_from(bytes).map_err(serdect::serde::de::Error::custom)
}
}
impl<P: XmssParameter> signature::SignatureEncoding for Signature<P> {
type Repr = Vec<u8>;
}
// ---------------------------------------------------------------------------
// DetachedSignature<P>
// ---------------------------------------------------------------------------
/// A fixed-size detached XMSS signature (without appended message).
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct DetachedSignature<P: XmssParameter> {
bytes: Box<[u8]>,
_marker: PhantomData<P>,
}
impl<P: XmssParameter> AsRef<[u8]> for DetachedSignature<P> {
fn as_ref(&self) -> &[u8] {
&self.bytes
}
}
impl<P: XmssParameter> TryFrom<&[u8]> for DetachedSignature<P> {
type Error = Error;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
if value.len() != P::SIG_LEN {
return Err(Error::InvalidSignatureLength {
expected: P::SIG_LEN,
got: value.len(),
});
}
Ok(DetachedSignature {
bytes: value.into(),
_marker: PhantomData,
})
}
}
impl<P: XmssParameter> TryFrom<Vec<u8>> for DetachedSignature<P> {
type Error = Error;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
if value.len() != P::SIG_LEN {
return Err(Error::InvalidSignatureLength {
expected: P::SIG_LEN,
got: value.len(),
});
}
Ok(DetachedSignature {
bytes: value.into_boxed_slice(),
_marker: PhantomData,
})
}
}
impl<P: XmssParameter> TryFrom<&Vec<u8>> for DetachedSignature<P> {
type Error = Error;
fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {
DetachedSignature::<P>::try_from(value.as_slice())
}
}
impl<P: XmssParameter> TryFrom<Box<[u8]>> for DetachedSignature<P> {
type Error = Error;
fn try_from(value: Box<[u8]>) -> Result<Self, Self::Error> {
if value.len() != P::SIG_LEN {
return Err(Error::InvalidSignatureLength {
expected: P::SIG_LEN,
got: value.len(),
});
}
Ok(DetachedSignature {
bytes: value,
_marker: PhantomData,
})
}
}
impl<P: XmssParameter> From<DetachedSignature<P>> for Vec<u8> {
fn from(sig: DetachedSignature<P>) -> Vec<u8> {
sig.bytes.into_vec()
}
}
impl<P: XmssParameter> From<DetachedSignature<P>> for Box<[u8]> {
fn from(sig: DetachedSignature<P>) -> Box<[u8]> {
sig.bytes
}
}
#[cfg(feature = "serde")]
impl<P: XmssParameter> serdect::serde::Serialize for DetachedSignature<P> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serdect::serde::Serializer,
{
serdect::slice::serialize_hex_lower_or_bin(&self.bytes, serializer)
}
}
#[cfg(feature = "serde")]
impl<'de, P: XmssParameter> serdect::serde::Deserialize<'de> for DetachedSignature<P> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serdect::serde::Deserializer<'de>,
{
let bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?;
Self::try_from(bytes).map_err(serdect::serde::de::Error::custom)
}
}
impl<P: XmssParameter> signature::SignatureEncoding for DetachedSignature<P> {
type Repr = Box<[u8]>;
}
// ---------------------------------------------------------------------------
// KeyPair<P>
// ---------------------------------------------------------------------------
/// An XMSS key pair containing both signing and verifying keys.
#[derive(Clone, Debug)]
pub struct KeyPair<P: XmssParameter> {
signing_key: SigningKey<P>,
verifying_key: VerifyingKey<P>,
}
impl<P: XmssParameter> KeyPair<P> {
#[cfg(feature = "pkcs8")]
pub(crate) fn new(signing_key: SigningKey<P>, verifying_key: VerifyingKey<P>) -> Self {
Self {
signing_key,
verifying_key,
}
}
/// Generates a random key pair for the parameter set `P`.
pub fn generate<R: rand::CryptoRng>(rng: &mut R) -> XmssResult<Self> {
let oid = P::oid();
let (params, mut pk, mut sk) = oid.init_keypair_buffers(None)?;
xmss_core::xmssmt_core_keypair(
&params,
&mut pk[XMSS_OID_LEN..],
&mut sk[XMSS_OID_LEN..],
rng,
)?;
let pk_array = Array::<u8, P::VkLen>::try_from(pk.as_slice()).map_err(|_| {
Error::InvalidKeyLength {
expected: P::VK_LEN,
got: pk.len(),
}
})?;
let sk_array = Array::<u8, P::SkLen>::try_from(sk.as_slice()).map_err(|_| {
Error::InvalidKeyLength {
expected: P::SK_LEN,
got: sk.len(),
}
})?;
Ok(Self {
verifying_key: VerifyingKey::new(pk_array, params),
signing_key: SigningKey::new(sk_array, params),
})
}
/// Generates a key pair from a deterministic seed.
/// Seed must be `P::SEED_LEN` bytes (3*n).
pub fn from_seed(seed: &[u8]) -> XmssResult<Self> {
let oid = P::oid();
let (params, mut pk, mut sk) = oid.init_keypair_buffers(Some(seed))?;
xmss_core::xmssmt_core_seed_keypair(
&params,
&mut pk[XMSS_OID_LEN..],
&mut sk[XMSS_OID_LEN..],
seed,
)?;
let pk_array = Array::<u8, P::VkLen>::try_from(pk.as_slice()).map_err(|_| {
Error::InvalidKeyLength {
expected: P::VK_LEN,
got: pk.len(),
}
})?;
let sk_array = Array::<u8, P::SkLen>::try_from(sk.as_slice()).map_err(|_| {
Error::InvalidKeyLength {
expected: P::SK_LEN,
got: sk.len(),
}
})?;
Ok(Self {
verifying_key: VerifyingKey::new(pk_array, params),
signing_key: SigningKey::new(sk_array, params),
})
}
/// Returns a mutable reference to the signing key.
pub fn signing_key(&mut self) -> &mut SigningKey<P> {
&mut self.signing_key
}
/// Returns a reference to the verifying key.
pub fn verifying_key(&self) -> &VerifyingKey<P> {
&self.verifying_key
}
/// Returns a shared reference to the signing key (non-mut).
#[cfg(any(feature = "pkcs8", test))]
pub(crate) fn signing_key_ref(&self) -> &SigningKey<P> {
&self.signing_key
}
}
+218
View File
@@ -0,0 +1,218 @@
use subtle::ConstantTimeEq;
use crate::error::{Error, XmssResult};
use crate::hash::{hash_message, thash_h};
use crate::hash_address::*;
use crate::params::XmssParams;
use crate::utils::bytes_to_ull;
use crate::wots::{wots_pk_from_sig, wots_pkgen};
/// Computes a leaf node from a WOTS public key using an L-tree.
/// Note that this destroys the used WOTS public key.
fn l_tree(
params: &XmssParams,
leaf: &mut [u8],
wots_pk: &mut [u8],
pub_seed: &[u8],
addr: &mut [u32; 8],
) -> XmssResult<()> {
let n = params.n as usize;
let mut l = params.wots_len as usize;
let mut height: u32 = 0;
set_tree_height(addr, height);
while l > 1 {
let parent_nodes = l >> 1;
for i in 0..parent_nodes {
#[allow(clippy::cast_possible_truncation)]
set_tree_index(addr, i as u32);
let mut tmp = vec![0u8; 2 * n];
tmp.copy_from_slice(&wots_pk[i * 2 * n..(i * 2 + 2) * n]);
thash_h(
params,
&mut wots_pk[i * n..(i + 1) * n],
&tmp,
pub_seed,
addr,
)?;
}
if l & 1 != 0 {
let src_start = (l - 1) * n;
let dst_start = (l >> 1) * n;
let mut tmp = vec![0u8; n];
tmp.copy_from_slice(&wots_pk[src_start..src_start + n]);
wots_pk[dst_start..dst_start + n].copy_from_slice(&tmp);
l = (l >> 1) + 1;
} else {
l >>= 1;
}
height += 1;
set_tree_height(addr, height);
}
leaf[..n].copy_from_slice(&wots_pk[..n]);
Ok(())
}
/// Computes a root node given a leaf and an auth path.
fn compute_root(
params: &XmssParams,
root: &mut [u8],
leaf: &[u8],
mut leafidx: u32,
auth_path: &[u8],
pub_seed: &[u8],
addr: &mut [u32; 8],
) -> XmssResult<()> {
let n = params.n as usize;
let mut buffer = vec![0u8; 2 * n];
let mut auth_offset = 0usize;
if leafidx & 1 != 0 {
buffer[n..2 * n].copy_from_slice(&leaf[..n]);
buffer[..n].copy_from_slice(&auth_path[..n]);
} else {
buffer[..n].copy_from_slice(&leaf[..n]);
buffer[n..2 * n].copy_from_slice(&auth_path[..n]);
}
auth_offset += n;
for i in 0..params.tree_height - 1 {
set_tree_height(addr, i);
leafidx >>= 1;
set_tree_index(addr, leafidx);
if leafidx & 1 != 0 {
let tmp = buffer.clone();
thash_h(params, &mut buffer[n..2 * n], &tmp, pub_seed, addr)?;
buffer[..n].copy_from_slice(&auth_path[auth_offset..auth_offset + n]);
} else {
let tmp = buffer.clone();
thash_h(params, &mut buffer[..n], &tmp, pub_seed, addr)?;
buffer[n..2 * n].copy_from_slice(&auth_path[auth_offset..auth_offset + n]);
}
auth_offset += n;
}
set_tree_height(addr, params.tree_height - 1);
leafidx >>= 1;
set_tree_index(addr, leafidx);
thash_h(params, root, &buffer, pub_seed, addr)
}
/// Computes the leaf at a given address. First generates the WOTS key pair,
/// then computes leaf using l_tree.
pub fn gen_leaf_wots(
params: &XmssParams,
leaf: &mut [u8],
sk_seed: &[u8],
pub_seed: &[u8],
ltree_addr: &mut [u32; 8],
ots_addr: &mut [u32; 8],
) -> XmssResult<()> {
let mut pk = vec![0u8; params.wots_sig_bytes as usize];
wots_pkgen(params, &mut pk, sk_seed, pub_seed, ots_addr)?;
l_tree(params, leaf, &mut pk, pub_seed, ltree_addr)
}
/// Verifies a given message signature pair under a given public key.
/// Note that this assumes a pk without an OID, i.e. [root || PUB_SEED].
pub fn xmssmt_core_sign_open(
params: &XmssParams,
m: &mut Vec<u8>,
sm: &[u8],
pk: &[u8],
) -> XmssResult<()> {
let n = params.n as usize;
let pub_root = &pk[..n];
let pub_seed = &pk[n..2 * n];
let smlen = sm.len();
if smlen < params.sig_bytes as usize {
return Err(Error::VerificationFailed);
}
let mlen = smlen - params.sig_bytes as usize;
let mut wots_pk = vec![0u8; params.wots_sig_bytes as usize];
let mut leaf = vec![0u8; n];
let mut root = vec![0u8; n];
let mut ots_addr = [0u32; 8];
let mut ltree_addr = [0u32; 8];
let mut node_addr = [0u32; 8];
set_type(&mut ots_addr, XMSS_ADDR_TYPE_OTS);
set_type(&mut ltree_addr, XMSS_ADDR_TYPE_LTREE);
set_type(&mut node_addr, XMSS_ADDR_TYPE_HASHTREE);
let idx = bytes_to_ull(&sm[..params.index_bytes as usize]);
let prefix_len = params.padding_len as usize + 3 * n;
m.resize(params.sig_bytes as usize + mlen, 0);
m[params.sig_bytes as usize..].copy_from_slice(&sm[params.sig_bytes as usize..]);
let mhash = &mut root;
let prefix_start = params.sig_bytes as usize - prefix_len;
hash_message(
params,
mhash,
&sm[params.index_bytes as usize..],
pk,
idx,
&mut m[prefix_start..],
mlen as u64,
)?;
let mut sm_offset = params.index_bytes as usize + n;
for i in 0..params.d {
#[allow(clippy::cast_possible_truncation)] // masked to tree_height bits, always fits u32
let idx_leaf =
((idx >> (params.tree_height * i)) & ((1u64 << params.tree_height) - 1)) as u32;
let tree_idx = idx >> (params.tree_height * (i + 1));
set_layer_addr(&mut ots_addr, i);
set_layer_addr(&mut ltree_addr, i);
set_layer_addr(&mut node_addr, i);
set_tree_addr(&mut ltree_addr, tree_idx);
set_tree_addr(&mut ots_addr, tree_idx);
set_tree_addr(&mut node_addr, tree_idx);
set_ots_addr(&mut ots_addr, idx_leaf);
wots_pk_from_sig(
params,
&mut wots_pk,
&sm[sm_offset..],
&root,
pub_seed,
&mut ots_addr,
)?;
sm_offset += params.wots_sig_bytes as usize;
set_ltree_addr(&mut ltree_addr, idx_leaf);
l_tree(params, &mut leaf, &mut wots_pk, pub_seed, &mut ltree_addr)?;
compute_root(
params,
&mut root,
&leaf,
idx_leaf,
&sm[sm_offset..],
pub_seed,
&mut node_addr,
)?;
sm_offset += params.tree_height as usize * n;
}
if !bool::from(root.ct_eq(pub_root)) {
m.clear();
return Err(Error::VerificationFailed);
}
let msg = sm[params.sig_bytes as usize..].to_vec();
*m = msg;
Ok(())
}
+274
View File
@@ -0,0 +1,274 @@
use zeroize::Zeroize;
use crate::error::{Error, XmssResult};
use crate::hash::{hash_message, prf, thash_h};
use crate::hash_address::*;
use crate::params::XmssParams;
use crate::utils::{bytes_to_ull, ull_to_bytes};
use crate::wots::wots_sign;
use crate::xmss_commons::gen_leaf_wots;
/// For a given leaf index, computes the authentication path and the resulting
/// root node using Merkle's TreeHash algorithm.
fn treehash(
params: &XmssParams,
root: &mut [u8],
auth_path: &mut [u8],
sk_seed: &[u8],
pub_seed: &[u8],
leaf_idx: u32,
subtree_addr: &[u32; 8],
) -> XmssResult<()> {
let n = params.n as usize;
let tree_height = params.tree_height as usize;
let mut stack = vec![0u8; (tree_height + 1) * n];
let mut heights = vec![0u32; tree_height + 1];
let mut offset: usize = 0;
let mut ots_addr = [0u32; 8];
let mut ltree_addr = [0u32; 8];
let mut node_addr = [0u32; 8];
copy_subtree_addr(&mut ots_addr, subtree_addr);
copy_subtree_addr(&mut ltree_addr, subtree_addr);
copy_subtree_addr(&mut node_addr, subtree_addr);
set_type(&mut ots_addr, XMSS_ADDR_TYPE_OTS);
set_type(&mut ltree_addr, XMSS_ADDR_TYPE_LTREE);
set_type(&mut node_addr, XMSS_ADDR_TYPE_HASHTREE);
let num_leaves: u32 = 1 << params.tree_height;
for idx in 0..num_leaves {
set_ltree_addr(&mut ltree_addr, idx);
set_ots_addr(&mut ots_addr, idx);
gen_leaf_wots(
params,
&mut stack[offset * n..(offset + 1) * n],
sk_seed,
pub_seed,
&mut ltree_addr,
&mut ots_addr,
)?;
offset += 1;
heights[offset - 1] = 0;
if (leaf_idx ^ 0x1) == idx {
auth_path[..n].copy_from_slice(&stack[(offset - 1) * n..offset * n]);
}
while offset >= 2 && heights[offset - 1] == heights[offset - 2] {
let tree_idx = idx >> (heights[offset - 1] + 1);
set_tree_height(&mut node_addr, heights[offset - 1]);
set_tree_index(&mut node_addr, tree_idx);
let tmp = stack[(offset - 2) * n..offset * n].to_vec();
thash_h(
params,
&mut stack[(offset - 2) * n..(offset - 1) * n],
&tmp,
pub_seed,
&mut node_addr,
)?;
offset -= 1;
heights[offset - 1] += 1;
if ((leaf_idx >> heights[offset - 1]) ^ 0x1) == tree_idx {
let h = heights[offset - 1] as usize;
auth_path[h * n..(h + 1) * n].copy_from_slice(&stack[(offset - 1) * n..offset * n]);
}
}
}
root[..n].copy_from_slice(&stack[..n]);
Ok(())
}
/// Given a set of parameters, this function returns the size of the secret key.
pub fn xmss_xmssmt_core_sk_bytes(params: &XmssParams) -> u64 {
params.index_bytes as u64 + 4 * params.n as u64
}
/// Derives a XMSSMT key pair from a given seed.
/// Seed must be 3*n long.
/// Format sk: [(ceil(h/8) bit) index || SK_SEED || SK_PRF || root || PUB_SEED]
/// Format pk: [root || PUB_SEED] omitting algorithm OID.
pub fn xmssmt_core_seed_keypair(
params: &XmssParams,
pk: &mut [u8],
sk: &mut [u8],
seed: &[u8],
) -> XmssResult<()> {
let n = params.n as usize;
let idx_bytes = params.index_bytes as usize;
let tree_height = params.tree_height as usize;
let mut auth_path = vec![0u8; tree_height * n];
let mut top_tree_addr = [0u32; 8];
set_layer_addr(&mut top_tree_addr, params.d - 1);
for b in sk[..idx_bytes].iter_mut() {
*b = 0;
}
sk[idx_bytes..idx_bytes + 2 * n].copy_from_slice(&seed[..2 * n]);
sk[idx_bytes + 3 * n..idx_bytes + 4 * n].copy_from_slice(&seed[2 * n..3 * n]);
pk[n..2 * n].copy_from_slice(&sk[idx_bytes + 3 * n..idx_bytes + 4 * n]);
// Copy pub_seed since pk is mutably borrowed by treehash.
let pub_seed_copy = pk[n..2 * n].to_vec();
treehash(
params,
pk,
&mut auth_path,
&sk[idx_bytes..],
&pub_seed_copy,
0,
&top_tree_addr,
)?;
sk[idx_bytes + 2 * n..idx_bytes + 3 * n].copy_from_slice(&pk[..n]);
Ok(())
}
/// Generates a XMSSMT key pair for a given parameter set.
/// Format sk: [(ceil(h/8) bit) index || SK_SEED || SK_PRF || root || PUB_SEED]
/// Format pk: [root || PUB_SEED] omitting algorithm OID.
pub fn xmssmt_core_keypair<R: rand::CryptoRng>(
params: &XmssParams,
pk: &mut [u8],
sk: &mut [u8],
rng: &mut R,
) -> XmssResult<()> {
let n = params.n as usize;
let mut seed = vec![0u8; 3 * n];
rng.fill_bytes(&mut seed[..]);
let result = xmssmt_core_seed_keypair(params, pk, sk, &seed);
seed.zeroize();
result
}
/// Signs a message. Returns the signature followed by the message
/// and an updated secret key.
pub fn xmssmt_core_sign(params: &XmssParams, sk: &mut [u8], m: &[u8]) -> XmssResult<Vec<u8>> {
let n = params.n as usize;
let idx_bytes = params.index_bytes as usize;
let mlen = m.len();
let sig_bytes = params.sig_bytes as usize;
let sk_seed_start = idx_bytes;
let sk_prf_start = idx_bytes + n;
let pub_root_start = idx_bytes + 2 * n;
let pub_seed_start = idx_bytes + 3 * n;
let idx = bytes_to_ull(&sk[..idx_bytes]);
// Check if key is exhausted before doing anything.
let max_idx = if params.full_height >= 64 {
u64::MAX
} else {
(1u64 << params.full_height) - 1
};
if idx > max_idx {
return Err(Error::KeyExhausted);
}
// Copy secret values out before mutating sk.
let mut sk_seed = sk[sk_seed_start..sk_seed_start + n].to_vec();
let mut sk_prf = sk[sk_prf_start..sk_prf_start + n].to_vec();
let pub_root = sk[pub_root_start..pub_root_start + n].to_vec();
let pub_seed = sk[pub_seed_start..pub_seed_start + n].to_vec();
let mut sm = vec![0u8; sig_bytes + mlen];
let mut ots_addr = [0u32; 8];
set_type(&mut ots_addr, XMSS_ADDR_TYPE_OTS);
sm[sig_bytes..].copy_from_slice(m);
// Write index into signature.
sm[..idx_bytes].copy_from_slice(&sk[..idx_bytes]);
// Advance the index in sk.
if idx == max_idx {
// Last valid index — mark as exhausted for next call.
for b in sk[..idx_bytes].iter_mut() {
*b = 0xFF;
}
} else {
ull_to_bytes(&mut sk[..idx_bytes], idx + 1);
}
// Compute R (randomness for message hashing).
let mut idx_bytes_32 = [0u8; 32];
ull_to_bytes(&mut idx_bytes_32, idx);
prf(
params,
&mut sm[idx_bytes..idx_bytes + n],
&idx_bytes_32,
&sk_prf,
)?;
let mut root = vec![0u8; n];
let prefix_len = params.padding_len as usize + 3 * n;
let prefix_start = sig_bytes - prefix_len;
// Copy R out to avoid borrow conflict (sm is both read for R and mutated for prefix).
let r_val = sm[idx_bytes..idx_bytes + n].to_vec();
hash_message(
params,
&mut root,
&r_val,
&pub_root,
idx,
&mut sm[prefix_start..],
mlen as u64,
)?;
let mut sm_offset = idx_bytes + n;
for i in 0..params.d {
let idx_leaf = (idx >> (params.tree_height * i)) & ((1u64 << params.tree_height) - 1);
#[allow(clippy::cast_possible_truncation)] // masked to tree_height bits, always fits u32
let idx_leaf = idx_leaf as u32;
let tree_idx = idx >> (params.tree_height * (i + 1));
set_layer_addr(&mut ots_addr, i);
set_tree_addr(&mut ots_addr, tree_idx);
set_ots_addr(&mut ots_addr, idx_leaf);
wots_sign(
params,
&mut sm[sm_offset..],
&root,
&sk_seed,
&pub_seed,
&mut ots_addr,
)?;
sm_offset += params.wots_sig_bytes as usize;
treehash(
params,
&mut root,
&mut sm[sm_offset..],
&sk_seed,
&pub_seed,
idx_leaf,
&ots_addr,
)?;
sm_offset += params.tree_height as usize * n;
}
// Zeroize secret copies.
sk_seed.zeroize();
sk_prf.zeroize();
// If this was the last valid index, zero the secret key material in sk.
if idx == max_idx {
#[allow(clippy::cast_possible_truncation)]
let sk_bytes_len = params.sk_bytes as usize;
for b in sk[idx_bytes..sk_bytes_len].iter_mut() {
*b = 0;
}
}
Ok(sm)
}