iuna

iuna

iuna - experimental mainnet-candidate protocol
git clone https://getiuna.org/git/iuna.git
Log | Files | Refs | README | LICENSE

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:
MCHANGELOG.md | 1+
MCargo.lock | 174++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
MCargo.toml | 1+
Mdocs/quantum-migration.md | 8+++++---
Mfuzz/Cargo.lock | 202+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Msrc/domain.rs | 2+-
Msrc/domain/signature.rs | 20++++++++++++++++++++
Msrc/domain/transaction_v2.rs | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
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]