commit 05d9fa00c2041c37298ca80e8f429037902fca65
parent 4de3186cb4b628ec2793665f42da50d3ed9ae2c0
Author: Joris Hartog <jorishartog@hotmail.com>
Date: Sun, 13 Sep 2026 20:56:36 +0200
feat(protocol): verify dormant hybrid signatures
Diffstat:
8 files changed, 433 insertions(+), 59 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
@@ -9,6 +9,7 @@ from the Git history and Conventional Commit titles by `deployment.sh`.
- expose complete peer handshake details in the P2P interface
- add dormant transaction-v2 encoding and activation gating
+- verify both components of dormant hybrid transaction authorizations
## [0.4.32] - 2026-09-13
diff --git a/Cargo.lock b/Cargo.lock
@@ -14,7 +14,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
dependencies = [
- "crypto-common",
+ "crypto-common 0.1.7",
"generic-array",
]
@@ -243,18 +243,30 @@ version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
- "crypto-common",
+ "crypto-common 0.1.7",
"inout",
"zeroize",
]
[[package]]
+name = "cmov"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
+
+[[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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
+
+[[package]]
name = "core-foundation"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -309,6 +321,26 @@ dependencies = [
]
[[package]]
+name = "crypto-common"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
+dependencies = [
+ "getrandom 0.4.3",
+ "hybrid-array",
+ "rand_core 0.10.1",
+]
+
+[[package]]
+name = "ctutils"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
+dependencies = [
+ "cmov",
+]
+
+[[package]]
name = "curve25519-dalek"
version = "4.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -317,7 +349,7 @@ dependencies = [
"cfg-if",
"cpufeatures 0.2.17",
"curve25519-dalek-derive",
- "digest",
+ "digest 0.10.7",
"fiat-crypto",
"rustc_version",
"subtle",
@@ -341,7 +373,17 @@ version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
- "const-oid",
+ "const-oid 0.9.6",
+ "zeroize",
+]
+
+[[package]]
+name = "der"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a878c850e9e421b20262e9b41f9c860e4785fa07541c266b62ff9d1ef998a80a"
+dependencies = [
+ "const-oid 0.10.2",
"zeroize",
]
@@ -352,11 +394,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
- "crypto-common",
+ "crypto-common 0.1.7",
"subtle",
]
[[package]]
+name = "digest"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
+dependencies = [
+ "crypto-common 0.2.2",
+]
+
+[[package]]
name = "displaydoc"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -373,8 +424,8 @@ version = "2.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
dependencies = [
- "pkcs8",
- "signature",
+ "pkcs8 0.10.2",
+ "signature 2.2.0",
]
[[package]]
@@ -582,7 +633,7 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
- "digest",
+ "digest 0.10.7",
]
[[package]]
@@ -631,6 +682,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
+name = "hybrid-array"
+version = "0.4.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17"
+dependencies = [
+ "ctutils",
+ "typenum",
+]
+
+[[package]]
name = "hyper"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -829,6 +890,7 @@ dependencies = [
"getrandom 0.2.17",
"kyn-vdf",
"minisign-verify",
+ "ml-dsa",
"num-bigint",
"num-integer",
"num-traits",
@@ -859,6 +921,16 @@ dependencies = [
]
[[package]]
+name = "keccak"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8f198d1db720e4940b5a493201d199d9f24f568f8f746bd13706243a2f71598"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.1",
+]
+
+[[package]]
name = "kyn-vdf"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -969,6 +1041,33 @@ dependencies = [
]
[[package]]
+name = "ml-dsa"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "add6b9d92e496f16f4526d68ff29da1483aba4b119baeab8bed3b9e3544a6f3d"
+dependencies = [
+ "const-oid 0.10.2",
+ "crypto-common 0.2.2",
+ "ctutils",
+ "hybrid-array",
+ "module-lattice",
+ "pkcs8 0.11.0",
+ "shake",
+ "signature 3.0.0",
+]
+
+[[package]]
+name = "module-lattice"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe"
+dependencies = [
+ "ctutils",
+ "hybrid-array",
+ "num-traits",
+]
+
+[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1043,7 +1142,7 @@ version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
dependencies = [
- "digest",
+ "digest 0.10.7",
"hmac",
]
@@ -1065,8 +1164,18 @@ version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
- "der",
- "spki",
+ "der 0.7.10",
+ "spki 0.7.3",
+]
+
+[[package]]
+name = "pkcs8"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7"
+dependencies = [
+ "der 0.8.2",
+ "spki 0.8.0",
]
[[package]]
@@ -1596,7 +1705,18 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures 0.2.17",
- "digest",
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "shake"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a"
+dependencies = [
+ "digest 0.11.3",
+ "keccak",
+ "sponge-cursor",
]
[[package]]
@@ -1625,6 +1745,16 @@ dependencies = [
]
[[package]]
+name = "signature"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5"
+dependencies = [
+ "digest 0.11.3",
+ "rand_core 0.10.1",
+]
+
+[[package]]
name = "simd-adler32"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1659,10 +1789,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
- "der",
+ "der 0.7.10",
]
[[package]]
+name = "spki"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f"
+dependencies = [
+ "base64ct",
+ "der 0.8.2",
+]
+
+[[package]]
+name = "sponge-cursor"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620"
+
+[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1928,7 +2074,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
dependencies = [
- "crypto-common",
+ "crypto-common 0.1.7",
"subtle",
]
diff --git a/Cargo.toml b/Cargo.toml
@@ -23,6 +23,7 @@ sha2 = "0.10.9"
rusqlite = { version = "0.32.1", features = ["bundled"] }
tokio = { version = "1.45.1", features = ["full"] }
kyn-vdf = "=0.1.1"
+ml-dsa = "=0.1.1"
secrecy = { version = "0.10.3", default-features = false, features = ["serde"] }
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls-native-roots"], optional = true }
semver = { version = "1", optional = true }
diff --git a/docs/quantum-migration.md b/docs/quantum-migration.md
@@ -108,9 +108,11 @@ the mempool and chain until a later reviewed release assigns an activation heigh
The reserved format binds the chain ID and genesis hash, uses typed versioned addresses, stores one
length-delimited authorization per spending input, and hashes the complete canonical signed bytes
-for its transaction ID. The initial spending authorization is Ed25519 + ML-DSA-44. Only the
-classical component is currently executable; selecting and reviewing an ML-DSA backend remains a
-separate prerequisite before activation. No transaction-v2 gossip capability is advertised yet.
+for its transaction ID. The initial spending authorization is Ed25519 + ML-DSA-44. Dormant
+verification uses the exact-pinned RustCrypto `ml-dsa` 0.1.1 implementation. That implementation
+has not been independently audited, so an independent review and an explicit backend acceptance
+decision remain prerequisites before activation. No transaction-v2 gossip capability is
+advertised yet.
## Other trust boundaries
diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock
@@ -8,7 +8,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
dependencies = [
- "crypto-common",
+ "crypto-common 0.1.7",
"generic-array",
]
@@ -185,7 +185,7 @@ checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
dependencies = [
"cfg-if",
"cipher",
- "cpufeatures",
+ "cpufeatures 0.2.17",
]
[[package]]
@@ -207,18 +207,30 @@ version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
- "crypto-common",
+ "crypto-common 0.1.7",
"inout",
"zeroize",
]
[[package]]
+name = "cmov"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
+
+[[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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
+
+[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -228,26 +240,55 @@ dependencies = [
]
[[package]]
+name = "cpufeatures"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
- "rand_core",
+ "rand_core 0.6.4",
"typenum",
]
[[package]]
+name = "crypto-common"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
+dependencies = [
+ "getrandom 0.4.3",
+ "hybrid-array",
+ "rand_core 0.10.1",
+]
+
+[[package]]
+name = "ctutils"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
+dependencies = [
+ "cmov",
+]
+
+[[package]]
name = "curve25519-dalek"
version = "4.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
dependencies = [
"cfg-if",
- "cpufeatures",
+ "cpufeatures 0.2.17",
"curve25519-dalek-derive",
- "digest",
+ "digest 0.10.7",
"fiat-crypto",
"rustc_version",
"subtle",
@@ -271,7 +312,17 @@ version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
- "const-oid",
+ "const-oid 0.9.6",
+ "zeroize",
+]
+
+[[package]]
+name = "der"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a878c850e9e421b20262e9b41f9c860e4785fa07541c266b62ff9d1ef998a80a"
+dependencies = [
+ "const-oid 0.10.2",
"zeroize",
]
@@ -282,18 +333,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
- "crypto-common",
+ "crypto-common 0.1.7",
"subtle",
]
[[package]]
+name = "digest"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
+dependencies = [
+ "crypto-common 0.2.2",
+]
+
+[[package]]
name = "ed25519"
version = "2.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
dependencies = [
- "pkcs8",
- "signature",
+ "pkcs8 0.10.2",
+ "signature 2.2.0",
]
[[package]]
@@ -416,6 +476,7 @@ dependencies = [
"cfg-if",
"libc",
"r-efi",
+ "rand_core 0.10.1",
]
[[package]]
@@ -451,7 +512,7 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
- "digest",
+ "digest 0.10.7",
]
[[package]]
@@ -500,6 +561,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
+name = "hybrid-array"
+version = "0.4.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17"
+dependencies = [
+ "ctutils",
+ "typenum",
+]
+
+[[package]]
name = "hyper"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -560,6 +631,7 @@ dependencies = [
"ed25519-dalek",
"getrandom 0.2.17",
"kyn-vdf",
+ "ml-dsa",
"num-bigint",
"num-integer",
"num-traits",
@@ -592,6 +664,16 @@ dependencies = [
]
[[package]]
+name = "keccak"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8f198d1db720e4940b5a493201d199d9f24f568f8f746bd13706243a2f71598"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.1",
+]
+
+[[package]]
name = "kyn-vdf"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -678,6 +760,33 @@ dependencies = [
]
[[package]]
+name = "ml-dsa"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "add6b9d92e496f16f4526d68ff29da1483aba4b119baeab8bed3b9e3544a6f3d"
+dependencies = [
+ "const-oid 0.10.2",
+ "crypto-common 0.2.2",
+ "ctutils",
+ "hybrid-array",
+ "module-lattice",
+ "pkcs8 0.11.0",
+ "shake",
+ "signature 3.0.0",
+]
+
+[[package]]
+name = "module-lattice"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe"
+dependencies = [
+ "ctutils",
+ "hybrid-array",
+ "num-traits",
+]
+
+[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -746,7 +855,7 @@ version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
dependencies = [
- "digest",
+ "digest 0.10.7",
"hmac",
]
@@ -768,8 +877,18 @@ version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
- "der",
- "spki",
+ "der 0.7.10",
+ "spki 0.7.3",
+]
+
+[[package]]
+name = "pkcs8"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7"
+dependencies = [
+ "der 0.8.2",
+ "spki 0.8.0",
]
[[package]]
@@ -784,7 +903,7 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
dependencies = [
- "cpufeatures",
+ "cpufeatures 0.2.17",
"opaque-debug",
"universal-hash",
]
@@ -823,6 +942,12 @@ dependencies = [
]
[[package]]
+name = "rand_core"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
+
+[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -961,8 +1086,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
- "cpufeatures",
- "digest",
+ "cpufeatures 0.2.17",
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "shake"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a"
+dependencies = [
+ "digest 0.11.3",
+ "keccak",
+ "sponge-cursor",
]
[[package]]
@@ -987,7 +1123,17 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
- "rand_core",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "signature"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5"
+dependencies = [
+ "digest 0.11.3",
+ "rand_core 0.10.1",
]
[[package]]
@@ -1019,10 +1165,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
- "der",
+ "der 0.7.10",
+]
+
+[[package]]
+name = "spki"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f"
+dependencies = [
+ "base64ct",
+ "der 0.8.2",
]
[[package]]
+name = "sponge-cursor"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620"
+
+[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1194,7 +1356,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
dependencies = [
- "crypto-common",
+ "crypto-common 0.1.7",
"subtle",
]
diff --git a/src/domain.rs b/src/domain.rs
@@ -86,7 +86,7 @@ pub use reveal::{
use selection::BlockSelection;
pub use signature::{ProtocolPublicKey, ProtocolSignature, SignatureScheme};
pub(crate) use signature::{
- ed25519_public_key, sign_ed25519, validate_ed25519_public_key, verify_ed25519,
+ ed25519_public_key, sign_ed25519, validate_ed25519_public_key, verify_ed25519, verify_ml_dsa44,
};
pub use stratum::{
STRATUM_EXTRANONCE1_HEX, STRATUM_EXTRANONCE2_SIZE, StratumMineShare, StratumMineTemplate,
diff --git a/src/domain/signature.rs b/src/domain/signature.rs
@@ -1,5 +1,8 @@
use anyhow::{Context, Result, bail};
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
+use ml_dsa::{
+ EncodedVerifyingKey, MlDsa44, Signature as MlDsaSignature, VerifyingKey as MlDsaVerifyingKey,
+};
/// Signature schemes understood by the protocol implementation.
///
@@ -177,6 +180,23 @@ pub(crate) fn verify_ed25519(
.with_context(|| format!("{label} signature is invalid"))
}
+pub(crate) fn verify_ml_dsa44(
+ public_key: &[u8; 1_312],
+ payload: &[u8],
+ signature: &[u8; 2_420],
+ label: &str,
+) -> Result<()> {
+ let encoded_public_key = EncodedVerifyingKey::<MlDsa44>::try_from(public_key.as_slice())
+ .with_context(|| format!("invalid {label} ML-DSA-44 public key length"))?;
+ let public_key = MlDsaVerifyingKey::<MlDsa44>::decode(&encoded_public_key);
+ let signature = MlDsaSignature::<MlDsa44>::try_from(signature.as_slice())
+ .with_context(|| format!("invalid {label} ML-DSA-44 signature encoding"))?;
+ if !public_key.verify_with_context(payload, &[], &signature) {
+ bail!("{label} ML-DSA-44 signature is invalid");
+ }
+ Ok(())
+}
+
#[cfg(test)]
mod tests {
use super::{
diff --git a/src/domain/transaction_v2.rs b/src/domain/transaction_v2.rs
@@ -3,7 +3,7 @@ use sha2::{Digest, Sha256};
use super::{
AddressVersion, ProtocolPublicKey, ProtocolSignature, SignatureScheme, VersionedAddress,
- verify_ed25519,
+ verify_ed25519, verify_ml_dsa44,
};
const TRANSACTION_V2_TAG: &[u8] = b"IUNA-TX-V2";
@@ -263,24 +263,40 @@ impl TransactionV2 {
Ok(())
}
- /// Verifies the Ed25519 half of every hybrid signature. ML-DSA verification remains dormant
- /// until a reviewed cryptographic backend is selected; this method must not imply activation.
- pub fn verify_classical_hybrid_components(&self, domain: &TransactionV2Domain) -> Result<()> {
+ /// Verifies both components of every hybrid spending authorization. This remains unreachable
+ /// from live consensus while `TRANSACTION_V2_ACTIVATION_HEIGHT` is `None`.
+ pub fn verify_hybrid_authorizations(&self, domain: &TransactionV2Domain) -> Result<()> {
self.validate_authorization_commitments()?;
let payload = self.signing_bytes(domain)?;
for authorization in self.authorizations() {
- let public_key: [u8; 32] = authorization.public_key().as_bytes()[..32]
+ let (ed25519_public_key, ml_dsa_public_key) =
+ authorization.public_key().as_bytes().split_at(32);
+ let (ed25519_signature, ml_dsa_signature) =
+ authorization.signature().as_bytes().split_at(64);
+ let ed25519_public_key: [u8; 32] = ed25519_public_key
.try_into()
.expect("validated hybrid public key length");
- let signature: [u8; 64] = authorization.signature().as_bytes()[..64]
+ let ml_dsa_public_key: [u8; 1_312] = ml_dsa_public_key
+ .try_into()
+ .expect("validated hybrid public key length");
+ let ed25519_signature: [u8; 64] = ed25519_signature
+ .try_into()
+ .expect("validated hybrid signature length");
+ let ml_dsa_signature: [u8; 2_420] = ml_dsa_signature
.try_into()
.expect("validated hybrid signature length");
verify_ed25519(
- &public_key,
+ &ed25519_public_key,
&payload,
- &signature,
+ &ed25519_signature,
"transaction v2 classical component",
)?;
+ verify_ml_dsa44(
+ &ml_dsa_public_key,
+ &payload,
+ &ml_dsa_signature,
+ "transaction v2 post-quantum component",
+ )?;
}
Ok(())
}
@@ -673,6 +689,7 @@ impl<'a> Reader<'a> {
#[cfg(test)]
mod tests {
use ed25519_dalek::{Signer, SigningKey};
+ use ml_dsa::{Keypair, MlDsa44, Seed, SigningKey as MlDsaSigningKey};
use super::*;
use crate::domain::hex::hex_encode;
@@ -681,14 +698,28 @@ mod tests {
TransactionV2Domain::new("iuna-v2-test", [0x22; 32]).unwrap()
}
- fn hybrid_authorization(payload: &[u8]) -> V2SpendingAuthorization {
+ fn hybrid_public_key() -> ProtocolPublicKey {
let signing_key = SigningKey::from_bytes(&[7; 32]);
let mut public_key = signing_key.verifying_key().to_bytes().to_vec();
- public_key.extend_from_slice(&vec![0x44; 1_312]);
+ let ml_dsa_signing_key = MlDsaSigningKey::<MlDsa44>::from_seed(&Seed::from([9; 32]));
+ public_key.extend_from_slice(&ml_dsa_signing_key.verifying_key().encode());
+ ProtocolPublicKey::new(SignatureScheme::HybridEd25519MlDsa44, public_key).unwrap()
+ }
+
+ fn hybrid_authorization(payload: &[u8]) -> V2SpendingAuthorization {
+ let signing_key = SigningKey::from_bytes(&[7; 32]);
+ let public_key = hybrid_public_key();
let mut signature = signing_key.sign(payload).to_bytes().to_vec();
- signature.extend_from_slice(&vec![0x55; 2_420]);
+ let ml_dsa_signing_key = MlDsaSigningKey::<MlDsa44>::from_seed(&Seed::from([9; 32]));
+ signature.extend_from_slice(
+ &ml_dsa_signing_key
+ .expanded_key()
+ .sign_deterministic(payload, &[])
+ .unwrap()
+ .encode(),
+ );
V2SpendingAuthorization::new(
- ProtocolPublicKey::new(SignatureScheme::HybridEd25519MlDsa44, public_key).unwrap(),
+ public_key,
ProtocolSignature::new(SignatureScheme::HybridEd25519MlDsa44, signature).unwrap(),
)
.unwrap()
@@ -723,12 +754,7 @@ mod tests {
#[test]
fn hybrid_transfer_roundtrips_and_has_a_hash_id() {
- let signing_key = SigningKey::from_bytes(&[7; 32]);
- let mut public_key_bytes = signing_key.verifying_key().to_bytes().to_vec();
- public_key_bytes.extend_from_slice(&vec![0x44; 1_312]);
- let public_key =
- ProtocolPublicKey::new(SignatureScheme::HybridEd25519MlDsa44, public_key_bytes)
- .unwrap();
+ let public_key = hybrid_public_key();
let owner = hybrid_key_commitment_address(&public_key).unwrap();
let mut transaction = unsigned_transfer(owner);
let signing_bytes = transaction.signing_bytes(&domain()).unwrap();
@@ -742,9 +768,7 @@ mod tests {
owner
);
transaction.validate_authorization_commitments().unwrap();
- transaction
- .verify_classical_hybrid_components(&domain())
- .unwrap();
+ transaction.verify_hybrid_authorizations(&domain()).unwrap();
let encoded = transaction.encode(&domain()).unwrap();
let (decoded_domain, decoded) = TransactionV2::decode(&encoded).unwrap();
assert_eq!(decoded_domain, domain());
@@ -766,6 +790,24 @@ mod tests {
inputs[0].owner.payload[0] ^= 1;
}
assert!(wrong_owner.validate_authorization_commitments().is_err());
+
+ let mut invalid_post_quantum_signature = transaction.clone();
+ if let TransactionV2::Transfer { authorizations, .. } = &mut invalid_post_quantum_signature
+ {
+ let public_key = authorizations[0].public_key().clone();
+ let mut signature = authorizations[0].signature().as_bytes().to_vec();
+ signature[64] ^= 1;
+ authorizations[0] = V2SpendingAuthorization::new(
+ public_key,
+ ProtocolSignature::new(SignatureScheme::HybridEd25519MlDsa44, signature).unwrap(),
+ )
+ .unwrap();
+ }
+ assert!(
+ invalid_post_quantum_signature
+ .verify_hybrid_authorizations(&domain())
+ .is_err()
+ );
}
#[test]