Replace Ciphersuite::hash_to_F

The prior-present `Ciphersuite::hash_to_F` was a sin. Implementations took a
DST, yet were not require to securely handle it. It was also biased towards the
requirements of `modular-frost` as `ciphersuite` was originally written all
those years ago, when `modular-frost` had needs exceeding what `ff`, `group`
satisfied.

Now, the hash is bound to produce an output which can be converted to a scalar
with `ff::FromUniformBytes`. A new `hash_to_F`, which accepts a single argument
of the value to hash (removing the potential to insecurely handle the DST by
removing the DST entirely). Due to `digest` yielding a `GenericArray`, yet
`FromUniformBytes` taking a `const usize`, the `ciphersuite` crate now defines
a `FromUniformBytes` trait taking an array (then implemented for all satisfiers
of `ff::FromUniformBytes`). In order to get the array type from the
`GenericArray`, the output of the hash, `digest` is updated to the `0.11`
release candidate which moves to `flexible-array` which solves that problem.

The existing, specific `hash_to_F` functions have been moved to `modular-frost`
as necessary.

`flexible-array` itself is patched to a fork due to
https://github.com/RustCrypto/hybrid-array/issues/131.
This commit is contained in:
Luke Parker
2025-08-29 05:04:03 -04:00
parent a4811c9a41
commit 90bc364f9f
37 changed files with 355 additions and 416 deletions

168
Cargo.lock generated
View File

