From 56fc39fff533d956af97048a8af3b0d83be8e9f0 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 3 May 2022 07:42:09 -0400 Subject: [PATCH] Fix https://github.com/serai-dex/serai/issues/5 --- Cargo.toml | 1 + crypto/frost/Cargo.toml | 2 ++ crypto/frost/src/lib.rs | 67 ++---------------------------------- crypto/frost/tests/common.rs | 2 +- crypto/multiexp/Cargo.toml | 11 ++++++ crypto/multiexp/LICENSE | 21 +++++++++++ crypto/multiexp/src/lib.rs | 55 +++++++++++++++++++++++++++++ 7 files changed, 93 insertions(+), 66 deletions(-) create mode 100644 crypto/multiexp/Cargo.toml create mode 100644 crypto/multiexp/LICENSE create mode 100644 crypto/multiexp/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 012a580f..4305df32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "crypto/transcript", + "crypto/multiexp", "crypto/frost", "crypto/dalek-ff-group", "coins/monero", diff --git a/crypto/frost/Cargo.toml b/crypto/frost/Cargo.toml index 4f6fc5ad..d444c83c 100644 --- a/crypto/frost/Cargo.toml +++ b/crypto/frost/Cargo.toml @@ -17,6 +17,8 @@ group = "0.11" blake2 = "0.10" transcript = { path = "../transcript" } +multiexp = { path = "../multiexp" } + [dev-dependencies] rand = "0.8" sha2 = "0.10" diff --git a/crypto/frost/src/lib.rs b/crypto/frost/src/lib.rs index 3d55d4e4..917931cf 100644 --- a/crypto/frost/src/lib.rs +++ b/crypto/frost/src/lib.rs @@ -5,6 +5,8 @@ use thiserror::Error; use ff::{Field, PrimeField}; use group::{Group, GroupOps, ScalarMul}; +pub use multiexp::multiexp_vartime; + pub mod key_gen; pub mod algorithm; pub mod sign; @@ -372,68 +374,3 @@ impl MultisigKeys { ) } } - -/* -An implementation of Straus, which should be more efficient than Pippenger for the expected amount -of points - -Completing key generation from the round 2 messages takes: -- Naive - Completed 33-of-50 in 2.66s - Completed 5-of-8 in 11.05ms - -- crate Straus - Completed 33-of-50 in 730-833ms (extremely notable effects from taking variable time) - Completed 5-of-8 in 2.8ms - -- dalek VartimeMultiscalarMul - Completed 33-of-50 in 266ms - Completed 5-of-8 in 1.6ms - -This does show this algorithm isn't appropriately tuned (and potentially isn't even the right -choice), at least with that quantity. Unfortunately, we can't use dalek's multiexp implementation -everywhere, and this does work -*/ -pub fn multiexp_vartime(scalars: &[C::F], points: &[C::G]) -> C::G { - let mut tables = vec![]; - // dalek uses 8 in their impl, along with a carry scheme where values are [-8, 8) - // Moving to a similar system here did save a marginal amount, yet not one significant enough for - // its pain (as some fields do have scalars which can have their top bit set, a scenario dalek - // assumes is never true) - tables.resize(points.len(), Vec::with_capacity(15)); - for p in 0 .. points.len() { - let mut accum = C::G::identity(); - tables[p].push(accum); - for _ in 0 .. 15 { - accum += points[p]; - tables[p].push(accum); - } - } - - let mut nibbles = vec![]; - nibbles.resize(scalars.len(), vec![]); - for s in 0 .. scalars.len() { - let bytes = C::F_to_le_bytes(&scalars[s]); - nibbles[s].resize(C::F_len() * 2, 0); - for i in 0 .. bytes.len() { - nibbles[s][i * 2] = bytes[i] & 0b1111; - nibbles[s][(i * 2) + 1] = (bytes[i] >> 4) & 0b1111; - } - } - - let mut res = C::G::identity(); - for b in (0 .. (C::F_len() * 2)).rev() { - for _ in 0 .. 4 { - res = res.double(); - } - - for s in 0 .. scalars.len() { - // This creates a 250% performance increase on key gen, which uses a bunch of very low - // scalars. This is why this function is now committed to being vartime - if nibbles[s][b] != 0 { - res += tables[s][nibbles[s][b] as usize]; - } - } - } - res -} diff --git a/crypto/frost/tests/common.rs b/crypto/frost/tests/common.rs index 286372c1..057927d4 100644 --- a/crypto/frost/tests/common.rs +++ b/crypto/frost/tests/common.rs @@ -37,7 +37,7 @@ impl Curve for Secp256k1 { } fn multiexp_vartime(scalars: &[Self::F], points: &[Self::G]) -> Self::G { - multiexp_vartime::(scalars, points) + multiexp_vartime(scalars, points, false) } // The IETF draft doesn't specify a secp256k1 ciphersuite diff --git a/crypto/multiexp/Cargo.toml b/crypto/multiexp/Cargo.toml new file mode 100644 index 00000000..960ed8ff --- /dev/null +++ b/crypto/multiexp/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "multiexp" +version = "0.1.0" +description = "Multiexponentation algorithms for ff/group" +license = "MIT" +authors = ["Luke Parker "] +edition = "2021" + +[dependencies] +ff = "0.11" +group = "0.11" diff --git a/crypto/multiexp/LICENSE b/crypto/multiexp/LICENSE new file mode 100644 index 00000000..f05b748b --- /dev/null +++ b/crypto/multiexp/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Luke Parker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crypto/multiexp/src/lib.rs b/crypto/multiexp/src/lib.rs new file mode 100644 index 00000000..40a2591f --- /dev/null +++ b/crypto/multiexp/src/lib.rs @@ -0,0 +1,55 @@ +use ff::PrimeField; +use group::{Group, GroupEncoding, ScalarMul}; + +// An implementation of Straus, with a extremely minimal API that lets us add other algorithms in +// the future. Takes in a list of scalars and points with a boolean for if the scalars are little +// endian encoded or not +pub fn multiexp_vartime>( + scalars: &[F], + points: &[G], + little: bool +) -> G { + let mut tables = vec![]; + // dalek uses 8 in their impl, along with a carry scheme where values are [-8, 8) + // Moving to a similar system here did save a marginal amount, yet not one significant enough for + // its pain (as some fields do have scalars which can have their top bit set, a scenario dalek + // assumes is never true) + tables.resize(points.len(), [G::identity(); 16]); + for p in 0 .. points.len() { + let mut accum = G::identity(); + for i in 1 .. 16 { + accum += points[p]; + tables[p][i] = accum; + } + } + + let mut nibbles = vec![]; + nibbles.resize(scalars.len(), vec![]); + for s in 0 .. scalars.len() { + let mut repr = scalars[s].to_repr(); + let bytes = repr.as_mut(); + if !little { + bytes.reverse(); + } + + nibbles[s].resize(bytes.len() * 2, 0); + for i in 0 .. bytes.len() { + nibbles[s][i * 2] = bytes[i] & 0b1111; + nibbles[s][(i * 2) + 1] = (bytes[i] >> 4) & 0b1111; + } + } + + let mut res = G::identity(); + for b in (0 .. nibbles[0].len()).rev() { + for _ in 0 .. 4 { + res = res.double(); + } + + for s in 0 .. scalars.len() { + if nibbles[s][b] != 0 { + res += tables[s][nibbles[s][b] as usize]; + } + } + } + res +}