* Partial move to ff 0.13

It turns out the newly released k256 0.12 isn't on ff 0.13, preventing further
work at this time.

* Update all crates to work on ff 0.13

The provided curves still need to be expanded to fit the new API.

* Finish adding dalek-ff-group ff 0.13 constants

* Correct FieldElement::product definition

Also stops exporting macros.

* Test most new parts of ff 0.13

* Additionally test ff-group-tests with BLS12-381 and the pasta curves

We only tested curves from RustCrypto. Now we test a curve offered by zk-crypto,
the group behind ff/group, and the pasta curves, which is by Zcash (though
Zcash developers are also behind zk-crypto).

* Finish Ed448

Fully specifies all constants, passes all tests in ff-group-tests, and finishes moving to ff-0.13.

* Add RustCrypto/elliptic-curves to allowed git repos

Needed due to k256/p256 incorrectly defining product.

* Finish writing ff 0.13 tests

* Add additional comments to dalek

* Further comments

* Update ethereum-serai to ff 0.13
This commit is contained in:
Luke Parker 2023-03-28 04:38:01 -04:00 committed by GitHub
parent a9f6300e86
commit 79aff5d4c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
59 changed files with 865 additions and 429 deletions

162
Cargo.lock generated
View file

@ -585,7 +585,7 @@ dependencies = [
"bitcoin",
"flexible-transcript",
"hex",
"k256 0.12.0",
"k256",
"lazy_static",
"modular-frost",
"rand_core 0.6.4",
@ -735,6 +735,19 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
[[package]]
name = "bls12_381"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7bc6d6292be3a19e6379786dac800f551e5865a5bb51ebbe3064ab80433f403"
dependencies = [
"ff 0.13.0",
"group 0.13.0",
"pairing",
"rand_core 0.6.4",
"subtle",
]
[[package]]
name = "bounded-collections"
version = "0.1.5"
@ -1017,15 +1030,15 @@ version = "0.3.0"
dependencies = [
"dalek-ff-group",
"digest 0.10.6",
"elliptic-curve 0.12.3",
"ff 0.12.1",
"elliptic-curve 0.13.2",
"ff 0.13.0",
"ff-group-tests",
"flexible-transcript",
"group 0.12.1",
"group 0.13.0",
"hex",
"k256 0.12.0",
"k256",
"minimal-ed448",
"p256 0.12.0",
"p256 0.13.0",
"rand_core 0.6.4",
"sha2 0.10.6",
"sha3",
@ -1141,7 +1154,7 @@ dependencies = [
"digest 0.10.6",
"getrandom 0.2.8",
"hmac 0.12.1",
"k256 0.13.0",
"k256",
"lazy_static",
"serde",
"sha2 0.10.6",
@ -1641,9 +1654,9 @@ dependencies = [
"crypto-bigint 0.5.1",
"curve25519-dalek 3.2.0",
"digest 0.10.6",
"ff 0.12.1",
"ff 0.13.0",
"ff-group-tests",
"group 0.12.1",
"group 0.13.0",
"rand_core 0.6.4",
"sha2 0.9.9",
"subtle",
@ -1975,11 +1988,11 @@ dependencies = [
"blake2",
"dalek-ff-group",
"digest 0.10.6",
"ff 0.12.1",
"ff 0.13.0",
"flexible-transcript",
"group 0.12.1",
"group 0.13.0",
"hex-literal",
"k256 0.12.0",
"k256",
"multiexp",
"rand_core 0.6.4",
"thiserror",
@ -2049,18 +2062,6 @@ dependencies = [
"signature 1.6.4",
]
[[package]]
name = "ecdsa"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12844141594ad74185a926d030f3b605f6a903b4e3fec351f3ea338ac5b7637e"
dependencies = [
"der 0.6.1",
"elliptic-curve 0.12.3",
"rfc6979 0.3.1",
"signature 2.0.0",
]
[[package]]
name = "ecdsa"
version = "0.16.1"
@ -2190,7 +2191,7 @@ dependencies = [
"base64 0.13.1",
"bytes",
"hex",
"k256 0.13.0",
"k256",
"log",
"rand 0.8.5",
"rlp",
@ -2323,8 +2324,8 @@ dependencies = [
"ethers",
"ethers-solc",
"eyre",
"group 0.12.1",
"k256 0.12.0",
"group 0.13.0",
"k256",
"modular-frost",
"rand_core 0.6.4",
"serde",
@ -2455,7 +2456,7 @@ dependencies = [
"generic-array 0.14.6",
"getrandom 0.2.8",
"hex",
"k256 0.13.0",
"k256",
"num_enum",
"once_cell",
"open-fastrlp",
@ -2677,7 +2678,6 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160"
dependencies = [
"bitvec 1.0.1",
"rand_core 0.6.4",
"subtle",
]
@ -2688,18 +2688,22 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
dependencies = [
"bitvec 1.0.1",
"rand_core 0.6.4",
"subtle",
]
[[package]]
name = "ff-group-tests"
version = "0.12.2"
version = "0.13.0"
dependencies = [
"group 0.12.1",
"k256 0.12.0",
"p256 0.12.0",
"bls12_381",
"group 0.13.0",
"k256",
"p256 0.13.0",
"pasta_curves",
"rand_core 0.6.4",
"subtle",
]
[[package]]
@ -2960,7 +2964,7 @@ dependencies = [
"frame-metadata",
"frame-support-procedural",
"impl-trait-for-tuples",
"k256 0.13.0",
"k256",
"log",
"once_cell",
"parity-scale-codec",
@ -4119,25 +4123,10 @@ dependencies = [
"tracing",
]
[[package]]
name = "k256"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92a55e0ff3b72c262bcf041d9e97f1b84492b68f1c1a384de2323d3dc9403397"
dependencies = [
"cfg-if",
"ecdsa 0.15.1",
"elliptic-curve 0.12.3",
"once_cell",
"sha2 0.10.6",
"signature 2.0.0",
]
[[package]]
name = "k256"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955890845095ccf31ef83ad41a05aabb4d8cc23dc3cac5a9f5c89cf26dd0da75"
source = "git+https://github.com/RustCrypto/elliptic-curves?rev=43d95961b77ad18e54d2874403052fdee248076c#43d95961b77ad18e54d2874403052fdee248076c"
dependencies = [
"cfg-if",
"ecdsa 0.16.1",
@ -4226,6 +4215,9 @@ name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
dependencies = [
"spin",
]
[[package]]
name = "lazycell"
@ -5047,11 +5039,10 @@ name = "minimal-ed448"
version = "0.3.0"
dependencies = [
"crypto-bigint 0.5.1",
"dalek-ff-group",
"ff 0.12.1",
"ff 0.13.0",
"ff-group-tests",
"generic-array 0.14.6",
"group 0.12.1",
"group 0.13.0",
"hex",
"lazy_static",
"rand_core 0.6.4",
@ -5169,7 +5160,7 @@ version = "0.3.0"
dependencies = [
"curve25519-dalek 3.2.0",
"dalek-ff-group",
"group 0.12.1",
"group 0.13.0",
"lazy_static",
"sha3",
"subtle",
@ -5207,7 +5198,7 @@ dependencies = [
"dleq",
"flexible-transcript",
"futures",
"group 0.12.1",
"group 0.13.0",
"hex",
"hex-literal",
"lazy_static",
@ -5283,9 +5274,9 @@ name = "multiexp"
version = "0.3.1"
dependencies = [
"dalek-ff-group",
"ff 0.12.1",
"group 0.12.1",
"k256 0.12.0",
"ff 0.13.0",
"group 0.13.0",
"k256",
"rand_core 0.6.4",
"zeroize",
]
@ -5759,12 +5750,11 @@ dependencies = [
[[package]]
name = "p256"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49c124b3cbce43bcbac68c58ec181d98ed6cc7e6d0aa7c3ba97b2563410b0e55"
version = "0.13.0"
source = "git+https://github.com/RustCrypto/elliptic-curves?rev=43d95961b77ad18e54d2874403052fdee248076c#43d95961b77ad18e54d2874403052fdee248076c"
dependencies = [
"ecdsa 0.15.1",
"elliptic-curve 0.12.3",
"ecdsa 0.16.1",
"elliptic-curve 0.13.2",
"primeorder",
"sha2 0.10.6",
]
@ -5790,6 +5780,15 @@ dependencies = [
"libm 0.1.4",
]
[[package]]
name = "pairing"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f"
dependencies = [
"group 0.13.0",
]
[[package]]
name = "pallet-assets"
version = "4.0.0-dev"
@ -6104,6 +6103,21 @@ dependencies = [
"subtle",
]
[[package]]
name = "pasta_curves"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095"
dependencies = [
"blake2b_simd",
"ff 0.13.0",
"group 0.13.0",
"lazy_static",
"rand 0.8.5",
"static_assertions",
"subtle",
]
[[package]]
name = "paste"
version = "1.0.12"
@ -6479,11 +6493,10 @@ dependencies = [
[[package]]
name = "primeorder"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b54f7131b3dba65a2f414cf5bd25b66d4682e4608610668eae785750ba4c5b2"
version = "0.13.0"
source = "git+https://github.com/RustCrypto/elliptic-curves?rev=43d95961b77ad18e54d2874403052fdee248076c#43d95961b77ad18e54d2874403052fdee248076c"
dependencies = [
"elliptic-curve 0.12.3",
"elliptic-curve 0.13.2",
]
[[package]]
@ -6554,9 +6567,9 @@ dependencies = [
"env_logger",
"flexible-transcript",
"futures",
"group 0.12.1",
"group 0.13.0",
"hex",
"k256 0.12.0",
"k256",
"lazy_static",
"log",
"modular-frost",
@ -11899,23 +11912,22 @@ dependencies = [
[[package]]
name = "zeroize"
version = "1.5.7"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f"
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
dependencies = [
"zeroize_derive",
]
[[package]]
name = "zeroize_derive"
version = "1.3.3"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c"
checksum = "57090580b8b26d9fd2288c4ac982b3b3c6446cd8e91112bcf672d5ddb2f17441"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"synstructure",
"syn 2.0.10",
]
[[package]]

View file

@ -60,3 +60,7 @@ monero-serai = { opt-level = 3 }
[profile.release]
panic = "unwind"
[patch.crates-io]
k256 = { git = "https://github.com/RustCrypto/elliptic-curves", rev = "43d95961b77ad18e54d2874403052fdee248076c" }
p256 = { git = "https://github.com/RustCrypto/elliptic-curves", rev = "43d95961b77ad18e54d2874403052fdee248076c" }

View file

@ -19,7 +19,7 @@ sha2 = "0.10"
secp256k1 = { version = "0.24", features = ["global-context"] }
bitcoin = { version = "0.29", features = ["serde"] }
k256 = { version = "0.12", features = ["arithmetic"] }
k256 = { version = "0.13", features = ["arithmetic"] }
transcript = { package = "flexible-transcript", path = "../../crypto/transcript", version = "0.3", features = ["recommended"] }
frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.7", features = ["secp256k1"] }

View file

@ -71,7 +71,7 @@ impl HramTrait<Secp256k1> for Hram {
data.update(x(A));
data.update(m);
Scalar::from_uint_reduced(U256::from_be_slice(&data.finalize()))
Scalar::reduce(U256::from_be_slice(&data.finalize()))
}
}

View file

@ -22,8 +22,8 @@ serde = "1"
sha2 = "0.10"
sha3 = "0.10"
group = "0.12"
k256 = { version = "0.12", features = ["arithmetic", "ecdsa"] }
group = "0.13"
k256 = { version = "0.13", features = ["arithmetic", "ecdsa"] }
frost = { package = "modular-frost", path = "../../crypto/frost", features = ["secp256k1", "tests"] }
eyre = "0.6"

View file

@ -2,7 +2,9 @@ use sha3::{Digest, Keccak256};
use group::Group;
use k256::{
elliptic_curve::{bigint::ArrayEncoding, ops::Reduce, sec1::ToEncodedPoint, DecompressPoint},
elliptic_curve::{
bigint::ArrayEncoding, ops::Reduce, point::DecompressPoint, sec1::ToEncodedPoint,
},
AffinePoint, ProjectivePoint, Scalar, U256,
};
@ -13,7 +15,7 @@ pub fn keccak256(data: &[u8]) -> [u8; 32] {
}
pub fn hash_to_scalar(data: &[u8]) -> Scalar {
Scalar::from_uint_reduced(U256::from_be_slice(&keccak256(data)))
Scalar::reduce(U256::from_be_slice(&keccak256(data)))
}
pub fn address(point: &ProjectivePoint) -> [u8; 20] {
@ -56,7 +58,7 @@ impl Hram<Secp256k1> for EthereumHram {
let mut data = address(R).to_vec();
data.append(&mut a_encoded);
data.append(&mut m.to_vec());
Scalar::from_uint_reduced(U256::from_be_slice(&keccak256(&data)))
Scalar::reduce(U256::from_be_slice(&keccak256(&data)))
}
}
@ -92,7 +94,7 @@ pub fn process_signature_for_contract(
) -> ProcessedSignature {
let encoded_pk = A.to_encoded_point(true);
let px = &encoded_pk.as_ref()[1 .. 33];
let px_scalar = Scalar::from_uint_reduced(U256::from_be_slice(px));
let px_scalar = Scalar::reduce(U256::from_be_slice(px));
let e = EthereumHram::hram(R, A, &[chain_id.to_be_byte_array().as_slice(), &m].concat());
ProcessedSignature {
s,

View file

@ -19,7 +19,7 @@ fn test_ecrecover() {
const MESSAGE: &[u8] = b"Hello, World!";
let (sig, recovery_id) = private
.as_nonzero_scalar()
.try_sign_prehashed_rfc6979::<Sha256>(Keccak256::digest(MESSAGE), b"")
.try_sign_prehashed_rfc6979::<Sha256>(&Keccak256::digest(MESSAGE), b"")
.unwrap();
#[allow(clippy::unit_cmp)] // Intended to assert this wasn't changed to Result<bool>
{
@ -68,7 +68,7 @@ fn test_ecrecover_hack() {
let group_key = keys[&Participant::new(1).unwrap()].group_key();
let group_key_encoded = group_key.to_encoded_point(true);
let group_key_compressed = group_key_encoded.as_ref();
let group_key_x = Scalar::from_uint_reduced(U256::from_be_slice(&group_key_compressed[1 .. 33]));
let group_key_x = Scalar::reduce(U256::from_be_slice(&group_key_compressed[1 .. 33]));
const MESSAGE: &[u8] = b"Hello, World!";
let hashed_message = keccak256(MESSAGE);

View file

@ -30,7 +30,7 @@ sha3 = "0.10"
curve25519-dalek = { version = "^3.2", features = ["std"] }
group = "0.12"
group = "0.13"
dalek-ff-group = { path = "../../crypto/dalek-ff-group", version = "0.3" }
multiexp = { path = "../../crypto/multiexp", version = "0.3", features = ["batch"] }

View file

@ -20,5 +20,5 @@ sha3 = "0.10"
curve25519-dalek = { version = "3", features = ["std"] }
group = "0.12"
group = "0.13"
dalek-ff-group = { path = "../../../crypto/dalek-ff-group", version = "0.3" }

View file

@ -13,7 +13,7 @@ pub fn hash_to_point(bytes: [u8; 32]) -> EdwardsPoint {
let A = FieldElement::from(486662u64);
let v = FieldElement::from_square(hash(&bytes)).double();
let w = v + FieldElement::one();
let w = v + FieldElement::ONE;
let x = w.square() + (-A.square() * v);
// This isn't the complete X, yet its initial value

View file

@ -80,8 +80,8 @@ pub(crate) fn bit_decompose(commitments: &[Commitment]) -> (ScalarVector, Scalar
if j < sv.len() {
bit = Choice::from((sv[j][i / 8] >> (i % 8)) & 1);
}
aL.0[(j * N) + i] = Scalar::conditional_select(&Scalar::zero(), &Scalar::one(), bit);
aR.0[(j * N) + i] = Scalar::conditional_select(&-Scalar::one(), &Scalar::zero(), bit);
aL.0[(j * N) + i] = Scalar::conditional_select(&Scalar::ZERO, &Scalar::ONE, bit);
aR.0[(j * N) + i] = Scalar::conditional_select(&-Scalar::ONE, &Scalar::ZERO, bit);
}
}
@ -129,7 +129,7 @@ lazy_static! {
}
pub(crate) fn challenge_products(w: &[Scalar], winv: &[Scalar]) -> Vec<Scalar> {
let mut products = vec![Scalar::zero(); 1 << w.len()];
let mut products = vec![Scalar::ZERO; 1 << w.len()];
products[0] = winv[0];
products[1] = w[0];
for j in 1 .. w.len() {

View file

@ -15,7 +15,7 @@ use crate::{Commitment, ringct::bulletproofs::core::*};
include!(concat!(env!("OUT_DIR"), "/generators.rs"));
lazy_static! {
static ref ONE_N: ScalarVector = ScalarVector(vec![Scalar::one(); N]);
static ref ONE_N: ScalarVector = ScalarVector(vec![Scalar::ONE; N]);
static ref IP12: Scalar = inner_product(&ONE_N, &TWO_N);
}
@ -250,7 +250,7 @@ impl OriginalStruct {
proof.push((z3, *H));
proof.push((-Scalar(self.mu), G));
proof.push((Scalar::one(), A));
proof.push((Scalar::ONE, A));
proof.push((x, S));
{

View file

@ -31,7 +31,7 @@ fn hash_plus<C: IntoIterator<Item = DalekPoint>>(commitments: C) -> (Scalar, Vec
// d[j*N+i] = z**(2*(j+1)) * 2**i
fn d(z: Scalar, M: usize, MN: usize) -> (ScalarVector, ScalarVector) {
let zpow = ScalarVector::even_powers(z, 2 * M);
let mut d = vec![Scalar::zero(); MN];
let mut d = vec![Scalar::ZERO; MN];
for j in 0 .. M {
for i in 0 .. N {
d[(j * N) + i] = zpow[j] * TWO_N[i];
@ -239,7 +239,7 @@ impl PlusStruct {
// Invert B, instead of the Scalar, as the latter is only 2x as expensive yet enables reduction
// to a single addition under vartime for the first BP verified in the batch, which is expected
// to be much more significant
proof.push((Scalar::one(), -B));
proof.push((Scalar::ONE, -B));
proof.push((-e, A1));
proof.push((minus_esq, A));
proof.push((Scalar(self.d1), G));

View file

@ -48,14 +48,14 @@ math_op!(Mul, mul, |(a, b): (&Scalar, &Scalar)| *a * *b);
impl ScalarVector {
pub(crate) fn new(len: usize) -> ScalarVector {
ScalarVector(vec![Scalar::zero(); len])
ScalarVector(vec![Scalar::ZERO; len])
}
pub(crate) fn powers(x: Scalar, len: usize) -> ScalarVector {
debug_assert!(len != 0);
let mut res = Vec::with_capacity(len);
res.push(Scalar::one());
res.push(Scalar::ONE);
for i in 1 .. len {
res.push(res[i - 1] * x);
}

View file

@ -304,7 +304,7 @@ impl Algorithm<Ed25519> for ClsagMultisig {
Ok(vec![
(share, dfg::EdwardsPoint::generator()),
(dfg::Scalar(interim.p), verification_share),
(-dfg::Scalar::one(), nonces[0][0]),
(-dfg::Scalar::ONE, nonces[0][0]),
])
}
}

View file

@ -148,7 +148,7 @@ impl SignableTransaction {
let clsag = ClsagMultisig::new(transcript.clone(), input.key(), inputs[i].clone());
key_images.push((
clsag.H,
keys.current_offset().unwrap_or_else(dfg::Scalar::zero).0 + self.inputs[i].key_offset(),
keys.current_offset().unwrap_or(dfg::Scalar::ZERO).0 + self.inputs[i].key_offset(),
));
clsags.push(AlgorithmMachine::new(clsag, offset));
}

View file

@ -23,21 +23,21 @@ transcript = { package = "flexible-transcript", path = "../transcript", version
sha2 = { version = "0.10", optional = true }
sha3 = { version = "0.10", optional = true }
ff = { version = "0.12", features = ["bits"] }
group = "0.12"
ff = { version = "0.13", features = ["bits"] }
group = "0.13"
dalek-ff-group = { path = "../dalek-ff-group", version = "0.3", optional = true }
elliptic-curve = { version = "0.12", features = ["hash2curve"], optional = true }
p256 = { version = "0.12", features = ["arithmetic", "bits", "hash2curve"], optional = true }
k256 = { version = "0.12", features = ["arithmetic", "bits", "hash2curve"], optional = true }
elliptic-curve = { version = "0.13", features = ["hash2curve"], optional = true }
p256 = { version = "0.13", features = ["arithmetic", "bits", "hash2curve"], optional = true }
k256 = { version = "0.13", features = ["arithmetic", "bits", "hash2curve"], optional = true }
minimal-ed448 = { path = "../ed448", version = "0.3", optional = true }
[dev-dependencies]
hex = "0.4"
ff-group-tests = { version = "0.12", path = "../ff-group-tests" }
ff-group-tests = { version = "0.13", path = "../ff-group-tests" }
[features]
std = []

View file

@ -2,11 +2,11 @@ use zeroize::Zeroize;
use sha2::Sha256;
use group::ff::{Field, PrimeField};
use group::ff::PrimeField;
use elliptic_curve::{
generic_array::GenericArray,
bigint::{CheckedAdd, Encoding, U384},
bigint::{NonZero, CheckedAdd, Encoding, U384},
hash2curve::{Expander, ExpandMsg, ExpandMsgXmd},
};
@ -61,7 +61,7 @@ macro_rules! kp_curve {
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());
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
@ -71,11 +71,12 @@ macro_rules! kp_curve {
// 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);
ExpandMsgXmd::<Sha256>::expand_message(&[msg], &[dst], 48)
.unwrap()
.fill_bytes(&mut bytes);
bytes
})
.reduce(&modulus)
.unwrap()
.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

View file

@ -79,7 +79,7 @@ pub trait Ciphersuite:
let mut res;
while {
res = Self::F::random(&mut *rng);
res.ct_eq(&Self::F::zero()).into()
res.ct_eq(&Self::F::ZERO).into()
} {}
res
}

View file

@ -19,8 +19,8 @@ digest = "0.10"
zeroize = { version = "^1.5", features = ["zeroize_derive"] }
subtle = "^2.4"
ff = { version = "0.12", features = ["bits"] }
group = "0.12"
ff = "0.13"
group = "0.13"
crypto-bigint = "0.5"

View file

@ -1,4 +1,7 @@
use core::ops::{DerefMut, Add, AddAssign, Sub, SubAssign, Neg, Mul, MulAssign};
use core::{
ops::{DerefMut, Add, AddAssign, Sub, SubAssign, Neg, Mul, MulAssign},
iter::{Sum, Product},
};
use zeroize::Zeroize;
use rand_core::RngCore;
@ -12,7 +15,7 @@ use crypto_bigint::{Integer, NonZero, Encoding, U256, U512};
use group::ff::{Field, PrimeField, FieldBits, PrimeFieldBits};
use crate::{u8_from_bool, constant_time, math, from_uint};
use crate::{u8_from_bool, constant_time, math_op, math, from_wrapper, from_uint};
// 2^255 - 19
// Uses saturating_sub because checked_sub isn't available at compile time
@ -107,18 +110,15 @@ impl<'a> Neg for &'a FieldElement {
}
impl Field for FieldElement {
const ZERO: Self = Self(U256::ZERO);
const ONE: Self = Self(U256::ONE);
fn random(mut rng: impl RngCore) -> Self {
let mut bytes = [0; 64];
rng.fill_bytes(&mut bytes);
FieldElement(reduce(U512::from_le_bytes(bytes)))
}
fn zero() -> Self {
Self(U256::ZERO)
}
fn one() -> Self {
Self(U256::ONE)
}
fn square(&self) -> Self {
FieldElement(reduce(self.0.square()))
}
@ -138,12 +138,68 @@ impl Field for FieldElement {
let candidate = Self::conditional_select(&tv2, &tv1, tv1.square().ct_eq(self));
CtOption::new(candidate, candidate.square().ct_eq(self))
}
fn sqrt_ratio(u: &FieldElement, v: &FieldElement) -> (Choice, FieldElement) {
let i = SQRT_M1;
let u = *u;
let v = *v;
let v3 = v.square() * v;
let v7 = v3.square() * v;
let mut r = (u * v3) * (u * v7).pow(MOD_5_8);
let check = v * r.square();
let correct_sign = check.ct_eq(&u);
let flipped_sign = check.ct_eq(&(-u));
let flipped_sign_i = check.ct_eq(&((-u) * i));
r.conditional_assign(&(r * i), flipped_sign | flipped_sign_i);
let r_is_negative = r.is_odd();
r.conditional_negate(r_is_negative);
(correct_sign | flipped_sign, r)
}
}
impl PrimeField for FieldElement {
type Repr = [u8; 32];
// Big endian representation of the modulus
const MODULUS: &'static str = "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed";
const NUM_BITS: u32 = 255;
const CAPACITY: u32 = 254;
// 2.invert()
const TWO_INV: Self = FieldElement(U256::from_be_hex(
"3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7",
));
// This was calculated with the method from the ff crate docs
// SageMath GF(modulus).primitive_element()
const MULTIPLICATIVE_GENERATOR: Self = Self(U256::from_u8(2));
// This was set per the specification in the ff crate docs
// The number of leading zero bits in the little-endian bit representation of (modulus - 1)
const S: u32 = 2;
// This was calculated via the formula from the ff crate docs
// Self::MULTIPLICATIVE_GENERATOR ** ((modulus - 1) >> Self::S)
const ROOT_OF_UNITY: Self = FieldElement(U256::from_be_hex(
"2b8324804fc1df0b2b4d00993dfbd7a72f431806ad2fe478c4ee1b274a0ea0b0",
));
// Self::ROOT_OF_UNITY.invert()
const ROOT_OF_UNITY_INV: Self = FieldElement(U256::from_be_hex(
"547cdb7fb03e20f4d4b2ff66c2042858d0bce7f952d01b873b11e4d8b5f15f3d",
));
// This was calculated via the formula from the ff crate docs
// Self::MULTIPLICATIVE_GENERATOR ** (2 ** Self::S)
const DELTA: Self = FieldElement(U256::from_be_hex(
"0000000000000000000000000000000000000000000000000000000000000010",
));
fn from_repr(bytes: [u8; 32]) -> CtOption<Self> {
let res = Self(U256::from_le_bytes(bytes));
CtOption::new(res, res.0.ct_lt(&MODULUS))
@ -152,23 +208,12 @@ impl PrimeField for FieldElement {
self.0.to_le_bytes()
}
// This was set per the specification in the ff crate docs
// The number of leading zero bits in the little-endian bit representation of (modulus - 1)
const S: u32 = 2;
fn is_odd(&self) -> Choice {
self.0.is_odd()
}
fn multiplicative_generator() -> Self {
// This was calculated with the method from the ff crate docs
// SageMath GF(modulus).primitive_element()
2u64.into()
}
fn root_of_unity() -> Self {
// This was calculated via the formula from the ff crate docs
// Self::multiplicative_generator() ** ((modulus - 1) >> Self::S)
FieldElement(U256::from_be_hex(
"2b8324804fc1df0b2b4d00993dfbd7a72f431806ad2fe478c4ee1b274a0ea0b0",
))
fn from_u128(num: u128) -> Self {
Self::from(num)
}
}
@ -193,13 +238,13 @@ impl FieldElement {
/// Perform an exponentation.
pub fn pow(&self, other: FieldElement) -> FieldElement {
let mut table = [FieldElement::one(); 16];
let mut table = [FieldElement::ONE; 16];
table[1] = *self;
for i in 2 .. 16 {
table[i] = table[i - 1] * self;
}
let mut res = FieldElement::one();
let mut res = FieldElement::ONE;
let mut bits = 0;
for (i, mut bit) in other.to_le_bits().iter_mut().rev().enumerate() {
bits <<= 1;
@ -257,6 +302,38 @@ impl FieldElement {
}
}
impl Sum<FieldElement> for FieldElement {
fn sum<I: Iterator<Item = FieldElement>>(iter: I) -> FieldElement {
let mut res = FieldElement::ZERO;
for item in iter {
res += item;
}
res
}
}
impl<'a> Sum<&'a FieldElement> for FieldElement {
fn sum<I: Iterator<Item = &'a FieldElement>>(iter: I) -> FieldElement {
iter.cloned().sum()
}
}
impl Product<FieldElement> for FieldElement {
fn product<I: Iterator<Item = FieldElement>>(iter: I) -> FieldElement {
let mut res = FieldElement::ONE;
for item in iter {
res *= item;
}
res
}
}
impl<'a> Product<&'a FieldElement> for FieldElement {
fn product<I: Iterator<Item = &'a FieldElement>>(iter: I) -> FieldElement {
iter.cloned().product()
}
}
#[test]
fn test_wide_modulus() {
let mut wide = [0; 64];
@ -275,9 +352,8 @@ fn test_sqrt_m1() {
// 2 ** ((MODULUS - 1) // 4) % MODULUS
assert_eq!(
SQRT_M1,
FieldElement::from(2u8).pow(FieldElement(
(FieldElement::zero() - FieldElement::one()).0.wrapping_div(&U256::from(4u8))
))
FieldElement::from(2u8)
.pow(FieldElement((FieldElement::ZERO - FieldElement::ONE).0.wrapping_div(&U256::from(4u8))))
);
}

View file

@ -1,11 +1,11 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![no_std]
#![no_std] // Prevents writing new code, in what should be a simple wrapper, which requires std
#![doc = include_str!("../README.md")]
use core::{
borrow::Borrow,
ops::{Deref, DerefMut, Add, AddAssign, Sub, SubAssign, Neg, Mul, MulAssign},
iter::{Iterator, Sum},
iter::{Iterator, Sum, Product},
hash::{Hash, Hasher},
};
@ -30,7 +30,7 @@ use dalek::{
pub use constants::{ED25519_BASEPOINT_TABLE, RISTRETTO_BASEPOINT_TABLE};
use group::{
ff::{Field, PrimeField, FieldBits, PrimeFieldBits},
ff::{Field, PrimeField, FieldBits, PrimeFieldBits, helpers::sqrt_ratio_generic},
Group, GroupEncoding,
prime::PrimeGroup,
};
@ -90,8 +90,6 @@ macro_rules! deref_borrow {
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! constant_time {
($Value: ident, $Inner: ident) => {
impl ConstantTimeEq for $Value {
@ -107,9 +105,8 @@ macro_rules! constant_time {
}
};
}
pub(crate) use constant_time;
#[doc(hidden)]
#[macro_export]
macro_rules! math_op {
(
$Value: ident,
@ -144,9 +141,8 @@ macro_rules! math_op {
}
};
}
pub(crate) use math_op;
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! math {
($Value: ident, $Factor: ident, $add: expr, $sub: expr, $mul: expr) => {
math_op!($Value, $Value, Add, add, AddAssign, add_assign, $add);
@ -154,9 +150,8 @@ macro_rules! math {
math_op!($Value, $Factor, Mul, mul, MulAssign, mul_assign, $mul);
};
}
pub(crate) use math;
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! math_neg {
($Value: ident, $Factor: ident, $add: expr, $sub: expr, $mul: expr) => {
math!($Value, $Factor, $add, $sub, $mul);
@ -170,8 +165,6 @@ macro_rules! math_neg {
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! from_wrapper {
($wrapper: ident, $inner: ident, $uint: ident) => {
impl From<$uint> for $wrapper {
@ -181,17 +174,18 @@ macro_rules! from_wrapper {
}
};
}
pub(crate) use from_wrapper;
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! from_uint {
($wrapper: ident, $inner: ident) => {
from_wrapper!($wrapper, $inner, u8);
from_wrapper!($wrapper, $inner, u16);
from_wrapper!($wrapper, $inner, u32);
from_wrapper!($wrapper, $inner, u64);
from_wrapper!($wrapper, $inner, u128);
};
}
pub(crate) use from_uint;
/// Wrapper around the dalek Scalar type.
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Zeroize)]
@ -207,13 +201,13 @@ const MODULUS: U256 =
impl Scalar {
pub fn pow(&self, other: Scalar) -> Scalar {
let mut table = [Scalar::one(); 16];
let mut table = [Scalar::ONE; 16];
table[1] = *self;
for i in 2 .. 16 {
table[i] = table[i - 1] * self;
}
let mut res = Scalar::one();
let mut res = Scalar::ONE;
let mut bits = 0;
for (i, mut bit) in other.to_le_bits().iter_mut().rev().enumerate() {
bits <<= 1;
@ -250,18 +244,19 @@ impl Scalar {
}
impl Field for Scalar {
const ZERO: Scalar = Scalar(DScalar::from_bits([0; 32]));
const ONE: Scalar = Scalar(DScalar::from_bits({
let mut bytes = [0; 32];
bytes[0] = 1;
bytes
}));
fn random(mut rng: impl RngCore) -> Self {
let mut r = [0; 64];
rng.fill_bytes(&mut r);
Self(DScalar::from_bytes_mod_order_wide(&r))
}
fn zero() -> Self {
Self(DScalar::zero())
}
fn one() -> Self {
Self(DScalar::one())
}
fn square(&self) -> Self {
*self * self
}
@ -271,36 +266,77 @@ impl Field for Scalar {
fn invert(&self) -> CtOption<Self> {
CtOption::new(Self(self.0.invert()), !self.is_zero())
}
fn sqrt(&self) -> CtOption<Self> {
let mod_3_8 = MODULUS.saturating_add(&U256::from_u8(3)).wrapping_div(&U256::from_u8(8));
let mod_3_8 = Scalar::from_repr(mod_3_8.to_le_bytes()).unwrap();
let sqrt_m1 = MODULUS.saturating_sub(&U256::from_u8(1)).wrapping_div(&U256::from_u8(4));
let sqrt_m1 = Scalar::one().double().pow(Scalar::from_repr(sqrt_m1.to_le_bytes()).unwrap());
let sqrt_m1 = Scalar::from(2u8).pow(Scalar::from_repr(sqrt_m1.to_le_bytes()).unwrap());
let tv1 = self.pow(mod_3_8);
let tv2 = tv1 * sqrt_m1;
let candidate = Self::conditional_select(&tv2, &tv1, tv1.square().ct_eq(self));
CtOption::new(candidate, candidate.square().ct_eq(self))
}
fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) {
sqrt_ratio_generic(num, div)
}
}
impl PrimeField for Scalar {
type Repr = [u8; 32];
const MODULUS: &'static str = "1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed";
const NUM_BITS: u32 = 253;
const CAPACITY: u32 = 252;
// 2.invert()
const TWO_INV: Scalar = Scalar(DScalar::from_bits([
247, 233, 122, 46, 141, 49, 9, 44, 107, 206, 123, 81, 239, 124, 111, 10, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 8,
]));
// This was calculated with the method from the ff crate docs
// SageMath GF(modulus).primitive_element()
const MULTIPLICATIVE_GENERATOR: Scalar = Scalar(DScalar::from_bits({
let mut bytes = [0; 32];
bytes[0] = 2;
bytes
}));
// This was set per the specification in the ff crate docs
// The number of leading zero bits in the little-endian bit representation of (modulus - 1)
const S: u32 = 2;
// This was calculated via the formula from the ff crate docs
// Self::MULTIPLICATIVE_GENERATOR ** ((modulus - 1) >> Self::S)
const ROOT_OF_UNITY: Scalar = Scalar(DScalar::from_bits([
212, 7, 190, 235, 223, 117, 135, 190, 254, 131, 206, 66, 83, 86, 240, 14, 122, 194, 193, 171,
96, 109, 61, 125, 231, 129, 121, 224, 16, 115, 74, 9,
]));
// Self::ROOT_OF_UNITY.invert()
const ROOT_OF_UNITY_INV: Scalar = Scalar(DScalar::from_bits([
25, 204, 55, 113, 58, 237, 138, 153, 215, 24, 41, 96, 139, 163, 238, 5, 134, 61, 62, 84, 159,
146, 194, 130, 24, 126, 134, 31, 239, 140, 181, 6,
]));
// This was calculated via the formula from the ff crate docs
// Self::MULTIPLICATIVE_GENERATOR ** (2 ** Self::S)
const DELTA: Scalar = Scalar(DScalar::from_bits([
16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
]));
fn from_repr(bytes: [u8; 32]) -> CtOption<Self> {
let scalar = DScalar::from_canonical_bytes(bytes);
// TODO: This unwrap_or isn't constant time, yet we don't exactly have an alternative...
CtOption::new(Scalar(scalar.unwrap_or_else(DScalar::zero)), choice(scalar.is_some()))
// TODO: This unwrap_or_else isn't constant time, yet we don't exactly have an alternative...
CtOption::new(Scalar(scalar.unwrap_or_else(DScalar::zero)), choice(black_box(scalar).is_some()))
}
fn to_repr(&self) -> [u8; 32] {
self.0.to_bytes()
}
// This was set per the specification in the ff crate docs
// The number of leading zero bits in the little-endian bit representation of (modulus - 1)
const S: u32 = 2;
fn is_odd(&self) -> Choice {
// This is probably overkill? Yet it's better safe than sorry since this is a complete
// decomposition of the scalar
@ -315,19 +351,9 @@ impl PrimeField for Scalar {
}
res
}
fn multiplicative_generator() -> Self {
// This was calculated with the method from the ff crate docs
// SageMath GF(modulus).primitive_element()
2u64.into()
}
fn root_of_unity() -> Self {
// This was calculated via the formula from the ff crate docs
// Self::multiplicative_generator() ** ((modulus - 1) >> Self::S)
Scalar::from_repr([
212, 7, 190, 235, 223, 117, 135, 190, 254, 131, 206, 66, 83, 86, 240, 14, 122, 194, 193, 171,
96, 109, 61, 125, 231, 129, 121, 224, 16, 115, 74, 9,
])
.unwrap()
fn from_u128(num: u128) -> Self {
Self::from(num)
}
}
@ -339,7 +365,7 @@ impl PrimeFieldBits for Scalar {
}
fn char_le_bits() -> FieldBits<Self::ReprBits> {
let mut bytes = (Scalar::zero() - Scalar::one()).to_repr();
let mut bytes = (Scalar::ZERO - Scalar::ONE).to_repr();
bytes[0] += 1;
debug_assert_eq!(DScalar::from_bytes_mod_order(bytes), DScalar::zero());
bytes.into()
@ -352,6 +378,24 @@ impl Sum<Scalar> for Scalar {
}
}
impl<'a> Sum<&'a Scalar> for Scalar {
fn sum<I: Iterator<Item = &'a Scalar>>(iter: I) -> Scalar {
Self(DScalar::sum(iter))
}
}
impl Product<Scalar> for Scalar {
fn product<I: Iterator<Item = Scalar>>(iter: I) -> Scalar {
Self(DScalar::product(iter))
}
}
impl<'a> Product<&'a Scalar> for Scalar {
fn product<I: Iterator<Item = &'a Scalar>>(iter: I) -> Scalar {
Self(DScalar::product(iter))
}
}
macro_rules! dalek_group {
(
$Point: ident,
@ -420,7 +464,10 @@ macro_rules! dalek_group {
let decompressed = $DCompressed(*bytes).decompress();
// TODO: Same note on unwrap_or as above
let point = decompressed.unwrap_or($DPoint::identity());
CtOption::new($Point(point), choice(decompressed.is_some()) & choice($torsion_free(point)))
CtOption::new(
$Point(point),
choice(black_box(decompressed).is_some()) & choice($torsion_free(point)),
)
}
fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {

View file

@ -190,7 +190,7 @@ impl<C: Ciphersuite, E: Encryptable> EncryptedMessage<C, E> {
#[cfg(test)]
pub(crate) fn invalidate_pop(&mut self) {
self.pop.s += C::F::one();
self.pop.s += C::F::ONE;
}
#[cfg(test)]
@ -250,7 +250,7 @@ impl<C: Ciphersuite, E: Encryptable> EncryptedMessage<C, E> {
use ciphersuite::group::ff::PrimeField;
// Assumes the share isn't randomly 1
let repr = C::F::one().to_repr();
let repr = C::F::ONE.to_repr();
self.msg.as_mut().as_mut().copy_from_slice(repr.as_ref());
*self = encrypt(rng, context, from, to, self.msg.clone());
}

View file

@ -156,8 +156,8 @@ fn polynomial<F: PrimeField + Zeroize>(
) -> Zeroizing<F> {
let l = F::from(u64::from(u16::from(l)));
// This should never be reached since Participant is explicitly non-zero
assert!(l != F::zero(), "zero participant passed to polynomial");
let mut share = Zeroizing::new(F::zero());
assert!(l != F::ZERO, "zero participant passed to polynomial");
let mut share = Zeroizing::new(F::ZERO);
for (idx, coefficient) in coefficients.iter().rev().enumerate() {
*share += coefficient.deref();
if idx != (coefficients.len() - 1) {
@ -366,7 +366,7 @@ impl<C: Ciphersuite> Zeroize for KeyMachine<C> {
fn exponential<C: Ciphersuite>(i: Participant, values: &[C::G]) -> Vec<(C::F, C::G)> {
let i = C::F::from(u16::from(i).into());
let mut res = Vec::with_capacity(values.len());
(0 .. values.len()).fold(C::F::one(), |exp, l| {
(0 .. values.len()).fold(C::F::ONE, |exp, l| {
res.push((exp, values[l]));
exp * i
});
@ -389,7 +389,7 @@ fn share_verification_statements<C: Ciphersuite>(
// converts whatever we give to an iterator and then builds a Vec internally, welcoming copies
let neg_share_pub = C::generator() * -*share;
share.zeroize();
values.push((C::F::one(), neg_share_pub));
values.push((C::F::ONE, neg_share_pub));
values
}

View file

@ -176,8 +176,8 @@ impl ThresholdParams {
pub fn lagrange<F: PrimeField>(i: Participant, included: &[Participant]) -> F {
let i_f = F::from(u64::from(u16::from(i)));
let mut num = F::one();
let mut denom = F::one();
let mut num = F::ONE;
let mut denom = F::ONE;
for l in included {
if i == *l {
continue;
@ -405,7 +405,7 @@ impl<C: Ciphersuite> ThresholdKeys<C> {
// Carry any existing offset
// Enables schemes like Monero's subaddresses which have a per-subaddress offset and then a
// one-time-key offset
res.offset = Some(offset + res.offset.unwrap_or_else(C::F::zero));
res.offset = Some(offset + res.offset.unwrap_or(C::F::ZERO));
res
}
@ -426,7 +426,7 @@ impl<C: Ciphersuite> ThresholdKeys<C> {
/// Return the group key, with any offset applied.
pub fn group_key(&self) -> C::G {
self.core.group_key + (C::generator() * self.offset.unwrap_or_else(C::F::zero))
self.core.group_key + (C::generator() * self.offset.unwrap_or(C::F::ZERO))
}
/// Return all participants' verification shares without any offsetting.
@ -457,7 +457,7 @@ impl<C: Ciphersuite> ThresholdKeys<C> {
}
// The offset is included by adding it to the participant with the lowest ID
let offset = self.offset.unwrap_or_else(C::F::zero);
let offset = self.offset.unwrap_or(C::F::ZERO);
if included[0] == self.params().i() {
*secret_share += offset;
}

View file

@ -36,7 +36,7 @@ pub fn recover_key<C: Ciphersuite>(keys: &HashMap<Participant, ThresholdKeys<C>>
assert!(keys.len() >= first.params().t().into(), "not enough keys provided");
let included = keys.keys().cloned().collect::<Vec<_>>();
let group_private = keys.iter().fold(C::F::zero(), |accum, (i, keys)| {
let group_private = keys.iter().fold(C::F::ZERO, |accum, (i, keys)| {
accum + (lagrange::<C::F>(*i, &included) * keys.secret_share().deref())
});
assert_eq!(C::generator() * group_private, first.group_key(), "failed to recover keys");

View file

@ -21,8 +21,8 @@ digest = "0.10"
transcript = { package = "flexible-transcript", path = "../transcript", version = "0.3" }
ff = "0.12"
group = "0.12"
ff = "0.13"
group = "0.13"
multiexp = { path = "../multiexp", version = "0.3", features = ["batch"], optional = true }
@ -31,7 +31,7 @@ hex-literal = "0.3"
blake2 = "0.10"
k256 = { version = "0.12", features = ["arithmetic", "bits"] }
k256 = { version = "0.13", features = ["arithmetic", "bits"] }
dalek-ff-group = { path = "../dalek-ff-group" }
transcript = { package = "flexible-transcript", path = "../transcript", features = ["recommended"] }

View file

@ -42,7 +42,7 @@ impl<G0: PrimeGroup, G1: PrimeGroup> Re<G0, G1> {
}
pub(crate) fn e_default() -> Re<G0, G1> {
Re::e(G0::Scalar::zero())
Re::e(G0::Scalar::ZERO)
}
}
@ -114,7 +114,7 @@ where
debug_assert!((RING_LEN == 2) || (RING_LEN == 4));
debug_assert_eq!(RING_LEN, ring.len());
let mut s = [(G0::Scalar::zero(), G1::Scalar::zero()); RING_LEN];
let mut s = [(G0::Scalar::ZERO, G1::Scalar::ZERO); RING_LEN];
let mut r = (G0::Scalar::random(&mut *rng), G1::Scalar::random(&mut *rng));
#[allow(non_snake_case)]
@ -178,8 +178,8 @@ where
let mut statements =
Self::R_batch(generators, *self.s.last().unwrap(), *ring.last().unwrap(), e);
statements.0.push((G0::Scalar::one(), R0_0));
statements.1.push((G1::Scalar::one(), R1_0));
statements.0.push((G0::Scalar::ONE, R0_0));
statements.1.push((G1::Scalar::ONE, R1_0));
batch.0.queue(&mut *rng, (), statements.0);
batch.1.queue(&mut *rng, (), statements.1);
}
@ -239,7 +239,7 @@ where
Re::e(ref mut e) => *e = read_scalar(r)?,
}
let mut s = [(G0::Scalar::zero(), G1::Scalar::zero()); RING_LEN];
let mut s = [(G0::Scalar::ZERO, G1::Scalar::ZERO); RING_LEN];
for s in s.iter_mut() {
*s = (read_scalar(r)?, read_scalar(r)?);
}

View file

@ -269,15 +269,15 @@ where
SchnorrPoK::<G1>::prove(rng, transcript, generators.1.primary, &f.1),
);
let mut blinding_key_total = (G0::Scalar::zero(), G1::Scalar::zero());
let mut blinding_key_total = (G0::Scalar::ZERO, G1::Scalar::ZERO);
let mut blinding_key = |rng: &mut R, last| {
let blinding_key = (
Self::blinding_key(&mut *rng, &mut blinding_key_total.0, last),
Self::blinding_key(&mut *rng, &mut blinding_key_total.1, last),
);
if last {
debug_assert_eq!(blinding_key_total.0, G0::Scalar::zero());
debug_assert_eq!(blinding_key_total.1, G1::Scalar::zero());
debug_assert_eq!(blinding_key_total.0, G0::Scalar::ZERO);
debug_assert_eq!(blinding_key_total.1, G1::Scalar::ZERO);
}
blinding_key
};

View file

@ -18,8 +18,8 @@ pub fn scalar_normalize<F0: PrimeFieldBits + Zeroize, F1: PrimeFieldBits>(
#[cfg(feature = "secure_capacity_difference")]
assert!((F0::CAPACITY.max(F1::CAPACITY) - mutual_capacity) <= 4);
let mut res1 = F0::zero();
let mut res2 = F1::zero();
let mut res1 = F0::ZERO;
let mut res2 = F1::ZERO;
// Uses the bits API to ensure a consistent endianess
let mut bits = scalar.to_le_bits();
scalar.zeroize();
@ -66,7 +66,7 @@ pub fn mutual_scalar_from_bytes<F0: PrimeFieldBits + Zeroize, F1: PrimeFieldBits
let capacity = usize::try_from(F0::CAPACITY.min(F1::CAPACITY)).unwrap();
debug_assert!((bytes.len() * 8) >= capacity);
let mut accum = F0::zero();
let mut accum = F0::ZERO;
for b in 0 .. capacity {
accum = accum.double();
accum += F0::from(((bytes[b / 8] >> (b % 8)) & 1).into());

View file

@ -72,7 +72,7 @@ where
(),
[
(-self.s, generator),
(G::Scalar::one(), self.R),
(G::Scalar::ONE, self.R),
(Self::hra(transcript, generator, self.R, public_key), public_key),
],
);

View file

@ -32,7 +32,7 @@ pub(crate) fn challenge<T: Transcript, F: PrimeField>(transcript: &mut T) -> F {
// and loading it in
// 3: Iterating over each byte and manually doubling/adding. This is simplest
let mut challenge = F::zero();
let mut challenge = F::ZERO;
// Get a wide amount of bytes to safely reduce without bias
// In most cases, <=1.5x bytes is enough. 2x is still standard and there's some theoretical
@ -105,13 +105,19 @@ pub enum DLEqError {
/// A proof that points have the same discrete logarithm across generators.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub struct DLEqProof<G: PrimeGroup> {
pub struct DLEqProof<G: PrimeGroup>
where
G::Scalar: Zeroize,
{
c: G::Scalar,
s: G::Scalar,
}
#[allow(non_snake_case)]
impl<G: PrimeGroup> DLEqProof<G> {
impl<G: PrimeGroup> DLEqProof<G>
where
G::Scalar: Zeroize,
{
fn transcript<T: Transcript>(transcript: &mut T, generator: G, nonce: G, point: G) {
transcript.append_message(b"generator", generator.to_bytes());
transcript.append_message(b"nonce", nonce.to_bytes());
@ -125,10 +131,7 @@ impl<G: PrimeGroup> DLEqProof<G> {
transcript: &mut T,
generators: &[G],
scalar: &Zeroizing<G::Scalar>,
) -> DLEqProof<G>
where
G::Scalar: Zeroize,
{
) -> DLEqProof<G> {
let r = Zeroizing::new(G::Scalar::random(rng));
transcript.domain_separate(b"dleq");
@ -210,14 +213,20 @@ impl<G: PrimeGroup> DLEqProof<G> {
/// across some generators, yet with a smaller overall proof size.
#[cfg(feature = "std")]
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
pub struct MultiDLEqProof<G: PrimeGroup> {
pub struct MultiDLEqProof<G: PrimeGroup>
where
G::Scalar: Zeroize,
{
c: G::Scalar,
s: Vec<G::Scalar>,
}
#[cfg(feature = "std")]
#[allow(non_snake_case)]
impl<G: PrimeGroup> MultiDLEqProof<G> {
impl<G: PrimeGroup> MultiDLEqProof<G>
where
G::Scalar: Zeroize,
{
/// Prove for each scalar that the series of points created by multiplying it against its
/// matching generators share a discrete logarithm.
/// This function panics if `generators.len() != scalars.len()`.
@ -226,10 +235,7 @@ impl<G: PrimeGroup> MultiDLEqProof<G> {
transcript: &mut T,
generators: &[Vec<G>],
scalars: &[Zeroizing<G::Scalar>],
) -> MultiDLEqProof<G>
where
G::Scalar: Zeroize,
{
) -> MultiDLEqProof<G> {
assert_eq!(
generators.len(),
scalars.len(),

View file

@ -21,7 +21,7 @@ fn test_aos_serialization<const RING_LEN: usize>(proof: Aos<G0, G1, RING_LEN>, R
fn test_aos<const RING_LEN: usize>(default: Re<G0, G1>) {
let generators = generators();
let mut ring_keys = [(<G0 as Group>::Scalar::zero(), <G1 as Group>::Scalar::zero()); RING_LEN];
let mut ring_keys = [(<G0 as Group>::Scalar::ZERO, <G1 as Group>::Scalar::ZERO); RING_LEN];
// Side-effect of G0 being a type-alias with identity() deprecated
#[allow(deprecated)]
let mut ring = [(G0::identity(), G1::identity()); RING_LEN];

View file

@ -154,7 +154,7 @@ test_dleq!(
#[test]
fn test_rejection_sampling() {
let mut pow_2 = Scalar::one();
let mut pow_2 = Scalar::ONE;
for _ in 0 .. dfg::Scalar::CAPACITY {
pow_2 = pow_2.double();
}
@ -179,7 +179,7 @@ fn test_remainder() {
// This will ignore any unused bits, ensuring every remaining one is set
let keys = mutual_scalar_from_bytes::<Scalar, Scalar>(&[0xFF; 32]);
let keys = (Zeroizing::new(keys.0), Zeroizing::new(keys.1));
assert_eq!(Scalar::one() + keys.0.deref(), Scalar::from(2u64).pow_vartime([255]));
assert_eq!(Scalar::ONE + keys.0.deref(), Scalar::from(2u64).pow_vartime([255]));
assert_eq!(keys.0, keys.1);
let (proof, res) = ConciseLinearDLEq::prove_without_bias(

View file

@ -10,13 +10,13 @@ use crate::cross_group::scalar::{scalar_normalize, scalar_convert};
#[test]
fn test_scalar() {
assert_eq!(
scalar_normalize::<_, DalekScalar>(K256Scalar::zero()),
(K256Scalar::zero(), DalekScalar::zero())
scalar_normalize::<_, DalekScalar>(K256Scalar::ZERO),
(K256Scalar::ZERO, DalekScalar::ZERO)
);
assert_eq!(
scalar_normalize::<_, DalekScalar>(K256Scalar::one()),
(K256Scalar::one(), DalekScalar::one())
scalar_normalize::<_, DalekScalar>(K256Scalar::ONE),
(K256Scalar::ONE, DalekScalar::ONE)
);
let mut initial;

View file

@ -20,14 +20,12 @@ rand_core = "0.6"
zeroize = { version = "^1.5", features = ["zeroize_derive"] }
subtle = "^2.4"
ff = { version = "0.12", features = ["bits"] }
group = "0.12"
ff = "0.13"
group = "0.13"
generic-array = "0.14"
crypto-bigint = { version = "0.5", features = ["zeroize"] }
dalek-ff-group = { path = "../dalek-ff-group", version = "0.3" }
[dev-dependencies]
hex = "0.4"

View file

@ -3,8 +3,7 @@
Inefficient, barebones implementation of Ed448 bound to the ff/group API,
rejecting torsion to achieve a PrimeGroup definition. This likely should not be
used and was only done so another library under Serai could confirm its
completion. It is minimally tested, yet should be correct for what it has. The
functions it doesn't have are marked `unimplemented!()`. This has not undergone
auditing.
completion. It is minimally tested, yet should be correct for what it has. This
has not undergone auditing.
constant time and no_std.

View file

@ -23,11 +23,70 @@ pub(crate) fn u8_from_bool(bit_ref: &mut bool) -> u8 {
res
}
#[doc(hidden)]
#[macro_export]
macro_rules! math_op {
(
$Value: ident,
$Other: ident,
$Op: ident,
$op_fn: ident,
$Assign: ident,
$assign_fn: ident,
$function: expr
) => {
impl $Op<$Other> for $Value {
type Output = $Value;
fn $op_fn(self, other: $Other) -> Self::Output {
Self($function(self.0, other.0))
}
}
impl $Assign<$Other> for $Value {
fn $assign_fn(&mut self, other: $Other) {
self.0 = $function(self.0, other.0);
}
}
impl<'a> $Op<&'a $Other> for $Value {
type Output = $Value;
fn $op_fn(self, other: &'a $Other) -> Self::Output {
Self($function(self.0, other.0))
}
}
impl<'a> $Assign<&'a $Other> for $Value {
fn $assign_fn(&mut self, other: &'a $Other) {
self.0 = $function(self.0, other.0);
}
}
};
}
macro_rules! from_wrapper {
($wrapper: ident, $inner: ident, $uint: ident) => {
impl From<$uint> for $wrapper {
fn from(a: $uint) -> $wrapper {
Self($inner::from(a))
}
}
};
}
macro_rules! field {
($FieldName: ident, $MODULUS: ident, $WIDE_MODULUS: ident, $NUM_BITS: literal) => {
use core::ops::{DerefMut, Add, AddAssign, Neg, Sub, SubAssign, Mul, MulAssign};
(
$FieldName: ident,
$MODULUS_STR: ident,
$MODULUS: ident,
$WIDE_MODULUS: ident,
$NUM_BITS: literal,
$TWO_INV: expr,
$MULTIPLICATIVE_GENERATOR: literal,
$ROOT_OF_UNITY_INV: expr,
$DELTA: expr,
) => {
use core::{
ops::{DerefMut, Add, AddAssign, Neg, Sub, SubAssign, Mul, MulAssign},
iter::{Sum, Product},
};
use subtle::{Choice, CtOption, ConstantTimeEq, ConstantTimeLess, ConditionallySelectable};
use rand_core::RngCore;
@ -35,12 +94,7 @@ macro_rules! field {
use generic_array::{typenum::U57, GenericArray};
use crypto_bigint::{Integer, NonZero, Encoding};
use group::ff::{Field, PrimeField, FieldBits, PrimeFieldBits};
// Needed to publish for some reason? Yet not actually needed
#[allow(unused_imports)]
use dalek_ff_group::{from_wrapper, math_op};
use dalek_ff_group::{constant_time, from_uint, math};
use ff::{Field, PrimeField, FieldBits, PrimeFieldBits, helpers::sqrt_ratio_generic};
use $crate::backend::u8_from_bool;
@ -48,15 +102,37 @@ macro_rules! field {
U512::from_le_slice(&x.rem(&NonZero::new($WIDE_MODULUS).unwrap()).to_le_bytes()[.. 64])
}
constant_time!($FieldName, U512);
math!(
$FieldName,
$FieldName,
|x, y| U512::add_mod(&x, &y, &$MODULUS.0),
|x, y| U512::sub_mod(&x, &y, &$MODULUS.0),
|x, y| reduce(U1024::from(U512::mul_wide(&x, &y)))
);
from_uint!($FieldName, U512);
impl ConstantTimeEq for $FieldName {
fn ct_eq(&self, other: &Self) -> Choice {
self.0.ct_eq(&other.0)
}
}
impl ConditionallySelectable for $FieldName {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
$FieldName(U512::conditional_select(&a.0, &b.0, choice))
}
}
math_op!($FieldName, $FieldName, Add, add, AddAssign, add_assign, |x, y| U512::add_mod(
&x,
&y,
&$MODULUS.0
));
math_op!($FieldName, $FieldName, Sub, sub, SubAssign, sub_assign, |x, y| U512::sub_mod(
&x,
&y,
&$MODULUS.0
));
math_op!($FieldName, $FieldName, Mul, mul, MulAssign, mul_assign, |x, y| reduce(U1024::from(
U512::mul_wide(&x, &y)
)));
from_wrapper!($FieldName, U512, u8);
from_wrapper!($FieldName, U512, u16);
from_wrapper!($FieldName, U512, u32);
from_wrapper!($FieldName, U512, u64);
from_wrapper!($FieldName, U512, u128);
impl Neg for $FieldName {
type Output = $FieldName;
@ -104,18 +180,15 @@ macro_rules! field {
}
impl Field for $FieldName {
const ZERO: Self = Self(U512::ZERO);
const ONE: Self = Self(U512::ONE);
fn random(mut rng: impl RngCore) -> Self {
let mut bytes = [0; 128];
rng.fill_bytes(&mut bytes);
$FieldName(reduce(U1024::from_le_slice(bytes.as_ref())))
}
fn zero() -> Self {
Self(U512::ZERO)
}
fn one() -> Self {
Self(U512::ONE)
}
fn square(&self) -> Self {
*self * self
}
@ -134,12 +207,32 @@ macro_rules! field {
let res = self.pow(MOD_1_4);
CtOption::new(res, res.square().ct_eq(self))
}
fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) {
sqrt_ratio_generic(num, div)
}
}
impl PrimeField for $FieldName {
type Repr = GenericArray<u8, U57>;
const MODULUS: &'static str = $MODULUS_STR;
const NUM_BITS: u32 = $NUM_BITS;
const CAPACITY: u32 = $NUM_BITS - 1;
const TWO_INV: Self = $FieldName(U512::from_le_hex($TWO_INV));
const MULTIPLICATIVE_GENERATOR: Self = Self(U512::from_u8($MULTIPLICATIVE_GENERATOR));
// True for both the Ed448 Scalar field and FieldElement field
const S: u32 = 1;
// Both fields have their root of unity as -1
const ROOT_OF_UNITY: Self = Self($MODULUS.0.saturating_sub(&U512::from_u8(1)));
const ROOT_OF_UNITY_INV: Self = $FieldName(U512::from_le_hex($ROOT_OF_UNITY_INV));
const DELTA: Self = $FieldName(U512::from_le_hex($DELTA));
fn from_repr(bytes: Self::Repr) -> CtOption<Self> {
let res = $FieldName(U512::from_le_slice(&[bytes.as_ref(), [0; 7].as_ref()].concat()));
CtOption::new(res, res.0.ct_lt(&$MODULUS.0))
@ -150,17 +243,9 @@ macro_rules! field {
repr
}
// True for both the Ed448 Scalar field and FieldElement field
const S: u32 = 1;
fn is_odd(&self) -> Choice {
self.0.is_odd()
}
fn multiplicative_generator() -> Self {
unimplemented!()
}
fn root_of_unity() -> Self {
unimplemented!()
}
}
impl PrimeFieldBits for $FieldName {
@ -176,5 +261,37 @@ macro_rules! field {
MODULUS.to_le_bits()
}
}
impl Sum<$FieldName> for $FieldName {
fn sum<I: Iterator<Item = $FieldName>>(iter: I) -> $FieldName {
let mut res = $FieldName::ZERO;
for item in iter {
res += item;
}
res
}
}
impl<'a> Sum<&'a $FieldName> for $FieldName {
fn sum<I: Iterator<Item = &'a $FieldName>>(iter: I) -> $FieldName {
iter.cloned().sum()
}
}
impl Product<$FieldName> for $FieldName {
fn product<I: Iterator<Item = $FieldName>>(iter: I) -> $FieldName {
let mut res = $FieldName::ONE;
for item in iter {
res *= item;
}
res
}
}
impl<'a> Product<&'a $FieldName> for $FieldName {
fn product<I: Iterator<Item = &'a $FieldName>>(iter: I) -> $FieldName {
iter.cloned().product()
}
}
};
}

View file

@ -2,12 +2,15 @@ use zeroize::Zeroize;
use crypto_bigint::{U512, U1024};
use crate::field;
/// Ed448 field element.
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Zeroize)]
pub struct FieldElement(pub(crate) U512);
const MODULUS_STR: &str = concat!(
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffe",
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
);
// 2**448 - 2**224 - 1
pub(crate) const MODULUS: FieldElement = FieldElement(U512::from_be_hex(concat!(
"00000000000000",
@ -22,16 +25,34 @@ const WIDE_MODULUS: U1024 = U1024::from_be_hex(concat!(
"00000000000000",
"00",
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffe",
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
));
pub(crate) const Q_4: FieldElement =
FieldElement(MODULUS.0.saturating_add(&U512::ONE).wrapping_div(&U512::from_u8(4)));
field!(FieldElement, MODULUS, WIDE_MODULUS, 448);
field!(
FieldElement,
MODULUS_STR,
MODULUS,
WIDE_MODULUS,
448,
concat!(
"00000000000000000000000000000000000000000000000000000080ffffffff",
"ffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000",
),
7,
concat!(
"fefffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff",
"ffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000",
),
concat!(
"3100000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000",
),
);
#[test]
fn test_field() {
// TODO: Move to test_prime_field_bits once the impl is finished
ff_group_tests::prime_field::test_prime_field::<_, FieldElement>(&mut rand_core::OsRng);
ff_group_tests::prime_field::test_prime_field_bits::<_, FieldElement>(&mut rand_core::OsRng);
}

View file

@ -2,6 +2,7 @@
#![no_std]
#![doc = include_str!("../README.md")]
#[macro_use]
mod backend;
mod scalar;

View file

@ -37,13 +37,13 @@ fn recover_x(y: FieldElement) -> CtOption<FieldElement> {
let ysq = y.square();
#[allow(non_snake_case)]
let D_ysq = D * ysq;
(D_ysq - FieldElement::one()).invert().and_then(|inverted| {
let temp = (ysq - FieldElement::one()) * inverted;
(D_ysq - FieldElement::ONE).invert().and_then(|inverted| {
let temp = (ysq - FieldElement::ONE) * inverted;
let mut x = temp.pow(Q_4);
x.conditional_negate(x.is_odd());
let xsq = x.square();
CtOption::new(x, (xsq + ysq).ct_eq(&(FieldElement::one() + (xsq * D_ysq))))
CtOption::new(x, (xsq + ysq).ct_eq(&(FieldElement::ONE + (xsq * D_ysq))))
})
}
@ -56,7 +56,7 @@ pub struct Point {
}
lazy_static! {
static ref G: Point = Point { x: recover_x(G_Y).unwrap(), y: G_Y, z: FieldElement::one() };
static ref G: Point = Point { x: recover_x(G_Y).unwrap(), y: G_Y, z: FieldElement::ONE };
}
impl ConstantTimeEq for Point {
@ -180,7 +180,7 @@ impl Group for Point {
}
}
fn identity() -> Self {
Point { x: FieldElement::zero(), y: FieldElement::one(), z: FieldElement::one() }
Point { x: FieldElement::ZERO, y: FieldElement::ONE, z: FieldElement::ONE }
}
fn generator() -> Self {
*G
@ -291,7 +291,7 @@ impl GroupEncoding for Point {
recover_x(y).and_then(|mut x| {
x.conditional_negate(x.is_odd().ct_eq(&!sign));
let not_negative_zero = !(x.is_zero() & sign);
let point = Point { x, y, z: FieldElement::one() };
let point = Point { x, y, z: FieldElement::ONE };
CtOption::new(point, not_negative_zero & point.is_torsion_free())
})
})
@ -317,22 +317,7 @@ impl PrimeGroup for Point {}
#[test]
fn test_group() {
// TODO: Move to test_prime_group_bits once the impl is finished
use ff_group_tests::group::*;
test_eq::<Point>();
test_identity::<Point>();
test_generator::<Point>();
test_double::<Point>();
test_add::<Point>();
test_sum::<Point>();
test_neg::<Point>();
test_sub::<Point>();
test_mul::<Point>();
test_order::<Point>();
test_random::<_, Point>(&mut rand_core::OsRng);
test_encoding::<Point>();
ff_group_tests::group::test_prime_group_bits::<_, Point>(&mut rand_core::OsRng);
}
#[test]
@ -350,7 +335,7 @@ fn torsion() {
.unwrap(),
))
.unwrap();
let old = Point { x: -recover_x(old_y).unwrap(), y: old_y, z: FieldElement::one() };
let old = Point { x: -recover_x(old_y).unwrap(), y: old_y, z: FieldElement::ONE };
assert!(bool::from(!old.is_torsion_free()));
}

View file

@ -2,12 +2,15 @@ use zeroize::Zeroize;
use crypto_bigint::{U512, U1024};
use crate::field;
/// Ed448 Scalar field element.
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Zeroize)]
pub struct Scalar(pub(crate) U512);
const MODULUS_STR: &str = concat!(
"3fffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3",
);
// 2**446 - 13818066809895115352007386748515426880336692474882178609894547503885
pub(crate) const MODULUS: Scalar = Scalar(U512::from_be_hex(concat!(
"00000000000000",
@ -25,7 +28,26 @@ const WIDE_MODULUS: U1024 = U1024::from_be_hex(concat!(
"7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3",
));
field!(Scalar, MODULUS, WIDE_MODULUS, 446);
field!(
Scalar,
MODULUS_STR,
MODULUS,
WIDE_MODULUS,
446,
concat!(
"7a22ac554961bc91aac7e2463961b610481b6bd7a46d27e2f41165beffffffff",
"ffffffffffffffffffffffffffffffffffffffffffffff1f0000000000000000",
),
2,
concat!(
"f24458ab92c27823558fc58d72c26c219036d6ae49db4ec4e923ca7cffffffff",
"ffffffffffffffffffffffffffffffffffffffffffffff3f0000000000000000",
),
concat!(
"0400000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000",
),
);
impl Scalar {
/// Perform a wide reduction to obtain a non-biased Scalar.
@ -35,7 +57,6 @@ impl Scalar {
}
#[test]
fn test_scalar_field() {
// TODO: Move to test_prime_field_bits once the impl is finished
ff_group_tests::prime_field::test_prime_field::<_, Scalar>(&mut rand_core::OsRng);
fn test_scalar() {
ff_group_tests::prime_field::test_prime_field_bits::<_, Scalar>(&mut rand_core::OsRng);
}

View file

@ -1,6 +1,6 @@
[package]
name = "ff-group-tests"
version = "0.12.2"
version = "0.13.0"
description = "A collection of sanity tests for implementors of ff/group APIs"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/ff-group-tests"
@ -14,8 +14,15 @@ rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
rand_core = "0.6"
group = "0.12"
subtle = "^2.4"
group = "0.13"
[dev-dependencies]
k256 = { version = "0.12", features = ["bits"] }
p256 = { version = "0.12", features = ["bits"] }
k256 = { version = "0.13", features = ["bits"] }
p256 = { version = "0.13", features = ["bits"] }
bls12_381 = "0.8"
pasta_curves = "0.5"

View file

@ -1,109 +1,183 @@
use rand_core::RngCore;
use subtle::Choice;
use group::ff::Field;
/// Perform basic tests on equality.
pub fn test_eq<F: Field>() {
let zero = F::zero();
let one = F::one();
let zero = F::ZERO;
let one = F::ONE;
assert!(zero != one, "0 == 1");
assert!(!bool::from(zero.ct_eq(&one)), "0 ct_eq 1");
assert_eq!(zero, F::zero(), "0 != 0");
assert!(bool::from(zero.ct_eq(&F::zero())), "0 !ct_eq 0");
assert_eq!(zero, F::ZERO, "0 != 0");
assert!(bool::from(zero.ct_eq(&F::ZERO)), "0 !ct_eq 0");
assert_eq!(one, F::one(), "1 != 1");
assert!(bool::from(one.ct_eq(&F::one())), "1 !ct_eq 1");
assert_eq!(one, F::ONE, "1 != 1");
assert!(bool::from(one.ct_eq(&F::ONE)), "1 !ct_eq 1");
}
/// Verify conditional selection works. Doesn't verify it's actually constant time.
pub fn test_conditional_select<F: Field>() {
let zero = F::zero();
let one = F::one();
let zero = F::ZERO;
let one = F::ONE;
assert_eq!(F::conditional_select(&zero, &one, 0.into()), zero, "couldn't select when false");
assert_eq!(F::conditional_select(&zero, &one, 1.into()), one, "couldn't select when true");
}
/// Perform basic tests on addition.
pub fn test_add<F: Field>() {
assert_eq!(F::zero() + F::zero(), F::zero(), "0 + 0 != 0");
assert_eq!(F::zero() + F::one(), F::one(), "0 + 1 != 1");
assert_eq!(F::one() + F::zero(), F::one(), "1 + 0 != 1");
assert_eq!(F::ZERO + F::ZERO, F::ZERO, "0 + 0 != 0");
assert_eq!(F::ZERO + F::ONE, F::ONE, "0 + 1 != 1");
assert_eq!(F::ONE + F::ZERO, F::ONE, "1 + 0 != 1");
// Only PrimeField offers From<u64>
// Accordingly, we assume either double or addition is correct
// They either have to be matchingly correct or matchingly incorrect, yet we can't
// reliably determine that here
assert_eq!(F::one() + F::one(), F::one().double(), "1 + 1 != 2");
assert_eq!(F::ONE + F::ONE, F::ONE.double(), "1 + 1 != 2");
}
/// Perform basic tests on sum.
pub fn test_sum<F: Field>() {
assert_eq!((&[] as &[F]).iter().sum::<F>(), F::ZERO, "[].sum() != 0");
assert_eq!([F::ZERO].iter().sum::<F>(), F::ZERO, "[0].sum() != 0");
assert_eq!([F::ONE].iter().sum::<F>(), F::ONE, "[1].sum() != 1");
let two = F::ONE + F::ONE;
assert_eq!([F::ONE, F::ONE].iter().sum::<F>(), two, "[1, 1].sum() != 2");
assert_eq!([two, F::ONE].iter().sum::<F>(), two + F::ONE, "[2, 1].sum() != 3");
assert_eq!([two, F::ZERO, F::ONE].iter().sum::<F>(), two + F::ONE, "[2, 0, 1].sum() != 3");
}
/// Perform basic tests on subtraction.
pub fn test_sub<F: Field>() {
assert_eq!(F::zero() - F::zero(), F::zero(), "0 - 0 != 0");
assert_eq!(F::one() - F::zero(), F::one(), "1 - 0 != 1");
assert_eq!(F::one() - F::one(), F::zero(), "1 - 1 != 0");
#[allow(clippy::eq_op)]
let expr = F::ZERO - F::ZERO;
assert_eq!(expr, F::ZERO, "0 - 0 != 0");
assert_eq!(F::ONE - F::ZERO, F::ONE, "1 - 0 != 1");
#[allow(clippy::eq_op)]
let expr = F::ONE - F::ONE;
assert_eq!(expr, F::ZERO, "1 - 1 != 0");
}
/// Perform basic tests on negation.
pub fn test_neg<F: Field>() {
assert_eq!(-F::zero(), F::zero(), "-0 != 0");
assert_eq!(-(-F::one()), F::one(), "-(-1) != 1");
assert_eq!(F::one() + (-F::one()), F::zero(), "1 + -1 != 0");
assert_eq!(F::one() - (-F::one()), F::one().double(), "1 - -1 != 2");
assert_eq!(-F::ZERO, F::ZERO, "-0 != 0");
assert_eq!(-(-F::ONE), F::ONE, "-(-1) != 1");
assert_eq!(F::ONE + (-F::ONE), F::ZERO, "1 + -1 != 0");
assert_eq!(F::ONE - (-F::ONE), F::ONE.double(), "1 - -1 != 2");
}
/// Perform basic tests on multiplication.
pub fn test_mul<F: Field>() {
assert_eq!(F::zero() * F::zero(), F::zero(), "0 * 0 != 0");
assert_eq!(F::one() * F::zero(), F::zero(), "1 * 0 != 0");
assert_eq!(F::one() * F::one(), F::one(), "1 * 1 != 1");
let two = F::one().double();
assert_eq!(two * (two + F::one()), two + two + two, "2 * 3 != 6");
assert_eq!(F::ZERO * F::ZERO, F::ZERO, "0 * 0 != 0");
assert_eq!(F::ONE * F::ZERO, F::ZERO, "1 * 0 != 0");
assert_eq!(F::ONE * F::ONE, F::ONE, "1 * 1 != 1");
let two = F::ONE.double();
assert_eq!(two * (two + F::ONE), two + two + two, "2 * 3 != 6");
}
/// Perform basic tests on product.
pub fn test_product<F: Field>() {
assert_eq!((&[] as &[F]).iter().product::<F>(), F::ONE, "[].product() != 1");
assert_eq!([F::ZERO].iter().product::<F>(), F::ZERO, "[0].product() != 0");
assert_eq!([F::ONE].iter().product::<F>(), F::ONE, "[1].product() != 1");
assert_eq!([F::ONE, F::ONE].iter().product::<F>(), F::ONE, "[1, 1].product() != 2");
let two = F::ONE + F::ONE;
assert_eq!([two, F::ONE].iter().product::<F>(), two, "[2, 1].product() != 2");
assert_eq!([two, two].iter().product::<F>(), two + two, "[2, 2].product() != 4");
assert_eq!([two, two, F::ONE].iter().product::<F>(), two + two, "[2, 2, 1].product() != 4");
assert_eq!([two, F::ZERO, F::ONE].iter().product::<F>(), F::ZERO, "[2, 0, 1].product() != 0");
}
/// Perform basic tests on the square function.
pub fn test_square<F: Field>() {
assert_eq!(F::zero().square(), F::zero(), "0^2 != 0");
assert_eq!(F::one().square(), F::one(), "1^2 != 1");
let two = F::one().double();
assert_eq!(F::ZERO.square(), F::ZERO, "0^2 != 0");
assert_eq!(F::ONE.square(), F::ONE, "1^2 != 1");
let two = F::ONE.double();
assert_eq!(two.square(), two + two, "2^2 != 4");
let three = two + F::one();
let three = two + F::ONE;
assert_eq!(three.square(), three * three, "3^2 != 9");
}
/// Perform basic tests on the invert function.
pub fn test_invert<F: Field>() {
assert!(bool::from(F::zero().invert().is_none()), "0.invert() is some");
assert_eq!(F::one().invert().unwrap(), F::one(), "1.invert() != 1");
assert!(bool::from(F::ZERO.invert().is_none()), "0.invert() is some");
assert_eq!(F::ONE.invert().unwrap(), F::ONE, "1.invert() != 1");
let two = F::one().double();
let three = two + F::one();
let two = F::ONE.double();
let three = two + F::ONE;
assert_eq!(two * three.invert().unwrap() * three, two, "2 * 3.invert() * 3 != 2");
}
/// Perform basic tests on the sqrt function.
/// Perform basic tests on the sqrt functions.
pub fn test_sqrt<F: Field>() {
assert_eq!(F::zero().sqrt().unwrap(), F::zero(), "sqrt(0) != 0");
assert_eq!(F::one().sqrt().unwrap(), F::one(), "sqrt(1) != 1");
assert_eq!(F::ZERO.sqrt().unwrap(), F::ZERO, "sqrt(0) != 0");
assert!(
(F::ONE.sqrt().unwrap() == F::ONE) || (F::ONE.sqrt().unwrap() == -F::ONE),
"sqrt(1) != 1"
);
let mut has_root = F::one().double();
let mut has_root = F::ONE.double();
while bool::from(has_root.sqrt().is_none()) {
has_root += F::one();
has_root += F::ONE;
}
// The following code doesn't assume which root is returned, yet it does assume a consistent root
// is returned
let root = has_root.sqrt().unwrap();
assert_eq!(root * root, has_root, "sqrt(x)^2 != x");
let check = |value: (_, _), expected: (_, F), msg| {
assert_eq!(bool::from(value.0), bool::from(expected.0), "{}", msg);
assert!((value.1 == expected.1) || (value.1 == -expected.1), "{}", msg);
};
check(
F::sqrt_ratio(&has_root, &F::ONE),
(Choice::from(1), root),
"sqrt_ratio didn't return the root with a divisor of 1",
);
check(
F::sqrt_ratio(&(has_root * F::ONE.double()), &F::ONE.double()),
(Choice::from(1), root),
"sqrt_ratio didn't return the root with a divisor of 2",
);
check(F::sqrt_alt(&F::ZERO), F::sqrt_ratio(&F::ZERO, &F::ONE), "sqrt_alt(0) != sqrt_ratio(0, 1)");
check(F::sqrt_alt(&F::ONE), F::sqrt_ratio(&F::ONE, &F::ONE), "sqrt_alt(1) != sqrt_ratio(1, 1)");
check(F::sqrt_alt(&has_root), (Choice::from(1), root), "sqrt_alt(square) != (1, root)");
// Check 0 divisors are properly implemented
check(
F::sqrt_ratio(&has_root, &F::ZERO),
(Choice::from(0), F::ZERO),
"sqrt_ratio didn't return the right value for a 0 divisor",
);
// Check non-squares are appropriately marked
let mut no_root = has_root + F::ONE;
while bool::from(no_root.sqrt().is_some()) {
no_root += F::ONE;
}
assert!(
!bool::from(F::sqrt_ratio(&no_root, &F::ONE).0),
"sqrt_ratio claimed non-square had root"
);
assert!(!bool::from(F::sqrt_alt(&no_root).0), "sqrt_alt claimed non-square had root");
}
/// Perform basic tests on the is_zero functions.
pub fn test_is_zero<F: Field>() {
assert!(bool::from(F::zero().is_zero()), "0 is not 0");
assert!(F::zero().is_zero_vartime(), "0 is not 0");
assert!(bool::from(F::ZERO.is_zero()), "0 is not 0");
assert!(F::ZERO.is_zero_vartime(), "0 is not 0");
}
/// Perform basic tests on the cube function.
pub fn test_cube<F: Field>() {
assert_eq!(F::zero().cube(), F::zero(), "0^3 != 0");
assert_eq!(F::one().cube(), F::one(), "1^3 != 1");
let two = F::one().double();
assert_eq!(F::ZERO.cube(), F::ZERO, "0^3 != 0");
assert_eq!(F::ONE.cube(), F::ONE, "1^3 != 1");
let two = F::ONE.double();
assert_eq!(two.cube(), two * two * two, "2^3 != 8");
}
@ -130,14 +204,22 @@ pub fn test_random<R: RngCore, F: Field>(rng: &mut R) {
pub fn test_field<R: RngCore, F: Field>(rng: &mut R) {
test_eq::<F>();
test_conditional_select::<F>();
test_add::<F>();
test_sum::<F>();
test_sub::<F>();
test_neg::<F>();
test_mul::<F>();
test_product::<F>();
test_square::<F>();
test_invert::<F>();
test_sqrt::<F>();
test_is_zero::<F>();
test_cube::<F>();
test_random::<R, F>(rng);
}

View file

@ -108,7 +108,7 @@ pub fn test_mul<G: Group>() {
/// Test `((order - 1) * G) + G == identity`.
pub fn test_order<G: Group>() {
let minus_one = G::generator() * (G::Scalar::zero() - G::Scalar::one());
let minus_one = G::generator() * (G::Scalar::ZERO - G::Scalar::ONE);
assert!(minus_one != G::identity(), "(modulus - 1) * G was identity");
assert_eq!(minus_one + G::generator(), G::identity(), "((modulus - 1) * G) + G wasn't identity");
}
@ -203,3 +203,15 @@ fn test_k256() {
fn test_p256() {
test_prime_group_bits::<_, p256::ProjectivePoint>(&mut rand_core::OsRng);
}
#[test]
fn test_bls12_381() {
test_prime_group_bits::<_, bls12_381::G1Projective>(&mut rand_core::OsRng);
test_prime_group_bits::<_, bls12_381::G2Projective>(&mut rand_core::OsRng);
}
#[test]
fn test_pallas_vesta() {
test_prime_group_bits::<_, pasta_curves::pallas::Point>(&mut rand_core::OsRng);
test_prime_group_bits::<_, pasta_curves::vesta::Point>(&mut rand_core::OsRng);
}

View file

@ -6,17 +6,28 @@ use crate::field::test_field;
// Ideally, this and test_one would be under Field, yet these tests require access to From<u64>
/// Test zero returns F::from(0).
pub fn test_zero<F: PrimeField>() {
assert_eq!(F::zero(), F::from(0u64), "0 != 0");
assert_eq!(F::ZERO, F::from(0u64), "0 != 0");
}
/// Test one returns F::from(1).
pub fn test_one<F: PrimeField>() {
assert_eq!(F::one(), F::from(1u64), "1 != 1");
assert_eq!(F::ONE, F::from(1u64), "1 != 1");
}
/// Test `From<u64>` for F works.
pub fn test_from_u64<F: PrimeField>() {
assert_eq!(F::one().double(), F::from(2u64), "2 != 2");
assert_eq!(F::ZERO, F::from(0u64), "0 != 0u64");
assert_eq!(F::ONE, F::from(1u64), "1 != 1u64");
assert_eq!(F::ONE.double(), F::from(2u64), "2 != 2u64");
assert_eq!(F::ONE.double() + F::ONE, F::from(3u64), "3 != 3u64");
}
/// Test from_u128 for F works.
pub fn test_from_u128<F: PrimeField>() {
assert_eq!(F::ZERO, F::from_u128(0u128), "0 != 0u128");
assert_eq!(F::ONE, F::from_u128(1u128), "1 != 1u128");
assert_eq!(F::from(2u64), F::from_u128(2u128), "2u64 != 2u128");
assert_eq!(F::from(3u64), F::from_u128(3u128), "3u64 != 3u128");
}
/// Test is_odd/is_even works.
@ -24,14 +35,14 @@ pub fn test_from_u64<F: PrimeField>() {
/// Accordingly, this test doesn't support fields alternatively defined.
/// TODO: Improve in the future.
pub fn test_is_odd<F: PrimeField>() {
assert_eq!(F::zero().is_odd().unwrap_u8(), 0, "0 was odd");
assert_eq!(F::zero().is_even().unwrap_u8(), 1, "0 wasn't even");
assert_eq!(F::ZERO.is_odd().unwrap_u8(), 0, "0 was odd");
assert_eq!(F::ZERO.is_even().unwrap_u8(), 1, "0 wasn't even");
assert_eq!(F::one().is_odd().unwrap_u8(), 1, "1 was even");
assert_eq!(F::one().is_even().unwrap_u8(), 0, "1 wasn't odd");
assert_eq!(F::ONE.is_odd().unwrap_u8(), 1, "1 was even");
assert_eq!(F::ONE.is_even().unwrap_u8(), 0, "1 wasn't odd");
// Make sure an odd value added to an odd value is even
let two = F::one().double();
let two = F::ONE.double();
assert_eq!(two.is_odd().unwrap_u8(), 0, "2 was odd");
assert_eq!(two.is_even().unwrap_u8(), 1, "2 wasn't even");
@ -40,7 +51,7 @@ pub fn test_is_odd<F: PrimeField>() {
assert_eq!(four.is_odd().unwrap_u8(), 0, "4 was odd");
assert_eq!(four.is_even().unwrap_u8(), 1, "4 wasn't even");
let neg_one = -F::one();
let neg_one = -F::ONE;
assert_eq!(neg_one.is_odd().unwrap_u8(), 0, "-1 was odd");
assert_eq!(neg_one.is_even().unwrap_u8(), 1, "-1 wasn't even");
@ -66,13 +77,13 @@ pub fn test_encoding<F: PrimeField>() {
"canonical encoding decoded produced distinct encoding"
);
};
test(F::zero(), "0");
test(F::one(), "1");
test(F::one() + F::one(), "2");
test(-F::one(), "-1");
test(F::ZERO, "0");
test(F::ONE, "1");
test(F::ONE + F::ONE, "2");
test(-F::ONE, "-1");
// Also check if a non-canonical encoding is possible
let mut high = (F::zero() - F::one()).to_repr();
let mut high = (F::ZERO - F::ONE).to_repr();
let mut possible_non_canon = false;
for byte in high.as_mut() {
// The fact a bit isn't set in the highest possible value suggests there's unused bits
@ -97,6 +108,7 @@ pub fn test_prime_field<R: RngCore, F: PrimeField>(rng: &mut R) {
test_zero::<F>();
test_one::<F>();
test_from_u64::<F>();
test_from_u128::<F>();
test_is_odd::<F>();
// Do a sanity check on the CAPACITY. A full test can't be done at this time
@ -109,12 +121,12 @@ pub fn test_prime_field<R: RngCore, F: PrimeField>(rng: &mut R) {
// This test assumes that the modulus is at least 4.
pub fn test_to_le_bits<F: PrimeField + PrimeFieldBits>() {
{
let bits = F::zero().to_le_bits();
let bits = F::ZERO.to_le_bits();
assert_eq!(bits.iter().filter(|bit| **bit).count(), 0, "0 had bits set");
}
{
let bits = F::one().to_le_bits();
let bits = F::ONE.to_le_bits();
assert!(bits[0], "1 didn't have its least significant bit set");
assert_eq!(bits.iter().filter(|bit| **bit).count(), 1, "1 had multiple bits set");
}
@ -140,20 +152,20 @@ pub fn test_char_le_bits<F: PrimeField + PrimeFieldBits>() {
assert!(F::char_le_bits().iter().any(|bit| *bit), "char_le_bits contained 0");
// Test this is the bit pattern of the modulus by reconstructing the modulus from it
let mut bit = F::one();
let mut modulus = F::zero();
let mut bit = F::ONE;
let mut modulus = F::ZERO;
for set in F::char_le_bits() {
if set {
modulus += bit;
}
bit = bit.double();
}
assert_eq!(modulus, F::zero(), "char_le_bits did not contain the field's modulus");
assert_eq!(modulus, F::ZERO, "char_le_bits did not contain the field's modulus");
}
/// Test NUM_BITS is accurate.
pub fn test_num_bits<F: PrimeField + PrimeFieldBits>() {
let mut val = F::one();
let mut val = F::ONE;
let mut bit = 0;
while ((bit + 1) < val.to_le_bits().len()) && val.double().to_le_bits()[bit + 1] {
val = val.double();
@ -171,11 +183,11 @@ pub fn test_num_bits<F: PrimeField + PrimeFieldBits>() {
pub fn test_capacity<F: PrimeField + PrimeFieldBits>() {
assert!(F::CAPACITY <= F::NUM_BITS, "capacity exceeded number of bits");
let mut val = F::one();
let mut val = F::ONE;
assert!(val.to_le_bits()[0], "1 didn't have its least significant bit set");
for b in 1 .. F::CAPACITY {
val = val.double();
val += F::one();
val += F::ONE;
for i in 0 ..= b {
assert!(
val.to_le_bits()[usize::try_from(i).unwrap()],
@ -192,7 +204,7 @@ pub fn test_capacity<F: PrimeField + PrimeFieldBits>() {
F::CAPACITY,
"field has a power of two modulus yet CAPACITY doesn't equal NUM_BITS",
);
assert_eq!(val + F::one(), F::zero());
assert_eq!(val + F::ONE, F::ZERO, "CAPACITY set bits, + 1, != zero for a binary field");
return;
}
@ -200,7 +212,7 @@ pub fn test_capacity<F: PrimeField + PrimeFieldBits>() {
}
fn pow<F: PrimeFieldBits>(base: F, exp: F) -> F {
let mut res = F::one();
let mut res = F::ONE;
for bit in exp.to_le_bits().iter().rev() {
res *= res;
if *bit {
@ -214,22 +226,20 @@ fn pow<F: PrimeFieldBits>(base: F, exp: F) -> F {
/// Perform basic tests on the pow functions, even when passed non-canonical inputs.
pub fn test_pow<F: PrimeFieldBits>() {
// Sanity check the local pow algorithm. Does not have assert messages as these shouldn't fail
assert_eq!(pow(F::one(), F::zero()), F::one());
assert_eq!(pow(F::one().double(), F::zero()), F::one());
assert_eq!(pow(F::one(), F::one()), F::one());
assert_eq!(pow(F::ONE, F::ZERO), F::ONE);
assert_eq!(pow(F::ONE.double(), F::ZERO), F::ONE);
assert_eq!(pow(F::ONE, F::ONE), F::ONE);
let two = F::one().double();
assert_eq!(pow(two, F::one()), two);
let two = F::ONE.double();
assert_eq!(pow(two, F::ONE), two);
assert_eq!(pow(two, two), two.double());
let three = two + F::one();
assert_eq!(pow(three, F::one()), three);
let three = two + F::ONE;
assert_eq!(pow(three, F::ONE), three);
assert_eq!(pow(three, two), three * three);
assert_eq!(pow(three, three), three * three * three);
// TODO: Test against Field::pow once updated to ff 0.13
// Choose a small base without a notably uniform bit pattern
let bit_0 = F::one();
let bit_0 = F::ONE;
let base = {
let bit_1 = bit_0.double();
let bit_2 = bit_1.double();
@ -241,36 +251,53 @@ pub fn test_pow<F: PrimeFieldBits>() {
bit_7 + bit_6 + bit_5 + bit_2 + bit_0
};
// Ensure pow_vartime returns 1 when the base is raised to 0, handling malleated inputs
assert_eq!(base.pow_vartime([]), F::one(), "pow_vartime x^0 ([]) != 1");
assert_eq!(base.pow_vartime([0]), F::one(), "pow_vartime x^0 ([0]) != 1");
assert_eq!(base.pow_vartime([0, 0]), F::one(), "pow_vartime x^0 ([0, 0]) != 1");
// Ensure pow/pow_vartime return 1 when the base is raised to 0, handling malleated inputs
assert_eq!(base.pow([]), F::ONE, "pow x^0 ([]) != 1");
assert_eq!(base.pow_vartime([]), F::ONE, "pow x^0 ([]) != 1");
assert_eq!(base.pow([0]), F::ONE, "pow_vartime x^0 ([0]) != 1");
assert_eq!(base.pow_vartime([0]), F::ONE, "pow_vartime x^0 ([0]) != 1");
assert_eq!(base.pow([0, 0]), F::ONE, "pow x^0 ([0, 0]) != 1");
assert_eq!(base.pow_vartime([0, 0]), F::ONE, "pow_vartime x^0 ([0, 0]) != 1");
// Ensure pow_vartime returns the base when raised to 1, handling malleated inputs
assert_eq!(base.pow_vartime([1]), base, "pow_vartime x^1 ([1]) != x");
// Ensure pow/pow_vartime return the base when raised to 1, handling malleated inputs
assert_eq!(base.pow([1]), base, "pow x^1 ([1]) != x");
assert_eq!(base.pow_vartime([1, 0]), base, "pow_vartime x^1 ([1, 0]) != x");
assert_eq!(base.pow([1]), base, "pow x^1 ([1]) != x");
assert_eq!(base.pow_vartime([1, 0]), base, "pow_vartime x^1 ([1, 0]) != x");
// Ensure pow_vartime can handle multiple u64s properly
// Ensure pow/pow_vartime can handle multiple u64s properly
// Create a scalar which exceeds u64
let mut bit_64 = bit_0;
for _ in 0 .. 64 {
bit_64 = bit_64.double();
}
// Run the tests
assert_eq!(base.pow([0, 1]), pow(base, bit_64), "pow x^(2^64) != x^(2^64)");
assert_eq!(base.pow_vartime([0, 1]), pow(base, bit_64), "pow_vartime x^(2^64) != x^(2^64)");
assert_eq!(base.pow([1, 1]), pow(base, bit_64 + F::ONE), "pow x^(2^64 + 1) != x^(2^64 + 1)");
assert_eq!(
base.pow_vartime([1, 1]),
pow(base, bit_64 + F::one()),
pow(base, bit_64 + F::ONE),
"pow_vartime x^(2^64 + 1) != x^(2^64 + 1)"
);
}
/// Test the inverted constants are correct.
pub fn test_inv_consts<F: PrimeFieldBits>() {
assert_eq!(F::TWO_INV, F::from(2u64).invert().unwrap(), "F::TWO_INV != 2.invert()");
assert_eq!(
F::ROOT_OF_UNITY_INV,
F::ROOT_OF_UNITY.invert().unwrap(),
"F::ROOT_OF_UNITY_INV != F::ROOT_OF_UNITY.invert()"
);
}
/// Test S is correct.
pub fn test_s<F: PrimeFieldBits>() {
// "This is the number of leading zero bits in the little-endian bit representation of
// `modulus - 1`."
let mut s = 0;
for b in (F::zero() - F::one()).to_le_bits() {
for b in (F::ZERO - F::ONE).to_le_bits() {
if b {
break;
}
@ -285,14 +312,14 @@ pub fn test_root_of_unity<F: PrimeFieldBits>() {
// `t = (modulus - 1) >> Self::S`."
// Get the bytes to shift
let mut bits = (F::zero() - F::one()).to_le_bits().iter().map(|bit| *bit).collect::<Vec<_>>();
let mut bits = (F::ZERO - F::ONE).to_le_bits().iter().map(|bit| *bit).collect::<Vec<_>>();
for _ in 0 .. F::S {
bits.remove(0);
}
// Construct t
let mut bit = F::one();
let mut t = F::zero();
let mut bit = F::ONE;
let mut t = F::ZERO;
for set in bits {
if set {
t += bit;
@ -301,11 +328,20 @@ pub fn test_root_of_unity<F: PrimeFieldBits>() {
}
assert!(bool::from(t.is_odd()), "t wasn't odd");
assert_eq!(pow(F::multiplicative_generator(), t), F::root_of_unity(), "incorrect root of unity");
assert_eq!(pow(F::MULTIPLICATIVE_GENERATOR, t), F::ROOT_OF_UNITY, "incorrect root of unity");
assert_eq!(
pow(F::root_of_unity(), pow(F::from(2u64), F::from(F::S.into()))),
F::one(),
"root of unity raised to 2^S wasn't 1"
pow(F::ROOT_OF_UNITY, pow(F::from(2u64), F::from(F::S.into()))),
F::ONE,
"root of unity raised to 2^S wasn't 1",
);
}
/// Test DELTA is correct.
pub fn test_delta<F: PrimeFieldBits>() {
assert_eq!(
pow(F::MULTIPLICATIVE_GENERATOR, pow(F::from(2u64), F::from(u64::from(F::S)))),
F::DELTA,
"F::DELTA is incorrect"
);
}
@ -317,8 +353,11 @@ pub fn test_prime_field_bits<R: RngCore, F: PrimeFieldBits>(rng: &mut R) {
test_char_le_bits::<F>();
test_pow::<F>();
test_inv_consts::<F>();
test_s::<F>();
test_root_of_unity::<F>();
test_delta::<F>();
test_num_bits::<F>();
test_capacity::<F>();

View file

@ -92,7 +92,7 @@ pub trait Curve: Ciphersuite {
while {
seed.extend(repr.as_ref());
res = Zeroizing::new(<Self as Curve>::hash_to_F(b"nonce", seed.deref()));
res.ct_eq(&Self::F::zero()).into()
res.ct_eq(&Self::F::ZERO).into()
} {
seed = Zeroizing::new(vec![0; 32]);
rng.fill_bytes(&mut seed);

View file

@ -200,7 +200,7 @@ impl<C: Curve> SignatureShare<C> {
pub(crate) fn invalidate(&mut self) {
use ciphersuite::group::ff::Field;
self.0 += C::F::one();
self.0 += C::F::ONE;
}
}
@ -252,6 +252,7 @@ pub struct AlgorithmSignMachine<C: Curve, A: Algorithm<C>> {
params: Params<C, A>,
seed: CachedPreprocess,
#[zeroize(skip)]
commitments_challenge: <A::Transcript as Transcript>::Challenge,
pub(crate) nonces: Vec<Nonce<C>>,
// Skips the preprocess due to being too large a bound to feasibly enforce on users

View file

@ -106,7 +106,7 @@ impl<C: Curve> Algorithm<C> for MultiNonce<C> {
self.nonces = Some(nonce_sums.to_vec());
// Sum the nonces so we can later check they actually have a relationship to nonce_sums
let mut res = C::F::zero();
let mut res = C::F::ZERO;
// Weight each nonce
// This is probably overkill, since their unweighted forms would practically still require

View file

@ -15,15 +15,15 @@ rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
zeroize = { version = "^1.5", features = ["zeroize_derive"] }
ff = "0.12"
group = "0.12"
ff = "0.13"
group = "0.13"
rand_core = { version = "0.6", optional = true }
[dev-dependencies]
rand_core = "0.6"
k256 = { version = "0.12", features = ["bits"] }
k256 = { version = "0.13", features = ["bits"] }
dalek-ff-group = { path = "../dalek-ff-group" }
[features]

View file

@ -47,7 +47,7 @@ where
) {
// Define a unique scalar factor for this set of variables so individual items can't overlap
let u = if self.0.is_empty() {
G::Scalar::one()
G::Scalar::ONE
} else {
let mut weight;
while {

View file

@ -34,14 +34,13 @@ where
valid(batch);
// Test a batch with one set of statements
let valid_statements =
vec![(-G::Scalar::one(), G::generator()), (G::Scalar::one(), G::generator())];
let valid_statements = vec![(-G::Scalar::ONE, G::generator()), (G::Scalar::ONE, G::generator())];
let mut batch = BatchVerifier::new(1);
batch.queue(&mut OsRng, 0, valid_statements.clone());
valid(batch);
// Test a batch with an invalid set of statements fails properly
let invalid_statements = vec![(-G::Scalar::one(), G::generator())];
let invalid_statements = vec![(-G::Scalar::ONE, G::generator())];
let mut batch = BatchVerifier::new(1);
batch.queue(&mut OsRng, 0, invalid_statements.clone());
invalid(batch, 0);

View file

@ -110,8 +110,8 @@ where
test(&[], G::identity());
// Test an multiexp of identity/zero elements is identity
test(&[(G::Scalar::zero(), G::generator())], G::identity());
test(&[(G::Scalar::one(), G::identity())], G::identity());
test(&[(G::Scalar::ZERO, G::generator())], G::identity());
test(&[(G::Scalar::ONE, G::identity())], G::identity());
// Test a variety of multiexp sizes
let mut pairs = Vec::with_capacity(1000);

View file

@ -22,7 +22,7 @@ fn weight<D: Send + Clone + SecureDigest, F: PrimeField>(digest: &mut DigestTran
// This should be guaranteed thanks to SecureDigest
debug_assert!(bytes.len() >= 32);
let mut res = F::zero();
let mut res = F::ZERO;
let mut i = 0;
// Derive a scalar from enough bits of entropy that bias is < 2^128
@ -139,12 +139,18 @@ impl<C: Ciphersuite> SchnorrAggregate<C> {
/// A signature aggregator capable of consuming signatures in order to produce an aggregate.
#[allow(non_snake_case)]
#[derive(Clone, Debug, Zeroize)]
#[derive(Clone, Debug)]
pub struct SchnorrAggregator<C: Ciphersuite> {
digest: DigestTranscript<C::H>,
sigs: Vec<SchnorrSignature<C>>,
}
impl<C: Ciphersuite> Zeroize for SchnorrAggregator<C> {
fn zeroize(&mut self) {
self.sigs.zeroize();
}
}
impl<C: Ciphersuite> SchnorrAggregator<C> {
/// Create a new aggregator.
///
@ -168,8 +174,7 @@ impl<C: Ciphersuite> SchnorrAggregator<C> {
return None;
}
let mut aggregate =
SchnorrAggregate { Rs: Vec::with_capacity(self.sigs.len()), s: C::F::zero() };
let mut aggregate = SchnorrAggregate { Rs: Vec::with_capacity(self.sigs.len()), s: C::F::ZERO };
for i in 0 .. self.sigs.len() {
aggregate.Rs.push(self.sigs[i].R);
aggregate.s += self.sigs[i].s * weight::<_, C::F>(&mut self.digest);

View file

@ -82,7 +82,7 @@ impl<C: Ciphersuite> SchnorrSignature<C> {
// R + cA - sG == 0
[
// R
(C::F::one(), self.R),
(C::F::ONE, self.R),
// cA
(challenge, public_key),
// -sG

View file

@ -28,7 +28,7 @@ pub(crate) fn sign<C: Ciphersuite>() {
// This verifies invalid signatures don't pass, using zero signatures, which should effectively be
// random
pub(crate) fn verify<C: Ciphersuite>() {
assert!(!SchnorrSignature::<C> { R: C::G::identity(), s: C::F::zero() }
assert!(!SchnorrSignature::<C> { R: C::G::identity(), s: C::F::ZERO }
.verify(C::generator() * C::random_nonzero_F(&mut OsRng), C::random_nonzero_F(&mut OsRng)));
}
@ -62,10 +62,10 @@ pub(crate) fn batch_verify<C: Ciphersuite>() {
let mut batch = BatchVerifier::new(5);
for (i, mut sig) in sigs.clone().drain(..).enumerate() {
if i == 1 {
sig.s += C::F::one();
sig.s += C::F::ONE;
}
if i == 2 {
sig.s -= C::F::one();
sig.s -= C::F::ONE;
}
sig.batch_verify(&mut OsRng, &mut batch, i, C::generator() * keys[i].deref(), challenges[i]);
}

View file

@ -81,4 +81,5 @@ unknown-git = "deny"
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
allow-git = [
"https://github.com/serai-dex/substrate",
"https://github.com/RustCrypto/elliptic-curves"
]

View file

@ -32,14 +32,14 @@ bincode = "1"
serde_json = "1"
# Cryptography
group = "0.12"
group = "0.13"
transcript = { package = "flexible-transcript", path = "../crypto/transcript" }
frost = { package = "modular-frost", path = "../crypto/frost" }
# Bitcoin
secp256k1 = { version = "0.24", features = ["global-context", "rand-std"], optional = true }
k256 = { version = "0.12", features = ["arithmetic"], optional = true }
k256 = { version = "0.13", features = ["arithmetic"], optional = true }
bitcoin-serai = { path = "../coins/bitcoin", optional = true }
# Monero

View file

@ -188,7 +188,7 @@ impl Monero {
#[cfg(test)]
fn test_view_pair() -> ViewPair {
ViewPair::new(*EdwardsPoint::generator(), Zeroizing::new(Scalar::one().0))
ViewPair::new(*EdwardsPoint::generator(), Zeroizing::new(Scalar::ONE.0))
}
#[cfg(test)]
@ -398,7 +398,7 @@ impl Coin for Monero {
} else if outputs == 1 {
plan.payments.push(Payment {
address: Address::new(
ViewPair::new(EdwardsPoint::generator().0, Zeroizing::new(Scalar::one().0))
ViewPair::new(EdwardsPoint::generator().0, Zeroizing::new(Scalar::ONE.0))
.address(Network::Mainnet, AddressSpec::Standard),
)
.unwrap(),
@ -584,7 +584,7 @@ impl Coin for Monero {
self.rpc.get_fee().await.unwrap(),
)
.unwrap()
.sign(&mut OsRng, &self.rpc, &Zeroizing::new(Scalar::one().0))
.sign(&mut OsRng, &self.rpc, &Zeroizing::new(Scalar::ONE.0))
.await
.unwrap();