@@ -42,7 +42,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
dependencies = [ dependencies = [
"crypto-common", "crypto-common 0.1.6",
"generic-array 0.14.7", "generic-array 0.14.7",
] ]
@@ -353,7 +353,7 @@ dependencies = [
"ruint", "ruint",
"rustc-hash 2.1.1", "rustc-hash 2.1.1",
"serde", "serde",
"sha3", "sha3 0.10.8",
"tiny-keccak", "tiny-keccak",
] ]
@@ -1524,6 +1524,15 @@ dependencies = [
"digest 0.10.7", "digest 0.10.7",
] ]
[[package]]
name = "blake2"
version = "0.11.0-rc.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce3d950855224a23299348898f8a2127860e1afea78df3e51deebb89d1cb2f8f"
dependencies = [
"digest 0.11.0-rc.0",
]
[[package]] [[package]]
name = "blake2b_simd" name = "blake2b_simd"
version = "1.0.3" version = "1.0.3"
@@ -1577,6 +1586,15 @@ dependencies = [
"generic-array 0.14.7", "generic-array 0.14.7",
] ]
[[package]]
name = "block-buffer"
version = "0.11.0-rc.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a229bfd78e4827c91b9b95784f69492c1b77c1ab75a45a8a037b139215086f94"
dependencies = [
"hybrid-array",
]
[[package]] [[package]]
name = "blst" name = "blst"
version = "0.3.15" version = "0.3.15"
@@ -1895,7 +1913,7 @@ version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [ dependencies = [
"crypto-common", "crypto-common 0.1.6",
"inout", "inout",
"zeroize", "zeroize",
] ]
@@ -1904,7 +1922,7 @@ dependencies = [
name = "ciphersuite" name = "ciphersuite"
version = "0.4.2" version = "0.4.2"
dependencies = [ dependencies = [
"digest 0.10.7", "digest 0.11.0-rc.0",
"ff", "ff",
"ff-group-tests", "ff-group-tests",
"flexible-transcript", "flexible-transcript",
@@ -1929,13 +1947,12 @@ name = "ciphersuite-kp256"
version = "0.4.0" version = "0.4.0"
dependencies = [ dependencies = [
"ciphersuite 0.4.2", "ciphersuite 0.4.2",
"elliptic-curve",
"ff-group-tests", "ff-group-tests",
"hex", "hex",
"k256", "k256",
"p256", "p256",
"rand_core 0.6.4", "rand_core 0.6.4",
"sha2 0.10.9", "sha2 0.11.0-rc.0",
"zeroize", "zeroize",
] ]
@@ -2326,6 +2343,15 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "crypto-common"
version = "0.2.0-rc.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a23fa214dea9efd4dacee5a5614646b30216ae0f05d4bb51bafb50e9da1c5be"
dependencies = [
"hybrid-array",
]
[[package]] [[package]]
name = "ctr" name = "ctr"
version = "0.9.2" version = "0.9.2"
@@ -2440,7 +2466,7 @@ dependencies = [
"hex", "hex",
"rand_core 0.6.4", "rand_core 0.6.4",
"rustversion", "rustversion",
"sha2 0.10.9", "sha2 0.11.0-rc.0",
"subtle", "subtle",
"zeroize", "zeroize",
] ]
@@ -2517,7 +2543,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976"
dependencies = [ dependencies = [
"data-encoding", "data-encoding",
"syn 1.0.109", "syn 2.0.106",
] ]
[[package]] [[package]]
@@ -2674,7 +2700,18 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [ dependencies = [
"block-buffer 0.10.4", "block-buffer 0.10.4",
"const-oid", "const-oid",
"crypto-common", "crypto-common 0.1.6",
"subtle",
]
[[package]]
name = "digest"
version = "0.11.0-rc.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460dd7f37e4950526b54a5a6b1f41b6c8e763c58eb9a8fc8fc05ba5c2f44ca7b"
dependencies = [
"block-buffer 0.11.0-rc.4",
"crypto-common 0.2.0-rc.3",
"subtle", "subtle",
] ]
@@ -2756,7 +2793,7 @@ dependencies = [
name = "dkg-evrf" name = "dkg-evrf"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"blake2", "blake2 0.10.6",
"ciphersuite 0.4.2", "ciphersuite 0.4.2",
"ciphersuite-kp256", "ciphersuite-kp256",
"dalek-ff-group", "dalek-ff-group",
@@ -2986,7 +3023,7 @@ dependencies = [
name = "embedwards25519" name = "embedwards25519"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"blake2", "blake2 0.11.0-rc.0",
"ciphersuite 0.4.2", "ciphersuite 0.4.2",
"curve25519-dalek", "curve25519-dalek",
"dalek-ff-group", "dalek-ff-group",
@@ -3102,7 +3139,7 @@ dependencies = [
"group", "group",
"k256", "k256",
"rand_core 0.6.4", "rand_core 0.6.4",
"sha3", "sha3 0.10.8",
"subtle", "subtle",
"tokio", "tokio",
] ]
@@ -3149,7 +3186,7 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f86a749cf851891866c10515ef6c299b5c69661465e9c3bbe7e07a2b77fb0f7" checksum = "5f86a749cf851891866c10515ef6c299b5c69661465e9c3bbe7e07a2b77fb0f7"
dependencies = [ dependencies = [
"blake2", "blake2 0.10.6",
"fs-err", "fs-err",
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -3276,10 +3313,10 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
name = "flexible-transcript" name = "flexible-transcript"
version = "0.3.4" version = "0.3.4"
dependencies = [ dependencies = [
"blake2", "blake2 0.11.0-rc.0",
"digest 0.10.7", "digest 0.11.0-rc.0",
"merlin", "merlin",
"sha2 0.10.9", "sha2 0.11.0-rc.0",
"zeroize", "zeroize",
] ]
@@ -3728,7 +3765,7 @@ name = "generalized-bulletproofs"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/monero-oxide/monero-oxide?rev=a6f8797007e768488568b821435cf5006517a962#a6f8797007e768488568b821435cf5006517a962" source = "git+https://github.com/monero-oxide/monero-oxide?rev=a6f8797007e768488568b821435cf5006517a962#a6f8797007e768488568b821435cf5006517a962"
dependencies = [ dependencies = [
"blake2", "blake2 0.10.6",
"ciphersuite 0.4.99", "ciphersuite 0.4.99",
"ff", "ff",
"flexible-transcript", "flexible-transcript",
@@ -4174,6 +4211,14 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f"
[[package]]
name = "hybrid-array"
version = "0.3.1"
source = "git+https://github.com/kayabaNerve/hybrid-array?rev=8caa508976c93696a67f40734537c91be7cecd96#8caa508976c93696a67f40734537c91be7cecd96"
dependencies = [
"typenum",
]
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "0.14.30" version = "0.14.30"
@@ -4685,7 +4730,7 @@ dependencies = [
[[package]] [[package]]
name = "k256" name = "k256"
version = "0.13.4" version = "0.13.4"
source = "git+https://github.com/kayabaNerve/elliptic-curves?rev=fc92333e222b7f0cbe268d2ca92ed572f71f3e1d#fc92333e222b7f0cbe268d2ca92ed572f71f3e1d" source = "git+https://github.com/kayabaNerve/elliptic-curves?rev=4994c9ab163781a88cd4a49beae812a89a44e8c3#4994c9ab163781a88cd4a49beae812a89a44e8c3"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"ecdsa", "ecdsa",
@@ -4704,6 +4749,15 @@ dependencies = [
"cpufeatures", "cpufeatures",
] ]
[[package]]
name = "keccak"
version = "0.2.0-pre.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7cdd4f0dc5807b9a2b25dd48a3f58e862606fe7bd47f41ecde36e97422d7e90"
dependencies = [
"cpufeatures",
]
[[package]] [[package]]
name = "keccak-asm" name = "keccak-asm"
version = "0.1.4" version = "0.1.4"
@@ -5936,7 +5990,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"keccak", "keccak 0.1.5",
"rand_core 0.6.4", "rand_core 0.6.4",
"zeroize", "zeroize",
] ]
@@ -5957,7 +6011,7 @@ dependencies = [
"hex", "hex",
"prime-field", "prime-field",
"rand_core 0.6.4", "rand_core 0.6.4",
"sha3", "sha3 0.11.0-rc.0",
"zeroize", "zeroize",
] ]
@@ -6021,10 +6075,10 @@ dependencies = [
"ciphersuite 0.4.2", "ciphersuite 0.4.2",
"ciphersuite-kp256", "ciphersuite-kp256",
"dalek-ff-group", "dalek-ff-group",
"digest 0.10.7",
"dkg", "dkg",
"dkg-dealer", "dkg-dealer",
"dkg-recovery", "dkg-recovery",
"elliptic-curve",
"flexible-transcript", "flexible-transcript",
"hex", "hex",
"minimal-ed448", "minimal-ed448",
@@ -6033,6 +6087,7 @@ dependencies = [
"rand_core 0.6.4", "rand_core 0.6.4",
"schnorr-signatures", "schnorr-signatures",
"serde_json", "serde_json",
"sha2 0.10.9",
"subtle", "subtle",
"thiserror 2.0.16", "thiserror 2.0.16",
"zeroize", "zeroize",
@@ -6118,7 +6173,7 @@ dependencies = [
"dalek-ff-group", "dalek-ff-group",
"group", "group",
"monero-io", "monero-io",
"sha3", "sha3 0.10.8",
"std-shims", "std-shims",
"subtle", "subtle",
] ]
@@ -6172,7 +6227,7 @@ dependencies = [
"curve25519-dalek", "curve25519-dalek",
"monero-generators", "monero-generators",
"monero-io", "monero-io",
"sha3", "sha3 0.10.8",
"std-shims", "std-shims",
"zeroize", "zeroize",
] ]
@@ -6286,7 +6341,7 @@ dependencies = [
"digest 0.10.7", "digest 0.10.7",
"multihash-derive 0.8.0", "multihash-derive 0.8.0",
"sha2 0.10.9", "sha2 0.10.9",
"sha3", "sha3 0.10.8",
"unsigned-varint 0.7.2", "unsigned-varint 0.7.2",
] ]
@@ -6315,7 +6370,7 @@ dependencies = [
"ripemd", "ripemd",
"sha1", "sha1",
"sha2 0.10.9", "sha2 0.10.9",
"sha3", "sha3 0.10.8",
"strobe-rs", "strobe-rs",
] ]
@@ -6714,8 +6769,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]] [[package]]
name = "p256" name = "p256"
version = "0.13.2" version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/kayabaNerve/elliptic-curves?rev=4994c9ab163781a88cd4a49beae812a89a44e8c3#4994c9ab163781a88cd4a49beae812a89a44e8c3"
checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
dependencies = [ dependencies = [
"ecdsa", "ecdsa",
"elliptic-curve", "elliptic-curve",
@@ -6873,7 +6927,7 @@ version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "592a28a24b09c9dc20ac8afaa6839abc417c720afe42c12e1e4a9d6aa2508d2e" checksum = "592a28a24b09c9dc20ac8afaa6839abc417c720afe42c12e1e4a9d6aa2508d2e"
dependencies = [ dependencies = [
"blake2", "blake2 0.10.6",
"crc32fast", "crc32fast",
"fs2", "fs2",
"hex", "hex",
@@ -7245,8 +7299,7 @@ dependencies = [
[[package]] [[package]]
name = "primeorder" name = "primeorder"
version = "0.13.6" version = "0.13.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/kayabaNerve/elliptic-curves?rev=4994c9ab163781a88cd4a49beae812a89a44e8c3#4994c9ab163781a88cd4a49beae812a89a44e8c3"
checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
dependencies = [ dependencies = [
"elliptic-curve", "elliptic-curve",
] ]
@@ -9539,7 +9592,7 @@ dependencies = [
name = "secq256k1" name = "secq256k1"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"blake2", "blake2 0.11.0-rc.0",
"ciphersuite 0.4.2", "ciphersuite 0.4.2",
"ff-group-tests", "ff-group-tests",
"generalized-bulletproofs-ec-gadgets", "generalized-bulletproofs-ec-gadgets",
@@ -9692,7 +9745,7 @@ dependencies = [
"async-lock", "async-lock",
"bitcoin", "bitcoin",
"bitvec", "bitvec",
"blake2", "blake2 0.10.6",
"borsh", "borsh",
"ciphersuite 0.4.2", "ciphersuite 0.4.2",
"ciphersuite-kp256", "ciphersuite-kp256",
@@ -9754,7 +9807,7 @@ name = "serai-coordinator"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bitvec", "bitvec",
"blake2", "blake2 0.10.6",
"borsh", "borsh",
"ciphersuite 0.4.2", "ciphersuite 0.4.2",
"dalek-ff-group", "dalek-ff-group",
@@ -9791,7 +9844,7 @@ name = "serai-coordinator-libp2p-p2p"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"blake2", "blake2 0.10.6",
"borsh", "borsh",
"futures-util", "futures-util",
"hex", "hex",
@@ -9845,7 +9898,7 @@ dependencies = [
name = "serai-coordinator-tributary" name = "serai-coordinator-tributary"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"blake2", "blake2 0.10.6",
"borsh", "borsh",
"ciphersuite 0.4.2", "ciphersuite 0.4.2",
"dalek-ff-group", "dalek-ff-group",
@@ -9868,7 +9921,7 @@ dependencies = [
name = "serai-cosign" name = "serai-cosign"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"blake2", "blake2 0.10.6",
"borsh", "borsh",
"log", "log",
"parity-scale-codec", "parity-scale-codec",
@@ -10408,7 +10461,7 @@ dependencies = [
name = "serai-processor-key-gen" name = "serai-processor-key-gen"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"blake2", "blake2 0.10.6",
"borsh", "borsh",
"ciphersuite 0.4.2", "ciphersuite 0.4.2",
"dkg-evrf", "dkg-evrf",
@@ -10456,7 +10509,7 @@ dependencies = [
name = "serai-processor-scanner" name = "serai-processor-scanner"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"blake2", "blake2 0.10.6",
"borsh", "borsh",
"group", "group",
"hex", "hex",
@@ -10488,7 +10541,7 @@ dependencies = [
name = "serai-processor-signers" name = "serai-processor-signers"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"blake2", "blake2 0.10.6",
"borsh", "borsh",
"ciphersuite 0.4.2", "ciphersuite 0.4.2",
"dalek-ff-group", "dalek-ff-group",
@@ -10887,6 +10940,17 @@ dependencies = [
"digest 0.10.7", "digest 0.10.7",
] ]
[[package]]
name = "sha2"
version = "0.11.0-rc.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa1d2e6b3cc4e43a8258a9a3b17aa5dfd2cc5186c7024bba8a64aa65b2c71a59"
dependencies = [
"cfg-if",
"cpufeatures",
"digest 0.11.0-rc.0",
]
[[package]] [[package]]
name = "sha3" name = "sha3"
version = "0.10.8" version = "0.10.8"
@@ -10894,7 +10958,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
dependencies = [ dependencies = [
"digest 0.10.7", "digest 0.10.7",
"keccak", "keccak 0.1.5",
]
[[package]]
name = "sha3"
version = "0.11.0-rc.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e6a92fd180fd205defdc0b78288ce847c7309d329fd6647a814567e67db50e"
dependencies = [
"digest 0.11.0-rc.0",
"keccak 0.2.0-pre.0",
] ]
[[package]] [[package]]
@@ -11026,7 +11100,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "850948bee068e713b8ab860fe1adc4d109676ab4c3b621fd8147f06b261f2f85" checksum = "850948bee068e713b8ab860fe1adc4d109676ab4c3b621fd8147f06b261f2f85"
dependencies = [ dependencies = [
"aes-gcm", "aes-gcm",
"blake2", "blake2 0.10.6",
"chacha20poly1305", "chacha20poly1305",
"curve25519-dalek", "curve25519-dalek",
"rand_core 0.6.4", "rand_core 0.6.4",
@@ -11114,7 +11188,7 @@ version = "4.0.0-dev"
source = "git+https://github.com/serai-dex/substrate#8587cd89bec74f789d4e23fdf776508a0ed5db6f" source = "git+https://github.com/serai-dex/substrate#8587cd89bec74f789d4e23fdf776508a0ed5db6f"
dependencies = [ dependencies = [
"Inflector", "Inflector",
"blake2", "blake2 0.10.6",
"expander", "expander",
"proc-macro-crate 1.3.1", "proc-macro-crate 1.3.1",
"proc-macro2", "proc-macro2",
@@ -11260,7 +11334,7 @@ source = "git+https://github.com/serai-dex/substrate#8587cd89bec74f789d4e23fdf77
dependencies = [ dependencies = [
"array-bytes", "array-bytes",
"bitflags 1.3.2", "bitflags 1.3.2",
"blake2", "blake2 0.10.6",
"bounded-collections", "bounded-collections",
"bs58", "bs58",
"dyn-clonable", "dyn-clonable",
@@ -11304,7 +11378,7 @@ dependencies = [
"blake2b_simd", "blake2b_simd",
"byteorder", "byteorder",
"digest 0.10.7", "digest 0.10.7",
"sha3", "sha3 0.10.8",
"twox-hash", "twox-hash",
] ]
@@ -11799,7 +11873,7 @@ checksum = "fabb238a1cccccfa4c4fb703670c0d157e1256c1ba695abf1b93bd2bb14bab2d"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
"byteorder", "byteorder",
"keccak", "keccak 0.1.5",
"subtle", "subtle",
"zeroize", "zeroize",
] ]
@@ -12534,7 +12608,7 @@ dependencies = [
name = "tributary-sdk" name = "tributary-sdk"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"blake2", "blake2 0.10.6",
"ciphersuite 0.4.2", "ciphersuite 0.4.2",
"dalek-ff-group", "dalek-ff-group",
"flexible-transcript", "flexible-transcript",
@@ -12747,7 +12821,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
dependencies = [ dependencies = [
"crypto-common", "crypto-common 0.1.6",
"subtle", "subtle",
] ]

View File

@@ -225,7 +225,11 @@ option-ext = { path = "patches/option-ext" }
directories-next = { path = "patches/directories-next" } directories-next = { path = "patches/directories-next" }
# Patch to include `FromUniformBytes<64>` over Scalar # Patch to include `FromUniformBytes<64>` over Scalar
k256 = { git = "https://github.com/kayabaNerve/elliptic-curves", rev = "fc92333e222b7f0cbe268d2ca92ed572f71f3e1d" } k256 = { git = "https://github.com/kayabaNerve/elliptic-curves", rev = "4994c9ab163781a88cd4a49beae812a89a44e8c3" }
p256 = { git = "https://github.com/kayabaNerve/elliptic-curves", rev = "4994c9ab163781a88cd4a49beae812a89a44e8c3" }
# https://github.com/RustCrypto/hybrid-array/issues/131
hybrid-array = { git = "https://github.com/kayabaNerve/hybrid-array", rev = "8caa508976c93696a67f40734537c91be7cecd96" }
[workspace.lints.clippy] [workspace.lints.clippy]
unwrap_or_default = "allow" unwrap_or_default = "allow"

View File

@@ -6,7 +6,7 @@ license = "AGPL-3.0-only"
repository = "https://github.com/serai-dex/serai/tree/develop/coordinator/tributary-sdk" repository = "https://github.com/serai-dex/serai/tree/develop/coordinator/tributary-sdk"
authors = ["Luke Parker <lukeparker5132@gmail.com>"] authors = ["Luke Parker <lukeparker5132@gmail.com>"]
edition = "2021" edition = "2021"
rust-version = "1.79" rust-version = "1.85"
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View File

@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/ciphersuite
authors = ["Luke Parker <lukeparker5132@gmail.com>"] authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["ciphersuite", "ff", "group"] keywords = ["ciphersuite", "ff", "group"]
edition = "2021" edition = "2021"
rust-version = "1.73" rust-version = "1.85"
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true
@@ -24,7 +24,7 @@ rand_core = { version = "0.6", default-features = false }
zeroize = { version = "^1.5", default-features = false, features = ["derive"] } zeroize = { version = "^1.5", default-features = false, features = ["derive"] }
subtle = { version = "^2.4", default-features = false } subtle = { version = "^2.4", default-features = false }
digest = { version = "0.10", default-features = false, features = ["core-api"] } digest = { version = "0.11.0-rc.0", default-features = false, features = ["block-api"] }
transcript = { package = "flexible-transcript", path = "../transcript", version = "^0.3.2", default-features = false } transcript = { package = "flexible-transcript", path = "../transcript", version = "^0.3.2", default-features = false }
ff = { version = "0.13", default-features = false, features = ["bits"] } ff = { version = "0.13", default-features = false, features = ["bits"] }
@@ -38,7 +38,7 @@ rand_core = { version = "0.6", default-features = false, features = ["std"] }
ff-group-tests = { version = "0.13", path = "../ff-group-tests" } ff-group-tests = { version = "0.13", path = "../ff-group-tests" }
[features] [features]
alloc = ["std-shims"] alloc = ["std-shims", "ff/alloc"]
std = [ std = [
"alloc", "alloc",
@@ -49,7 +49,6 @@ std = [
"zeroize/std", "zeroize/std",
"subtle/std", "subtle/std",
"digest/std",
"transcript/std", "transcript/std",
"ff/std", "ff/std",

View File

@@ -17,10 +17,6 @@ Secp256k1 and P-256 are offered via [k256](https://crates.io/crates/k256) and
[p256](https://crates.io/crates/p256), two libraries maintained by [p256](https://crates.io/crates/p256), two libraries maintained by
[RustCrypto](https://github.com/RustCrypto). [RustCrypto](https://github.com/RustCrypto).
Their `hash_to_F` is the
[IETF's hash to curve](https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html),
yet applied to their scalar field.
Please see the [`ciphersuite-kp256`](https://docs.rs/ciphersuite-kp256) crate for more info. Please see the [`ciphersuite-kp256`](https://docs.rs/ciphersuite-kp256) crate for more info.
### Ed25519/Ristretto ### Ed25519/Ristretto
@@ -29,12 +25,6 @@ Ed25519/Ristretto are offered via
[dalek-ff-group](https://crates.io/crates/dalek-ff-group), an ff/group wrapper [dalek-ff-group](https://crates.io/crates/dalek-ff-group), an ff/group wrapper
around [curve25519-dalek](https://crates.io/crates/curve25519-dalek). around [curve25519-dalek](https://crates.io/crates/curve25519-dalek).
Their `hash_to_F` is the wide reduction of SHA2-512, as used in
[RFC-8032](https://www.rfc-editor.org/rfc/rfc8032). This is also compliant with
the draft
[RFC-RISTRETTO](https://www.ietf.org/archive/id/draft-irtf-cfrg-ristretto255-decaf448-05.html).
The domain-separation tag is naively prefixed to the message.
Please see the [`dalek-ff-group`](https://docs.rs/dalek-ff-group) crate for more info. Please see the [`dalek-ff-group`](https://docs.rs/dalek-ff-group) crate for more info.
### Ed448 ### Ed448
@@ -43,8 +33,4 @@ Ed448 is offered via [minimal-ed448](https://crates.io/crates/minimal-ed448), an
explicitly not recommended, unaudited, incomplete Ed448 implementation, limited explicitly not recommended, unaudited, incomplete Ed448 implementation, limited
to its prime-order subgroup. to its prime-order subgroup.
Its `hash_to_F` is the wide reduction of SHAKE256, with a 114-byte output, as
used in [RFC-8032](https://www.rfc-editor.org/rfc/rfc8032). The
domain-separation tag is naively prefixed to the message.
Please see the [`minimal-ed448`](https://docs.rs/minimal-ed448) crate for more info. Please see the [`minimal-ed448`](https://docs.rs/minimal-ed448) crate for more info.

View File

@@ -21,9 +21,8 @@ rand_core = { version = "0.6", default-features = false }
zeroize = { version = "^1.5", default-features = false, features = ["derive"] } zeroize = { version = "^1.5", default-features = false, features = ["derive"] }
sha2 = { version = "0.10", default-features = false } sha2 = { version = "0.11.0-rc.0", default-features = false }
elliptic-curve = { version = "0.13", default-features = false, features = ["hash2curve"] }
p256 = { version = "^0.13.1", default-features = false, features = ["arithmetic", "bits", "hash2curve"] } p256 = { version = "^0.13.1", default-features = false, features = ["arithmetic", "bits", "hash2curve"] }
k256 = { version = "^0.13.1", default-features = false, features = ["arithmetic", "bits", "hash2curve"] } k256 = { version = "^0.13.1", default-features = false, features = ["arithmetic", "bits", "hash2curve"] }
@@ -43,9 +42,6 @@ std = [
"zeroize/std", "zeroize/std",
"sha2/std",
"elliptic-curve/std",
"p256/std", "p256/std",
"k256/std", "k256/std",

View File

@@ -3,15 +3,9 @@
use zeroize::Zeroize; use zeroize::Zeroize;
use sha2::Sha256; use sha2::Sha512;
use elliptic_curve::{ use ciphersuite::Ciphersuite;
generic_array::GenericArray,
bigint::{NonZero, CheckedAdd, Encoding, U384},
hash2curve::{Expander, ExpandMsg, ExpandMsgXmd},
};
use ciphersuite::{group::ff::PrimeField, Ciphersuite};
pub use k256; pub use k256;
pub use p256; pub use p256;
@@ -27,148 +21,31 @@ macro_rules! kp_curve {
impl Ciphersuite for $Ciphersuite { impl Ciphersuite for $Ciphersuite {
type F = $lib::Scalar; type F = $lib::Scalar;
type G = $lib::ProjectivePoint; type G = $lib::ProjectivePoint;
type H = Sha256; type H = Sha512;
const ID: &'static [u8] = $ID; const ID: &'static [u8] = $ID;
fn generator() -> Self::G { fn generator() -> Self::G {
$lib::ProjectivePoint::GENERATOR $lib::ProjectivePoint::GENERATOR
} }
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
// While one of these two libraries does support directly hashing to the Scalar field, the
// other doesn't. While that's probably an oversight, this is a universally working method
// This method is from
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html
// Specifically, Section 5
// While that draft, overall, is intended for hashing to curves, that necessitates
// detailing how to hash to a finite field. The draft comments that its mechanism for
// doing so, which it uses to derive field elements, is also applicable to the scalar field
// The hash_to_field function is intended to provide unbiased values
// In order to do so, a wide reduction from an extra k bits is applied, minimizing bias to
// 2^-k
// k is intended to be the bits of security of the suite, which is 128 for secp256k1 and
// P-256
const K: usize = 128;
// L is the amount of bytes of material which should be used in the wide reduction
// The 256 is for the bit-length of the primes, rounded up to the nearest byte threshold
// This is a simplification of the formula from the end of section 5
const L: usize = (256 + K) / 8; // 48
// In order to perform this reduction, we need to use 48-byte numbers
// First, convert the modulus to a 48-byte number
// This is done by getting -1 as bytes, parsing it into a U384, and then adding back one
let mut modulus = [0; L];
// The byte repr of scalars will be 32 big-endian bytes
// Set the lower 32 bytes of our 48-byte array accordingly
modulus[16 ..].copy_from_slice(&(Self::F::ZERO - Self::F::ONE).to_bytes());
// Use a checked_add + unwrap since this addition cannot fail (being a 32-byte value with
// 48-bytes of space)
// While a non-panicking saturating_add/wrapping_add could be used, they'd likely be less
// performant
let modulus = U384::from_be_slice(&modulus).checked_add(&U384::ONE).unwrap();
// The defined P-256 and secp256k1 ciphersuites both use expand_message_xmd
let mut wide = U384::from_be_bytes({
let mut bytes = [0; 48];
ExpandMsgXmd::<Sha256>::expand_message(&[msg], &[dst], 48)
.unwrap()
.fill_bytes(&mut bytes);
bytes
})
.rem(&NonZero::new(modulus).unwrap())
.to_be_bytes();
// Now that this has been reduced back to a 32-byte value, grab the lower 32-bytes
let mut array = *GenericArray::from_slice(&wide[16 ..]);
let res = $lib::Scalar::from_repr(array).unwrap();
// Zeroize the temp values we can due to the possibility hash_to_F is being used for nonces
wide.zeroize();
array.zeroize();
res
}
} }
}; };
} }
#[cfg(test)]
fn test_oversize_dst<C: Ciphersuite>() {
use sha2::Digest;
// The draft specifies DSTs >255 bytes should be hashed into a 32-byte DST
let oversize_dst = [0x00; 256];
let actual_dst = Sha256::digest([b"H2C-OVERSIZE-DST-".as_ref(), &oversize_dst].concat());
// Test the hash_to_F function handles this
// If it didn't, these would return different values
assert_eq!(C::hash_to_F(&oversize_dst, &[]), C::hash_to_F(&actual_dst, &[]));
}
/// Ciphersuite for Secp256k1. /// Ciphersuite for Secp256k1.
///
/// hash_to_F is implemented via the IETF draft for hash to curve's hash_to_field (v16).
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub struct Secp256k1; pub struct Secp256k1;
kp_curve!("secp256k1", k256, Secp256k1, b"secp256k1"); kp_curve!("secp256k1", k256, Secp256k1, b"secp256k1");
#[test] #[test]
fn test_secp256k1() { fn test_secp256k1() {
ff_group_tests::group::test_prime_group_bits::<_, k256::ProjectivePoint>(&mut rand_core::OsRng); ff_group_tests::group::test_prime_group_bits::<_, k256::ProjectivePoint>(&mut rand_core::OsRng);
// Ideally, a test vector from hash_to_field (not FROST) would be here
// Unfortunately, the IETF draft only provides vectors for field elements, not scalars
// Vectors have been requested in
// https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/issues/343
assert_eq!(
Secp256k1::hash_to_F(
b"FROST-secp256k1-SHA256-v11nonce",
&hex::decode(
"\
80cbea5e405d169999d8c4b30b755fedb26ab07ec8198cda4873ed8ce5e16773\
08f89ffe80ac94dcb920c26f3f46140bfc7f95b493f8310f5fc1ea2b01f4254c"
)
.unwrap()
)
.to_repr()
.iter()
.copied()
.collect::<Vec<_>>(),
hex::decode("acc83278035223c1ba464e2d11bfacfc872b2b23e1041cf5f6130da21e4d8068").unwrap()
);
test_oversize_dst::<Secp256k1>();
} }
/// Ciphersuite for P-256. /// Ciphersuite for P-256.
///
/// hash_to_F is implemented via the IETF draft for hash to curve's hash_to_field (v16).
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub struct P256; pub struct P256;
kp_curve!("p256", p256, P256, b"P-256"); kp_curve!("p256", p256, P256, b"P-256");
#[test] #[test]
fn test_p256() { fn test_p256() {
ff_group_tests::group::test_prime_group_bits::<_, p256::ProjectivePoint>(&mut rand_core::OsRng); ff_group_tests::group::test_prime_group_bits::<_, p256::ProjectivePoint>(&mut rand_core::OsRng);
assert_eq!(
P256::hash_to_F(
b"FROST-P256-SHA256-v11nonce",
&hex::decode(
"\
f4e8cf80aec3f888d997900ac7e3e349944b5a6b47649fc32186d2f1238103c6\
0c9c1a0fe806c184add50bbdcac913dda73e482daf95dcb9f35dbb0d8a9f7731"
)
.unwrap()
)
.to_repr()
.iter()
.copied()
.collect::<Vec<_>>(),
hex::decode("f871dfcf6bcd199342651adc361b92c941cb6a0d8c8c1a3b91d79e2c1bf3722d").unwrap()
);
test_oversize_dst::<P256>();
} }

View File

@@ -14,7 +14,8 @@ use rand_core::{RngCore, CryptoRng};
use zeroize::Zeroize; use zeroize::Zeroize;
use subtle::ConstantTimeEq; use subtle::ConstantTimeEq;
use digest::{core_api::BlockSizeUser, Digest, HashMarker}; pub use digest;
use digest::{array::ArraySize, block_api::BlockSizeUser, OutputSizeUser, Digest, HashMarker};
use transcript::SecureDigest; use transcript::SecureDigest;
pub use group; pub use group;
@@ -26,13 +27,25 @@ use group::{
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use group::GroupEncoding; use group::GroupEncoding;
pub trait FromUniformBytes<T> {
fn from_uniform_bytes(bytes: &T) -> Self;
}
impl<const N: usize, F: group::ff::FromUniformBytes<N>> FromUniformBytes<[u8; N]> for F {
fn from_uniform_bytes(bytes: &[u8; N]) -> Self {
F::from_uniform_bytes(bytes)
}
}
/// Unified trait defining a ciphersuite around an elliptic curve. /// Unified trait defining a ciphersuite around an elliptic curve.
pub trait Ciphersuite: pub trait Ciphersuite:
'static + Send + Sync + Clone + Copy + PartialEq + Eq + Debug + Zeroize 'static + Send + Sync + Clone + Copy + PartialEq + Eq + Debug + Zeroize
{ {
/// Scalar field element type. /// Scalar field element type.
// This is available via G::Scalar yet `C::G::Scalar` is ambiguous, forcing horrific accesses // This is available via G::Scalar yet `C::G::Scalar` is ambiguous, forcing horrific accesses
type F: PrimeField + PrimeFieldBits + Zeroize; type F: PrimeField
+ PrimeFieldBits
+ Zeroize
+ FromUniformBytes<<<Self::H as OutputSizeUser>::OutputSize as ArraySize>::ArrayType<u8>>;
/// Group element type. /// Group element type.
type G: Group<Scalar = Self::F> + GroupOps + PrimeGroup + Zeroize + ConstantTimeEq; type G: Group<Scalar = Self::F> + GroupOps + PrimeGroup + Zeroize + ConstantTimeEq;
/// Hash algorithm used with this curve. /// Hash algorithm used with this curve.
@@ -46,16 +59,10 @@ pub trait Ciphersuite:
// While group does provide this in its API, privacy coins may want to use a custom basepoint // While group does provide this in its API, privacy coins may want to use a custom basepoint
fn generator() -> Self::G; fn generator() -> Self::G;
/// Hash the provided domain-separation tag and message to a scalar. Ciphersuites MAY naively
/// prefix the tag to the message, enabling transpotion between the two. Accordingly, this
/// function should NOT be used in any scheme where one tag is a valid substring of another
/// UNLESS the specific Ciphersuite is verified to handle the DST securely.
///
/// Verifying specific ciphersuites have secure tag handling is not recommended, due to it
/// breaking the intended modularity of ciphersuites. Instead, component-specific tags with
/// further purpose tags are recommended ("Schnorr-nonce", "Schnorr-chal").
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F; fn hash_to_F(data: &[u8]) -> Self::F {
Self::F::from_uniform_bytes(&Self::H::digest(data).into())
}
/// Generate a random non-zero scalar. /// Generate a random non-zero scalar.
#[allow(non_snake_case)] #[allow(non_snake_case)]

View File

@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/dalek-ff-gr
authors = ["Luke Parker <lukeparker5132@gmail.com>"] authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["curve25519", "ed25519", "ristretto", "dalek", "group"] keywords = ["curve25519", "ed25519", "ristretto", "dalek", "group"]
edition = "2021" edition = "2021"
rust-version = "1.73" rust-version = "1.85"
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true
@@ -25,7 +25,7 @@ subtle = { version = "^2.4", default-features = false }
rand_core = { version = "0.6", default-features = false } rand_core = { version = "0.6", default-features = false }
digest = { version = "0.10", default-features = false } digest = { version = "0.10", default-features = false }
sha2 = { version = "0.10", default-features = false } sha2 = { version = "0.11.0-rc.0", default-features = false }
ff = { version = "0.13", default-features = false, features = ["bits"] } ff = { version = "0.13", default-features = false, features = ["bits"] }
group = { version = "0.13", default-features = false } group = { version = "0.13", default-features = false }
@@ -42,5 +42,5 @@ ff-group-tests = { path = "../ff-group-tests" }
[features] [features]
alloc = ["zeroize/alloc", "ciphersuite/alloc", "curve25519-dalek/alloc"] alloc = ["zeroize/alloc", "ciphersuite/alloc", "curve25519-dalek/alloc"]
std = ["alloc", "zeroize/std", "subtle/std", "rand_core/std", "digest/std", "sha2/std", "ciphersuite/std"] std = ["alloc", "zeroize/std", "subtle/std", "rand_core/std", "digest/std", "ciphersuite/std"]
default = ["std"] default = ["std"]

View File

@@ -1,6 +1,6 @@
use zeroize::Zeroize; use zeroize::Zeroize;
use sha2::{Digest, Sha512}; use sha2::Sha512;
use group::Group; use group::Group;
use crate::Scalar; use crate::Scalar;
@@ -27,71 +27,24 @@ macro_rules! dalek_curve {
fn generator() -> Self::G { fn generator() -> Self::G {
$Point::generator() $Point::generator()
} }
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
let mut digest = Sha512::new();
digest.update(dst);
digest.update(data);
Scalar::from_hash(digest)
}
} }
}; };
} }
/// Ciphersuite for Ristretto. /// Ciphersuite for Ristretto.
///
/// hash_to_F is implemented with a naive concatenation of the dst and data, allowing transposition
/// between the two. This means `dst: b"abc", data: b"def"`, will produce the same scalar as
/// `dst: "abcdef", data: b""`. Please use carefully, not letting dsts be substrings of each other.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub struct Ristretto; pub struct Ristretto;
dalek_curve!("ristretto", Ristretto, RistrettoPoint, b"ristretto"); dalek_curve!("ristretto", Ristretto, RistrettoPoint, b"ristretto");
#[test] #[test]
fn test_ristretto() { fn test_ristretto() {
ff_group_tests::group::test_prime_group_bits::<_, RistrettoPoint>(&mut rand_core::OsRng); ff_group_tests::group::test_prime_group_bits::<_, RistrettoPoint>(&mut rand_core::OsRng);
assert_eq!(
Ristretto::hash_to_F(
b"FROST-RISTRETTO255-SHA512-v11nonce",
&hex::decode(
"\
81800157bb554f299fe0b6bd658e4c4591d74168b5177bf55e8dceed59dc80c7\
5c3430d391552f6e60ecdc093ff9f6f4488756aa6cebdbad75a768010b8f830e"
)
.unwrap()
)
.to_bytes()
.as_ref(),
&hex::decode("40f58e8df202b21c94f826e76e4647efdb0ea3ca7ae7e3689bc0cbe2e2f6660c").unwrap()
);
} }
/// Ciphersuite for Ed25519, inspired by RFC-8032. /// Ciphersuite for Ed25519, inspired by RFC-8032.
///
/// hash_to_F is implemented with a naive concatenation of the dst and data, allowing transposition
/// between the two. This means `dst: b"abc", data: b"def"`, will produce the same scalar as
/// `dst: "abcdef", data: b""`. Please use carefully, not letting dsts be substrings of each other.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub struct Ed25519; pub struct Ed25519;
dalek_curve!("ed25519", Ed25519, EdwardsPoint, b"edwards25519"); dalek_curve!("ed25519", Ed25519, EdwardsPoint, b"edwards25519");
#[test] #[test]
fn test_ed25519() { fn test_ed25519() {
ff_group_tests::group::test_prime_group_bits::<_, EdwardsPoint>(&mut rand_core::OsRng); ff_group_tests::group::test_prime_group_bits::<_, EdwardsPoint>(&mut rand_core::OsRng);
// Ideally, a test vector from RFC-8032 (not FROST) would be here
// Unfortunately, the IETF draft doesn't provide any vectors for the derived challenges
assert_eq!(
Ed25519::hash_to_F(
b"FROST-ED25519-SHA512-v11nonce",
&hex::decode(
"\
9d06a6381c7a4493929761a73692776772b274236fb5cfcc7d1b48ac3a9c249f\
929dcc590407aae7d388761cddb0c0db6f5627aea8e217f4a033f2ec83d93509"
)
.unwrap()
)
.to_bytes()
.as_ref(),
&hex::decode("70652da3e8d7533a0e4b9e9104f01b48c396b5b553717784ed8d05c6a36b9609").unwrap()
);
} }

View File

@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/dkg"
authors = ["Luke Parker <lukeparker5132@gmail.com>"] authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["dkg", "multisig", "threshold", "ff", "group"] keywords = ["dkg", "multisig", "threshold", "ff", "group"]
edition = "2021" edition = "2021"
rust-version = "1.73" rust-version = "1.85"
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View File

@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/dkg/dealer"
authors = ["Luke Parker <lukeparker5132@gmail.com>"] authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["dkg", "multisig", "threshold", "ff", "group"] keywords = ["dkg", "multisig", "threshold", "ff", "group"]
edition = "2021" edition = "2021"
rust-version = "1.73" rust-version = "1.85"
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View File

@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/dkg/musig"
authors = ["Luke Parker <lukeparker5132@gmail.com>"] authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["dkg", "multisig", "threshold", "ff", "group"] keywords = ["dkg", "multisig", "threshold", "ff", "group"]
edition = "2021" edition = "2021"
rust-version = "1.79" rust-version = "1.85"
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View File

@@ -11,7 +11,7 @@ use std_shims::{
use zeroize::Zeroizing; use zeroize::Zeroizing;
use ciphersuite::{group::GroupEncoding, Ciphersuite}; use ciphersuite::{digest::Digest, group::GroupEncoding, FromUniformBytes, Ciphersuite};
pub use dkg::*; pub use dkg::*;
@@ -80,7 +80,7 @@ fn binding_factor_transcript<C: Ciphersuite>(
fn binding_factor<C: Ciphersuite>(mut transcript: Vec<u8>, i: u16) -> C::F { fn binding_factor<C: Ciphersuite>(mut transcript: Vec<u8>, i: u16) -> C::F {
transcript.extend(i.to_le_bytes()); transcript.extend(i.to_le_bytes());
C::hash_to_F(b"dkg-musig", &transcript) C::F::from_uniform_bytes(&C::H::digest(&transcript).into())
} }
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]

View File

@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/dkg/recover
authors = ["Luke Parker <lukeparker5132@gmail.com>"] authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["dkg", "multisig", "threshold", "ff", "group"] keywords = ["dkg", "multisig", "threshold", "ff", "group"]
edition = "2021" edition = "2021"
rust-version = "1.73" rust-version = "1.85"
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View File

@@ -19,7 +19,7 @@ workspace = true
[dependencies] [dependencies]
zeroize = { version = "1", default-features = false, features = ["zeroize_derive"] } zeroize = { version = "1", default-features = false, features = ["zeroize_derive"] }
sha3 = { version = "0.10", default-features = false } sha3 = { version = "0.11.0-rc.0", default-features = false }
prime-field = { path = "../prime-field", default-features = false } prime-field = { path = "../prime-field", default-features = false }
ciphersuite = { path = "../ciphersuite", default-features = false } ciphersuite = { path = "../ciphersuite", default-features = false }
@@ -33,5 +33,5 @@ ff-group-tests = { path = "../ff-group-tests" }
[features] [features]
alloc = ["zeroize/alloc", "prime-field/alloc", "ciphersuite/alloc"] alloc = ["zeroize/alloc", "prime-field/alloc", "ciphersuite/alloc"]
std = ["alloc", "zeroize/std", "sha3/std", "prime-field/std", "ciphersuite/std"] std = ["alloc", "zeroize/std", "prime-field/std", "ciphersuite/std"]
default = ["std"] default = ["std"]

View File

@@ -3,15 +3,12 @@ use zeroize::Zeroize;
use sha3::{ use sha3::{
digest::{ digest::{
typenum::U114, core_api::BlockSizeUser, Update, Output, OutputSizeUser, FixedOutput, typenum::U114, core_api::BlockSizeUser, Update, Output, OutputSizeUser, FixedOutput,
ExtendableOutput, XofReader, HashMarker, Digest, ExtendableOutput, XofReader, HashMarker,
}, },
Shake256, Shake256,
}; };
use ciphersuite::{ use ciphersuite::{group::Group, Ciphersuite};
group::{ff::FromUniformBytes, Group},
Ciphersuite,
};
use crate::{Scalar, Point}; use crate::{Scalar, Point};
@@ -52,11 +49,6 @@ impl FixedOutput for Shake256_114 {
} }
impl HashMarker for Shake256_114 {} impl HashMarker for Shake256_114 {}
/// Ciphersuite for Ed448, inspired by RFC-8032. This is not recommended for usage.
///
/// hash_to_F is implemented with a naive concatenation of the dst and data, allowing transposition
/// between the two. This means `dst: b"abc", data: b"def"`, will produce the same scalar as
/// `dst: "abcdef", data: b""`. Please use carefully, not letting dsts be substrings of each other.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub struct Ed448; pub struct Ed448;
impl Ciphersuite for Ed448 { impl Ciphersuite for Ed448 {
@@ -69,48 +61,9 @@ impl Ciphersuite for Ed448 {
fn generator() -> Self::G { fn generator() -> Self::G {
Point::generator() Point::generator()
} }
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
let mut digest = Self::H::new();
Update::update(&mut digest, dst);
Update::update(&mut digest, data);
let digest = digest.finalize();
let mut wide_scalar = [0; 114];
wide_scalar.copy_from_slice(digest.as_ref());
Scalar::from_uniform_bytes(&wide_scalar)
}
} }
#[test] #[test]
fn test_ed448() { fn test_ed448() {
use ciphersuite::group::ff::PrimeField;
ff_group_tests::group::test_prime_group_bits::<_, Point>(&mut rand_core::OsRng); ff_group_tests::group::test_prime_group_bits::<_, Point>(&mut rand_core::OsRng);
// Ideally, a test vector from RFC-8032 (not FROST) would be here
// Unfortunately, the IETF draft doesn't provide any vectors for the derived challenges
assert_eq!(
Ed448::hash_to_F(
b"FROST-ED448-SHAKE256-v11nonce",
&hex::decode(
"\
89bf16040081ff2990336b200613787937ebe1f024b8cdff90eb6f1c741d91c1\
4a2b2f5858a932ad3d3b18bd16e76ced3070d72fd79ae4402df201f5\
25e754716a1bc1b87a502297f2a99d89ea054e0018eb55d39562fd01\
00"
)
.unwrap()
)
.to_repr()
.as_ref(),
hex::decode(
"\
67a6f023e77361707c6e894c625e809e80f33fdb310810053ae29e28\
e7011f3193b9020e73c183a98cc3a519160ed759376dd92c94831622\
00"
)
.unwrap()
.as_slice()
);
} }

View File

@@ -27,7 +27,7 @@ short-weierstrass = { path = "../short-weierstrass", default-features = false }
curve25519-dalek = { version = "4", default-features = false, features = ["legacy_compatibility"] } curve25519-dalek = { version = "4", default-features = false, features = ["legacy_compatibility"] }
dalek-ff-group = { path = "../dalek-ff-group", version = "0.4", default-features = false } dalek-ff-group = { path = "../dalek-ff-group", version = "0.4", default-features = false }
blake2 = { version = "0.10", default-features = false } blake2 = { version = "0.11.0-rc.0", default-features = false }
ciphersuite = { path = "../ciphersuite", version = "0.4", default-features = false } ciphersuite = { path = "../ciphersuite", version = "0.4", default-features = false }
generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false, optional = true } generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false, optional = true }
@@ -41,5 +41,5 @@ ff-group-tests = { path = "../ff-group-tests" }
[features] [features]
alloc = ["std-shims", "zeroize/alloc", "prime-field/alloc", "short-weierstrass/alloc", "curve25519-dalek/alloc", "ciphersuite/alloc", "generalized-bulletproofs-ec-gadgets"] alloc = ["std-shims", "zeroize/alloc", "prime-field/alloc", "short-weierstrass/alloc", "curve25519-dalek/alloc", "ciphersuite/alloc", "generalized-bulletproofs-ec-gadgets"]
std = ["alloc", "std-shims/std", "zeroize/std", "prime-field/std", "short-weierstrass/std", "blake2/std", "ciphersuite/std", "generalized-bulletproofs-ec-gadgets/std"] std = ["alloc", "std-shims/std", "zeroize/std", "prime-field/std", "short-weierstrass/std", "ciphersuite/std", "generalized-bulletproofs-ec-gadgets/std"]
default = ["std"] default = ["std"]

View File

@@ -10,7 +10,7 @@ use std_shims::io::{self, Read};
use prime_field::{subtle::Choice, zeroize::Zeroize}; use prime_field::{subtle::Choice, zeroize::Zeroize};
use ciphersuite::group::{ use ciphersuite::group::{
ff::{Field, PrimeField, FromUniformBytes}, ff::{Field, PrimeField},
Group, Group,
}; };
@@ -53,7 +53,7 @@ impl ShortWeierstrass for Embedwards25519 {
fn encode_compressed(x: Self::FieldElement, odd_y: Choice) -> Self::Repr { fn encode_compressed(x: Self::FieldElement, odd_y: Choice) -> Self::Repr {
// The LE `x` coordinate, with if `y` is odd in the unused 256th bit // The LE `x` coordinate, with if `y` is odd in the unused 256th bit
let mut res = [0; 32]; let mut res = [0; 32];
res.as_mut().copy_from_slice(x.to_repr().as_ref()); res.copy_from_slice(x.to_repr().as_ref());
res[31] |= odd_y.unwrap_u8() << 7; res[31] |= odd_y.unwrap_u8() << 7;
res res
} }
@@ -65,7 +65,10 @@ impl ShortWeierstrass for Embedwards25519 {
// Copy from the point's representation to the field's // Copy from the point's representation to the field's
let mut repr = <Self::FieldElement as PrimeField>::Repr::default(); let mut repr = <Self::FieldElement as PrimeField>::Repr::default();
repr.as_mut().copy_from_slice(&bytes); {
let repr: &mut [u8] = repr.as_mut();
repr.copy_from_slice(&bytes);
}
(repr, odd_y) (repr, odd_y)
} }
@@ -88,18 +91,6 @@ impl ciphersuite::Ciphersuite for Embedwards25519 {
Point::generator() Point::generator()
} }
/// `hash_to_F` is implemented with a naive concatenation of the `dst` and `data`, allowing
/// transposition between the two. This means `dst: b"abc", data: b"def"`, will produce the same
/// scalar as `dst: "abcdef", data: b""`. Please use carefully, not letting `dst` valuess be
/// substrings of each other.
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
use blake2::Digest;
let mut digest = Self::H::new();
digest.update(dst);
digest.update(data);
<Scalar as FromUniformBytes<64>>::from_uniform_bytes(&digest.finalize().into())
}
// We override the provided impl, which compares against the reserialization, because // We override the provided impl, which compares against the reserialization, because
// we already require canonicity // we already require canonicity
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]

View File

@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/frost"
authors = ["Luke Parker <lukeparker5132@gmail.com>"] authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["frost", "multisig", "threshold"] keywords = ["frost", "multisig", "threshold"]
edition = "2021" edition = "2021"
rust-version = "1.79" rust-version = "1.85"
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true
@@ -27,13 +27,14 @@ subtle = { version = "^2.4", default-features = false, features = ["std"] }
hex = { version = "0.4", default-features = false, features = ["std"], optional = true } hex = { version = "0.4", default-features = false, features = ["std"], optional = true }
digest = { version = "0.10", default-features = false, features = ["std"] }
transcript = { package = "flexible-transcript", path = "../transcript", version = "^0.3.2", default-features = false, features = ["std", "recommended"] } transcript = { package = "flexible-transcript", path = "../transcript", version = "^0.3.2", default-features = false, features = ["std", "recommended"] }
dalek-ff-group = { path = "../dalek-ff-group", version = "0.4", default-features = false, features = ["std"], optional = true } dalek-ff-group = { path = "../dalek-ff-group", version = "0.4", default-features = false, features = ["std"], optional = true }
minimal-ed448 = { path = "../ed448", version = "0.4", default-features = false, features = ["std"], optional = true } minimal-ed448 = { path = "../ed448", version = "0.4", default-features = false, features = ["std"], optional = true }
ciphersuite = { path = "../ciphersuite", version = "^0.4.1", default-features = false, features = ["std"] } ciphersuite = { path = "../ciphersuite", version = "^0.4.1", default-features = false, features = ["std"] }
sha2 = { version = "0.10.0", default-features = false, optional = true }
elliptic-curve = { version = "0.13", default-features = false, features = ["hash2curve"], optional = true }
ciphersuite-kp256 = { path = "../ciphersuite/kp256", version = "0.4", default-features = false, features = ["std"], optional = true } ciphersuite-kp256 = { path = "../ciphersuite/kp256", version = "0.4", default-features = false, features = ["std"], optional = true }
multiexp = { path = "../multiexp", version = "0.4", default-features = false, features = ["std", "batch"] } multiexp = { path = "../multiexp", version = "0.4", default-features = false, features = ["std", "batch"] }
@@ -56,8 +57,8 @@ dkg-dealer = { path = "../dkg/dealer", default-features = false, features = ["st
ed25519 = ["dalek-ff-group"] ed25519 = ["dalek-ff-group"]
ristretto = ["dalek-ff-group"] ristretto = ["dalek-ff-group"]
secp256k1 = ["ciphersuite-kp256"] secp256k1 = ["sha2", "elliptic-curve", "ciphersuite-kp256"]
p256 = ["ciphersuite-kp256"] p256 = ["sha2", "elliptic-curve", "ciphersuite-kp256"]
ed448 = ["minimal-ed448"] ed448 = ["minimal-ed448"]

View File

@@ -1,9 +1,6 @@
use digest::Digest; use ciphersuite::{digest::Digest, FromUniformBytes, Ciphersuite};
use dalek_ff_group::Scalar; use dalek_ff_group::Scalar;
use ciphersuite::Ciphersuite;
use crate::{curve::Curve, algorithm::Hram}; use crate::{curve::Curve, algorithm::Hram};
macro_rules! dalek_curve { macro_rules! dalek_curve {
@@ -20,6 +17,13 @@ macro_rules! dalek_curve {
impl Curve for $Curve { impl Curve for $Curve {
const CONTEXT: &'static [u8] = $CONTEXT; const CONTEXT: &'static [u8] = $CONTEXT;
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
let mut digest = <Self as Ciphersuite>::H::new();
digest.update(Self::CONTEXT);
digest.update(dst);
digest.update(msg);
Self::F::from_uniform_bytes(&digest.finalize().into())
}
} }
/// The challenge function for this ciphersuite. /// The challenge function for this ciphersuite.
@@ -30,11 +34,13 @@ macro_rules! dalek_curve {
fn hram(R: &<$Curve as Ciphersuite>::G, A: &<$Curve as Ciphersuite>::G, m: &[u8]) -> Scalar { fn hram(R: &<$Curve as Ciphersuite>::G, A: &<$Curve as Ciphersuite>::G, m: &[u8]) -> Scalar {
let mut hash = <$Curve as Ciphersuite>::H::new(); let mut hash = <$Curve as Ciphersuite>::H::new();
if $chal.len() != 0 { if $chal.len() != 0 {
hash.update(&[$CONTEXT.as_ref(), $chal].concat()); hash.update($CONTEXT);
hash.update($chal);
} }
Scalar::from_hash( hash.update(R.compress().to_bytes());
hash.chain_update(&[&R.compress().to_bytes(), &A.compress().to_bytes(), m].concat()), hash.update(A.compress().to_bytes());
) hash.update(m);
Scalar::from_uniform_bytes(&hash.finalize().into())
} }
} }
}; };

View File

@@ -1,11 +1,6 @@
use digest::Digest; pub use ciphersuite::{digest::Digest, group::GroupEncoding, FromUniformBytes, Ciphersuite};
use minimal_ed448::{Scalar, Point}; use minimal_ed448::{Scalar, Point};
pub use minimal_ed448::Ed448; pub use minimal_ed448::Ed448;
pub use ciphersuite::{
group::{ff::FromUniformBytes, GroupEncoding},
Ciphersuite,
};
use crate::{curve::Curve, algorithm::Hram}; use crate::{curve::Curve, algorithm::Hram};
@@ -13,6 +8,13 @@ const CONTEXT: &[u8] = b"FROST-ED448-SHAKE256-v1";
impl Curve for Ed448 { impl Curve for Ed448 {
const CONTEXT: &'static [u8] = CONTEXT; const CONTEXT: &'static [u8] = CONTEXT;
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
let mut digest = <Self as Ciphersuite>::H::new();
digest.update(Self::CONTEXT);
digest.update(dst);
digest.update(msg);
Self::F::from_uniform_bytes(&digest.finalize().into())
}
} }
// The RFC-8032 Ed448 challenge function. // The RFC-8032 Ed448 challenge function.
@@ -21,20 +23,14 @@ pub(crate) struct Ietf8032Ed448Hram;
impl Ietf8032Ed448Hram { impl Ietf8032Ed448Hram {
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub(crate) fn hram(context: &[u8], R: &Point, A: &Point, m: &[u8]) -> Scalar { pub(crate) fn hram(context: &[u8], R: &Point, A: &Point, m: &[u8]) -> Scalar {
Scalar::from_uniform_bytes( let mut digest = <Ed448 as Ciphersuite>::H::new();
&<[u8; 114]>::try_from( digest.update(b"SigEd448");
<Ed448 as Ciphersuite>::H::digest( digest.update([0, u8::try_from(context.len()).unwrap()]);
[ digest.update(context);
&[b"SigEd448".as_ref(), &[0, u8::try_from(context.len()).unwrap()]].concat(), digest.update(R.to_bytes());
context, digest.update(A.to_bytes());
&[R.to_bytes().as_ref(), A.to_bytes().as_ref(), m].concat(), digest.update(m);
] Scalar::from_uniform_bytes(&digest.finalize().into())
.concat(),
)
.as_slice(),
)
.unwrap(),
)
} }
} }

View File

@@ -1,7 +1,85 @@
use ciphersuite::{group::GroupEncoding, Ciphersuite}; use core::convert::AsRef;
use sha2::{digest::Digest, Sha256};
use ciphersuite::{
group::{
ff::{Field, PrimeField},
GroupEncoding,
},
Ciphersuite,
};
use elliptic_curve::{
zeroize::Zeroize,
generic_array::{typenum::U32, GenericArray},
bigint::{NonZero, CheckedAdd, Encoding, U384},
hash2curve::{Expander, ExpandMsg, ExpandMsgXmd},
};
use crate::{curve::Curve, algorithm::Hram}; use crate::{curve::Curve, algorithm::Hram};
#[allow(non_snake_case)]
fn hash_to_F<C: Ciphersuite<F: PrimeField<Repr = GenericArray<u8, U32>>>>(
dst: &[u8],
msg: &[u8],
) -> C::F {
// While one of these two libraries does support directly hashing to the Scalar field, the
// other doesn't. While that's probably an oversight, this is a universally working method
// This method is from
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html
// Specifically, Section 5
// While that draft, overall, is intended for hashing to curves, that necessitates
// detailing how to hash to a finite field. The draft comments that its mechanism for
// doing so, which it uses to derive field elements, is also applicable to the scalar field
// The hash_to_field function is intended to provide unbiased values
// In order to do so, a wide reduction from an extra k bits is applied, minimizing bias to
// 2^-k
// k is intended to be the bits of security of the suite, which is 128 for secp256k1 and
// P-256
const K: usize = 128;
// L is the amount of bytes of material which should be used in the wide reduction
// The 256 is for the bit-length of the primes, rounded up to the nearest byte threshold
// This is a simplification of the formula from the end of section 5
const L: usize = (256 + K) / 8; // 48
// In order to perform this reduction, we need to use 48-byte numbers
// First, convert the modulus to a 48-byte number
// This is done by getting -1 as bytes, parsing it into a U384, and then adding back one
let mut modulus = [0; L];
// The byte repr of scalars will be 32 big-endian bytes
// Set the lower 32 bytes of our 48-byte array accordingly
modulus[16 ..].copy_from_slice(&(C::F::ZERO - C::F::ONE).to_repr());
// Use a checked_add + unwrap since this addition cannot fail (being a 32-byte value with
// 48-bytes of space)
// While a non-panicking saturating_add/wrapping_add could be used, they'd likely be less
// performant
let modulus = U384::from_be_slice(&modulus).checked_add(&U384::ONE).unwrap();
// The defined P-256 and secp256k1 ciphersuites both use expand_message_xmd
let mut wide = U384::from_be_bytes({
let mut bytes = [0; 48];
ExpandMsgXmd::<Sha256>::expand_message(&[msg], &[dst], 48).unwrap().fill_bytes(&mut bytes);
bytes
})
.rem(&NonZero::new(modulus).unwrap())
.to_be_bytes();
// Now that this has been reduced back to a 32-byte value, grab the lower 32-bytes
let mut array = *GenericArray::from_slice(&wide[16 ..]);
let res = C::F::from_repr(array).unwrap();
// Zeroize the temp values we can due to the possibility `hash_to_F` is being used for
// nonces
wide.zeroize();
array.zeroize();
res
}
macro_rules! kp_curve { macro_rules! kp_curve {
( (
$feature: literal, $feature: literal,
@@ -15,6 +93,17 @@ macro_rules! kp_curve {
impl Curve for $Curve { impl Curve for $Curve {
const CONTEXT: &'static [u8] = $CONTEXT; const CONTEXT: &'static [u8] = $CONTEXT;
// These ciphersuites define their hash as SHA-512, yet FROST uses SHA-256
fn hash(dst: &[u8], data: &[u8]) -> impl AsRef<[u8]> {
sha2::Sha256::digest([Self::CONTEXT, dst, data].concat())
}
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
let dst = [Self::CONTEXT, dst].concat();
let dst = dst.as_slice();
hash_to_F::<Self>(dst, msg)
}
} }
/// The challenge function for this ciphersuite. /// The challenge function for this ciphersuite.
@@ -41,3 +130,27 @@ kp_curve!("p256", P256, IetfP256Hram, b"FROST-P256-SHA256-v1");
#[cfg(feature = "secp256k1")] #[cfg(feature = "secp256k1")]
kp_curve!("secp256k1", Secp256k1, IetfSecp256k1Hram, b"FROST-secp256k1-SHA256-v1"); kp_curve!("secp256k1", Secp256k1, IetfSecp256k1Hram, b"FROST-secp256k1-SHA256-v1");
#[cfg(test)]
fn test_oversize_dst<C: Ciphersuite<F: PrimeField<Repr = GenericArray<u8, U32>>>>() {
use sha2::Digest;
// The draft specifies DSTs >255 bytes should be hashed into a 32-byte DST
let oversize_dst = [0x00; 256];
let actual_dst = Sha256::digest([b"H2C-OVERSIZE-DST-".as_slice(), &oversize_dst].concat());
// Test the hash_to_F function handles this
// If it didn't, these would return different values
assert_eq!(hash_to_F::<C>(&oversize_dst, &[]), hash_to_F::<C>(&actual_dst, &[]));
}
#[cfg(feature = "secp256k1")]
#[test]
fn test_secp256k1() {
test_oversize_dst::<Secp256k1>();
}
#[cfg(feature = "p256")]
#[test]
fn test_p256() {
test_oversize_dst::<P256>();
}

View File

@@ -1,4 +1,4 @@
use core::ops::Deref; use core::{ops::Deref, convert::AsRef};
use std::io::{self, Read}; use std::io::{self, Read};
use rand_core::{RngCore, CryptoRng}; use rand_core::{RngCore, CryptoRng};
@@ -6,9 +6,8 @@ use rand_core::{RngCore, CryptoRng};
use zeroize::{Zeroize, Zeroizing}; use zeroize::{Zeroize, Zeroizing};
use subtle::ConstantTimeEq; use subtle::ConstantTimeEq;
use digest::{Digest, Output};
pub use ciphersuite::{ pub use ciphersuite::{
digest::Digest,
group::{ group::{
ff::{Field, PrimeField}, ff::{Field, PrimeField},
Group, Group,
@@ -46,24 +45,23 @@ pub trait Curve: Ciphersuite {
const CONTEXT: &'static [u8]; const CONTEXT: &'static [u8];
/// Hash the given dst and data to a byte vector. Used to instantiate H4 and H5. /// Hash the given dst and data to a byte vector. Used to instantiate H4 and H5.
fn hash(dst: &[u8], data: &[u8]) -> Output<Self::H> { fn hash(dst: &[u8], data: &[u8]) -> impl AsRef<[u8]> {
Self::H::digest([Self::CONTEXT, dst, data].concat()) Self::H::digest([Self::CONTEXT, dst, data].concat())
} }
/// Field element from hash. Used during key gen and by other crates under Serai as a general /// Field element from hash. Used to instantiate H1 and H3.
/// utility. Used to instantiate H1 and H3. ///
/// The `dst` MUST be prefixed by `Self::CONTEXT` by the implementor.
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F { fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F;
<Self as Ciphersuite>::hash_to_F(&[Self::CONTEXT, dst].concat(), msg)
}
/// Hash the message for the binding factor. H4 from the IETF draft. /// Hash the message for the binding factor. H4 from the IETF draft.
fn hash_msg(msg: &[u8]) -> Output<Self::H> { fn hash_msg(msg: &[u8]) -> impl AsRef<[u8]> {
Self::hash(b"msg", msg) Self::hash(b"msg", msg)
} }
/// Hash the commitments for the binding factor. H5 from the IETF draft. /// Hash the commitments for the binding factor. H5 from the IETF draft.
fn hash_commitments(commitments: &[u8]) -> Output<Self::H> { fn hash_commitments(commitments: &[u8]) -> impl AsRef<[u8]> {
Self::hash(b"com", commitments) Self::hash(b"com", commitments)
} }

View File

@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/schnorr"
authors = ["Luke Parker <lukeparker5132@gmail.com>"] authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["schnorr", "ff", "group"] keywords = ["schnorr", "ff", "group"]
edition = "2021" edition = "2021"
rust-version = "1.79" rust-version = "1.85"
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View File

@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/schnorrkel"
authors = ["Luke Parker <lukeparker5132@gmail.com>"] authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["frost", "multisig", "threshold", "schnorrkel"] keywords = ["frost", "multisig", "threshold", "schnorrkel"]
edition = "2021" edition = "2021"
rust-version = "1.79" rust-version = "1.85"
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View File

@@ -23,7 +23,7 @@ k256 = { version = "0.13", default-features = false, features = ["arithmetic"] }
prime-field = { path = "../prime-field", default-features = false } prime-field = { path = "../prime-field", default-features = false }
short-weierstrass = { path = "../short-weierstrass", default-features = false } short-weierstrass = { path = "../short-weierstrass", default-features = false }
blake2 = { version = "0.10", default-features = false } blake2 = { version = "0.11.0-rc.0", default-features = false }
ciphersuite = { path = "../ciphersuite", version = "0.4", default-features = false } ciphersuite = { path = "../ciphersuite", version = "0.4", default-features = false }
generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false, optional = true } generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false, optional = true }
@@ -36,5 +36,5 @@ ff-group-tests = { path = "../ff-group-tests" }
[features] [features]
alloc = ["std-shims", "generic-array/alloc", "k256/alloc", "prime-field/alloc", "short-weierstrass/alloc", "ciphersuite/alloc", "generalized-bulletproofs-ec-gadgets"] alloc = ["std-shims", "generic-array/alloc", "k256/alloc", "prime-field/alloc", "short-weierstrass/alloc", "ciphersuite/alloc", "generalized-bulletproofs-ec-gadgets"]
std = ["alloc", "std-shims/std", "k256/std", "prime-field/std", "blake2/std", "ciphersuite/std", "generalized-bulletproofs-ec-gadgets/std"] std = ["alloc", "std-shims/std", "k256/std", "prime-field/std", "ciphersuite/std", "generalized-bulletproofs-ec-gadgets/std"]
default = ["std"] default = ["std"]

View File

@@ -13,10 +13,7 @@ use generic_array::{typenum::U33, GenericArray};
use k256::elliptic_curve::{ use k256::elliptic_curve::{
subtle::{Choice, ConstantTimeEq, ConditionallySelectable}, subtle::{Choice, ConstantTimeEq, ConditionallySelectable},
zeroize::Zeroize, zeroize::Zeroize,
group::{ group::{ff::PrimeField, Group},
ff::{PrimeField, FromUniformBytes},
Group,
},
sec1::Tag, sec1::Tag,
}; };
@@ -121,18 +118,6 @@ impl ciphersuite::Ciphersuite for Secq256k1 {
Point::generator() Point::generator()
} }
/// `hash_to_F` is implemented with a naive concatenation of the `dst` and `data`, allowing
/// transposition between the two. This means `dst: b"abc", data: b"def"`, will produce the same
/// scalar as `dst: "abcdef", data: b""`. Please use carefully, not letting `dst` valuess be
/// substrings of each other.
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
use blake2::Digest;
let mut digest = Self::H::new();
digest.update(dst);
digest.update(data);
<Scalar as FromUniformBytes<64>>::from_uniform_bytes(&digest.finalize().into())
}
// We override the provided impl, which compares against the reserialization, because // We override the provided impl, which compares against the reserialization, because
// we already require canonicity // we already require canonicity
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]

View File

@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/crypto/transcript"
authors = ["Luke Parker <lukeparker5132@gmail.com>"] authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["transcript"] keywords = ["transcript"]
edition = "2021" edition = "2021"
rust-version = "1.73" rust-version = "1.85"
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true
@@ -19,17 +19,17 @@ workspace = true
[dependencies] [dependencies]
zeroize = { version = "^1.5", default-features = false } zeroize = { version = "^1.5", default-features = false }
digest = { version = "0.10", default-features = false, features = ["core-api"] } digest = { version = "0.11.0-rc.0", default-features = false, features = ["block-api"] }
blake2 = { version = "0.10", default-features = false, optional = true } blake2 = { version = "0.11.0-rc.0", default-features = false, optional = true }
merlin = { version = "3", default-features = false, optional = true } merlin = { version = "3", default-features = false, optional = true }
[dev-dependencies] [dev-dependencies]
sha2 = { version = "0.10", default-features = false } sha2 = { version = "0.11.0-rc.0", default-features = false }
blake2 = { version = "0.10", default-features = false } blake2 = { version = "0.11.0-rc.0", default-features = false }
[features] [features]
std = ["zeroize/std", "digest/std", "blake2?/std", "merlin?/std"] std = ["zeroize/std", "merlin?/std"]
recommended = ["blake2"] recommended = ["blake2"]
tests = [] tests = []
default = ["std"] default = ["std"]

View File

@@ -8,7 +8,7 @@ use digest::{
typenum::{ typenum::{
consts::U32, marker_traits::NonZero, type_operators::IsGreaterOrEqual, operator_aliases::GrEq, consts::U32, marker_traits::NonZero, type_operators::IsGreaterOrEqual, operator_aliases::GrEq,
}, },
core_api::BlockSizeUser, block_api::BlockSizeUser,
Digest, Output, HashMarker, Digest, Output, HashMarker,
}; };

View File

@@ -52,7 +52,7 @@ pub fn message_challenge(
transcript.append_message(b"msg", msg); transcript.append_message(b"msg", msg);
transcript.domain_separate(b"signature"); transcript.domain_separate(b"signature");
transcript.append_message(b"nonce", nonce.to_bytes()); transcript.append_message(b"nonce", nonce.to_bytes());
<Ristretto as Ciphersuite>::hash_to_F(b"message_challenge", &transcript.challenge(b"challenge")) <Ristretto as Ciphersuite>::hash_to_F(&transcript.challenge(b"challenge"))
} }
pub fn ack_challenge( pub fn ack_challenge(
@@ -71,5 +71,5 @@ pub fn ack_challenge(
transcript.append_message(b"id", id.to_le_bytes()); transcript.append_message(b"id", id.to_le_bytes());
transcript.domain_separate(b"signature"); transcript.domain_separate(b"signature");
transcript.append_message(b"nonce", nonce.to_bytes()); transcript.append_message(b"nonce", nonce.to_bytes());
<Ristretto as Ciphersuite>::hash_to_F(b"ack_challenge", &transcript.challenge(b"challenge")) <Ristretto as Ciphersuite>::hash_to_F(&transcript.challenge(b"challenge"))
} }

View File

@@ -19,13 +19,13 @@ use primitives::OutputType;
use crate::hash_bytes; use crate::hash_bytes;
const KEY_DST: &[u8] = b"Serai Bitcoin Processor Key Offset"; // TODO: Bitcoin HD derivation, instead of these bespoke labels?
static BRANCH_BASE_OFFSET: LazyLock<<Secp256k1 as Ciphersuite>::F> = static BRANCH_BASE_OFFSET: LazyLock<<Secp256k1 as Ciphersuite>::F> =
LazyLock::new(|| Secp256k1::hash_to_F(KEY_DST, b"branch")); LazyLock::new(|| Secp256k1::hash_to_F(b"branch"));
static CHANGE_BASE_OFFSET: LazyLock<<Secp256k1 as Ciphersuite>::F> = static CHANGE_BASE_OFFSET: LazyLock<<Secp256k1 as Ciphersuite>::F> =
LazyLock::new(|| Secp256k1::hash_to_F(KEY_DST, b"change")); LazyLock::new(|| Secp256k1::hash_to_F(b"change"));
static FORWARD_BASE_OFFSET: LazyLock<<Secp256k1 as Ciphersuite>::F> = static FORWARD_BASE_OFFSET: LazyLock<<Secp256k1 as Ciphersuite>::F> =
LazyLock::new(|| Secp256k1::hash_to_F(KEY_DST, b"forward")); LazyLock::new(|| Secp256k1::hash_to_F(b"forward"));
// Unfortunately, we have per-key offsets as it's the root key plus the base offset may not be // Unfortunately, we have per-key offsets as it's the root key plus the base offset may not be
// even. While we could tweak the key until all derivations are even, that'd require significantly // even. While we could tweak the key until all derivations are even, that'd require significantly

View File

@@ -8,7 +8,7 @@ authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = [] keywords = []
edition = "2021" edition = "2021"
publish = false publish = false
rust-version = "1.79" rust-version = "1.85"
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View File

@@ -7,7 +7,7 @@ repository = "https://github.com/serai-dex/serai/tree/develop/processor/view-key
authors = ["Luke Parker <lukeparker5132@gmail.com>"] authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = [] keywords = []
edition = "2021" edition = "2021"
rust-version = "1.73" rust-version = "1.85"
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View File

@@ -9,5 +9,5 @@ use ciphersuite::Ciphersuite;
/// `k` is the index of the key to generate (enabling generating multiple view keys within a /// `k` is the index of the key to generate (enabling generating multiple view keys within a
/// single context). /// single context).
pub fn view_key<C: Ciphersuite>(k: u64) -> C::F { pub fn view_key<C: Ciphersuite>(k: u64) -> C::F {
C::hash_to_F(b"Serai DEX View Key", &k.to_le_bytes()) C::hash_to_F(format!("Serai DEX View Key {k}").as_bytes())
} }

View File

@@ -143,9 +143,9 @@ where
// We don't know the eth address before the smart contract is deployed. // We don't know the eth address before the smart contract is deployed.
ExternalNetworkId::Ethereum => Ok(String::new()), ExternalNetworkId::Ethereum => Ok(String::new()),
ExternalNetworkId::Monero => { ExternalNetworkId::Monero => {
// TODO: Serai view-key crate
let view_private = zeroize::Zeroizing::new( let view_private = zeroize::Zeroizing::new(
<Ed25519 as Ciphersuite>::hash_to_F( <Ed25519 as Ciphersuite>::hash_to_F(
b"Serai DEX Additional Key",
&["Monero".as_bytes(), &0u64.to_le_bytes()].concat(), &["Monero".as_bytes(), &0u64.to_le_bytes()].concat(),
) )
.0, .0,

View File

@@ -103,7 +103,7 @@ pub fn insecure_pair_from_name(name: &str) -> Pair {
/// This key should never be considered a secure private key. It has effectively no entropy. /// This key should never be considered a secure private key. It has effectively no entropy.
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn insecure_arbitrary_key_from_name<C: ciphersuite::Ciphersuite>(name: &str) -> C::F { pub fn insecure_arbitrary_key_from_name<C: ciphersuite::Ciphersuite>(name: &str) -> C::F {
C::hash_to_F(b"insecure arbitrary key", name.as_bytes()) C::hash_to_F(name.as_bytes())
} }
pub struct AccountLookup; pub struct AccountLookup;