Merge branch 'next' into next-polkadot-sdk

This commit is contained in:
Luke Parker
2025-09-03 16:44:26 -04:00
130 changed files with 1102 additions and 1172 deletions

View File

@@ -1 +1 @@
nightly-2025-08-01
nightly-2025-09-01

View File

@@ -9,10 +9,12 @@ members = [
"patches/option-ext",
"patches/directories-next",
# monero-oxide expects ciphersuite, yet the ciphersuite in-tree here has breaking changes
# This re-exports the in-tree ciphersuite _without_ changes breaking to monero-oxide
# monero-oxide expects `ciphersuite`, yet the `ciphersuite` in-tree here has breaking changes
# This re-exports the in-tree `ciphersuite` _without_ changes breaking to monero-oxide
# Not included in workspace to prevent having two crates with the same name (an error)
# "patches/ciphersuite",
# Same for `dalek-ff-group`
# "patches/dalek-ff-group",
"common/std-shims",
"common/zalloc",
@@ -178,7 +180,7 @@ simple-request = { path = "common/request" }
multiexp = { path = "crypto/multiexp" }
flexible-transcript = { path = "crypto/transcript" }
ciphersuite = { path = "patches/ciphersuite" }
dalek-ff-group = { path = "crypto/dalek-ff-group" }
dalek-ff-group = { path = "patches/dalek-ff-group" }
minimal-ed448 = { path = "crypto/ed448" }
modular-frost = { path = "crypto/frost" }
@@ -201,9 +203,6 @@ directories-next = { path = "patches/directories-next" }
k256 = { git = "https://github.com/kayabaNerve/elliptic-curves", rev = "4994c9ab163781a88cd4a49beae812a89a44e8c3" }
p256 = { git = "https://github.com/kayabaNerve/elliptic-curves", rev = "4994c9ab163781a88cd4a49beae812a89a44e8c3" }
# https://github.com/RustCrypto/hybrid-array/issues/131
hybrid-array = { git = "https://github.com/kayabaNerve/hybrid-array", rev = "8caa508976c93696a67f40734537c91be7cecd96" }
[workspace.lints.clippy]
incompatible_msrv = "allow" # Manually verified with a GitHub workflow
manual_is_multiple_of = "allow"

View File

@@ -24,10 +24,8 @@ rand_core = { version = "0.6", default-features = false, features = ["std"] }
blake2 = { version = "0.11.0-rc.0", default-features = false, features = ["alloc"] }
schnorrkel = { version = "0.11", default-features = false, features = ["std"] }
transcript = { package = "flexible-transcript", path = "../crypto/transcript", default-features = false, features = ["std", "recommended"] }
dalek-ff-group = { path = "../crypto/dalek-ff-group", default-features = false, features = ["std"] }
ciphersuite = { path = "../crypto/ciphersuite", default-features = false, features = ["std"] }
schnorr = { package = "schnorr-signatures", path = "../crypto/schnorr", default-features = false, features = ["std", "aggregate"] }
dkg = { package = "dkg-musig", path = "../crypto/dkg/musig", default-features = false, features = ["std"] }
frost = { package = "modular-frost", path = "../crypto/frost" }
frost-schnorrkel = { path = "../crypto/schnorrkel" }

View File

@@ -3,11 +3,10 @@ use std::{boxed::Box, collections::HashMap};
use zeroize::Zeroizing;
use rand_core::OsRng;
use ciphersuite::{group::GroupEncoding, Ciphersuite};
use dalek_ff_group::Ristretto;
use ciphersuite::{group::GroupEncoding, *};
use dkg::{Participant, musig};
use frost_schnorrkel::{
frost::{FrostError, sign::*},
frost::{curve::Ristretto, FrostError, sign::*},
Schnorrkel,
};
@@ -31,7 +30,7 @@ fn schnorrkel() -> Schnorrkel {
fn our_i(
set: &NewSetInformation,
key: &Zeroizing<<Ristretto as Ciphersuite>::F>,
key: &Zeroizing<<Ristretto as WrappedGroup>::F>,
data: &HashMap<Participant, Vec<u8>>,
) -> Participant {
let public = SeraiAddress((Ristretto::generator() * key.deref()).to_bytes());
@@ -125,7 +124,7 @@ pub(crate) struct ConfirmDkgTask<CD: DbTrait, TD: DbTrait> {
set: NewSetInformation,
tributary_db: TD,
key: Zeroizing<<Ristretto as Ciphersuite>::F>,
key: Zeroizing<<Ristretto as WrappedGroup>::F>,
signer: Option<Signer>,
}
@@ -134,7 +133,7 @@ impl<CD: DbTrait, TD: DbTrait> ConfirmDkgTask<CD, TD> {
db: CD,
set: NewSetInformation,
tributary_db: TD,
key: Zeroizing<<Ristretto as Ciphersuite>::F>,
key: Zeroizing<<Ristretto as WrappedGroup>::F>,
) -> Self {
Self { db, set, tributary_db, key, signer: None }
}
@@ -153,7 +152,7 @@ impl<CD: DbTrait, TD: DbTrait> ConfirmDkgTask<CD, TD> {
db: &mut CD,
set: ExternalValidatorSet,
attempt: u32,
key: Zeroizing<<Ristretto as Ciphersuite>::F>,
key: Zeroizing<<Ristretto as WrappedGroup>::F>,
signer: &mut Option<Signer>,
) {
// Perform the preprocess

View File

@@ -7,7 +7,7 @@ use rand_core::{RngCore, OsRng};
use dalek_ff_group::Ristretto;
use ciphersuite::{
group::{ff::PrimeField, GroupEncoding},
Ciphersuite,
*,
};
use borsh::BorshDeserialize;
@@ -352,7 +352,7 @@ async fn main() {
let mut key_bytes = [0; 32];
key_bytes.copy_from_slice(&key_vec);
key_vec.zeroize();
let key = Zeroizing::new(<Ristretto as Ciphersuite>::F::from_repr(key_bytes).unwrap());
let key = Zeroizing::new(<Ristretto as WrappedGroup>::F::from_repr(key_bytes).unwrap());
key_bytes.zeroize();
key
};
@@ -439,7 +439,7 @@ async fn main() {
EphemeralEventStream::new(
db.clone(),
serai.clone(),
SeraiAddress((<Ristretto as Ciphersuite>::generator() * serai_key.deref()).to_bytes()),
SeraiAddress((<Ristretto as WrappedGroup>::generator() * serai_key.deref()).to_bytes()),
)
.continually_run(substrate_ephemeral_task_def, vec![substrate_task]),
);

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use zeroize::Zeroizing;
use ciphersuite::Ciphersuite;
use ciphersuite::*;
use dalek_ff_group::Ristretto;
use tokio::sync::mpsc;
@@ -23,7 +23,7 @@ use serai_coordinator_p2p::P2p;
use crate::{Db, KeySet};
pub(crate) struct SubstrateTask<P: P2p> {
pub(crate) serai_key: Zeroizing<<Ristretto as Ciphersuite>::F>,
pub(crate) serai_key: Zeroizing<<Ristretto as WrappedGroup>::F>,
pub(crate) db: Db,
pub(crate) message_queue: Arc<MessageQueue>,
pub(crate) p2p: P,

View File

@@ -4,7 +4,7 @@ use std::sync::Arc;
use zeroize::Zeroizing;
use rand_core::OsRng;
use blake2::{digest::typenum::U32, Digest, Blake2s};
use ciphersuite::Ciphersuite;
use ciphersuite::*;
use dalek_ff_group::Ristretto;
use tokio::sync::mpsc;
@@ -159,7 +159,7 @@ impl<CD: DbTrait, TD: DbTrait, P: P2p> ContinuallyRan
#[must_use]
async fn add_signed_unsigned_transaction<TD: DbTrait, P: P2p>(
tributary: &Tributary<TD, Transaction, P>,
key: &Zeroizing<<Ristretto as Ciphersuite>::F>,
key: &Zeroizing<<Ristretto as WrappedGroup>::F>,
mut tx: Transaction,
) -> bool {
// If this is a signed transaction, sign it
@@ -212,7 +212,7 @@ async fn add_with_recognition_check<TD: DbTrait, P: P2p>(
set: ExternalValidatorSet,
tributary_db: &mut TD,
tributary: &Tributary<TD, Transaction, P>,
key: &Zeroizing<<Ristretto as Ciphersuite>::F>,
key: &Zeroizing<<Ristretto as WrappedGroup>::F>,
tx: Transaction,
) -> bool {
let kind = tx.kind();
@@ -251,7 +251,7 @@ pub(crate) struct AddTributaryTransactionsTask<CD: DbTrait, TD: DbTrait, P: P2p>
tributary_db: TD,
tributary: Tributary<TD, Transaction, P>,
set: NewSetInformation,
key: Zeroizing<<Ristretto as Ciphersuite>::F>,
key: Zeroizing<<Ristretto as WrappedGroup>::F>,
}
impl<CD: DbTrait, TD: DbTrait, P: P2p> ContinuallyRan for AddTributaryTransactionsTask<CD, TD, P> {
type Error = DoesNotError;
@@ -381,7 +381,7 @@ pub(crate) struct SignSlashReportTask<CD: DbTrait, TD: DbTrait, P: P2p> {
tributary_db: TD,
tributary: Tributary<TD, Transaction, P>,
set: NewSetInformation,
key: Zeroizing<<Ristretto as Ciphersuite>::F>,
key: Zeroizing<<Ristretto as WrappedGroup>::F>,
}
impl<CD: DbTrait, TD: DbTrait, P: P2p> ContinuallyRan for SignSlashReportTask<CD, TD, P> {
type Error = DoesNotError;
@@ -469,7 +469,7 @@ pub(crate) async fn spawn_tributary<P: P2p>(
p2p: P,
p2p_add_tributary: &mpsc::UnboundedSender<(ExternalValidatorSet, Tributary<Db, Transaction, P>)>,
set: NewSetInformation,
serai_key: Zeroizing<<Ristretto as Ciphersuite>::F>,
serai_key: Zeroizing<<Ristretto as WrappedGroup>::F>,
) {
// Don't spawn retired Tributaries
if crate::db::RetiredTributary::get(&db, set.set.network).map(|session| session.0) >=
@@ -490,7 +490,7 @@ pub(crate) async fn spawn_tributary<P: P2p>(
let mut tributary_validators = Vec::with_capacity(set.validators.len());
for (validator, weight) in set.validators.iter().copied() {
let validator_key = <Ristretto as Ciphersuite>::read_G(&mut validator.0.as_slice())
let validator_key = <Ristretto as GroupIo>::read_G(&mut validator.0.as_slice())
.expect("Serai validator had an invalid public key");
let weight = u64::from(weight);
tributary_validators.push((validator_key, weight));

View File

@@ -1,7 +1,7 @@
use std::collections::{VecDeque, HashSet};
use dalek_ff_group::Ristretto;
use ciphersuite::{group::GroupEncoding, Ciphersuite};
use ciphersuite::{group::GroupEncoding, *};
use serai_db::{Get, DbTxn, Db};
@@ -21,7 +21,7 @@ pub(crate) struct Blockchain<D: Db, T: TransactionTrait> {
block_number: u64,
tip: [u8; 32],
participants: HashSet<<Ristretto as Ciphersuite>::G>,
participants: HashSet<<Ristretto as WrappedGroup>::G>,
provided: ProvidedTransactions<D, T>,
mempool: Mempool<D, T>,
@@ -56,7 +56,7 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
}
fn next_nonce_key(
genesis: &[u8; 32],
signer: &<Ristretto as Ciphersuite>::G,
signer: &<Ristretto as WrappedGroup>::G,
order: &[u8],
) -> Vec<u8> {
D::key(
@@ -69,7 +69,7 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
pub(crate) fn new(
db: D,
genesis: [u8; 32],
participants: &[<Ristretto as Ciphersuite>::G],
participants: &[<Ristretto as WrappedGroup>::G],
) -> Self {
let mut res = Self {
db: Some(db.clone()),
@@ -196,7 +196,7 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
pub(crate) fn next_nonce(
&self,
signer: &<Ristretto as Ciphersuite>::G,
signer: &<Ristretto as WrappedGroup>::G,
order: &[u8],
) -> Option<u32> {
if let Some(next_nonce) = self.mempool.next_nonce_in_mempool(signer, order.to_vec()) {

View File

@@ -5,7 +5,7 @@ use zeroize::Zeroizing;
use borsh::BorshDeserialize;
use ciphersuite::Ciphersuite;
use ciphersuite::*;
use dalek_ff_group::Ristretto;
use futures_channel::mpsc::UnboundedReceiver;
@@ -163,8 +163,8 @@ impl<D: Db, T: TransactionTrait, P: P2p> Tributary<D, T, P> {
db: D,
genesis: [u8; 32],
start_time: u64,
key: Zeroizing<<Ristretto as Ciphersuite>::F>,
validators: Vec<(<Ristretto as Ciphersuite>::G, u64)>,
key: Zeroizing<<Ristretto as WrappedGroup>::F>,
validators: Vec<(<Ristretto as WrappedGroup>::G, u64)>,
p2p: P,
) -> Option<Self> {
log::info!("new Tributary with genesis {}", hex::encode(genesis));
@@ -236,7 +236,7 @@ impl<D: Db, T: TransactionTrait, P: P2p> Tributary<D, T, P> {
pub async fn next_nonce(
&self,
signer: &<Ristretto as Ciphersuite>::G,
signer: &<Ristretto as WrappedGroup>::G,
order: &[u8],
) -> Option<u32> {
self.network.blockchain.read().await.next_nonce(signer, order)

View File

@@ -1,7 +1,7 @@
use std::collections::HashMap;
use dalek_ff_group::Ristretto;
use ciphersuite::Ciphersuite;
use ciphersuite::*;
use serai_db::{DbTxn, Db};
@@ -21,9 +21,9 @@ pub(crate) struct Mempool<D: Db, T: TransactionTrait> {
db: D,
genesis: [u8; 32],
last_nonce_in_mempool: HashMap<(<Ristretto as Ciphersuite>::G, Vec<u8>), u32>,
last_nonce_in_mempool: HashMap<(<Ristretto as WrappedGroup>::G, Vec<u8>), u32>,
txs: HashMap<[u8; 32], Transaction<T>>,
txs_per_signer: HashMap<<Ristretto as Ciphersuite>::G, u32>,
txs_per_signer: HashMap<<Ristretto as WrappedGroup>::G, u32>,
}
impl<D: Db, T: TransactionTrait> Mempool<D, T> {
@@ -107,7 +107,7 @@ impl<D: Db, T: TransactionTrait> Mempool<D, T> {
// Returns Ok(true) if new, Ok(false) if an already present unsigned, or the error.
pub(crate) fn add<
N: Network,
F: FnOnce(<Ristretto as Ciphersuite>::G, Vec<u8>) -> Option<u32>,
F: FnOnce(<Ristretto as WrappedGroup>::G, Vec<u8>) -> Option<u32>,
>(
&mut self,
blockchain_next_nonce: F,
@@ -179,7 +179,7 @@ impl<D: Db, T: TransactionTrait> Mempool<D, T> {
// Returns None if the mempool doesn't have a nonce tracked.
pub(crate) fn next_nonce_in_mempool(
&self,
signer: &<Ristretto as Ciphersuite>::G,
signer: &<Ristretto as WrappedGroup>::G,
order: Vec<u8>,
) -> Option<u32> {
self.last_nonce_in_mempool.get(&(*signer, order)).copied().map(|nonce| nonce + 1)

View File

@@ -10,11 +10,8 @@ use rand_chacha::ChaCha12Rng;
use transcript::{Transcript, RecommendedTranscript};
use ciphersuite::{
group::{
GroupEncoding,
ff::{Field, PrimeField},
},
Ciphersuite,
group::{ff::PrimeField, GroupEncoding},
*,
};
use dalek_ff_group::Ristretto;
use schnorr::{
@@ -51,24 +48,26 @@ fn challenge(
key: [u8; 32],
nonce: &[u8],
msg: &[u8],
) -> <Ristretto as Ciphersuite>::F {
) -> <Ristretto as WrappedGroup>::F {
let mut transcript = RecommendedTranscript::new(b"Tributary Chain Tendermint Message");
transcript.append_message(b"genesis", genesis);
transcript.append_message(b"key", key);
transcript.append_message(b"nonce", nonce);
transcript.append_message(b"message", msg);
<Ristretto as Ciphersuite>::F::from_bytes_mod_order_wide(&transcript.challenge(b"schnorr").into())
<Ristretto as WrappedGroup>::F::from_bytes_mod_order_wide(
&transcript.challenge(b"schnorr").into(),
)
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Signer {
genesis: [u8; 32],
key: Zeroizing<<Ristretto as Ciphersuite>::F>,
key: Zeroizing<<Ristretto as WrappedGroup>::F>,
}
impl Signer {
pub(crate) fn new(genesis: [u8; 32], key: Zeroizing<<Ristretto as Ciphersuite>::F>) -> Signer {
pub(crate) fn new(genesis: [u8; 32], key: Zeroizing<<Ristretto as WrappedGroup>::F>) -> Signer {
Signer { genesis, key }
}
}
@@ -101,10 +100,10 @@ impl SignerTrait for Signer {
assert_eq!(nonce_ref, [0; 64].as_ref());
let nonce =
Zeroizing::new(<Ristretto as Ciphersuite>::F::from_bytes_mod_order_wide(&nonce_arr));
Zeroizing::new(<Ristretto as WrappedGroup>::F::from_bytes_mod_order_wide(&nonce_arr));
nonce_arr.zeroize();
assert!(!bool::from(nonce.ct_eq(&<Ristretto as Ciphersuite>::F::ZERO)));
assert!(!bool::from(nonce.ct_eq(&<Ristretto as WrappedGroup>::F::ZERO)));
let challenge = challenge(
self.genesis,
@@ -133,7 +132,7 @@ pub struct Validators {
impl Validators {
pub(crate) fn new(
genesis: [u8; 32],
validators: Vec<(<Ristretto as Ciphersuite>::G, u64)>,
validators: Vec<(<Ristretto as WrappedGroup>::G, u64)>,
) -> Option<Validators> {
let mut total_weight = 0;
let mut weights = HashMap::new();
@@ -220,7 +219,7 @@ impl SignatureScheme for Validators {
signers
.iter()
.zip(challenges)
.map(|(s, c)| (<Ristretto as Ciphersuite>::read_G(&mut s.as_slice()).unwrap(), c))
.map(|(s, c)| (<Ristretto as GroupIo>::read_G(&mut s.as_slice()).unwrap(), c))
.collect::<Vec<_>>()
.as_slice(),
)

View File

@@ -5,7 +5,7 @@ use borsh::BorshDeserialize;
use blake2::{Digest, Blake2s256};
use dalek_ff_group::Ristretto;
use ciphersuite::Ciphersuite;
use ciphersuite::*;
use crate::{
transaction::{Transaction, TransactionKind, TransactionError},
@@ -50,7 +50,7 @@ impl Transaction for TendermintTx {
Blake2s256::digest(self.serialize()).into()
}
fn sig_hash(&self, _genesis: [u8; 32]) -> <Ristretto as Ciphersuite>::F {
fn sig_hash(&self, _genesis: [u8; 32]) -> <Ristretto as WrappedGroup>::F {
match self {
TendermintTx::SlashEvidence(_) => panic!("sig_hash called on slash evidence transaction"),
}

View File

@@ -3,10 +3,7 @@ use std::{sync::Arc, io, collections::HashMap, fmt::Debug};
use blake2::{Digest, Blake2s256};
use dalek_ff_group::Ristretto;
use ciphersuite::{
group::{ff::Field, Group},
Ciphersuite,
};
use ciphersuite::{group::Group, *};
use schnorr::SchnorrSignature;
use serai_db::MemDb;
@@ -32,11 +29,11 @@ impl NonceTransaction {
nonce,
distinguisher,
Signed {
signer: <Ristretto as Ciphersuite>::G::identity(),
signer: <Ristretto as WrappedGroup>::G::identity(),
nonce,
signature: SchnorrSignature::<Ristretto> {
R: <Ristretto as Ciphersuite>::G::identity(),
s: <Ristretto as Ciphersuite>::F::ZERO,
R: <Ristretto as WrappedGroup>::G::identity(),
s: <Ristretto as WrappedGroup>::F::ZERO,
},
},
)

View File

@@ -11,7 +11,7 @@ use rand::rngs::OsRng;
use blake2::{Digest, Blake2s256};
use dalek_ff_group::Ristretto;
use ciphersuite::{group::ff::Field, Ciphersuite};
use ciphersuite::*;
use serai_db::{DbTxn, Db, MemDb};
@@ -31,7 +31,7 @@ type N = TendermintNetwork<MemDb, SignedTransaction, DummyP2p>;
fn new_blockchain<T: TransactionTrait>(
genesis: [u8; 32],
participants: &[<Ristretto as Ciphersuite>::G],
participants: &[<Ristretto as WrappedGroup>::G],
) -> (MemDb, Blockchain<MemDb, T>) {
let db = MemDb::new();
let blockchain = Blockchain::new(db.clone(), genesis, participants);
@@ -82,7 +82,7 @@ fn invalid_block() {
assert!(blockchain.verify_block::<N>(&block, &validators, false).is_err());
}
let key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
let key = Zeroizing::new(<Ristretto as WrappedGroup>::F::random(&mut OsRng));
let tx = crate::tests::signed_transaction(&mut OsRng, genesis, &key, 0);
// Not a participant
@@ -134,7 +134,7 @@ fn invalid_block() {
blockchain.verify_block::<N>(&block, &validators, false).unwrap();
match &mut block.transactions[0] {
Transaction::Application(tx) => {
tx.1.signature.s += <Ristretto as Ciphersuite>::F::ONE;
tx.1.signature.s += <Ristretto as WrappedGroup>::F::ONE;
}
_ => panic!("non-signed tx found"),
}
@@ -150,7 +150,7 @@ fn invalid_block() {
fn signed_transaction() {
let genesis = new_genesis();
let validators = Arc::new(Validators::new(genesis, vec![]).unwrap());
let key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
let key = Zeroizing::new(<Ristretto as WrappedGroup>::F::random(&mut OsRng));
let tx = crate::tests::signed_transaction(&mut OsRng, genesis, &key, 0);
let signer = tx.1.signer;
@@ -339,7 +339,7 @@ fn provided_transaction() {
#[tokio::test]
async fn tendermint_evidence_tx() {
let genesis = new_genesis();
let key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
let key = Zeroizing::new(<Ristretto as WrappedGroup>::F::random(&mut OsRng));
let signer = Signer::new(genesis, key.clone());
let signer_id = Ristretto::generator() * key.deref();
let validators = Arc::new(Validators::new(genesis, vec![(signer_id, 1)]).unwrap());
@@ -379,7 +379,7 @@ async fn tendermint_evidence_tx() {
let mut mempool: Vec<Transaction<SignedTransaction>> = vec![];
let mut signers = vec![];
for _ in 0 .. 5 {
let key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
let key = Zeroizing::new(<Ristretto as WrappedGroup>::F::random(&mut OsRng));
let signer = Signer::new(genesis, key.clone());
let signer_id = Ristretto::generator() * key.deref();
signers.push((signer_id, 1));
@@ -446,7 +446,7 @@ async fn block_tx_ordering() {
}
let genesis = new_genesis();
let key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
let key = Zeroizing::new(<Ristretto as WrappedGroup>::F::random(&mut OsRng));
// signer
let signer = crate::tests::signed_transaction(&mut OsRng, genesis, &key, 0).1.signer;

View File

@@ -4,7 +4,7 @@ use zeroize::Zeroizing;
use rand::{RngCore, rngs::OsRng};
use dalek_ff_group::Ristretto;
use ciphersuite::{group::ff::Field, Ciphersuite};
use ciphersuite::*;
use tendermint::ext::Commit;
@@ -33,7 +33,7 @@ async fn mempool_addition() {
Some(Commit::<Arc<Validators>> { end_time: 0, validators: vec![], signature: vec![] })
};
let unsigned_in_chain = |_: [u8; 32]| false;
let key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
let key = Zeroizing::new(<Ristretto as WrappedGroup>::F::random(&mut OsRng));
let first_tx = signed_transaction(&mut OsRng, genesis, &key, 0);
let signer = first_tx.1.signer;
@@ -125,7 +125,7 @@ async fn mempool_addition() {
// If the mempool doesn't have a nonce for an account, it should successfully use the
// blockchain's
let second_key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
let second_key = Zeroizing::new(<Ristretto as WrappedGroup>::F::random(&mut OsRng));
let tx = signed_transaction(&mut OsRng, genesis, &second_key, 2);
let second_signer = tx.1.signer;
assert_eq!(mempool.next_nonce_in_mempool(&second_signer, vec![]), None);
@@ -165,7 +165,7 @@ fn too_many_mempool() {
Some(Commit::<Arc<Validators>> { end_time: 0, validators: vec![], signature: vec![] })
};
let unsigned_in_chain = |_: [u8; 32]| false;
let key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
let key = Zeroizing::new(<Ristretto as WrappedGroup>::F::random(&mut OsRng));
// We should be able to add transactions up to the limit
for i in 0 .. ACCOUNT_MEMPOOL_LIMIT {

View File

@@ -7,10 +7,7 @@ use rand::{RngCore, CryptoRng, rngs::OsRng};
use blake2::{Digest, Blake2s256};
use dalek_ff_group::Ristretto;
use ciphersuite::{
group::{ff::Field, Group},
Ciphersuite,
};
use ciphersuite::{group::Group, *};
use schnorr::SchnorrSignature;
use ::tendermint::{
@@ -32,11 +29,11 @@ mod tendermint;
pub fn random_signed<R: RngCore + CryptoRng>(rng: &mut R) -> Signed {
Signed {
signer: <Ristretto as Ciphersuite>::G::random(&mut *rng),
signer: <Ristretto as WrappedGroup>::G::random(&mut *rng),
nonce: u32::try_from(rng.next_u64() >> 32 >> 1).unwrap(),
signature: SchnorrSignature::<Ristretto> {
R: <Ristretto as Ciphersuite>::G::random(&mut *rng),
s: <Ristretto as Ciphersuite>::F::random(rng),
R: <Ristretto as WrappedGroup>::G::random(&mut *rng),
s: <Ristretto as WrappedGroup>::F::random(rng),
},
}
}
@@ -135,18 +132,18 @@ impl Transaction for SignedTransaction {
pub fn signed_transaction<R: RngCore + CryptoRng>(
rng: &mut R,
genesis: [u8; 32],
key: &Zeroizing<<Ristretto as Ciphersuite>::F>,
key: &Zeroizing<<Ristretto as WrappedGroup>::F>,
nonce: u32,
) -> SignedTransaction {
let mut data = vec![0; 512];
rng.fill_bytes(&mut data);
let signer = <Ristretto as Ciphersuite>::generator() * **key;
let signer = <Ristretto as WrappedGroup>::generator() * **key;
let mut tx =
SignedTransaction(data, Signed { signer, nonce, signature: random_signed(rng).signature });
let sig_nonce = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(rng));
let sig_nonce = Zeroizing::new(<Ristretto as WrappedGroup>::F::random(rng));
tx.1.signature.R = Ristretto::generator() * sig_nonce.deref();
tx.1.signature = SchnorrSignature::sign(key, sig_nonce, tx.sig_hash(genesis));
@@ -161,7 +158,7 @@ pub fn random_signed_transaction<R: RngCore + CryptoRng>(
let mut genesis = [0; 32];
rng.fill_bytes(&mut genesis);
let key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut *rng));
let key = Zeroizing::new(<Ristretto as WrappedGroup>::F::random(&mut *rng));
// Shift over an additional bit to ensure it won't overflow when incremented
let nonce = u32::try_from(rng.next_u64() >> 32 >> 1).unwrap();
@@ -178,12 +175,11 @@ pub async fn tendermint_meta() -> ([u8; 32], Signer, [u8; 32], Arc<Validators>)
// signer
let genesis = new_genesis();
let signer =
Signer::new(genesis, Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng)));
Signer::new(genesis, Zeroizing::new(<Ristretto as WrappedGroup>::F::random(&mut OsRng)));
let validator_id = signer.validator_id().await.unwrap();
// schema
let signer_pub =
<Ristretto as Ciphersuite>::read_G::<&[u8]>(&mut validator_id.as_slice()).unwrap();
let signer_pub = <Ristretto as GroupIo>::read_G::<&[u8]>(&mut validator_id.as_slice()).unwrap();
let validators = Arc::new(Validators::new(genesis, vec![(signer_pub, 1)]).unwrap());
(genesis, signer, validator_id, validators)

View File

@@ -3,7 +3,7 @@ use rand::rngs::OsRng;
use blake2::{Digest, Blake2s256};
use dalek_ff_group::Ristretto;
use ciphersuite::{group::ff::Field, Ciphersuite};
use ciphersuite::*;
use crate::{
ReadWrite,
@@ -69,7 +69,7 @@ fn signed_transaction() {
}
{
let mut tx = tx.clone();
tx.1.signature.s += <Ristretto as Ciphersuite>::F::ONE;
tx.1.signature.s += <Ristretto as WrappedGroup>::F::ONE;
assert!(verify_transaction(&tx, genesis, &mut |_, _| Some(tx.1.nonce)).is_err());
}

View File

@@ -4,7 +4,7 @@ use zeroize::Zeroizing;
use rand::{RngCore, rngs::OsRng};
use dalek_ff_group::Ristretto;
use ciphersuite::{Ciphersuite, group::ff::Field};
use ciphersuite::*;
use tendermint::{
time::CanonicalInstant,
@@ -275,7 +275,7 @@ async fn conflicting_msgs_evidence_tx() {
let signed_1 = signed_for_b_r(0, 0, Data::Proposal(None, TendermintBlock(vec![0x11]))).await;
let signer_2 =
Signer::new(genesis, Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng)));
Signer::new(genesis, Zeroizing::new(<Ristretto as WrappedGroup>::F::random(&mut OsRng)));
let signed_id_2 = signer_2.validator_id().await.unwrap();
let signed_2 = signed_from_data::<N>(
signer_2.into(),
@@ -292,10 +292,9 @@ async fn conflicting_msgs_evidence_tx() {
));
// update schema so that we don't fail due to invalid signature
let signer_pub =
<Ristretto as Ciphersuite>::read_G::<&[u8]>(&mut signer_id.as_slice()).unwrap();
let signer_pub = <Ristretto as GroupIo>::read_G::<&[u8]>(&mut signer_id.as_slice()).unwrap();
let signer_pub_2 =
<Ristretto as Ciphersuite>::read_G::<&[u8]>(&mut signed_id_2.as_slice()).unwrap();
<Ristretto as GroupIo>::read_G::<&[u8]>(&mut signed_id_2.as_slice()).unwrap();
let validators =
Arc::new(Validators::new(genesis, vec![(signer_pub, 1), (signer_pub_2, 1)]).unwrap());

View File

@@ -8,7 +8,7 @@ use blake2::{Digest, Blake2b512};
use ciphersuite::{
group::{Group, GroupEncoding},
Ciphersuite,
*,
};
use dalek_ff_group::Ristretto;
use schnorr::SchnorrSignature;
@@ -43,7 +43,7 @@ pub enum TransactionError {
/// Data for a signed transaction.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Signed {
pub signer: <Ristretto as Ciphersuite>::G,
pub signer: <Ristretto as WrappedGroup>::G,
pub nonce: u32,
pub signature: SchnorrSignature<Ristretto>,
}
@@ -160,10 +160,10 @@ pub trait Transaction: 'static + Send + Sync + Clone + Eq + Debug + ReadWrite {
/// Do not override this unless you know what you're doing.
///
/// Panics if called on non-signed transactions.
fn sig_hash(&self, genesis: [u8; 32]) -> <Ristretto as Ciphersuite>::F {
fn sig_hash(&self, genesis: [u8; 32]) -> <Ristretto as WrappedGroup>::F {
match self.kind() {
TransactionKind::Signed(order, Signed { signature, .. }) => {
<Ristretto as Ciphersuite>::F::from_bytes_mod_order_wide(
<Ristretto as WrappedGroup>::F::from_bytes_mod_order_wide(
&Blake2b512::digest(
[
b"Tributary Signed Transaction",
@@ -182,8 +182,8 @@ pub trait Transaction: 'static + Send + Sync + Clone + Eq + Debug + ReadWrite {
}
}
pub trait GAIN: FnMut(&<Ristretto as Ciphersuite>::G, &[u8]) -> Option<u32> {}
impl<F: FnMut(&<Ristretto as Ciphersuite>::G, &[u8]) -> Option<u32>> GAIN for F {}
pub trait GAIN: FnMut(&<Ristretto as WrappedGroup>::G, &[u8]) -> Option<u32> {}
impl<F: FnMut(&<Ristretto as WrappedGroup>::G, &[u8]) -> Option<u32>> GAIN for F {}
pub(crate) fn verify_transaction<F: GAIN, T: Transaction>(
tx: &T,

View File

@@ -6,8 +6,8 @@ use rand_core::{RngCore, CryptoRng};
use blake2::{digest::typenum::U32, Digest, Blake2b};
use ciphersuite::{
group::{ff::Field, Group, GroupEncoding},
Ciphersuite,
group::{Group, GroupEncoding},
*,
};
use dalek_ff_group::Ristretto;
use schnorr::SchnorrSignature;
@@ -51,7 +51,7 @@ impl SigningProtocolRound {
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Signed {
/// The signer.
signer: <Ristretto as Ciphersuite>::G,
signer: <Ristretto as WrappedGroup>::G,
/// The signature.
signature: SchnorrSignature<Ristretto>,
}
@@ -72,7 +72,7 @@ impl BorshDeserialize for Signed {
impl Signed {
/// Fetch the signer.
pub(crate) fn signer(&self) -> <Ristretto as Ciphersuite>::G {
pub(crate) fn signer(&self) -> <Ristretto as WrappedGroup>::G {
self.signer
}
@@ -85,10 +85,10 @@ impl Signed {
impl Default for Signed {
fn default() -> Self {
Self {
signer: <Ristretto as Ciphersuite>::G::identity(),
signer: <Ristretto as WrappedGroup>::G::identity(),
signature: SchnorrSignature {
R: <Ristretto as Ciphersuite>::G::identity(),
s: <Ristretto as Ciphersuite>::F::ZERO,
R: <Ristretto as WrappedGroup>::G::identity(),
s: <Ristretto as WrappedGroup>::F::ZERO,
},
}
}
@@ -357,7 +357,7 @@ impl Transaction {
&mut self,
rng: &mut R,
genesis: [u8; 32],
key: &Zeroizing<<Ristretto as Ciphersuite>::F>,
key: &Zeroizing<<Ristretto as WrappedGroup>::F>,
) {
fn signed(tx: &mut Transaction) -> &mut Signed {
#[allow(clippy::match_same_arms)] // This doesn't make semantic sense here
@@ -381,13 +381,13 @@ impl Transaction {
}
// Decide the nonce to sign with
let sig_nonce = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(rng));
let sig_nonce = Zeroizing::new(<Ristretto as WrappedGroup>::F::random(rng));
{
// Set the signer and the nonce
let signed = signed(self);
signed.signer = Ristretto::generator() * key.deref();
signed.signature.R = <Ristretto as Ciphersuite>::generator() * sig_nonce.deref();
signed.signature.R = <Ristretto as WrappedGroup>::generator() * sig_nonce.deref();
}
// Get the signature hash (which now includes `R || A` making it valid as the challenge)

View File

@@ -17,15 +17,12 @@ rustdoc-args = ["--cfg", "docsrs"]
workspace = true
[dependencies]
std-shims = { path = "../../common/std-shims", version = "^0.1.1", default-features = false, optional = true }
rand_core = { version = "0.6", default-features = false }
std-shims = { path = "../../common/std-shims", version = "0.1.4", default-features = false, optional = true }
zeroize = { version = "^1.5", default-features = false, features = ["derive"] }
subtle = { version = "^2.4", default-features = false }
digest = { version = "0.11.0-rc.0", default-features = false, features = ["block-api"] }
transcript = { package = "flexible-transcript", path = "../transcript", version = "^0.3.2", default-features = false }
digest = { version = "0.11.0-rc.1", default-features = false }
ff = { version = "0.13", default-features = false, features = ["bits"] }
group = { version = "0.13", default-features = false }
@@ -33,24 +30,18 @@ group = { version = "0.13", default-features = false }
[dev-dependencies]
hex = { version = "0.4", default-features = false, features = ["std"] }
rand_core = { version = "0.6", default-features = false, features = ["std"] }
ff-group-tests = { version = "0.13", path = "../ff-group-tests" }
[features]
alloc = ["std-shims", "digest/alloc", "ff/alloc"]
alloc = ["std-shims", "zeroize/alloc", "digest/alloc", "ff/alloc"]
std = [
"alloc",
"std-shims/std",
"rand_core/std",
"zeroize/std",
"subtle/std",
"transcript/std",
"ff/std",
]

View File

@@ -21,7 +21,7 @@ rand_core = { version = "0.6", default-features = false }
zeroize = { version = "^1.5", default-features = false, features = ["derive"] }
sha2 = { version = "0.11.0-rc.0", default-features = false }
sha2 = { version = "0.11.0-rc.2", default-features = false }
p256 = { version = "^0.13.1", default-features = false, features = ["arithmetic", "bits", "hash2curve"] }
k256 = { version = "^0.13.1", default-features = false, features = ["arithmetic", "bits", "hash2curve"] }

View File

@@ -5,7 +5,7 @@ use zeroize::Zeroize;
use sha2::Sha512;
use ciphersuite::Ciphersuite;
use ciphersuite::{WrappedGroup, Id, WithPreferredHash, GroupCanonicalEncoding};
pub use k256;
pub use p256;
@@ -18,17 +18,20 @@ macro_rules! kp_curve {
$Ciphersuite: ident,
$ID: literal
) => {
impl Ciphersuite for $Ciphersuite {
impl WrappedGroup for $Ciphersuite {
type F = $lib::Scalar;
type G = $lib::ProjectivePoint;
type H = Sha512;
const ID: &'static [u8] = $ID;
fn generator() -> Self::G {
$lib::ProjectivePoint::GENERATOR
}
}
impl Id for $Ciphersuite {
const ID: &'static [u8] = $ID;
}
impl WithPreferredHash for $Ciphersuite {
type H = Sha512;
}
impl GroupCanonicalEncoding for $Ciphersuite {}
};
}

View File

@@ -9,22 +9,18 @@ use std_shims::prelude::*;
#[cfg(feature = "alloc")]
use std_shims::io::{self, Read};
use rand_core::{RngCore, CryptoRng};
use subtle::{CtOption, ConstantTimeEq, ConditionallySelectable};
use zeroize::Zeroize;
use subtle::ConstantTimeEq;
pub use digest;
use digest::{array::ArraySize, block_api::BlockSizeUser, OutputSizeUser, Digest, HashMarker};
use transcript::SecureDigest;
use digest::{array::ArraySize, OutputSizeUser, Digest, HashMarker};
pub use group;
use group::{
ff::{Field, PrimeField, PrimeFieldBits},
ff::{PrimeField, PrimeFieldBits},
Group, GroupOps,
prime::PrimeGroup,
};
#[cfg(feature = "alloc")]
use group::GroupEncoding;
pub trait FromUniformBytes<T> {
@@ -36,74 +32,118 @@ impl<const N: usize, F: group::ff::FromUniformBytes<N>> FromUniformBytes<[u8; N]
}
}
/// Unified trait defining a ciphersuite around an elliptic curve.
pub trait Ciphersuite:
/// A marker trait for fields which fleshes them out a bit more.
pub trait F: PrimeField + PrimeFieldBits + Zeroize {}
impl<Fi: PrimeField + PrimeFieldBits + Zeroize> F for Fi {}
/// A marker trait for groups which fleshes them out a bit more.
pub trait G:
Group + GroupOps + GroupEncoding + PrimeGroup + ConstantTimeEq + ConditionallySelectable + Zeroize
{
}
impl<
Gr: Group
+ GroupOps
+ GroupEncoding
+ PrimeGroup
+ ConstantTimeEq
+ ConditionallySelectable
+ Zeroize,
> G for Gr
{
}
/// A `Group` type which has been wrapped into the current type.
///
/// This avoids having to re-implement all of the `Group` traits on the wrapper.
// TODO: Remove these bounds
pub trait WrappedGroup:
'static + Send + Sync + Clone + Copy + PartialEq + Eq + Debug + Zeroize
{
/// Scalar field element type.
// This is available via G::Scalar yet `C::G::Scalar` is ambiguous, forcing horrific accesses
type F: PrimeField
+ PrimeFieldBits
+ Zeroize
+ FromUniformBytes<<<Self::H as OutputSizeUser>::OutputSize as ArraySize>::ArrayType<u8>>;
// This is available via `G::Scalar` yet `WG::G::Scalar` is ambiguous, forcing horrific accesses
type F: F;
/// Group element type.
type G: Group<Scalar = Self::F> + GroupOps + PrimeGroup + Zeroize + ConstantTimeEq;
/// Hash algorithm used with this curve.
// Requires BlockSizeUser so it can be used within Hkdf which requires that.
type H: Send + Clone + BlockSizeUser + Digest + HashMarker + SecureDigest;
/// ID for this curve.
const ID: &'static [u8];
type G: Group<Scalar = Self::F> + G;
/// Generator for the group.
// While group does provide this in its API, privacy coins may want to use a custom basepoint
fn generator() -> Self::G;
}
impl<Gr: G<Scalar: F>> WrappedGroup for Gr {
type F = <Gr as Group>::Scalar;
type G = Gr;
fn generator() -> Self::G {
<Self::G as Group>::generator()
}
}
/// An ID for an object.
pub trait Id {
// The ID.
const ID: &'static [u8];
}
/// A group with a preferred hash function.
pub trait WithPreferredHash:
WrappedGroup<
F: FromUniformBytes<<<Self::H as OutputSizeUser>::OutputSize as ArraySize>::ArrayType<u8>>,
>
{
type H: Send + Clone + Digest + HashMarker;
#[allow(non_snake_case)]
fn hash_to_F(data: &[u8]) -> Self::F {
Self::F::from_uniform_bytes(&Self::H::digest(data).into())
}
}
/// Generate a random non-zero scalar.
#[allow(non_snake_case)]
fn random_nonzero_F<R: RngCore + CryptoRng>(rng: &mut R) -> Self::F {
let mut res;
while {
res = Self::F::random(&mut *rng);
res.ct_eq(&Self::F::ZERO).into()
} {}
res
}
/// Read a canonical scalar from something implementing std::io::Read.
#[cfg(feature = "alloc")]
#[allow(non_snake_case)]
fn read_F<R: Read>(reader: &mut R) -> io::Result<Self::F> {
let mut encoding = <Self::F as PrimeField>::Repr::default();
reader.read_exact(encoding.as_mut())?;
// ff mandates this is canonical
let res = Option::<Self::F>::from(Self::F::from_repr(encoding))
.ok_or_else(|| io::Error::other("non-canonical scalar"));
encoding.as_mut().zeroize();
res
}
/// Read a canonical point from something implementing std::io::Read.
/// A group which always encodes points canonically and supports decoding points while checking
/// they have a canonical encoding.
pub trait GroupCanonicalEncoding: WrappedGroup {
/// Decode a point from its canonical encoding.
///
/// The provided implementation is safe so long as `GroupEncoding::to_bytes` always returns a
/// canonical serialization.
/// Returns `None` if the point was invalid or not the encoding wasn't canonical.
///
/// If `<Self::G as GroupEncoding>::from_bytes` already only accepts canonical encodings, this
/// SHOULD be overriden with `<Self::G as GroupEncoding>::from_bytes(bytes)`.
fn from_canonical_bytes(bytes: &<Self::G as GroupEncoding>::Repr) -> CtOption<Self::G> {
let res = Self::G::from_bytes(bytes).unwrap_or(Self::generator());
// Safe due to the bound points are always encoded canonically
let canonical = res.to_bytes().as_ref().ct_eq(bytes.as_ref());
CtOption::new(res, canonical)
}
}
/// `std::io` extensions for `GroupCanonicalEncoding.`
#[cfg(feature = "alloc")]
#[allow(non_snake_case)]
pub trait GroupIo: GroupCanonicalEncoding {
/// Read a canonical field element from something implementing `std::io::Read`.
fn read_F<R: Read>(reader: &mut R) -> io::Result<Self::F> {
let mut bytes = <Self::F as PrimeField>::Repr::default();
reader.read_exact(bytes.as_mut())?;
// `ff` mandates this is canonical
let res = Option::<Self::F>::from(Self::F::from_repr(bytes))
.ok_or_else(|| io::Error::other("non-canonical scalar"));
bytes.as_mut().zeroize();
res
}
/// Read a canonical point from something implementing `std::io::Read`.
#[cfg(feature = "alloc")]
#[allow(non_snake_case)]
fn read_G<R: Read>(reader: &mut R) -> io::Result<Self::G> {
let mut encoding = <Self::G as GroupEncoding>::Repr::default();
reader.read_exact(encoding.as_mut())?;
let mut bytes = <Self::G as GroupEncoding>::Repr::default();
reader.read_exact(bytes.as_mut())?;
let point = Option::<Self::G>::from(Self::G::from_bytes(&encoding))
let res = Option::<Self::G>::from(Self::from_canonical_bytes(&bytes))
.ok_or_else(|| io::Error::other("invalid point"))?;
if point.to_bytes().as_ref() != encoding.as_ref() {
Err(io::Error::other("non-canonical point"))?;
}
Ok(point)
bytes.as_mut().zeroize();
Ok(res)
}
}
impl<Gr: GroupCanonicalEncoding> GroupIo for Gr {}
/// Unified trait defining a ciphersuite around an elliptic curve.
pub trait Ciphersuite: Id + WithPreferredHash + GroupCanonicalEncoding {}
impl<C: Id + WithPreferredHash + GroupCanonicalEncoding> Ciphersuite for C {}

View File

@@ -1,6 +1,6 @@
[package]
name = "dalek-ff-group"
version = "0.4.6"
version = "0.5.0"
description = "ff/group bindings around curve25519-dalek"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/dalek-ff-group"
@@ -22,15 +22,13 @@ subtle = { version = "^2.4", default-features = false }
rand_core = { version = "0.6", default-features = false }
digest = { version = "0.10", default-features = false }
sha2 = { version = "0.11.0-rc.0", default-features = false }
sha2 = { version = "0.11.0-rc.2", default-features = false, features = ["zeroize"] }
blake2 = { version = "0.11.0-rc.2", default-features = false, features = ["zeroize"] }
prime-field = { path = "../prime-field", default-features = false }
ciphersuite = { version = "0.4.2", path = "../ciphersuite", default-features = false }
crypto-bigint = { version = "0.5", default-features = false, features = ["zeroize"] }
curve25519-dalek = { version = ">= 4.0, < 4.2", default-features = false, features = ["zeroize", "digest", "group", "precomputed-tables"] }
curve25519-dalek = { version = ">= 4.0, < 4.2", default-features = false, features = ["zeroize", "digest", "group-bits", "precomputed-tables"] }
[dev-dependencies]
hex = "0.4"
@@ -38,6 +36,6 @@ rand_core = { version = "0.6", default-features = false, features = ["std"] }
ff-group-tests = { path = "../ff-group-tests" }
[features]
alloc = ["zeroize/alloc", "digest/alloc", "prime-field/alloc", "ciphersuite/alloc", "curve25519-dalek/alloc"]
std = ["alloc", "zeroize/std", "subtle/std", "rand_core/std", "digest/std", "prime-field/std", "ciphersuite/std"]
alloc = ["zeroize/alloc", "prime-field/alloc", "ciphersuite/alloc", "curve25519-dalek/alloc"]
std = ["alloc", "zeroize/std", "subtle/std", "rand_core/std", "prime-field/std", "ciphersuite/std"]
default = ["std"]

View File

@@ -1,49 +1,48 @@
use zeroize::Zeroize;
use sha2::Sha512;
use blake2::Blake2b512;
use ciphersuite::{group::Group, Ciphersuite};
use ::ciphersuite::{group::Group, *};
use crate::Scalar;
macro_rules! dalek_curve {
(
$feature: literal,
$Ciphersuite: ident,
$Point: ident,
$ID: literal
) => {
use crate::$Point;
impl Ciphersuite for $Ciphersuite {
type F = Scalar;
type G = $Point;
type H = Sha512;
const ID: &'static [u8] = $ID;
fn generator() -> Self::G {
$Point::generator()
}
}
};
}
use crate::*;
/// Ciphersuite for Ristretto.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub struct Ristretto;
dalek_curve!("ristretto", Ristretto, RistrettoPoint, b"ristretto");
#[test]
fn test_ristretto() {
ff_group_tests::group::test_prime_group_bits::<_, RistrettoPoint>(&mut rand_core::OsRng);
impl WrappedGroup for Ristretto {
type F = Scalar;
type G = RistrettoPoint;
fn generator() -> Self::G {
<RistrettoPoint as Group>::generator()
}
}
impl Id for Ristretto {
const ID: &[u8] = b"ristretto";
}
impl WithPreferredHash for Ristretto {
type H = Blake2b512;
}
impl GroupCanonicalEncoding for Ristretto {
fn from_canonical_bytes(bytes: &<Self::G as GroupEncoding>::Repr) -> CtOption<Self::G> {
Self::G::from_bytes(bytes)
}
}
/// Ciphersuite for Ed25519, inspired by RFC-8032.
/// Ciphersuite for Ed25519.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub struct Ed25519;
dalek_curve!("ed25519", Ed25519, EdwardsPoint, b"edwards25519");
#[test]
fn test_ed25519() {
ff_group_tests::group::test_prime_group_bits::<_, EdwardsPoint>(&mut rand_core::OsRng);
impl WrappedGroup for Ed25519 {
type F = Scalar;
type G = EdwardsPoint;
fn generator() -> Self::G {
<EdwardsPoint as Group>::generator()
}
}
impl Id for Ed25519 {
const ID: &[u8] = b"ed25519";
}
impl WithPreferredHash for Ed25519 {
type H = Sha512;
}
impl GroupCanonicalEncoding for Ed25519 {}

View File

@@ -7,7 +7,7 @@
use core::{
borrow::Borrow,
ops::{Deref, Add, AddAssign, Sub, SubAssign, Neg, Mul, MulAssign},
iter::{Iterator, Sum, Product},
iter::{Iterator, Sum},
hash::{Hash, Hasher},
};
@@ -15,25 +15,16 @@ use zeroize::Zeroize;
use subtle::{ConstantTimeEq, ConditionallySelectable};
use rand_core::RngCore;
use digest::{consts::U64, Digest, HashMarker};
use subtle::{Choice, CtOption};
pub use curve25519_dalek as dalek;
use dalek::{
constants::{self, BASEPOINT_ORDER},
scalar::Scalar as DScalar,
edwards::{EdwardsPoint as DEdwardsPoint, EdwardsBasepointTable, CompressedEdwardsY},
ristretto::{RistrettoPoint as DRistrettoPoint, RistrettoBasepointTable, CompressedRistretto},
use curve25519_dalek::{
edwards::{EdwardsPoint as DEdwardsPoint, CompressedEdwardsY},
ristretto::{RistrettoPoint as DRistrettoPoint, CompressedRistretto},
};
pub use constants::{ED25519_BASEPOINT_TABLE, RISTRETTO_BASEPOINT_TABLE};
pub use curve25519_dalek::Scalar;
use ::ciphersuite::group::{
ff::{Field, PrimeField, FieldBits, PrimeFieldBits, FromUniformBytes},
Group, GroupEncoding,
prime::PrimeGroup,
};
use ::ciphersuite::group::{Group, GroupEncoding, prime::PrimeGroup};
mod ciphersuite;
pub use crate::ciphersuite::{Ed25519, Ristretto};
@@ -97,7 +88,41 @@ macro_rules! constant_time {
}
};
}
pub(crate) use constant_time;
macro_rules! math_op_without_wrapping {
(
$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))
}
}
impl $Assign<$Other> for $Value {
fn $assign_fn(&mut self, other: $Other) {
self.0 = $function(self.0, other);
}
}
impl<'a> $Op<&'a $Other> for $Value {
type Output = $Value;
fn $op_fn(self, other: &'a $Other) -> Self::Output {
Self($function(self.0, other))
}
}
impl<'a> $Assign<&'a $Other> for $Value {
fn $assign_fn(&mut self, other: &'a $Other) {
self.0 = $function(self.0, other);
}
}
};
}
macro_rules! math_op {
(
@@ -133,20 +158,12 @@ macro_rules! math_op {
}
};
}
pub(crate) use math_op;
macro_rules! math {
($Value: ident, $Factor: ident, $add: expr, $sub: expr, $mul: expr) => {
math_op!($Value, $Value, Add, add, AddAssign, add_assign, $add);
math_op!($Value, $Value, Sub, sub, SubAssign, sub_assign, $sub);
math_op!($Value, $Factor, Mul, mul, MulAssign, mul_assign, $mul);
};
}
pub(crate) use math;
macro_rules! math_neg {
($Value: ident, $Factor: ident, $add: expr, $sub: expr, $mul: expr) => {
math!($Value, $Factor, $add, $sub, $mul);
math_op!($Value, $Value, Add, add, AddAssign, add_assign, $add);
math_op!($Value, $Value, Sub, sub, SubAssign, sub_assign, $sub);
math_op_without_wrapping!($Value, $Factor, Mul, mul, MulAssign, mul_assign, $mul);
impl Neg for $Value {
type Output = Self;
@@ -157,187 +174,6 @@ macro_rules! math_neg {
};
}
/// Wrapper around the dalek Scalar type.
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Zeroize)]
pub struct Scalar(pub DScalar);
deref_borrow!(Scalar, DScalar);
constant_time!(Scalar, DScalar);
math_neg!(Scalar, Scalar, DScalar::add, DScalar::sub, DScalar::mul);
macro_rules! from_wrapper {
($uint: ident) => {
impl From<$uint> for Scalar {
fn from(a: $uint) -> Scalar {
Scalar(DScalar::from(a))
}
}
};
}
from_wrapper!(u8);
from_wrapper!(u16);
from_wrapper!(u32);
from_wrapper!(u64);
from_wrapper!(u128);
impl Scalar {
pub fn pow(&self, other: Scalar) -> Scalar {
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 bits = 0;
for (i, mut bit) in other.to_le_bits().iter_mut().rev().enumerate() {
bits <<= 1;
let mut bit = u8_from_bool(&mut bit);
bits |= bit;
bit.zeroize();
if ((i + 1) % 4) == 0 {
if i != 3 {
for _ in 0 .. 4 {
res *= res;
}
}
let mut scale_by = Scalar::ONE;
#[allow(clippy::needless_range_loop)]
for i in 0 .. 16 {
#[allow(clippy::cast_possible_truncation)] // Safe since 0 .. 16
{
scale_by = <_>::conditional_select(&scale_by, &table[i], bits.ct_eq(&(i as u8)));
}
}
res *= scale_by;
bits = 0;
}
}
res
}
/// Perform wide reduction on a 64-byte array to create a Scalar without bias.
pub fn from_bytes_mod_order_wide(bytes: &[u8; 64]) -> Scalar {
Self(DScalar::from_bytes_mod_order_wide(bytes))
}
/// Derive a Scalar without bias from a digest via wide reduction.
pub fn from_hash<D: Digest<OutputSize = U64> + HashMarker>(hash: D) -> Scalar {
let mut output = [0u8; 64];
output.copy_from_slice(&hash.finalize());
let res = Scalar(DScalar::from_bytes_mod_order_wide(&output));
output.zeroize();
res
}
}
impl Field for Scalar {
const ZERO: Scalar = Scalar(DScalar::ZERO);
const ONE: Scalar = Scalar(DScalar::ONE);
fn random(rng: impl RngCore) -> Self {
Self(<DScalar as Field>::random(rng))
}
fn square(&self) -> Self {
Self(self.0.square())
}
fn double(&self) -> Self {
Self(self.0.double())
}
fn invert(&self) -> CtOption<Self> {
<DScalar as Field>::invert(&self.0).map(Self)
}
fn sqrt(&self) -> CtOption<Self> {
self.0.sqrt().map(Self)
}
fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) {
let (choice, res) = DScalar::sqrt_ratio(num, div);
(choice, Self(res))
}
}
impl PrimeField for Scalar {
type Repr = [u8; 32];
const MODULUS: &'static str = <DScalar as PrimeField>::MODULUS;
const NUM_BITS: u32 = <DScalar as PrimeField>::NUM_BITS;
const CAPACITY: u32 = <DScalar as PrimeField>::CAPACITY;
const TWO_INV: Scalar = Scalar(<DScalar as PrimeField>::TWO_INV);
const MULTIPLICATIVE_GENERATOR: Scalar =
Scalar(<DScalar as PrimeField>::MULTIPLICATIVE_GENERATOR);
const S: u32 = <DScalar as PrimeField>::S;
const ROOT_OF_UNITY: Scalar = Scalar(<DScalar as PrimeField>::ROOT_OF_UNITY);
const ROOT_OF_UNITY_INV: Scalar = Scalar(<DScalar as PrimeField>::ROOT_OF_UNITY_INV);
const DELTA: Scalar = Scalar(<DScalar as PrimeField>::DELTA);
fn from_repr(bytes: [u8; 32]) -> CtOption<Self> {
<DScalar as PrimeField>::from_repr(bytes).map(Scalar)
}
fn to_repr(&self) -> [u8; 32] {
self.0.to_repr()
}
fn is_odd(&self) -> Choice {
self.0.is_odd()
}
fn from_u128(num: u128) -> Self {
Scalar(DScalar::from_u128(num))
}
}
impl PrimeFieldBits for Scalar {
type ReprBits = [u8; 32];
fn to_le_bits(&self) -> FieldBits<Self::ReprBits> {
self.to_repr().into()
}
fn char_le_bits() -> FieldBits<Self::ReprBits> {
BASEPOINT_ORDER.to_bytes().into()
}
}
impl FromUniformBytes<64> for Scalar {
fn from_uniform_bytes(bytes: &[u8; 64]) -> Self {
Self::from_bytes_mod_order_wide(bytes)
}
}
impl Sum<Scalar> for Scalar {
fn sum<I: Iterator<Item = Scalar>>(iter: I) -> Scalar {
Self(DScalar::sum(iter))
}
}
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,
@@ -347,9 +183,6 @@ macro_rules! dalek_group {
$Table: ident,
$DCompressed: ident,
$BASEPOINT_POINT: ident,
$BASEPOINT_TABLE: ident
) => {
/// Wrapper around the dalek Point type.
///
@@ -363,9 +196,6 @@ macro_rules! dalek_group {
constant_time!($Point, $DPoint);
math_neg!($Point, Scalar, $DPoint::add, $DPoint::sub, $DPoint::mul);
/// The basepoint for this curve.
pub const $BASEPOINT_POINT: $Point = $Point(constants::$BASEPOINT_POINT);
impl Sum<$Point> for $Point {
fn sum<I: Iterator<Item = $Point>>(iter: I) -> $Point {
Self($DPoint::sum(iter))
@@ -396,7 +226,7 @@ macro_rules! dalek_group {
Self($DPoint::identity())
}
fn generator() -> Self {
$BASEPOINT_POINT
Self(<$DPoint as Group>::generator())
}
fn is_identity(&self) -> Choice {
self.0.ct_eq(&$DPoint::identity())
@@ -430,13 +260,6 @@ macro_rules! dalek_group {
impl PrimeGroup for $Point {}
impl Mul<Scalar> for &$Table {
type Output = $Point;
fn mul(self, b: Scalar) -> $Point {
$Point(&b.0 * self)
}
}
// Support being used as a key in a table
// While it is expensive as a key, due to the field operations required, there's frequently
// use cases for public key -> value lookups
@@ -456,24 +279,14 @@ dalek_group!(
|point: DEdwardsPoint| point.is_torsion_free(),
EdwardsBasepointTable,
CompressedEdwardsY,
ED25519_BASEPOINT_POINT,
ED25519_BASEPOINT_TABLE
);
impl EdwardsPoint {
pub fn mul_by_cofactor(&self) -> EdwardsPoint {
EdwardsPoint(self.0.mul_by_cofactor())
}
}
dalek_group!(
RistrettoPoint,
DRistrettoPoint,
|_| true,
RistrettoBasepointTable,
CompressedRistretto,
RISTRETTO_BASEPOINT_POINT,
RISTRETTO_BASEPOINT_TABLE
);
#[test]
@@ -494,21 +307,3 @@ prime_field::odd_prime_field_with_specific_repr!(
false,
crate::ThirtyTwoArray
);
impl FieldElement {
/// Create a FieldElement from a `crypto_bigint::U256`.
///
/// This will reduce the `U256` by the modulus, into a member of the field.
#[deprecated]
pub const fn from_u256(u256: &crypto_bigint::U256) -> Self {
FieldElement::from(&prime_field::crypto_bigint::U256::from_words(*u256.as_words()))
}
/// Create a `FieldElement` from the reduction of a 512-bit number.
///
/// The bytes are interpreted in little-endian format.
#[deprecated]
pub fn wide_reduce(value: [u8; 64]) -> Self {
<FieldElement as ::ciphersuite::group::ff::FromUniformBytes<_>>::from_uniform_bytes(&value)
}
}

View File

@@ -10,12 +10,12 @@ use rand_core::{RngCore, CryptoRng};
use ciphersuite::{
group::ff::{Field, PrimeField},
Ciphersuite,
GroupIo, Id,
};
pub use dkg::*;
/// Create a key via a dealer key generation protocol.
pub fn key_gen<R: RngCore + CryptoRng, C: Ciphersuite>(
pub fn key_gen<R: RngCore + CryptoRng, C: GroupIo + Id>(
rng: &mut R,
threshold: u16,
participants: u16,

View File

@@ -31,13 +31,13 @@ ciphersuite = { path = "../../ciphersuite", version = "^0.4.1", default-features
multiexp = { path = "../../multiexp", version = "0.4", default-features = false }
generic-array = { version = "1", default-features = false, features = ["alloc"] }
blake2 = { version = "0.11.0-rc.0", default-features = false }
blake2 = { version = "0.11.0-rc.2", default-features = false }
rand_chacha = { version = "0.3", default-features = false }
generalized-bulletproofs = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false }
ec-divisors = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false }
generalized-bulletproofs-circuit-abstraction = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false }
generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false }
generalized-bulletproofs = { git = "https://github.com/monero-oxide/monero-oxide", rev = "7216a2e84c7671c167c3d81eafe0d2b1f418f102", default-features = false }
ec-divisors = { git = "https://github.com/monero-oxide/monero-oxide", rev = "7216a2e84c7671c167c3d81eafe0d2b1f418f102", default-features = false }
generalized-bulletproofs-circuit-abstraction = { git = "https://github.com/monero-oxide/monero-oxide", rev = "7216a2e84c7671c167c3d81eafe0d2b1f418f102", default-features = false }
generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "7216a2e84c7671c167c3d81eafe0d2b1f418f102", default-features = false }
dkg = { path = "..", default-features = false }
@@ -52,7 +52,7 @@ rand = { version = "0.8", default-features = false, features = ["std"] }
ciphersuite = { path = "../../ciphersuite", default-features = false, features = ["std"] }
embedwards25519 = { path = "../../embedwards25519", default-features = false, features = ["std"] }
dalek-ff-group = { path = "../../dalek-ff-group", default-features = false, features = ["std"] }
generalized-bulletproofs = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", features = ["tests"] }
generalized-bulletproofs = { git = "https://github.com/monero-oxide/monero-oxide", rev = "7216a2e84c7671c167c3d81eafe0d2b1f418f102", features = ["tests"] }
dkg-recovery = { path = "../recovery" }
[features]
@@ -86,6 +86,5 @@ std = [
]
secp256k1 = ["ciphersuite-kp256", "secq256k1"]
ed25519 = ["dalek-ff-group", "embedwards25519"]
ristretto = ["dalek-ff-group", "embedwards25519"]
tests = ["rand_core/getrandom"]
default = ["std"]

View File

@@ -17,7 +17,7 @@ type Blake2s256Keyed = Blake2sMac<U32>;
use ciphersuite::{
group::{ff::FromUniformBytes, GroupEncoding},
Ciphersuite,
WrappedGroup, Id, GroupIo,
};
use ec_divisors::DivisorCurve;
@@ -27,10 +27,10 @@ use generalized_bulletproofs_ec_gadgets::*;
/// A pair of curves to perform the eVRF with.
pub trait Curves {
/// The towering curve, for which the resulting key is on.
type ToweringCurve: Ciphersuite<F: FromUniformBytes<64>>;
type ToweringCurve: Id + GroupIo<F: FromUniformBytes<64>>;
/// The embedded curve which participants represent their public keys over.
type EmbeddedCurve: Ciphersuite<
G: DivisorCurve<FieldElement = <Self::ToweringCurve as Ciphersuite>::F>,
type EmbeddedCurve: GroupIo<
G: DivisorCurve<FieldElement = <Self::ToweringCurve as WrappedGroup>::F>,
>;
/// The parameters to use the embedded curve with the discrete-log gadget.
type EmbeddedCurveParameters: DiscreteLogParameters;
@@ -49,14 +49,14 @@ impl<C: Curves> Generators<C> {
pub fn new(max_threshold: u16, max_participants: u16) -> Generators<C> {
let entropy = <Blake2s256Keyed as KeyInit>::new(&{
let mut key = Array::<u8, <Blake2s256Keyed as KeySizeUser>::KeySize>::default();
let key_len = key.len().min(<C::ToweringCurve as Ciphersuite>::ID.len());
let key_len = key.len().min(<C::ToweringCurve as Id>::ID.len());
{
let key: &mut [u8] = key.as_mut();
key[.. key_len].copy_from_slice(&<C::ToweringCurve as Ciphersuite>::ID[.. key_len])
key[.. key_len].copy_from_slice(&<C::ToweringCurve as Id>::ID[.. key_len])
}
key
})
.chain_update(<C::ToweringCurve as Ciphersuite>::generator().to_bytes())
.chain_update(<C::ToweringCurve as WrappedGroup>::generator().to_bytes())
.finalize()
.into_bytes();
let mut rng = ChaCha20Rng::from_seed(entropy.into());
@@ -71,7 +71,8 @@ impl<C: Curves> Generators<C> {
h_bold.push(crate::sample_point::<C::ToweringCurve>(&mut rng));
}
Self(
BpGenerators::new(<C::ToweringCurve as Ciphersuite>::generator(), h, g_bold, h_bold).unwrap(),
BpGenerators::new(<C::ToweringCurve as WrappedGroup>::generator(), h, g_bold, h_bold)
.unwrap(),
)
}
}
@@ -95,13 +96,3 @@ impl Curves for Ed25519 {
type EmbeddedCurve = embedwards25519::Embedwards25519;
type EmbeddedCurveParameters = embedwards25519::Embedwards25519;
}
/// Ristretto, and an elliptic curve defined over its scalar field (embedwards25519).
#[cfg(any(test, feature = "ristretto"))]
pub struct Ristretto;
#[cfg(any(test, feature = "ristretto"))]
impl Curves for Ristretto {
type ToweringCurve = dalek_ff_group::Ristretto;
type EmbeddedCurve = embedwards25519::Embedwards25519;
type EmbeddedCurveParameters = embedwards25519::Embedwards25519;
}

View File

@@ -21,7 +21,7 @@ use ciphersuite::{
ff::{Field, PrimeField},
Group, GroupEncoding,
},
Ciphersuite,
WrappedGroup, GroupIo,
};
use multiexp::multiexp_vartime;
@@ -49,7 +49,7 @@ mod tests;
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Participation<C: Curves> {
proof: Vec<u8>,
encrypted_secret_shares: HashMap<Participant, <C::ToweringCurve as Ciphersuite>::F>,
encrypted_secret_shares: HashMap<Participant, <C::ToweringCurve as WrappedGroup>::F>,
}
impl<C: Curves> Participation<C> {
@@ -79,7 +79,7 @@ impl<C: Curves> Participation<C> {
let mut encrypted_secret_shares = HashMap::with_capacity(usize::from(n));
for i in Participant::iter().take(usize::from(n)) {
encrypted_secret_shares.insert(i, <C::ToweringCurve as Ciphersuite>::read_F(reader)?);
encrypted_secret_shares.insert(i, <C::ToweringCurve as GroupIo>::read_F(reader)?);
}
Ok(Self { proof, encrypted_secret_shares })
@@ -151,14 +151,14 @@ pub enum VerifyResult<C: Curves> {
pub struct Dkg<C: Curves> {
t: u16,
n: u16,
evrf_public_keys: Vec<<C::EmbeddedCurve as Ciphersuite>::G>,
verification_shares: HashMap<Participant, <C::ToweringCurve as Ciphersuite>::G>,
evrf_public_keys: Vec<<C::EmbeddedCurve as WrappedGroup>::G>,
verification_shares: HashMap<Participant, <C::ToweringCurve as WrappedGroup>::G>,
#[allow(clippy::type_complexity)]
encrypted_secret_shares: HashMap<
Participant,
HashMap<
Participant,
([<C::EmbeddedCurve as Ciphersuite>::G; 2], <C::ToweringCurve as Ciphersuite>::F),
([<C::EmbeddedCurve as WrappedGroup>::G; 2], <C::ToweringCurve as WrappedGroup>::F),
>,
>,
}
@@ -167,7 +167,7 @@ impl<C: Curves> Dkg<C> {
// Form the initial transcript for the proofs.
fn initial_transcript(
invocation: [u8; 32],
evrf_public_keys: &[<C::EmbeddedCurve as Ciphersuite>::G],
evrf_public_keys: &[<C::EmbeddedCurve as WrappedGroup>::G],
t: u16,
) -> [u8; 32] {
let mut transcript = Blake2s256::new();
@@ -188,8 +188,8 @@ impl<C: Curves> Dkg<C> {
generators: &Generators<C>,
context: [u8; 32],
t: u16,
evrf_public_keys: &[<C::EmbeddedCurve as Ciphersuite>::G],
evrf_private_key: &Zeroizing<<C::EmbeddedCurve as Ciphersuite>::F>,
evrf_public_keys: &[<C::EmbeddedCurve as WrappedGroup>::G],
evrf_private_key: &Zeroizing<<C::EmbeddedCurve as WrappedGroup>::F>,
) -> Result<Participation<C>, Error> {
let Ok(n) = u16::try_from(evrf_public_keys.len()) else {
Err(Error::TooManyParticipants { provided: evrf_public_keys.len() })?
@@ -202,7 +202,8 @@ impl<C: Curves> Dkg<C> {
};
// This also ensures the private key is not 0, due to the prior check the identity point wasn't
// present
let evrf_public_key = <C::EmbeddedCurve as Ciphersuite>::generator() * evrf_private_key.deref();
let evrf_public_key =
<C::EmbeddedCurve as WrappedGroup>::generator() * evrf_private_key.deref();
if !evrf_public_keys.contains(&evrf_public_key) {
Err(Error::NotAParticipant)?;
};
@@ -231,7 +232,7 @@ impl<C: Curves> Dkg<C> {
let mut encrypted_secret_shares = HashMap::with_capacity(usize::from(n));
for (l, encryption_key) in Participant::iter().take(usize::from(n)).zip(encryption_keys) {
let share = polynomial::<<C::ToweringCurve as Ciphersuite>::F>(&coefficients, l);
let share = polynomial::<<C::ToweringCurve as WrappedGroup>::F>(&coefficients, l);
encrypted_secret_shares.insert(l, *share + *encryption_key);
}
@@ -243,26 +244,26 @@ impl<C: Curves> Dkg<C> {
#[allow(clippy::type_complexity)]
fn verifiable_encryption_statements<C: Curves>(
rng: &mut (impl RngCore + CryptoRng),
coefficients: &[<C::ToweringCurve as Ciphersuite>::G],
encryption_key_commitments: &[<C::ToweringCurve as Ciphersuite>::G],
encrypted_secret_shares: &HashMap<Participant, <C::ToweringCurve as Ciphersuite>::F>,
coefficients: &[<C::ToweringCurve as WrappedGroup>::G],
encryption_key_commitments: &[<C::ToweringCurve as WrappedGroup>::G],
encrypted_secret_shares: &HashMap<Participant, <C::ToweringCurve as WrappedGroup>::F>,
) -> (
<C::ToweringCurve as Ciphersuite>::F,
Vec<(<C::ToweringCurve as Ciphersuite>::F, <C::ToweringCurve as Ciphersuite>::G)>,
<C::ToweringCurve as WrappedGroup>::F,
Vec<(<C::ToweringCurve as WrappedGroup>::F, <C::ToweringCurve as WrappedGroup>::G)>,
) {
let mut g_scalar = <C::ToweringCurve as Ciphersuite>::F::ZERO;
let mut g_scalar = <C::ToweringCurve as WrappedGroup>::F::ZERO;
let mut pairs = Vec::with_capacity(coefficients.len() + encryption_key_commitments.len());
// Push on the commitments to the polynomial being secret-shared
for coefficient in coefficients {
// This uses `0` as we'll add to it later, given its fixed position
pairs.push((<C::ToweringCurve as Ciphersuite>::F::ZERO, *coefficient));
pairs.push((<C::ToweringCurve as WrappedGroup>::F::ZERO, *coefficient));
}
for (i, encrypted_secret_share) in encrypted_secret_shares {
let encryption_key_commitment = encryption_key_commitments[usize::from(u16::from(*i)) - 1];
let weight = <C::ToweringCurve as Ciphersuite>::F::random(&mut *rng);
let weight = <C::ToweringCurve as WrappedGroup>::F::random(&mut *rng);
/*
The encrypted secret share scaling `G`, minus the encryption key commitment, minus the
@@ -274,7 +275,7 @@ fn verifiable_encryption_statements<C: Curves>(
pairs.push((weight, encryption_key_commitment));
// Calculate the commitment to the secret share via the commitments to the polynomial
{
let i = <C::ToweringCurve as Ciphersuite>::F::from(u64::from(u16::from(*i)));
let i = <C::ToweringCurve as WrappedGroup>::F::from(u64::from(u16::from(*i)));
(0 .. coefficients.len()).fold(weight, |exp, j| {
pairs[j].0 += exp;
exp * i
@@ -300,7 +301,7 @@ impl<C: Curves> Dkg<C> {
generators: &Generators<C>,
context: [u8; 32],
t: u16,
evrf_public_keys: &[<C::EmbeddedCurve as Ciphersuite>::G],
evrf_public_keys: &[<C::EmbeddedCurve as WrappedGroup>::G],
participations: &HashMap<Participant, Participation<C>>,
) -> Result<VerifyResult<C>, Error> {
let Ok(n) = u16::try_from(evrf_public_keys.len()) else {
@@ -386,7 +387,7 @@ impl<C: Curves> Dkg<C> {
{
let mut share_verification_statements_actual = HashMap::with_capacity(valid.len());
if !{
let mut g_scalar = <C::ToweringCurve as Ciphersuite>::F::ZERO;
let mut g_scalar = <C::ToweringCurve as WrappedGroup>::F::ZERO;
let mut pairs = Vec::with_capacity(valid.len() * (usize::from(t) + evrf_public_keys.len()));
for (i, (encrypted_secret_shares, data)) in &valid {
let (this_g_scalar, mut these_pairs) = verifiable_encryption_statements::<C>(
@@ -417,9 +418,11 @@ impl<C: Curves> Dkg<C> {
let sum_encrypted_secret_share = sum_encrypted_secret_shares
.get(j)
.copied()
.unwrap_or(<C::ToweringCurve as Ciphersuite>::F::ZERO);
let sum_mask =
sum_masks.get(j).copied().unwrap_or(<C::ToweringCurve as Ciphersuite>::G::identity());
.unwrap_or(<C::ToweringCurve as WrappedGroup>::F::ZERO);
let sum_mask = sum_masks
.get(j)
.copied()
.unwrap_or(<C::ToweringCurve as WrappedGroup>::G::identity());
sum_encrypted_secret_shares.insert(*j, sum_encrypted_secret_share + enc_share);
let j_index = usize::from(u16::from(*j)) - 1;
@@ -487,7 +490,7 @@ impl<C: Curves> Dkg<C> {
for i in Participant::iter().take(usize::from(n)) {
verification_shares.insert(
i,
(<C::ToweringCurve as Ciphersuite>::generator() * sum_encrypted_secret_shares[&i]) -
(<C::ToweringCurve as WrappedGroup>::generator() * sum_encrypted_secret_shares[&i]) -
sum_masks[&i],
);
}
@@ -506,9 +509,10 @@ impl<C: Curves> Dkg<C> {
/// This will return _all_ keys belong to the participant.
pub fn keys(
&self,
evrf_private_key: &Zeroizing<<C::EmbeddedCurve as Ciphersuite>::F>,
evrf_private_key: &Zeroizing<<C::EmbeddedCurve as WrappedGroup>::F>,
) -> Vec<ThresholdKeys<C::ToweringCurve>> {
let evrf_public_key = <C::EmbeddedCurve as Ciphersuite>::generator() * evrf_private_key.deref();
let evrf_public_key =
<C::EmbeddedCurve as WrappedGroup>::generator() * evrf_private_key.deref();
let mut is = Vec::with_capacity(1);
for (i, evrf_key) in Participant::iter().zip(self.evrf_public_keys.iter()) {
if *evrf_key == evrf_public_key {
@@ -518,14 +522,14 @@ impl<C: Curves> Dkg<C> {
let mut res = Vec::with_capacity(is.len());
for i in is {
let mut secret_share = Zeroizing::new(<C::ToweringCurve as Ciphersuite>::F::ZERO);
let mut secret_share = Zeroizing::new(<C::ToweringCurve as WrappedGroup>::F::ZERO);
for shares in self.encrypted_secret_shares.values() {
let (ecdh_commitments, encrypted_secret_share) = shares[&i];
let mut ecdh = Zeroizing::new(<C::ToweringCurve as Ciphersuite>::F::ZERO);
let mut ecdh = Zeroizing::new(<C::ToweringCurve as WrappedGroup>::F::ZERO);
for point in ecdh_commitments {
let (mut x, mut y) =
<C::EmbeddedCurve as Ciphersuite>::G::to_xy(point * evrf_private_key.deref()).unwrap();
<C::EmbeddedCurve as WrappedGroup>::G::to_xy(point * evrf_private_key.deref()).unwrap();
*ecdh += x;
x.zeroize();
y.zeroize();
@@ -534,7 +538,7 @@ impl<C: Curves> Dkg<C> {
}
debug_assert_eq!(
self.verification_shares[&i],
<C::ToweringCurve as Ciphersuite>::G::generator() * secret_share.deref()
<C::ToweringCurve as WrappedGroup>::generator() * secret_share.deref()
);
res.push(

View File

@@ -8,7 +8,7 @@ use zeroize::Zeroizing;
use rand_core::{RngCore, CryptoRng, SeedableRng};
use rand_chacha::ChaCha20Rng;
use ciphersuite::{group::ff::Field, Ciphersuite};
use ciphersuite::{group::ff::Field, WrappedGroup};
use generalized_bulletproofs::{
Generators, BatchVerifier, PedersenCommitment, PedersenVectorCommitment,
@@ -28,8 +28,8 @@ mod tape;
use tape::*;
type EmbeddedPoint<C> = (
<<<C as Curves>::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::FieldElement,
<<<C as Curves>::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::FieldElement,
<<<C as Curves>::EmbeddedCurve as WrappedGroup>::G as DivisorCurve>::FieldElement,
<<<C as Curves>::EmbeddedCurve as WrappedGroup>::G as DivisorCurve>::FieldElement,
);
#[allow(non_snake_case)]
@@ -37,14 +37,15 @@ struct Circuit<
'a,
C: Curves,
CG: Iterator<
Item = ChallengedGenerator<<C::ToweringCurve as Ciphersuite>::F, C::EmbeddedCurveParameters>,
Item = ChallengedGenerator<<C::ToweringCurve as WrappedGroup>::F, C::EmbeddedCurveParameters>,
>,
> {
curve_spec: &'a CurveSpec<<<C::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::FieldElement>,
curve_spec: &'a CurveSpec<<<C::EmbeddedCurve as WrappedGroup>::G as DivisorCurve>::FieldElement>,
circuit: &'a mut BpCircuit<C::ToweringCurve>,
challenge: DiscreteLogChallenge<<C::ToweringCurve as Ciphersuite>::F, C::EmbeddedCurveParameters>,
challenge:
DiscreteLogChallenge<<C::ToweringCurve as WrappedGroup>::F, C::EmbeddedCurveParameters>,
challenged_G:
ChallengedGenerator<<C::ToweringCurve as Ciphersuite>::F, C::EmbeddedCurveParameters>,
ChallengedGenerator<<C::ToweringCurve as WrappedGroup>::F, C::EmbeddedCurveParameters>,
challenged_generators: &'a mut CG,
tape: Tape,
pedersen_commitment_tape: PedersenCommitmentTape,
@@ -54,7 +55,7 @@ impl<
'a,
C: Curves,
CG: Iterator<
Item = ChallengedGenerator<<C::ToweringCurve as Ciphersuite>::F, C::EmbeddedCurveParameters>,
Item = ChallengedGenerator<<C::ToweringCurve as WrappedGroup>::F, C::EmbeddedCurveParameters>,
>,
> Circuit<'a, C, CG>
{
@@ -92,7 +93,7 @@ impl<
&self.challenge,
&challenged_generator,
);
lincomb = lincomb.term(<C::ToweringCurve as Ciphersuite>::F::ONE, point.x());
lincomb = lincomb.term(<C::ToweringCurve as WrappedGroup>::F::ONE, point.x());
}
/*
Constrain the sum of the two `x` coordinates to be equal to the value committed to in a
@@ -137,7 +138,7 @@ impl<
&self.challenge,
&challenged_public_key,
);
lincomb = lincomb.term(<C::ToweringCurve as Ciphersuite>::F::ONE, point.x());
lincomb = lincomb.term(<C::ToweringCurve as WrappedGroup>::F::ONE, point.x());
debug_assert!(point_with_dlogs.next().is_none());
}
@@ -152,20 +153,20 @@ impl<
/// The result of proving.
pub(super) struct ProveResult<C: Curves> {
/// The coefficients for use in the DKG.
pub(super) coefficients: Vec<Zeroizing<<C::ToweringCurve as Ciphersuite>::F>>,
pub(super) coefficients: Vec<Zeroizing<<C::ToweringCurve as WrappedGroup>::F>>,
/// The masks to encrypt secret shares with.
pub(super) encryption_keys: Vec<Zeroizing<<C::ToweringCurve as Ciphersuite>::F>>,
pub(super) encryption_keys: Vec<Zeroizing<<C::ToweringCurve as WrappedGroup>::F>>,
/// The proof itself.
pub(super) proof: Vec<u8>,
}
pub(super) struct Verified<C: Curves> {
/// The commitments to the coefficients used within the DKG.
pub(super) coefficients: Vec<<C::ToweringCurve as Ciphersuite>::G>,
pub(super) coefficients: Vec<<C::ToweringCurve as WrappedGroup>::G>,
/// The ephemeral public keys to perform ECDHs with
pub(super) ecdh_commitments: Vec<[<C::EmbeddedCurve as Ciphersuite>::G; 2]>,
pub(super) ecdh_commitments: Vec<[<C::EmbeddedCurve as WrappedGroup>::G; 2]>,
/// The commitments to the masks used to encrypt secret shares with.
pub(super) encryption_key_commitments: Vec<<C::ToweringCurve as Ciphersuite>::G>,
pub(super) encryption_key_commitments: Vec<<C::ToweringCurve as WrappedGroup>::G>,
}
impl<C: Curves> fmt::Debug for Verified<C> {
@@ -175,7 +176,7 @@ impl<C: Curves> fmt::Debug for Verified<C> {
}
type GeneratorTable<C> = generalized_bulletproofs_ec_gadgets::GeneratorTable<
<<<C as Curves>::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::FieldElement,
<<<C as Curves>::EmbeddedCurve as WrappedGroup>::G as DivisorCurve>::FieldElement,
<C as Curves>::EmbeddedCurveParameters,
>;
@@ -219,7 +220,7 @@ impl<C: Curves> Proof<C> {
}
fn circuit(
curve_spec: &CurveSpec<<<C::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::FieldElement>,
curve_spec: &CurveSpec<<<C::EmbeddedCurve as WrappedGroup>::G as DivisorCurve>::FieldElement>,
evrf_public_key: EmbeddedPoint<C>,
coefficients: usize,
ecdh_commitments: &[[EmbeddedPoint<C>; 2]],
@@ -281,7 +282,7 @@ impl<C: Curves> Proof<C> {
fn sample_coefficients_evrf_points(
seed: [u8; 32],
coefficients: usize,
) -> Vec<<C::EmbeddedCurve as Ciphersuite>::G> {
) -> Vec<<C::EmbeddedCurve as WrappedGroup>::G> {
let mut rng = ChaCha20Rng::from_seed(seed);
let quantity = 2 * coefficients;
let mut res = Vec::with_capacity(quantity);
@@ -293,28 +294,29 @@ impl<C: Curves> Proof<C> {
/// Create the required tables for the generators.
fn generator_tables(
coefficients_evrf_points: &[<C::EmbeddedCurve as Ciphersuite>::G],
participants: &[<<C as Curves>::EmbeddedCurve as Ciphersuite>::G],
coefficients_evrf_points: &[<C::EmbeddedCurve as WrappedGroup>::G],
participants: &[<<C as Curves>::EmbeddedCurve as WrappedGroup>::G],
) -> Vec<GeneratorTable<C>> {
let curve_spec = CurveSpec {
a: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G::a(),
b: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G::b(),
a: <<C as Curves>::EmbeddedCurve as WrappedGroup>::G::a(),
b: <<C as Curves>::EmbeddedCurve as WrappedGroup>::G::b(),
};
let mut generator_tables =
Vec::with_capacity(1 + coefficients_evrf_points.len() + participants.len());
{
let (x, y) =
<C::EmbeddedCurve as Ciphersuite>::G::to_xy(<C::EmbeddedCurve as Ciphersuite>::generator())
.unwrap();
let (x, y) = <C::EmbeddedCurve as WrappedGroup>::G::to_xy(
<C::EmbeddedCurve as WrappedGroup>::generator(),
)
.unwrap();
generator_tables.push(GeneratorTable::<C>::new(&curve_spec, x, y));
}
for generator in coefficients_evrf_points {
let (x, y) = <C::EmbeddedCurve as Ciphersuite>::G::to_xy(*generator).unwrap();
let (x, y) = <C::EmbeddedCurve as WrappedGroup>::G::to_xy(*generator).unwrap();
generator_tables.push(GeneratorTable::<C>::new(&curve_spec, x, y));
}
for generator in participants {
let (x, y) = <C::EmbeddedCurve as Ciphersuite>::G::to_xy(*generator).unwrap();
let (x, y) = <C::EmbeddedCurve as WrappedGroup>::G::to_xy(*generator).unwrap();
generator_tables.push(GeneratorTable::<C>::new(&curve_spec, x, y));
}
generator_tables
@@ -325,12 +327,12 @@ impl<C: Curves> Proof<C> {
generators: &Generators<C::ToweringCurve>,
transcript: [u8; 32],
coefficients: usize,
participant_public_keys: &[<<C as Curves>::EmbeddedCurve as Ciphersuite>::G],
evrf_private_key: &Zeroizing<<<C as Curves>::EmbeddedCurve as Ciphersuite>::F>,
participant_public_keys: &[<<C as Curves>::EmbeddedCurve as WrappedGroup>::G],
evrf_private_key: &Zeroizing<<<C as Curves>::EmbeddedCurve as WrappedGroup>::F>,
) -> Result<ProveResult<C>, AcProveError> {
let curve_spec = CurveSpec {
a: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G::a(),
b: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G::b(),
a: <<C as Curves>::EmbeddedCurve as WrappedGroup>::G::a(),
b: <<C as Curves>::EmbeddedCurve as WrappedGroup>::G::b(),
};
let coefficients_evrf_points = Self::sample_coefficients_evrf_points(transcript, coefficients);
@@ -340,7 +342,7 @@ impl<C: Curves> Proof<C> {
// Push a discrete logarithm onto the tape
let discrete_log =
|vector_commitment_tape: &mut Vec<_>,
dlog: &ScalarDecomposition<<<C as Curves>::EmbeddedCurve as Ciphersuite>::F>| {
dlog: &ScalarDecomposition<<<C as Curves>::EmbeddedCurve as WrappedGroup>::F>| {
for coefficient in dlog.decomposition() {
vector_commitment_tape.push(<_>::from(*coefficient));
}
@@ -351,8 +353,8 @@ impl<C: Curves> Proof<C> {
// Returns the point for which the claim was made.
let discrete_log_claim =
|vector_commitment_tape: &mut Vec<_>,
dlog: &ScalarDecomposition<<<C as Curves>::EmbeddedCurve as Ciphersuite>::F>,
generator: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G| {
dlog: &ScalarDecomposition<<<C as Curves>::EmbeddedCurve as WrappedGroup>::F>,
generator: <<C as Curves>::EmbeddedCurve as WrappedGroup>::G| {
{
let divisor =
Zeroizing::new(dlog.scalar_mul_divisor(generator).normalize_x_coefficient());
@@ -368,12 +370,12 @@ impl<C: Curves> Proof<C> {
.y_coefficients
.first()
.copied()
.unwrap_or(<C::ToweringCurve as Ciphersuite>::F::ZERO),
.unwrap_or(<C::ToweringCurve as WrappedGroup>::F::ZERO),
);
}
let dh = generator * dlog.scalar();
let (x, y) = <C::EmbeddedCurve as Ciphersuite>::G::to_xy(dh).unwrap();
let (x, y) = <C::EmbeddedCurve as WrappedGroup>::G::to_xy(dh).unwrap();
vector_commitment_tape.push(x);
vector_commitment_tape.push(y);
(dh, (x, y))
@@ -387,7 +389,7 @@ impl<C: Curves> Proof<C> {
let mut coefficients = Vec::with_capacity(coefficients);
let evrf_public_key = {
let evrf_private_key =
ScalarDecomposition::<<C::EmbeddedCurve as Ciphersuite>::F>::new(**evrf_private_key)
ScalarDecomposition::<<C::EmbeddedCurve as WrappedGroup>::F>::new(**evrf_private_key)
.expect("eVRF private key was zero");
discrete_log(&mut vector_commitment_tape, &evrf_private_key);
@@ -396,12 +398,12 @@ impl<C: Curves> Proof<C> {
let (_, evrf_public_key) = discrete_log_claim(
&mut vector_commitment_tape,
&evrf_private_key,
<<C as Curves>::EmbeddedCurve as Ciphersuite>::generator(),
<<C as Curves>::EmbeddedCurve as WrappedGroup>::generator(),
);
// Push the divisor for each point we use in the eVRF
for pair in coefficients_evrf_points.chunks(2) {
let mut coefficient = Zeroizing::new(<C::ToweringCurve as Ciphersuite>::F::ZERO);
let mut coefficient = Zeroizing::new(<C::ToweringCurve as WrappedGroup>::F::ZERO);
for point in pair {
let (_, (dh_x, _)) =
discrete_log_claim(&mut vector_commitment_tape, &evrf_private_key, *point);
@@ -418,15 +420,16 @@ impl<C: Curves> Proof<C> {
let mut ecdh_commitments = Vec::with_capacity(2 * participant_public_keys.len());
let mut ecdh_commitments_xy = Vec::with_capacity(participant_public_keys.len());
for participant_public_key in participant_public_keys {
let mut ecdh_commitments_xy_i =
[(<C::ToweringCurve as Ciphersuite>::F::ZERO, <C::ToweringCurve as Ciphersuite>::F::ZERO);
2];
let mut encryption_key = Zeroizing::new(<C::ToweringCurve as Ciphersuite>::F::ZERO);
let mut ecdh_commitments_xy_i = [(
<C::ToweringCurve as WrappedGroup>::F::ZERO,
<C::ToweringCurve as WrappedGroup>::F::ZERO,
); 2];
let mut encryption_key = Zeroizing::new(<C::ToweringCurve as WrappedGroup>::F::ZERO);
for ecdh_commitments_xy_i_j_dest in &mut ecdh_commitments_xy_i {
let mut ecdh_ephemeral_secret;
loop {
ecdh_ephemeral_secret =
Zeroizing::new(<C::EmbeddedCurve as Ciphersuite>::F::random(&mut *rng));
Zeroizing::new(<C::EmbeddedCurve as WrappedGroup>::F::random(&mut *rng));
// 0 would produce the identity, which isn't representable within the discrete-log proof.
if bool::from(!ecdh_ephemeral_secret.is_zero()) {
break;
@@ -434,7 +437,7 @@ impl<C: Curves> Proof<C> {
}
let ecdh_ephemeral_secret =
ScalarDecomposition::<<C::EmbeddedCurve as Ciphersuite>::F>::new(*ecdh_ephemeral_secret)
ScalarDecomposition::<<C::EmbeddedCurve as WrappedGroup>::F>::new(*ecdh_ephemeral_secret)
.expect("ECDH ephemeral secret zero");
discrete_log(&mut vector_commitment_tape, &ecdh_ephemeral_secret);
@@ -442,7 +445,7 @@ impl<C: Curves> Proof<C> {
let (ecdh_commitment, ecdh_commitment_xy_i_j) = discrete_log_claim(
&mut vector_commitment_tape,
&ecdh_ephemeral_secret,
<<C as Curves>::EmbeddedCurve as Ciphersuite>::generator(),
<<C as Curves>::EmbeddedCurve as WrappedGroup>::generator(),
);
ecdh_commitments.push(ecdh_commitment);
*ecdh_commitments_xy_i_j_dest = ecdh_commitment_xy_i_j;
@@ -470,7 +473,7 @@ impl<C: Curves> Proof<C> {
for chunk in vector_commitment_tape.chunks(generators_to_use) {
vector_commitments.push(PedersenVectorCommitment {
g_values: chunk.into(),
mask: <C::ToweringCurve as Ciphersuite>::F::random(&mut *rng),
mask: <C::ToweringCurve as WrappedGroup>::F::random(&mut *rng),
});
}
@@ -479,13 +482,13 @@ impl<C: Curves> Proof<C> {
for coefficient in &coefficients {
commitments.push(PedersenCommitment {
value: **coefficient,
mask: <C::ToweringCurve as Ciphersuite>::F::random(&mut *rng),
mask: <C::ToweringCurve as WrappedGroup>::F::random(&mut *rng),
});
}
for enc_mask in &encryption_keys {
commitments.push(PedersenCommitment {
value: **enc_mask,
mask: <C::ToweringCurve as Ciphersuite>::F::random(&mut *rng),
mask: <C::ToweringCurve as WrappedGroup>::F::random(&mut *rng),
});
}
@@ -536,13 +539,13 @@ impl<C: Curves> Proof<C> {
}
// Prove the openings of the commitments were correct
let mut x = Zeroizing::new(<C::ToweringCurve as Ciphersuite>::F::ZERO);
let mut x = Zeroizing::new(<C::ToweringCurve as WrappedGroup>::F::ZERO);
for commitment in commitments {
*x += commitment.mask * transcript.challenge::<C::ToweringCurve>();
}
// Produce a Schnorr PoK for the weighted-sum of the Pedersen commitments' blinding factors
let r = Zeroizing::new(<C::ToweringCurve as Ciphersuite>::F::random(&mut *rng));
let r = Zeroizing::new(<C::ToweringCurve as WrappedGroup>::F::random(&mut *rng));
transcript.push_point(&(generators.h() * r.deref()));
let c = transcript.challenge::<C::ToweringCurve>();
transcript.push_scalar((c * x.deref()) + r.deref());
@@ -557,14 +560,14 @@ impl<C: Curves> Proof<C> {
verifier: &mut BatchVerifier<C::ToweringCurve>,
transcript: [u8; 32],
coefficients: usize,
participant_public_keys: &[<<C as Curves>::EmbeddedCurve as Ciphersuite>::G],
evrf_public_key: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G,
participant_public_keys: &[<<C as Curves>::EmbeddedCurve as WrappedGroup>::G],
evrf_public_key: <<C as Curves>::EmbeddedCurve as WrappedGroup>::G,
proof: &[u8],
) -> Result<Verified<C>, ()> {
let (mut transcript, ecdh_commitments, pedersen_commitments) = {
let curve_spec = CurveSpec {
a: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G::a(),
b: <<C as Curves>::EmbeddedCurve as Ciphersuite>::G::b(),
a: <<C as Curves>::EmbeddedCurve as WrappedGroup>::G::a(),
b: <<C as Curves>::EmbeddedCurve as WrappedGroup>::G::b(),
};
let coefficients_evrf_points =
@@ -600,9 +603,9 @@ impl<C: Curves> Proof<C> {
ecdh_commitments.push(ecdh_commitments_i);
// This inherently bans using the identity point, as it won't have an affine representation
ecdh_commitments_xy.push([
<<C::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::to_xy(ecdh_commitments_i[0])
<<C::EmbeddedCurve as WrappedGroup>::G as DivisorCurve>::to_xy(ecdh_commitments_i[0])
.ok_or(())?,
<<C::EmbeddedCurve as Ciphersuite>::G as DivisorCurve>::to_xy(ecdh_commitments_i[1])
<<C::EmbeddedCurve as WrappedGroup>::G as DivisorCurve>::to_xy(ecdh_commitments_i[1])
.ok_or(())?,
]);
}
@@ -610,7 +613,7 @@ impl<C: Curves> Proof<C> {
let mut circuit = BpCircuit::verify();
Self::circuit(
&curve_spec,
<C::EmbeddedCurve as Ciphersuite>::G::to_xy(evrf_public_key).ok_or(())?,
<C::EmbeddedCurve as WrappedGroup>::G::to_xy(evrf_public_key).ok_or(())?,
coefficients,
&ecdh_commitments_xy,
&generator_tables.iter().collect::<Vec<_>>(),

View File

@@ -4,11 +4,11 @@ use zeroize::Zeroizing;
use rand_core::OsRng;
use rand::seq::SliceRandom;
use ciphersuite::{group::ff::Field, Ciphersuite};
use ciphersuite::{group::ff::Field, WrappedGroup};
use embedwards25519::Embedwards25519;
use dkg_recovery::recover_key;
use crate::{Participant, Curves, Generators, VerifyResult, Dkg, Ristretto};
use crate::{Participant, Curves, Generators, VerifyResult, Dkg, Ed25519};
mod proof;
@@ -17,14 +17,14 @@ const PARTICIPANTS: u16 = 5;
#[test]
fn dkg() {
let generators = Generators::<Ristretto>::new(THRESHOLD, PARTICIPANTS);
let generators = Generators::<Ed25519>::new(THRESHOLD, PARTICIPANTS);
let context = [0; 32];
let mut priv_keys = vec![];
let mut pub_keys = vec![];
for i in 0 .. PARTICIPANTS {
let priv_key = <Embedwards25519 as Ciphersuite>::F::random(&mut OsRng);
pub_keys.push(<Embedwards25519 as Ciphersuite>::generator() * priv_key);
let priv_key = <Embedwards25519 as WrappedGroup>::F::random(&mut OsRng);
pub_keys.push(<Embedwards25519 as WrappedGroup>::generator() * priv_key);
priv_keys.push((Participant::new(1 + i).unwrap(), Zeroizing::new(priv_key)));
}
@@ -34,27 +34,15 @@ fn dkg() {
for (i, priv_key) in priv_keys.iter().take(usize::from(THRESHOLD)) {
participations.insert(
*i,
Dkg::<Ristretto>::participate(
&mut OsRng,
&generators,
context,
THRESHOLD,
&pub_keys,
priv_key,
)
.unwrap(),
Dkg::<Ed25519>::participate(&mut OsRng, &generators, context, THRESHOLD, &pub_keys, priv_key)
.unwrap(),
);
}
let VerifyResult::Valid(dkg) = Dkg::<Ristretto>::verify(
&mut OsRng,
&generators,
context,
THRESHOLD,
&pub_keys,
&participations,
)
.unwrap() else {
let VerifyResult::Valid(dkg) =
Dkg::<Ed25519>::verify(&mut OsRng, &generators, context, THRESHOLD, &pub_keys, &participations)
.unwrap()
else {
panic!("verify didn't return VerifyResult::Valid")
};
@@ -80,7 +68,7 @@ fn dkg() {
// TODO: Test for all possible combinations of keys
assert_eq!(
<<Ristretto as Curves>::ToweringCurve as Ciphersuite>::generator() *
<<Ed25519 as Curves>::ToweringCurve as WrappedGroup>::generator() *
*recover_key(&all_keys.values().cloned().collect::<Vec<_>>()).unwrap(),
group_key.unwrap()
);

View File

@@ -6,13 +6,13 @@ use zeroize::Zeroizing;
use ciphersuite::{
group::{ff::Field, Group},
Ciphersuite,
WrappedGroup,
};
use generalized_bulletproofs::{Generators, tests::insecure_test_generators};
use crate::{
Curves, Ristretto,
Curves, Ed25519,
proof::*,
tests::{THRESHOLD, PARTICIPANTS},
};
@@ -20,9 +20,9 @@ use crate::{
fn proof<C: Curves>() {
let generators = insecure_test_generators(&mut OsRng, 2048).unwrap();
let embedded_private_key =
Zeroizing::new(<C::EmbeddedCurve as Ciphersuite>::F::random(&mut OsRng));
Zeroizing::new(<C::EmbeddedCurve as WrappedGroup>::F::random(&mut OsRng));
let ecdh_public_keys: [_; PARTICIPANTS as usize] =
core::array::from_fn(|_| <C::EmbeddedCurve as Ciphersuite>::G::random(&mut OsRng));
core::array::from_fn(|_| <C::EmbeddedCurve as WrappedGroup>::G::random(&mut OsRng));
let time = Instant::now();
let res = Proof::<C>::prove(
&mut OsRng,
@@ -54,5 +54,5 @@ fn proof<C: Curves>() {
#[test]
fn ristretto_proof() {
proof::<Ristretto>();
proof::<Ed25519>();
}

View File

@@ -5,7 +5,7 @@ use rand_core::{RngCore, CryptoRng};
use ciphersuite::{
group::{ff::PrimeField, Group, GroupEncoding},
Ciphersuite,
GroupIo,
};
use dkg::Participant;
@@ -13,7 +13,7 @@ use dkg::Participant;
/// Sample a random, unbiased point on the elliptic curve with an unknown discrete logarithm.
///
/// This keeps it simple by using rejection sampling.
pub(crate) fn sample_point<C: Ciphersuite>(rng: &mut (impl RngCore + CryptoRng)) -> C::G {
pub(crate) fn sample_point<C: GroupIo>(rng: &mut (impl RngCore + CryptoRng)) -> C::G {
let mut repr = <C::G as GroupEncoding>::Repr::default();
loop {
rng.fill_bytes(repr.as_mut());

View File

@@ -150,7 +150,7 @@ pub fn musig<C: Ciphersuite>(
}
let group_key = multiexp::multiexp(&multiexp);
debug_assert_eq!(our_pub_key, verification_shares[&params.i()]);
debug_assert_eq!(musig_key_vartime::<C>(context, keys).unwrap(), group_key);
debug_assert_eq!(musig_key_vartime::<C>(context, keys), Ok(group_key));
ThresholdKeys::new(
params,

View File

@@ -4,7 +4,7 @@ use zeroize::Zeroizing;
use rand_core::OsRng;
use dalek_ff_group::Ristretto;
use ciphersuite::{group::ff::Field, Ciphersuite};
use ciphersuite::WrappedGroup;
use dkg_recovery::recover_key;
use crate::*;
@@ -17,21 +17,21 @@ pub fn test_musig() {
let mut keys = vec![];
let mut pub_keys = vec![];
for _ in 0 .. PARTICIPANTS {
let key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
pub_keys.push(<Ristretto as Ciphersuite>::generator() * *key);
let key = Zeroizing::new(<Ristretto as WrappedGroup>::F::random(&mut OsRng));
pub_keys.push(<Ristretto as WrappedGroup>::generator() * *key);
keys.push(key);
}
const CONTEXT: [u8; 32] = *b"MuSig Test ";
// Empty signing set
musig::<Ristretto>(CONTEXT, Zeroizing::new(<Ristretto as Ciphersuite>::F::ZERO), &[])
musig::<Ristretto>(CONTEXT, Zeroizing::new(<Ristretto as WrappedGroup>::F::ZERO), &[])
.unwrap_err();
// Signing set we're not part of
musig::<Ristretto>(
CONTEXT,
Zeroizing::new(<Ristretto as Ciphersuite>::F::ZERO),
&[<Ristretto as Ciphersuite>::generator()],
Zeroizing::new(<Ristretto as WrappedGroup>::F::ZERO),
&[<Ristretto as WrappedGroup>::generator()],
)
.unwrap_err();
@@ -48,7 +48,7 @@ pub fn test_musig() {
verification_shares.insert(
these_keys.params().i(),
<Ristretto as Ciphersuite>::generator() * **these_keys.original_secret_share(),
<Ristretto as WrappedGroup>::generator() * **these_keys.original_secret_share(),
);
assert_eq!(these_keys.group_key(), group_key);
@@ -63,7 +63,7 @@ pub fn test_musig() {
}
assert_eq!(
<Ristretto as Ciphersuite>::generator() *
<Ristretto as WrappedGroup>::generator() *
*recover_key(&created_keys.values().cloned().collect::<Vec<_>>()).unwrap(),
group_key
);

View File

@@ -8,7 +8,7 @@ use alloc::vec::Vec;
use zeroize::Zeroizing;
use ciphersuite::Ciphersuite;
use ciphersuite::{GroupIo, Id};
pub use dkg::*;
@@ -34,7 +34,7 @@ pub enum RecoveryError {
}
/// Recover a shared secret from a collection of `dkg::ThresholdKeys`.
pub fn recover_key<C: Ciphersuite>(
pub fn recover_key<C: GroupIo + Id>(
keys: &[ThresholdKeys<C>],
) -> Result<Zeroizing<C::F>, RecoveryError> {
let included = keys.iter().map(|keys| keys.params().i()).collect::<Vec<_>>();

View File

@@ -17,7 +17,7 @@ use ciphersuite::{
ff::{Field, PrimeField},
GroupEncoding,
},
Ciphersuite,
GroupIo, Id,
};
/// The ID of a participant, defined as a non-zero u16.
@@ -268,7 +268,7 @@ impl<F: Zeroize + PrimeField> Interpolation<F> {
/// heap-allocated pointer to minimize copies on the stack (`ThresholdKeys`, the publicly exposed
/// type).
#[derive(Clone, PartialEq, Eq)]
struct ThresholdCore<C: Ciphersuite> {
struct ThresholdCore<C: GroupIo + Id> {
params: ThresholdParams,
group_key: C::G,
verification_shares: HashMap<Participant, C::G>,
@@ -276,7 +276,7 @@ struct ThresholdCore<C: Ciphersuite> {
secret_share: Zeroizing<C::F>,
}
impl<C: Ciphersuite> fmt::Debug for ThresholdCore<C> {
impl<C: GroupIo + Id> fmt::Debug for ThresholdCore<C> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt
.debug_struct("ThresholdCore")
@@ -288,7 +288,7 @@ impl<C: Ciphersuite> fmt::Debug for ThresholdCore<C> {
}
}
impl<C: Ciphersuite> Zeroize for ThresholdCore<C> {
impl<C: GroupIo + Id> Zeroize for ThresholdCore<C> {
fn zeroize(&mut self) {
self.params.zeroize();
self.group_key.zeroize();
@@ -302,7 +302,7 @@ impl<C: Ciphersuite> Zeroize for ThresholdCore<C> {
/// Threshold keys usable for signing.
#[derive(Clone, Debug, Zeroize)]
pub struct ThresholdKeys<C: Ciphersuite> {
pub struct ThresholdKeys<C: GroupIo + Id> {
// Core keys.
#[zeroize(skip)]
core: Arc<Zeroizing<ThresholdCore<C>>>,
@@ -315,7 +315,7 @@ pub struct ThresholdKeys<C: Ciphersuite> {
/// View of keys, interpolated and with the expected linear combination taken for usage.
#[derive(Clone)]
pub struct ThresholdView<C: Ciphersuite> {
pub struct ThresholdView<C: GroupIo + Id> {
interpolation: Interpolation<C::F>,
scalar: C::F,
offset: C::F,
@@ -326,7 +326,7 @@ pub struct ThresholdView<C: Ciphersuite> {
verification_shares: HashMap<Participant, C::G>,
}
impl<C: Ciphersuite> fmt::Debug for ThresholdView<C> {
impl<C: GroupIo + Id> fmt::Debug for ThresholdView<C> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt
.debug_struct("ThresholdView")
@@ -341,7 +341,7 @@ impl<C: Ciphersuite> fmt::Debug for ThresholdView<C> {
}
}
impl<C: Ciphersuite> Zeroize for ThresholdView<C> {
impl<C: GroupIo + Id> Zeroize for ThresholdView<C> {
fn zeroize(&mut self) {
self.scalar.zeroize();
self.offset.zeroize();
@@ -357,7 +357,7 @@ impl<C: Ciphersuite> Zeroize for ThresholdView<C> {
}
}
impl<C: Ciphersuite> ThresholdKeys<C> {
impl<C: GroupIo + Id> ThresholdKeys<C> {
/// Create a new set of ThresholdKeys.
pub fn new(
params: ThresholdParams,
@@ -632,7 +632,7 @@ impl<C: Ciphersuite> ThresholdKeys<C> {
let mut verification_shares = HashMap::new();
for l in (1 ..= n).map(Participant) {
verification_shares.insert(l, <C as Ciphersuite>::read_G(reader)?);
verification_shares.insert(l, C::read_G(reader)?);
}
ThresholdKeys::new(
@@ -645,7 +645,7 @@ impl<C: Ciphersuite> ThresholdKeys<C> {
}
}
impl<C: Ciphersuite> ThresholdView<C> {
impl<C: GroupIo + Id> ThresholdView<C> {
/// Return the scalar applied to this view.
pub fn scalar(&self) -> C::F {
self.scalar

View File

@@ -19,8 +19,9 @@ workspace = true
[dependencies]
zeroize = { version = "1", default-features = false, features = ["zeroize_derive"] }
sha3 = { version = "0.11.0-rc.0", default-features = false }
sha3 = { version = "0.11.0-rc.2", default-features = false }
crypto-bigint = { version = "0.6", default-features = false, features = ["zeroize"] }
prime-field = { path = "../prime-field", default-features = false }
ciphersuite = { path = "../ciphersuite", default-features = false }
@@ -32,6 +33,6 @@ rand_core = { version = "0.6", default-features = false, features = ["std"] }
ff-group-tests = { path = "../ff-group-tests" }
[features]
alloc = ["zeroize/alloc", "sha3/alloc", "prime-field/alloc", "ciphersuite/alloc"]
alloc = ["zeroize/alloc", "sha3/alloc", "crypto-bigint/alloc", "prime-field/alloc", "ciphersuite/alloc"]
std = ["alloc", "zeroize/std", "prime-field/std", "ciphersuite/std"]
default = ["std"]

View File

@@ -1,4 +1,4 @@
use zeroize::Zeroize;
use prime_field::subtle::CtOption;
use sha3::{
digest::{
@@ -8,9 +8,9 @@ use sha3::{
Shake256,
};
use ciphersuite::{group::Group, Ciphersuite};
use ciphersuite::{group::GroupEncoding, Id, WithPreferredHash, GroupCanonicalEncoding};
use crate::{Scalar, Point};
use crate::Point;
/// Shake256, fixed to a 114-byte output, as used by Ed448.
#[derive(Clone, Default)]
@@ -49,21 +49,14 @@ impl FixedOutput for Shake256_114 {
}
impl HashMarker for Shake256_114 {}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub struct Ed448;
impl Ciphersuite for Ed448 {
type F = Scalar;
type G = Point;
impl Id for Point {
const ID: &[u8] = b"ed448";
}
impl WithPreferredHash for Point {
type H = Shake256_114;
const ID: &'static [u8] = b"ed448";
fn generator() -> Self::G {
Point::generator()
}
impl GroupCanonicalEncoding for Point {
fn from_canonical_bytes(bytes: &<Self::G as GroupEncoding>::Repr) -> CtOption<Self::G> {
Self::G::from_bytes(bytes)
}
}
#[test]
fn test_ed448() {
ff_group_tests::group::test_prime_group_bits::<_, Point>(&mut rand_core::OsRng);
}

View File

@@ -29,7 +29,6 @@ mod point;
pub use point::Point;
mod ciphersuite;
pub use crate::ciphersuite::Ed448;
pub(crate) fn u8_from_bool(bit_ref: &mut bool) -> u8 {
use core::hint::black_box;

View File

@@ -7,8 +7,8 @@ use prime_field::{
subtle::{Choice, CtOption, ConstantTimeEq, ConditionallySelectable, ConditionallyNegatable},
zeroize::Zeroize,
rand_core::RngCore,
crypto_bigint::U512,
};
use crypto_bigint::U512;
use ciphersuite::group::{
ff::{Field, PrimeField, PrimeFieldBits},
@@ -18,17 +18,37 @@ use ciphersuite::group::{
use crate::{u8_from_bool, Scalar, FieldElement};
const G_Y: FieldElement = FieldElement::from(&U512::from_be_hex(concat!(
"0000000000000000",
"693f46716eb6bc248876203756c9c7624bea73736ca3984087789c1e",
"05a0c2d73ad3ff1ce67c39c4fdbd132c4ed7c8ad9808795bf230fa14",
)));
const G_Y: FieldElement = {
let bytes = U512::from_be_hex(concat!(
"0000000000000000",
"693f46716eb6bc248876203756c9c7624bea73736ca3984087789c1e",
"05a0c2d73ad3ff1ce67c39c4fdbd132c4ed7c8ad9808795bf230fa14",
))
.to_le_bytes();
let mut dest = [0; 57];
let mut i = 0;
while i < dest.len() {
dest[i] = bytes[i];
i += 1;
}
FieldElement::from_bytes(&dest).unwrap()
};
const G_X: FieldElement = FieldElement::from(&U512::from_be_hex(concat!(
"0000000000000000",
"4f1970c66bed0ded221d15a622bf36da9e146570470f1767ea6de324",
"a3d3a46412ae1af72ab66511433b80e18b00938e2626a82bc70cc05e",
)));
const G_X: FieldElement = {
let bytes = U512::from_be_hex(concat!(
"0000000000000000",
"4f1970c66bed0ded221d15a622bf36da9e146570470f1767ea6de324",
"a3d3a46412ae1af72ab66511433b80e18b00938e2626a82bc70cc05e",
))
.to_le_bytes();
let mut dest = [0; 57];
let mut i = 0;
while i < dest.len() {
dest[i] = bytes[i];
i += 1;
}
FieldElement::from_bytes(&dest).unwrap()
};
fn recover_x(y: FieldElement) -> CtOption<FieldElement> {
#[allow(non_snake_case)]

View File

@@ -25,12 +25,11 @@ typenum = { version = "1", default-features = false }
prime-field = { path = "../prime-field", default-features = false }
short-weierstrass = { path = "../short-weierstrass", default-features = false }
curve25519-dalek = { version = "4", default-features = false, features = ["legacy_compatibility"] }
dalek-ff-group = { path = "../dalek-ff-group", version = "0.4", default-features = false }
blake2 = { version = "0.11.0-rc.0", default-features = false }
blake2 = { version = "0.11.0-rc.2", default-features = false }
ciphersuite = { path = "../ciphersuite", version = "0.4", default-features = false }
generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false, optional = true }
generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "7216a2e84c7671c167c3d81eafe0d2b1f418f102", default-features = false, optional = true }
[dev-dependencies]
hex = "0.4"

View File

@@ -5,17 +5,14 @@
#[cfg(feature = "alloc")]
#[allow(unused_imports)]
use std_shims::prelude::*;
#[cfg(feature = "alloc")]
use std_shims::io::{self, Read};
use prime_field::{subtle::Choice, zeroize::Zeroize};
use ciphersuite::group::{
ff::{Field, PrimeField},
Group,
use prime_field::{
subtle::{Choice, CtOption},
zeroize::Zeroize,
};
use ciphersuite::group::{ff::PrimeField, Group, GroupEncoding};
use curve25519_dalek::Scalar as DalekScalar;
pub use dalek_ff_group::Scalar as FieldElement;
pub use curve25519_dalek::Scalar as FieldElement;
use short_weierstrass::{ShortWeierstrass, Affine, Projective};
@@ -32,18 +29,18 @@ pub struct Embedwards25519;
#[allow(deprecated)] // No other way to construct arbitrary `FieldElement` at compile-time :/
impl ShortWeierstrass for Embedwards25519 {
type FieldElement = FieldElement;
const A: FieldElement = FieldElement(DalekScalar::from_bits(hex_literal::hex!(
const A: FieldElement = FieldElement::from_bits(hex_literal::hex!(
"ead3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010"
)));
const B: FieldElement = FieldElement(DalekScalar::from_bits(hex_literal::hex!(
));
const B: FieldElement = FieldElement::from_bits(hex_literal::hex!(
"5f07603a853f20370b682036210d463e64903a23ea669d07ca26cfc13f594209"
)));
));
const PRIME_ORDER: bool = true;
const GENERATOR: Affine<Self> = Affine::from_xy_unchecked(
FieldElement::ONE,
FieldElement(DalekScalar::from_bits(hex_literal::hex!(
FieldElement::from_bits(hex_literal::hex!(
"2e4118080a484a3dfbafe2199a0e36b7193581d676c0dadfa376b0265616020c"
))),
)),
);
type Scalar = Scalar;
@@ -80,30 +77,23 @@ impl ShortWeierstrass for Embedwards25519 {
pub type Point = Projective<Embedwards25519>;
impl ciphersuite::Ciphersuite for Embedwards25519 {
impl ciphersuite::WrappedGroup for Embedwards25519 {
type F = Scalar;
type G = Point;
type H = blake2::Blake2b512;
const ID: &'static [u8] = b"embedwards25519";
fn generator() -> Self::G {
Point::generator()
<Point as Group>::generator()
}
// We override the provided impl, which compares against the reserialization, because
// we already require canonicity
#[cfg(feature = "alloc")]
#[allow(non_snake_case)]
fn read_G<R: Read>(reader: &mut R) -> io::Result<Self::G> {
use ciphersuite::group::GroupEncoding;
let mut encoding = <Self::G as GroupEncoding>::Repr::default();
reader.read_exact(encoding.as_mut())?;
let point = Option::<Self::G>::from(Self::G::from_bytes(&encoding))
.ok_or_else(|| io::Error::other("invalid point"))?;
Ok(point)
}
impl ciphersuite::Id for Embedwards25519 {
const ID: &[u8] = b"embedwards25519";
}
impl ciphersuite::WithPreferredHash for Embedwards25519 {
type H = blake2::Blake2b512;
}
impl ciphersuite::GroupCanonicalEncoding for Embedwards25519 {
fn from_canonical_bytes(bytes: &<Self::G as GroupEncoding>::Repr) -> CtOption<Self::G> {
Self::G::from_bytes(bytes)
}
}
@@ -119,9 +109,8 @@ fn test_curve() {
#[test]
fn generator() {
use ciphersuite::group::{Group, GroupEncoding};
assert_eq!(
Point::generator(),
<Point as Group>::generator(),
Point::from_bytes(&hex_literal::hex!(
"0100000000000000000000000000000000000000000000000000000000000000"
))
@@ -139,6 +128,5 @@ fn zero_x_is_off_curve() {
// Checks random won't infinitely loop
#[test]
fn random() {
use ciphersuite::group::Group;
Point::random(&mut rand_core::OsRng);
}

View File

@@ -1,6 +1,6 @@
[package]
name = "modular-frost"
version = "0.10.1"
version = "0.11.0"
description = "Modular implementation of FROST over ff/group"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/frost"
@@ -29,7 +29,7 @@ hex = { version = "0.4", default-features = false, features = ["std"], optional
transcript = { package = "flexible-transcript", path = "../transcript", version = "^0.3.2", default-features = false, features = ["std", "recommended"] }
dalek-ff-group = { path = "../dalek-ff-group", version = "0.4", default-features = false, features = ["std"], optional = true }
dalek-ff-group = { path = "../dalek-ff-group", version = "0.5", default-features = false, features = ["std"], optional = true }
minimal-ed448 = { path = "../ed448", version = "0.4", default-features = false, features = ["std"], optional = true }
ciphersuite = { path = "../ciphersuite", version = "^0.4.1", default-features = false, features = ["std"] }

View File

@@ -1,24 +1,24 @@
use ciphersuite::{digest::Digest, FromUniformBytes, Ciphersuite};
use subtle::CtOption;
use zeroize::Zeroize;
use ciphersuite::{
digest::Digest, group::GroupEncoding, FromUniformBytes, WrappedGroup, WithPreferredHash,
};
use dalek_ff_group::Scalar;
use crate::{curve::Curve, algorithm::Hram};
macro_rules! dalek_curve {
(
$feature: literal,
$Curve: ident,
$Hram: ident,
$CONTEXT: literal,
$chal: literal
) => {
pub use dalek_ff_group::$Curve;
impl Curve for $Curve {
const CONTEXT: &'static [u8] = $CONTEXT;
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
let mut digest = <Self as Ciphersuite>::H::new();
let mut digest = <Self as WithPreferredHash>::H::new();
digest.update(Self::CONTEXT);
digest.update(dst);
digest.update(msg);
@@ -31,8 +31,12 @@ macro_rules! dalek_curve {
pub struct $Hram;
impl Hram<$Curve> for $Hram {
#[allow(non_snake_case)]
fn hram(R: &<$Curve as Ciphersuite>::G, A: &<$Curve as Ciphersuite>::G, m: &[u8]) -> Scalar {
let mut hash = <$Curve as Ciphersuite>::H::new();
fn hram(
R: &<$Curve as WrappedGroup>::G,
A: &<$Curve as WrappedGroup>::G,
m: &[u8],
) -> Scalar {
let mut hash = <$Curve as WithPreferredHash>::H::new();
if $chal.len() != 0 {
hash.update($CONTEXT);
hash.update($chal);
@@ -46,8 +50,31 @@ macro_rules! dalek_curve {
};
}
#[cfg(feature = "ristretto")]
dalek_curve!("ristretto", Ristretto, IetfRistrettoHram, b"FROST-RISTRETTO255-SHA512-v1", b"chal");
/*
FROST defined Ristretto as using SHA2-512, while Blake2b512 is considered "preferred" by
`dalek-ff-group`. We define our own ciphersuite for it accordingly.
*/
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub struct Ristretto;
impl WrappedGroup for Ristretto {
type F = Scalar;
type G = dalek_ff_group::RistrettoPoint;
fn generator() -> Self::G {
dalek_ff_group::Ristretto::generator()
}
}
impl ciphersuite::Id for Ristretto {
const ID: &[u8] = b"FROST-RISTRETTO255";
}
impl WithPreferredHash for Ristretto {
type H = <Ed25519 as WithPreferredHash>::H;
}
impl ciphersuite::GroupCanonicalEncoding for Ristretto {
fn from_canonical_bytes(bytes: &<Self::G as GroupEncoding>::Repr) -> CtOption<Self::G> {
dalek_ff_group::Ristretto::from_canonical_bytes(bytes)
}
}
dalek_curve!(Ristretto, IetfRistrettoHram, b"FROST-RISTRETTO255-SHA512-v1", b"chal");
#[cfg(feature = "ed25519")]
dalek_curve!("ed25519", Ed25519, IetfEd25519Hram, b"FROST-ED25519-SHA512-v1", b"");
pub use dalek_ff_group::Ed25519;
dalek_curve!(Ed25519, IetfEd25519Hram, b"FROST-ED25519-SHA512-v1", b"");

View File

@@ -1,6 +1,6 @@
pub use ciphersuite::{digest::Digest, group::GroupEncoding, FromUniformBytes, Ciphersuite};
use minimal_ed448::{Scalar, Point};
pub use minimal_ed448::Ed448;
pub use ciphersuite::{digest::Digest, group::GroupEncoding, FromUniformBytes, WithPreferredHash};
use minimal_ed448::Scalar;
pub use minimal_ed448::Point as Ed448;
use crate::{curve::Curve, algorithm::Hram};
@@ -9,7 +9,7 @@ const CONTEXT: &[u8] = b"FROST-ED448-SHAKE256-v1";
impl Curve for Ed448 {
const CONTEXT: &'static [u8] = CONTEXT;
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
let mut digest = <Self as Ciphersuite>::H::new();
let mut digest = <Self as WithPreferredHash>::H::new();
digest.update(Self::CONTEXT);
digest.update(dst);
digest.update(msg);
@@ -22,8 +22,8 @@ impl Curve for Ed448 {
pub(crate) struct Ietf8032Ed448Hram;
impl Ietf8032Ed448Hram {
#[allow(non_snake_case)]
pub(crate) fn hram(context: &[u8], R: &Point, A: &Point, m: &[u8]) -> Scalar {
let mut digest = <Ed448 as Ciphersuite>::H::new();
pub(crate) fn hram(context: &[u8], R: &Ed448, A: &Ed448, m: &[u8]) -> Scalar {
let mut digest = <Ed448 as WithPreferredHash>::H::new();
digest.update(b"SigEd448");
digest.update([0, u8::try_from(context.len()).unwrap()]);
digest.update(context);
@@ -39,7 +39,7 @@ impl Ietf8032Ed448Hram {
pub struct IetfEd448Hram;
impl Hram<Ed448> for IetfEd448Hram {
#[allow(non_snake_case)]
fn hram(R: &Point, A: &Point, m: &[u8]) -> Scalar {
fn hram(R: &Ed448, A: &Ed448, m: &[u8]) -> Scalar {
Ietf8032Ed448Hram::hram(&[], R, A, m)
}
}

View File

@@ -7,7 +7,7 @@ use ciphersuite::{
ff::{Field, PrimeField},
GroupEncoding,
},
Ciphersuite,
WrappedGroup,
};
use elliptic_curve::{
@@ -20,7 +20,7 @@ use elliptic_curve::{
use crate::{curve::Curve, algorithm::Hram};
#[allow(non_snake_case)]
fn hash_to_F<C: Ciphersuite<F: PrimeField<Repr = GenericArray<u8, U32>>>>(
fn hash_to_F<C: WrappedGroup<F: PrimeField<Repr = GenericArray<u8, U32>>>>(
dst: &[u8],
msg: &[u8],
) -> C::F {
@@ -112,10 +112,10 @@ macro_rules! kp_curve {
impl Hram<$Curve> for $Hram {
#[allow(non_snake_case)]
fn hram(
R: &<$Curve as Ciphersuite>::G,
A: &<$Curve as Ciphersuite>::G,
R: &<$Curve as WrappedGroup>::G,
A: &<$Curve as WrappedGroup>::G,
m: &[u8],
) -> <$Curve as Ciphersuite>::F {
) -> <$Curve as WrappedGroup>::F {
<$Curve as Curve>::hash_to_F(
b"chal",
&[R.to_bytes().as_ref(), A.to_bytes().as_ref(), m].concat(),
@@ -132,7 +132,7 @@ kp_curve!("p256", P256, IetfP256Hram, b"FROST-P256-SHA256-v1");
kp_curve!("secp256k1", Secp256k1, IetfSecp256k1Hram, b"FROST-secp256k1-SHA256-v1");
#[cfg(test)]
fn test_oversize_dst<C: Ciphersuite<F: PrimeField<Repr = GenericArray<u8, U32>>>>() {
fn test_oversize_dst<C: WrappedGroup<F: PrimeField<Repr = GenericArray<u8, U32>>>>() {
use sha2::Digest;
// The draft specifies DSTs >255 bytes should be hashed into a 32-byte DST

View File

@@ -6,21 +6,16 @@ use rand_core::{RngCore, CryptoRng};
use zeroize::{Zeroize, Zeroizing};
use subtle::ConstantTimeEq;
pub use ciphersuite::{
digest::Digest,
group::{
ff::{Field, PrimeField},
Group,
},
Ciphersuite,
use ciphersuite::group::{
ff::{Field, PrimeField},
Group,
};
pub use ciphersuite::{digest::Digest, WrappedGroup, GroupIo, Ciphersuite};
#[cfg(any(feature = "ristretto", feature = "ed25519"))]
mod dalek;
#[cfg(feature = "ristretto")]
pub use dalek::{Ristretto, IetfRistrettoHram};
#[cfg(feature = "ed25519")]
pub use dalek::{Ed25519, IetfEd25519Hram};
#[cfg(any(feature = "ristretto", feature = "ed25519"))]
pub use dalek::*;
#[cfg(any(feature = "secp256k1", feature = "p256"))]
mod kp256;
@@ -38,11 +33,11 @@ pub(crate) use ed448::Ietf8032Ed448Hram;
/// FROST Ciphersuite.
///
/// This exclude the signing algorithm specific H2, making this solely the curve, its associated
/// This excludes the signing algorithm specific H2, making this solely the curve, its associated
/// hash function, and the functions derived from it.
pub trait Curve: Ciphersuite {
pub trait Curve: GroupIo + Ciphersuite {
/// Context string for this curve.
const CONTEXT: &'static [u8];
const CONTEXT: &[u8];
/// Hash the given dst and data to a byte vector. Used to instantiate H4 and H5.
fn hash(dst: &[u8], data: &[u8]) -> impl AsRef<[u8]> {
@@ -121,7 +116,7 @@ pub trait Curve: Ciphersuite {
/// Read a point from a reader, rejecting identity.
#[allow(non_snake_case)]
fn read_G<R: Read>(reader: &mut R) -> io::Result<Self::G> {
let res = <Self as Ciphersuite>::read_G(reader)?;
let res = <Self as GroupIo>::read_G(reader)?;
if res.is_identity().into() {
Err(io::Error::other("identity point"))?;
}

View File

@@ -11,10 +11,9 @@ use zeroize::{Zeroize, Zeroizing};
use transcript::Transcript;
use ciphersuite::group::{
ff::{Field, PrimeField},
GroupEncoding,
};
use ciphersuite::group::{ff::PrimeField, GroupEncoding};
#[cfg(any(test, feature = "tests"))]
use ciphersuite::group::ff::Field;
use multiexp::BatchVerifier;
use crate::{

View File

@@ -1,6 +1,6 @@
use rand_core::OsRng;
use ciphersuite::Ciphersuite;
use ciphersuite::GroupIo;
use schnorr::SchnorrSignature;

View File

@@ -2,7 +2,7 @@ use std::collections::HashMap;
use rand_core::{RngCore, CryptoRng};
use ciphersuite::Ciphersuite;
use ciphersuite::{GroupIo, Id};
pub use dkg_recovery::recover_key;
use crate::{
@@ -28,7 +28,7 @@ pub const PARTICIPANTS: u16 = 5;
pub const THRESHOLD: u16 = ((PARTICIPANTS * 2) / 3) + 1;
/// Create a key, for testing purposes.
pub fn key_gen<R: RngCore + CryptoRng, C: Ciphersuite>(
pub fn key_gen<R: RngCore + CryptoRng, C: GroupIo + Id>(
rng: &mut R,
) -> HashMap<Participant, ThresholdKeys<C>> {
let res = dkg_dealer::key_gen::<R, C>(rng, THRESHOLD, PARTICIPANTS).unwrap();

View File

@@ -5,11 +5,11 @@
pub use subtle;
pub use zeroize;
pub use rand_core;
pub use crypto_bigint;
pub use ff;
#[doc(hidden)]
pub mod __prime_field_private {
pub use crypto_bigint;
pub use paste;
#[cfg(feature = "std")]
pub use ff_group_tests;
@@ -94,6 +94,8 @@ pub mod __prime_field_private {
/// less than `modulus_as_be_hex`.
///
/// `big_endian` controls if the encoded representation will be big-endian or not.
///
/// `repr` must satisfy the requirements for `PrimeField::Repr`.
#[doc(hidden)]
#[macro_export]
macro_rules! odd_prime_field_with_specific_repr {
@@ -116,13 +118,15 @@ macro_rules! odd_prime_field_with_specific_repr {
},
zeroize::Zeroize,
rand_core::RngCore,
crypto_bigint::{
Limb, Encoding, Integer, Uint,
modular::{ConstMontyParams, ConstMontyForm},
impl_modulus,
},
ff::*,
__prime_field_private::*,
__prime_field_private::{
crypto_bigint::{
Limb, Encoding, Integer, Uint,
modular::{ConstMontyParams, ConstMontyForm},
impl_modulus,
},
*,
},
};
const MODULUS_WITHOUT_PREFIX: &str = hex_str_without_prefix($modulus_as_be_hex);
@@ -180,7 +184,7 @@ macro_rules! odd_prime_field_with_specific_repr {
const MODULUS_MINUS_TWO: UnderlyingUint = MODULUS.wrapping_sub(&UnderlyingUint::from_u8(2));
const T: UnderlyingUint = MODULUS_MINUS_ONE.shr_vartime($name::S);
/// A field automatically generated with `short-weierstrass`.
/// A field automatically generated with `prime-field`.
#[derive(Clone, Copy, Eq, Debug)]
pub struct $name(Underlying);
@@ -192,9 +196,47 @@ macro_rules! odd_prime_field_with_specific_repr {
impl $name {
/// Create a `$name` from the `Uint` type underlying it.
pub const fn from(value: &UnderlyingUint) -> Self {
const fn from(value: &UnderlyingUint) -> Self {
$name(Underlying::new(value))
}
/// Create a `$name` from bytes within a `const` context.
///
/// This function executes in variable time. `<$name as PrimeField>::from_repr` SHOULD
/// be used instead.
pub const fn from_bytes(value: &[u8; MODULUS_BYTES]) -> Option<Self> {
let mut expanded_repr = [0; UnderlyingUint::BYTES];
let repr: &[u8] = value.as_slice();
let (uint, repr) = if $big_endian {
let start = UnderlyingUint::BYTES - MODULUS_BYTES;
let mut i = 0;
while i < repr.len() {
expanded_repr[start + i] = repr[i];
i += 1;
}
let uint = Underlying::new(&UnderlyingUint::from_be_slice(&expanded_repr));
(uint, uint.retrieve().to_be_bytes())
} else {
let mut i = 0;
while i < repr.len() {
expanded_repr[i] = repr[i];
i += 1;
}
let uint = Underlying::new(&UnderlyingUint::from_le_slice(&expanded_repr));
(uint, uint.retrieve().to_le_bytes())
};
// Ensure the representations match
let mut i = 0;
while i < expanded_repr.len() {
if repr[i] != expanded_repr[i] {
return None;
}
i += 1;
}
Some(Self(uint))
}
}
impl From<u8> for $name {
fn from(value: u8) -> Self {
@@ -416,6 +458,7 @@ macro_rules! odd_prime_field_with_specific_repr {
}
/// The encoded representation of a `$name`.
// This is required to be bespoke to satisfy `Default`.
#[derive(Clone, Copy)]
pub struct Repr([u8; MODULUS_BYTES]);
impl Default for Repr {

View File

@@ -22,6 +22,7 @@ std-shims = { path = "../../common/std-shims", version = "^0.1.1", default-featu
rand_core = { version = "0.6", default-features = false }
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
digest = { version = "0.11.0-rc.1", default-features = false, features = ["block-api"] }
transcript = { package = "flexible-transcript", path = "../transcript", version = "^0.3.2", default-features = false, optional = true }

View File

@@ -5,74 +5,34 @@ use std_shims::{
use zeroize::Zeroize;
use transcript::{Transcript, SecureDigest, DigestTranscript};
use transcript::{Transcript, DigestTranscript};
use ciphersuite::{
group::{
ff::{Field, PrimeField},
Group, GroupEncoding,
},
Ciphersuite,
FromUniformBytes, GroupIo, WithPreferredHash,
};
use multiexp::multiexp_vartime;
use crate::SchnorrSignature;
// Returns a unbiased scalar weight to use on a signature in order to prevent malleability
fn weight<D: Send + Clone + SecureDigest, F: PrimeField>(digest: &mut DigestTranscript<D>) -> F {
let mut bytes = digest.challenge(b"aggregation_weight");
debug_assert_eq!(bytes.len() % 8, 0);
// This should be guaranteed thanks to SecureDigest
debug_assert!(bytes.len() >= 32);
let mut res = F::ZERO;
let mut i = 0;
// Derive a scalar from enough bits of entropy that bias is < 2^128
// This can't be const due to its usage of a generic
// Also due to the usize::try_from, yet that could be replaced with an `as`
#[allow(non_snake_case)]
let BYTES: usize = usize::try_from((F::NUM_BITS + 128).div_ceil(8)).unwrap();
let mut remaining = BYTES;
// We load bits in as u64s
const WORD_LEN_IN_BITS: usize = 64;
const WORD_LEN_IN_BYTES: usize = WORD_LEN_IN_BITS / 8;
let mut first = true;
while i < remaining {
// Shift over the already loaded bits
if !first {
for _ in 0 .. WORD_LEN_IN_BITS {
res += res;
}
}
first = false;
// Add the next 64 bits
res += F::from(u64::from_be_bytes(bytes[i .. (i + WORD_LEN_IN_BYTES)].try_into().unwrap()));
i += WORD_LEN_IN_BYTES;
// If we've exhausted this challenge, get another
if i == bytes.len() {
bytes = digest.challenge(b"aggregation_weight_continued");
remaining -= i;
i = 0;
}
}
res
fn weight<C: WithPreferredHash>(digest: &mut DigestTranscript<C::H>) -> C::F {
let bytes = digest.challenge(b"aggregation_weight");
C::F::from_uniform_bytes(&bytes.into())
}
/// Aggregate Schnorr signature as defined in <https://eprint.iacr.org/2021/350>.
#[allow(non_snake_case)]
#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
pub struct SchnorrAggregate<C: Ciphersuite> {
pub struct SchnorrAggregate<C: GroupIo + WithPreferredHash> {
Rs: Vec<C::G>,
s: C::F,
}
impl<C: Ciphersuite> SchnorrAggregate<C> {
impl<C: GroupIo + WithPreferredHash> SchnorrAggregate<C> {
/// Read a SchnorrAggregate from something implementing Read.
pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
let mut len = [0; 4];
@@ -137,7 +97,7 @@ impl<C: Ciphersuite> SchnorrAggregate<C> {
let mut pairs = Vec::with_capacity((2 * keys_and_challenges.len()) + 1);
for (i, (key, challenge)) in keys_and_challenges.iter().enumerate() {
let z = weight(&mut digest);
let z = weight::<C>(&mut digest);
pairs.push((z, self.Rs[i]));
pairs.push((z * challenge, *key));
}
@@ -148,13 +108,22 @@ 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)]
pub struct SchnorrAggregator<C: Ciphersuite> {
#[derive(Clone, Debug)]
pub struct SchnorrAggregator<C: GroupIo + WithPreferredHash> {
digest: DigestTranscript<C::H>,
sigs: Vec<SchnorrSignature<C>>,
}
impl<C: GroupIo + WithPreferredHash> Zeroize for SchnorrAggregator<C>
where
C::H: digest::block_api::BlockSizeUser,
{
fn zeroize(&mut self) {
self.digest.zeroize();
self.sigs.zeroize();
}
}
impl<C: Ciphersuite> SchnorrAggregator<C> {
impl<C: GroupIo + WithPreferredHash> SchnorrAggregator<C> {
/// Create a new aggregator.
///
/// The DST used here must prevent a collision with whatever hash function produced the
@@ -180,7 +149,7 @@ impl<C: Ciphersuite> SchnorrAggregator<C> {
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);
aggregate.s += self.sigs[i].s * weight::<C>(&mut self.digest);
}
Some(aggregate)
}

View File

@@ -20,7 +20,7 @@ use ciphersuite::{
ff::{Field, PrimeField},
Group, GroupEncoding,
},
Ciphersuite,
GroupIo,
};
use multiexp::{multiexp_vartime, BatchVerifier};
@@ -33,20 +33,20 @@ mod tests;
/// A Schnorr signature of the form (R, s) where s = r + cx.
///
/// These are intended to be strict. It is generic over Ciphersuite which is for PrimeGroups,
/// These are intended to be strict. It is generic over `GroupIo` which is for `PrimeGroup`s,
/// and mandates canonical encodings in its read function.
///
/// RFC 8032 has an alternative verification formula, 8R = 8s - 8cX, which is intended to handle
/// torsioned nonces/public keys. Due to this library's strict requirements, such signatures will
/// not be verifiable with this library.
/// RFC 8032 has an alternative verification formula for Ed25519, `8R = 8s - 8cX`, which is
/// intended to handle torsioned nonces/public keys. Due to this library's strict requirements,
/// such signatures will not be verifiable with this library.
#[allow(non_snake_case)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub struct SchnorrSignature<C: Ciphersuite> {
pub struct SchnorrSignature<C: GroupIo> {
pub R: C::G,
pub s: C::F,
}
impl<C: Ciphersuite> SchnorrSignature<C> {
impl<C: GroupIo> SchnorrSignature<C> {
/// Read a SchnorrSignature from something implementing Read.
pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
Ok(SchnorrSignature { R: C::read_G(reader)?, s: C::read_F(reader)? })

View File

@@ -6,7 +6,7 @@ use rand_core::OsRng;
use dalek_ff_group::Ed25519;
use ciphersuite::{
group::{ff::Field, Group},
Ciphersuite,
GroupIo, WithPreferredHash,
};
use multiexp::BatchVerifier;
@@ -16,10 +16,10 @@ use crate::aggregate::{SchnorrAggregator, SchnorrAggregate};
mod rfc8032;
pub(crate) fn sign<C: Ciphersuite>() {
let private_key = Zeroizing::new(C::random_nonzero_F(&mut OsRng));
let nonce = Zeroizing::new(C::random_nonzero_F(&mut OsRng));
let challenge = C::random_nonzero_F(&mut OsRng); // Doesn't bother to craft an HRAm
pub(crate) fn sign<C: GroupIo>() {
let private_key = Zeroizing::new(C::F::random(&mut OsRng));
let nonce = Zeroizing::new(C::F::random(&mut OsRng));
let challenge = C::F::random(&mut OsRng); // Doesn't bother to craft an HRAm
assert!(SchnorrSignature::<C>::sign(&private_key, nonce, challenge)
.verify(C::generator() * private_key.deref(), challenge));
}
@@ -27,22 +27,22 @@ pub(crate) fn sign<C: Ciphersuite>() {
// The above sign function verifies signing works
// This verifies invalid signatures don't pass, using zero signatures, which should effectively be
// random
pub(crate) fn verify<C: Ciphersuite>() {
pub(crate) fn verify<C: GroupIo>() {
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)));
.verify(C::generator() * C::F::random(&mut OsRng), C::F::random(&mut OsRng)));
}
pub(crate) fn batch_verify<C: Ciphersuite>() {
pub(crate) fn batch_verify<C: GroupIo>() {
// Create 5 signatures
let mut keys = vec![];
let mut challenges = vec![];
let mut sigs = vec![];
for i in 0 .. 5 {
keys.push(Zeroizing::new(C::random_nonzero_F(&mut OsRng)));
challenges.push(C::random_nonzero_F(&mut OsRng));
keys.push(Zeroizing::new(C::F::random(&mut OsRng)));
challenges.push(C::F::random(&mut OsRng));
sigs.push(SchnorrSignature::<C>::sign(
&keys[i],
Zeroizing::new(C::random_nonzero_F(&mut OsRng)),
Zeroizing::new(C::F::random(&mut OsRng)),
challenges[i],
));
}
@@ -78,7 +78,7 @@ pub(crate) fn batch_verify<C: Ciphersuite>() {
}
#[cfg(feature = "aggregate")]
pub(crate) fn aggregate<C: Ciphersuite>() {
pub(crate) fn aggregate<C: GroupIo + WithPreferredHash>() {
const DST: &[u8] = b"Schnorr Aggregator Test";
// Create 5 signatures
@@ -86,14 +86,14 @@ pub(crate) fn aggregate<C: Ciphersuite>() {
let mut challenges = vec![];
let mut aggregator = SchnorrAggregator::<C>::new(DST);
for i in 0 .. 5 {
keys.push(Zeroizing::new(C::random_nonzero_F(&mut OsRng)));
keys.push(Zeroizing::new(C::F::random(&mut OsRng)));
// In practice, this MUST be a secure challenge binding to the nonce, key, and any message
challenges.push(C::random_nonzero_F(&mut OsRng));
challenges.push(C::F::random(&mut OsRng));
aggregator.aggregate(
challenges[i],
SchnorrSignature::<C>::sign(
&keys[i],
Zeroizing::new(C::random_nonzero_F(&mut OsRng)),
Zeroizing::new(C::F::random(&mut OsRng)),
challenges[i],
),
);

View File

@@ -6,7 +6,7 @@
use sha2::{Digest, Sha512};
use dalek_ff_group::{Scalar, Ed25519};
use ciphersuite::{group::GroupEncoding, Ciphersuite};
use ciphersuite::{group::GroupEncoding, GroupIo};
use crate::SchnorrSignature;

View File

@@ -24,10 +24,9 @@ transcript = { package = "flexible-transcript", path = "../transcript", version
group = "0.13"
dalek-ff-group = { path = "../dalek-ff-group" }
ciphersuite = { path = "../ciphersuite", version = "^0.4.1", features = ["std"] }
schnorr = { package = "schnorr-signatures", path = "../schnorr", version = "^0.5.1" }
frost = { path = "../frost", package = "modular-frost", version = "^0.10.0", features = ["ristretto"] }
frost = { path = "../frost", package = "modular-frost", version = "0.11.0", features = ["ristretto"] }
schnorrkel = { version = "0.11" }

View File

@@ -9,16 +9,16 @@ use zeroize::Zeroizing;
use transcript::{Transcript, MerlinTranscript};
use dalek_ff_group::Ristretto;
use ciphersuite::{
group::{ff::PrimeField, GroupEncoding},
Ciphersuite,
WrappedGroup,
};
use schnorr::SchnorrSignature;
use ::frost::{
Participant, ThresholdKeys, ThresholdView, FrostError,
algorithm::{Hram, Algorithm, Schnorr},
curve::Ristretto,
};
/// The [modular-frost](https://docs.rs/modular-frost) library.
@@ -28,8 +28,8 @@ pub mod frost {
use schnorrkel::{PublicKey, Signature, context::SigningTranscript, signing_context};
type RistrettoPoint = <Ristretto as Ciphersuite>::G;
type Scalar = <Ristretto as Ciphersuite>::F;
type RistrettoPoint = <Ristretto as WrappedGroup>::G;
type Scalar = <Ristretto as WrappedGroup>::F;
#[cfg(test)]
mod tests;
@@ -83,7 +83,7 @@ impl Algorithm<Ristretto> for Schnorrkel {
self.schnorr.transcript()
}
fn nonces(&self) -> Vec<Vec<<Ristretto as Ciphersuite>::G>> {
fn nonces(&self) -> Vec<Vec<<Ristretto as WrappedGroup>::G>> {
self.schnorr.nonces()
}

View File

@@ -18,13 +18,13 @@ hex-literal = { version = "1", default-features = false }
std-shims = { version = "0.1", path = "../../common/std-shims", default-features = false, optional = true }
k256 = { version = "0.13", default-features = false, features = ["arithmetic"] }
sha2 = { version = "0.11.0-rc.0", default-features = false }
k256 = { version = "0.13", default-features = false, features = ["arithmetic", "expose-field"] }
prime-field = { path = "../prime-field", default-features = false }
short-weierstrass = { path = "../short-weierstrass", default-features = false }
sha2 = { version = "0.11.0-rc.0", default-features = false }
ciphersuite = { path = "../ciphersuite", version = "0.4", default-features = false }
generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false, optional = true }
generalized-bulletproofs-ec-gadgets = { git = "https://github.com/monero-oxide/monero-oxide", rev = "7216a2e84c7671c167c3d81eafe0d2b1f418f102", default-features = false, optional = true }
[dev-dependencies]
hex = "0.4"

View File

@@ -5,17 +5,12 @@
#[cfg(feature = "alloc")]
#[allow(unused_imports)]
use std_shims::prelude::*;
#[cfg(feature = "alloc")]
use std_shims::io::{self, Read};
use sha2::{
digest::array::{typenum::U33, Array},
Sha512,
};
use sha2::digest::array::{typenum::U33, Array};
use k256::elliptic_curve::{
subtle::{Choice, ConstantTimeEq, ConditionallySelectable},
subtle::{Choice, CtOption, ConstantTimeEq, ConditionallySelectable},
zeroize::Zeroize,
group::{ff::PrimeField, Group},
group::{ff::PrimeField, Group, GroupEncoding},
sec1::Tag,
};
@@ -109,32 +104,25 @@ impl ShortWeierstrass for Secq256k1 {
pub type Point = Projective<Secq256k1>;
impl ciphersuite::Ciphersuite for Secq256k1 {
impl ciphersuite::WrappedGroup for Secq256k1 {
type F = Scalar;
type G = Point;
type H = Sha512;
const ID: &'static [u8] = b"secq256k1";
fn generator() -> Self::G {
Point::generator()
<Point as Group>::generator()
}
// We override the provided impl, which compares against the reserialization, because
// we already require canonicity
#[cfg(feature = "alloc")]
#[allow(non_snake_case)]
fn read_G<R: Read>(reader: &mut R) -> io::Result<Self::G> {
use ciphersuite::group::GroupEncoding;
let mut encoding = <Self::G as GroupEncoding>::Repr::default();
reader.read_exact(encoding.as_mut())?;
let point = Option::<Self::G>::from(Self::G::from_bytes(&encoding))
.ok_or_else(|| io::Error::other("invalid point"))?;
Ok(point)
}
impl ciphersuite::Id for Secq256k1 {
const ID: &[u8] = b"secq256k1";
}
impl ciphersuite::GroupCanonicalEncoding for Secq256k1 {
fn from_canonical_bytes(bytes: &<Self::G as GroupEncoding>::Repr) -> CtOption<Self::G> {
Self::G::from_bytes(bytes)
}
}
impl ciphersuite::WithPreferredHash for Secq256k1 {
type H = sha2::Sha512;
}
#[cfg(feature = "alloc")]
impl generalized_bulletproofs_ec_gadgets::DiscreteLogParameter for Secq256k1 {
@@ -150,7 +138,7 @@ fn test_curve() {
fn generator() {
use ciphersuite::group::GroupEncoding;
assert_eq!(
Point::generator(),
<Point as Group>::generator(),
Point::from_bytes(&Array(hex_literal::hex!(
"020000000000000000000000000000000000000000000000000000000000000001"
)))

View File

@@ -21,7 +21,7 @@ rand_core = { version = "0.6", default-features = false }
ff = { version = "0.13", default-features = false, features = ["bits"] }
group = { version = "0.13", default-features = false }
ec-divisors = { git = "https://github.com/monero-oxide/monero-oxide", rev = "a6f8797007e768488568b821435cf5006517a962", default-features = false, optional = true }
ec-divisors = { git = "https://github.com/monero-oxide/monero-oxide", rev = "7216a2e84c7671c167c3d81eafe0d2b1f418f102", default-features = false, optional = true }
[features]
alloc = ["zeroize/alloc", "rand_core/alloc", "ff/alloc", "group/alloc", "ec-divisors"]

View File

@@ -413,7 +413,6 @@ impl<C: ShortWeierstrass<Scalar: PrimeFieldBits>> PrimeGroup for Projective<C> {
#[cfg(feature = "alloc")]
mod alloc {
use core::borrow::Borrow;
use ff::{PrimeField, PrimeFieldBits};
use crate::{ShortWeierstrass, Affine, Projective};
@@ -421,7 +420,8 @@ mod alloc {
type FieldElement = C::FieldElement;
type XyPoint = ec_divisors::Projective<Self>;
fn interpolator_for_scalar_mul() -> impl Borrow<ec_divisors::Interpolator<C::FieldElement>> {
type BorrowedInterpolator = ec_divisors::Interpolator<C::FieldElement>;
fn interpolator_for_scalar_mul() -> Self::BorrowedInterpolator {
ec_divisors::Interpolator::new((<C::Scalar as PrimeField>::NUM_BITS as usize).div_ceil(2) + 2)
}

View File

@@ -19,14 +19,14 @@ workspace = true
[dependencies]
zeroize = { version = "^1.5", default-features = false }
digest = { version = "0.11.0-rc.0", default-features = false, features = ["block-api"] }
digest = { version = "0.11.0-rc.1", default-features = false, features = ["block-api"] }
blake2 = { version = "0.11.0-rc.0", default-features = false, optional = true }
blake2 = { version = "0.11.0-rc.2", default-features = false, optional = true }
merlin = { version = "3", default-features = false, optional = true }
[dev-dependencies]
sha2 = { version = "0.11.0-rc.0", default-features = false }
blake2 = { version = "0.11.0-rc.0", default-features = false }
sha2 = { version = "0.11.0-rc.2", default-features = false }
blake2 = { version = "0.11.0-rc.2", default-features = false }
[features]
std = ["zeroize/std", "merlin?/std"]

View File

@@ -4,13 +4,7 @@
use zeroize::Zeroize;
use digest::{
typenum::{
consts::U32, marker_traits::NonZero, type_operators::IsGreaterOrEqual, operator_aliases::GrEq,
},
block_api::BlockSizeUser,
Digest, Output, HashMarker,
};
use digest::{block_api::BlockSizeUser, Digest, Output, HashMarker};
#[cfg(feature = "merlin")]
mod merlin;
@@ -75,24 +69,11 @@ impl DigestTranscriptMember {
}
}
/// A trait defining cryptographic Digests with at least a 256-bit output size, assuming at least a
/// 128-bit level of security accordingly.
pub trait SecureDigest: Digest + HashMarker {}
impl<D: Digest + HashMarker> SecureDigest for D
where
// This just lets us perform the comparison
D::OutputSize: IsGreaterOrEqual<U32>,
// Perform the comparison and make sure it's true (not zero), meaning D::OutputSize is >= U32
// This should be U32 as it's length in bytes, not bits
GrEq<D::OutputSize, U32>: NonZero,
{
}
/// A simple transcript format constructed around the specified hash algorithm.
#[derive(Clone, Debug)]
pub struct DigestTranscript<D: Send + Clone + SecureDigest>(D);
pub struct DigestTranscript<D: Send + Clone + Digest + HashMarker>(D);
impl<D: Send + Clone + SecureDigest> DigestTranscript<D> {
impl<D: Send + Clone + Digest + HashMarker> DigestTranscript<D> {
fn append(&mut self, kind: DigestTranscriptMember, value: &[u8]) {
self.0.update([kind.as_u8()]);
// Assumes messages don't exceed 16 exabytes
@@ -101,7 +82,7 @@ impl<D: Send + Clone + SecureDigest> DigestTranscript<D> {
}
}
impl<D: Send + Clone + SecureDigest> Transcript for DigestTranscript<D> {
impl<D: Send + Clone + Digest + HashMarker> Transcript for DigestTranscript<D> {
type Challenge = Output<D>;
fn new(name: &'static [u8]) -> Self {
@@ -140,7 +121,7 @@ impl<D: Send + Clone + SecureDigest> Transcript for DigestTranscript<D> {
// Digest doesn't implement Zeroize
// Implement Zeroize for DigestTranscript by writing twice the block size to the digest in an
// attempt to overwrite the internal hash state/any leftover bytes
impl<D: Send + Clone + SecureDigest> Zeroize for DigestTranscript<D>
impl<D: Send + Clone + Digest + HashMarker> Zeroize for DigestTranscript<D>
where
D: BlockSizeUser,
{
@@ -159,7 +140,7 @@ where
// These writes may be optimized out if they're never read
// Attempt to get them marked as read
fn mark_read<D: Send + Clone + SecureDigest>(transcript: &DigestTranscript<D>) {
fn mark_read<D: Send + Clone + Digest + HashMarker>(transcript: &DigestTranscript<D>) {
// Just get a challenge from the state
let mut challenge = core::hint::black_box(transcript.0.clone().finalize());
challenge.as_mut().zeroize();

View File

@@ -29,7 +29,7 @@ zeroize = { version = "1", default-features = false, features = ["std"] }
rand_core = { version = "0.6", default-features = false, features = ["std"] }
# Cryptography
transcript = { package = "flexible-transcript", path = "../crypto/transcript", default-features = false, features = ["std", "recommended"] }
transcript = { package = "flexible-transcript", path = "../crypto/transcript", default-features = false, features = ["std"] }
dalek-ff-group = { path = "../crypto/dalek-ff-group", default-features = false, features = ["std"] }
ciphersuite = { path = "../crypto/ciphersuite", default-features = false, features = ["std"] }
schnorr-signatures = { path = "../crypto/schnorr", default-features = false, features = ["std"] }

View File

@@ -4,10 +4,7 @@ use zeroize::{Zeroize, Zeroizing};
use rand_core::OsRng;
use dalek_ff_group::Ristretto;
use ciphersuite::{
group::ff::{Field, PrimeField},
Ciphersuite,
};
use ciphersuite::{group::ff::PrimeField, WrappedGroup};
use schnorr_signatures::SchnorrSignature;
use tokio::{
@@ -22,8 +19,8 @@ use crate::{Service, Metadata, QueuedMessage, MessageQueueRequest, message_chall
pub struct MessageQueue {
pub service: Service,
priv_key: Zeroizing<<Ristretto as Ciphersuite>::F>,
pub_key: <Ristretto as Ciphersuite>::G,
priv_key: Zeroizing<<Ristretto as WrappedGroup>::F>,
pub_key: <Ristretto as WrappedGroup>::G,
url: String,
}
@@ -31,7 +28,7 @@ impl MessageQueue {
pub fn new(
service: Service,
mut url: String,
priv_key: Zeroizing<<Ristretto as Ciphersuite>::F>,
priv_key: Zeroizing<<Ristretto as WrappedGroup>::F>,
) -> MessageQueue {
// Allow MESSAGE_QUEUE_RPC to either be a full URL or just a hostname
// While we could stitch together multiple variables, our control over this service makes this
@@ -46,16 +43,16 @@ impl MessageQueue {
pub fn from_env(service: Service) -> MessageQueue {
let url = env::var("MESSAGE_QUEUE_RPC").expect("message-queue RPC wasn't specified");
let priv_key: Zeroizing<<Ristretto as Ciphersuite>::F> = {
let priv_key: Zeroizing<<Ristretto as WrappedGroup>::F> = {
let key_str =
Zeroizing::new(env::var("MESSAGE_QUEUE_KEY").expect("message-queue key wasn't specified"));
let key_bytes = Zeroizing::new(
hex::decode(&key_str).expect("invalid message-queue key specified (wasn't hex)"),
);
let mut bytes = <<Ristretto as Ciphersuite>::F as PrimeField>::Repr::default();
let mut bytes = <<Ristretto as WrappedGroup>::F as PrimeField>::Repr::default();
bytes.copy_from_slice(&key_bytes);
let key = Zeroizing::new(
Option::from(<<Ristretto as Ciphersuite>::F as PrimeField>::from_repr(bytes))
Option::from(<<Ristretto as WrappedGroup>::F as PrimeField>::from_repr(bytes))
.expect("invalid message-queue key specified"),
);
bytes.zeroize();
@@ -79,7 +76,7 @@ impl MessageQueue {
}
pub async fn queue(&self, metadata: Metadata, msg: Vec<u8>) -> Result<(), String> {
let nonce = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
let nonce = Zeroizing::new(<Ristretto as WrappedGroup>::F::random(&mut OsRng));
let nonce_pub = Ristretto::generator() * nonce.deref();
let sig = SchnorrSignature::<Ristretto>::sign(
&self.priv_key,
@@ -215,7 +212,7 @@ impl MessageQueue {
pub async fn ack(&self, from: Service, id: u64) {
// TODO: Should this use OsRng? Deterministic or deterministic + random may be better.
let nonce = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
let nonce = Zeroizing::new(<Ristretto as WrappedGroup>::F::random(&mut OsRng));
let nonce_pub = Ristretto::generator() * nonce.deref();
let sig = SchnorrSignature::<Ristretto>::sign(
&self.priv_key,

View File

@@ -4,7 +4,7 @@ pub(crate) use std::{
};
use dalek_ff_group::Ristretto;
pub(crate) use ciphersuite::{group::GroupEncoding, Ciphersuite};
pub(crate) use ciphersuite::{group::GroupEncoding, WrappedGroup, GroupCanonicalEncoding};
pub(crate) use schnorr_signatures::SchnorrSignature;
pub(crate) use serai_primitives::network_id::ExternalNetworkId;
@@ -29,7 +29,7 @@ pub(crate) type Db = serai_db::RocksDB;
mod clippy {
use super::*;
use once_cell::sync::Lazy;
pub(crate) static KEYS: Lazy<Arc<RwLock<HashMap<Service, <Ristretto as Ciphersuite>::G>>>> =
pub(crate) static KEYS: Lazy<Arc<RwLock<HashMap<Service, <Ristretto as WrappedGroup>::G>>>> =
Lazy::new(|| Arc::new(RwLock::new(HashMap::new())));
pub(crate) static QUEUES: Lazy<Arc<RwLock<HashMap<(Service, Service), RwLock<Queue<Db>>>>>> =
Lazy::new(|| Arc::new(RwLock::new(HashMap::new())));
@@ -189,9 +189,9 @@ async fn main() {
let read_key = |str| {
let key = serai_env::var(str)?;
let mut repr = <<Ristretto as Ciphersuite>::G as GroupEncoding>::Repr::default();
let mut repr = <<Ristretto as WrappedGroup>::G as GroupEncoding>::Repr::default();
repr.as_mut().copy_from_slice(&hex::decode(key).unwrap());
Some(<Ristretto as Ciphersuite>::G::from_bytes(&repr).unwrap())
Some(<Ristretto as GroupCanonicalEncoding>::from_canonical_bytes(&repr).unwrap())
};
let register_service = |service, key| {

View File

@@ -1,6 +1,6 @@
use transcript::{Transcript, RecommendedTranscript};
use transcript::{Transcript, DigestTranscript};
use dalek_ff_group::Ristretto;
use ciphersuite::{group::GroupEncoding, Ciphersuite};
use ciphersuite::{group::GroupEncoding, FromUniformBytes, WrappedGroup, WithPreferredHash};
use borsh::{BorshSerialize, BorshDeserialize};
@@ -36,13 +36,15 @@ pub enum MessageQueueRequest {
pub fn message_challenge(
from: Service,
from_key: <Ristretto as Ciphersuite>::G,
from_key: <Ristretto as WrappedGroup>::G,
to: Service,
intent: &[u8],
msg: &[u8],
nonce: <Ristretto as Ciphersuite>::G,
) -> <Ristretto as Ciphersuite>::F {
let mut transcript = RecommendedTranscript::new(b"Serai Message Queue v0.1 Message");
nonce: <Ristretto as WrappedGroup>::G,
) -> <Ristretto as WrappedGroup>::F {
let mut transcript = DigestTranscript::<<Ristretto as WithPreferredHash>::H>::new(
b"Serai Message Queue v0.1 Message",
);
transcript.domain_separate(b"metadata");
transcript.append_message(b"from", borsh::to_vec(&from).unwrap());
transcript.append_message(b"from_key", from_key.to_bytes());
@@ -52,17 +54,19 @@ pub fn message_challenge(
transcript.append_message(b"msg", msg);
transcript.domain_separate(b"signature");
transcript.append_message(b"nonce", nonce.to_bytes());
<Ristretto as Ciphersuite>::hash_to_F(&transcript.challenge(b"challenge"))
<Ristretto as WrappedGroup>::F::from_uniform_bytes(&transcript.challenge(b"challenge").into())
}
pub fn ack_challenge(
to: Service,
to_key: <Ristretto as Ciphersuite>::G,
to_key: <Ristretto as WrappedGroup>::G,
from: Service,
id: u64,
nonce: <Ristretto as Ciphersuite>::G,
) -> <Ristretto as Ciphersuite>::F {
let mut transcript = RecommendedTranscript::new(b"Serai Message Queue v0.1 Acknowledgement");
nonce: <Ristretto as WrappedGroup>::G,
) -> <Ristretto as WrappedGroup>::F {
let mut transcript = DigestTranscript::<<Ristretto as WithPreferredHash>::H>::new(
b"Serai Message Queue v0.1 Acknowledgement",
);
transcript.domain_separate(b"metadata");
transcript.append_message(b"to", borsh::to_vec(&to).unwrap());
transcript.append_message(b"to_key", to_key.to_bytes());
@@ -71,5 +75,5 @@ pub fn ack_challenge(
transcript.append_message(b"id", id.to_le_bytes());
transcript.domain_separate(b"signature");
transcript.append_message(b"nonce", nonce.to_bytes());
<Ristretto as Ciphersuite>::hash_to_F(&transcript.challenge(b"challenge"))
<Ristretto as WrappedGroup>::F::from_uniform_bytes(&transcript.challenge(b"challenge").into())
}

View File

@@ -27,7 +27,7 @@ rand_core = { version = "0.6", default-features = false }
bitcoin = { version = "0.32", default-features = false }
k256 = { version = "^0.13.1", default-features = false, features = ["arithmetic", "bits"] }
frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.10", default-features = false, features = ["secp256k1"], optional = true }
frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.11", default-features = false, features = ["secp256k1"], optional = true }
hex = { version = "0.4", default-features = false, optional = true }
serde = { version = "1", default-features = false, features = ["derive"], optional = true }

View File

@@ -40,7 +40,7 @@ mod frost_crypto {
use k256::{elliptic_curve::ops::Reduce, U256, Scalar};
use frost::{
curve::{Ciphersuite, Secp256k1},
curve::{WrappedGroup, Secp256k1},
Participant, ThresholdKeys, ThresholdView, FrostError,
algorithm::{Hram as HramTrait, Algorithm, IetfSchnorr as FrostSchnorr},
};
@@ -128,10 +128,10 @@ mod frost_crypto {
fn sign_share(
&mut self,
params: &ThresholdView<Secp256k1>,
nonce_sums: &[Vec<<Secp256k1 as Ciphersuite>::G>],
nonces: Vec<Zeroizing<<Secp256k1 as Ciphersuite>::F>>,
nonce_sums: &[Vec<<Secp256k1 as WrappedGroup>::G>],
nonces: Vec<Zeroizing<<Secp256k1 as WrappedGroup>::F>>,
msg: &[u8],
) -> <Secp256k1 as Ciphersuite>::F {
) -> <Secp256k1 as WrappedGroup>::F {
self.0.sign_share(params, nonce_sums, nonces, msg)
}

View File

@@ -13,7 +13,7 @@ use k256::{
#[cfg(feature = "std")]
use frost::{
curve::{Ciphersuite, Secp256k1},
curve::{WrappedGroup, GroupIo, Secp256k1},
ThresholdKeys,
};
@@ -59,7 +59,7 @@ pub fn tweak_keys(keys: ThresholdKeys<Secp256k1>) -> ThresholdKeys<Secp256k1> {
would be unusable due to a check the script path hash is less than the order. That doesn't
impact us as we don't want the script path to be usable.
*/
keys.offset(<Secp256k1 as Ciphersuite>::F::reduce(U256::from_be_bytes(
keys.offset(<Secp256k1 as WrappedGroup>::F::reduce(U256::from_be_bytes(
*tweak_hash.to_raw_hash().as_ref(),
)))
};

View File

@@ -3,7 +3,7 @@ use std::path::Path;
use zeroize::Zeroizing;
use dalek_ff_group::Ristretto;
use ciphersuite::{group::ff::PrimeField, Ciphersuite};
use ciphersuite::{group::ff::PrimeField, WrappedGroup};
use crate::{Network, Os, mimalloc, os, build_serai_service, write_dockerfile};
@@ -11,8 +11,8 @@ use crate::{Network, Os, mimalloc, os, build_serai_service, write_dockerfile};
pub fn coordinator(
orchestration_path: &Path,
network: Network,
coordinator_key: Zeroizing<<Ristretto as Ciphersuite>::F>,
serai_key: &Zeroizing<<Ristretto as Ciphersuite>::F>,
coordinator_key: Zeroizing<<Ristretto as WrappedGroup>::F>,
serai_key: &Zeroizing<<Ristretto as WrappedGroup>::F>,
) {
let db = network.db();
let longer_reattempts = if network == Network::Dev { "longer-reattempts" } else { "" };

View File

@@ -24,7 +24,7 @@ use ciphersuite::{
ff::{Field, PrimeField},
GroupEncoding,
},
Ciphersuite,
WrappedGroup,
};
use embedwards25519::Embedwards25519;
use secq256k1::Secq256k1;
@@ -222,8 +222,10 @@ fn orchestration_path(network: Network) -> PathBuf {
orchestration_path
}
type InfrastructureKeys =
HashMap<&'static str, (Zeroizing<<Ristretto as Ciphersuite>::F>, <Ristretto as Ciphersuite>::G)>;
type InfrastructureKeys = HashMap<
&'static str,
(Zeroizing<<Ristretto as WrappedGroup>::F>, <Ristretto as WrappedGroup>::G),
>;
fn infrastructure_keys(network: Network) -> InfrastructureKeys {
// Generate entropy for the infrastructure keys
@@ -258,7 +260,7 @@ fn infrastructure_keys(network: Network) -> InfrastructureKeys {
let mut rng = ChaCha20Rng::from_seed(transcript.rng_seed(b"infrastructure_keys"));
let mut key_pair = || {
let key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut rng));
let key = Zeroizing::new(<Ristretto as WrappedGroup>::F::random(&mut rng));
let public = Ristretto::generator() * key.deref();
(key, public)
};
@@ -308,12 +310,12 @@ fn embedded_curve_keys(network: Network) -> EmbeddedCurveKeys {
EmbeddedCurveKeys {
embedwards25519: {
let key = Zeroizing::new(<Embedwards25519 as Ciphersuite>::F::random(&mut rng));
let key = Zeroizing::new(<Embedwards25519 as WrappedGroup>::F::random(&mut rng));
let pub_key = Embedwards25519::generator() * key.deref();
(Zeroizing::new(key.to_repr().as_ref().to_vec()), pub_key.to_bytes().to_vec())
},
secq256k1: {
let key = Zeroizing::new(<Secq256k1 as Ciphersuite>::F::random(&mut rng));
let key = Zeroizing::new(<Secq256k1 as WrappedGroup>::F::random(&mut rng));
let pub_key = Secq256k1::generator() * key.deref();
(Zeroizing::new(key.to_repr().as_ref().to_vec()), pub_key.to_bytes().to_vec())
},
@@ -382,9 +384,9 @@ fn dockerfiles(network: Network) {
.expect("couldn't read key for this network"),
);
let mut serai_key_repr =
Zeroizing::new(<<Ristretto as Ciphersuite>::F as PrimeField>::Repr::default());
Zeroizing::new(<<Ristretto as WrappedGroup>::F as PrimeField>::Repr::default());
serai_key_repr.as_mut().copy_from_slice(serai_key.as_ref());
Zeroizing::new(<Ristretto as Ciphersuite>::F::from_repr(*serai_key_repr).unwrap())
Zeroizing::new(<Ristretto as WrappedGroup>::F::from_repr(*serai_key_repr).unwrap())
};
coordinator(&orchestration_path, network, coordinator_key.0, &serai_key);
@@ -400,7 +402,7 @@ fn key_gen(network: Network) {
return;
}
let key = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
let key = <Ristretto as WrappedGroup>::F::random(&mut OsRng);
let _ = fs::create_dir_all(&serai_dir);
fs::write(key_file, key.to_repr()).expect("couldn't write key");
@@ -408,7 +410,7 @@ fn key_gen(network: Network) {
// TODO: Move embedded curve key gen here, and print them
println!(
"Public Key: {}",
hex::encode((<Ristretto as Ciphersuite>::generator() * key).to_bytes())
hex::encode((<Ristretto as WrappedGroup>::generator() * key).to_bytes())
);
}

View File

@@ -1,17 +1,17 @@
use std::path::Path;
use dalek_ff_group::Ristretto;
use ciphersuite::{group::GroupEncoding, Ciphersuite};
use ciphersuite::{group::GroupEncoding, WrappedGroup};
use crate::{Network, Os, mimalloc, os, build_serai_service, write_dockerfile};
pub fn message_queue(
orchestration_path: &Path,
network: Network,
coordinator_key: <Ristretto as Ciphersuite>::G,
bitcoin_key: <Ristretto as Ciphersuite>::G,
ethereum_key: <Ristretto as Ciphersuite>::G,
monero_key: <Ristretto as Ciphersuite>::G,
coordinator_key: <Ristretto as WrappedGroup>::G,
bitcoin_key: <Ristretto as WrappedGroup>::G,
ethereum_key: <Ristretto as WrappedGroup>::G,
monero_key: <Ristretto as WrappedGroup>::G,
) {
let setup = mimalloc(Os::Debian).to_string() +
&build_serai_service("", network.release(), network.db(), "serai-message-queue");

View File

@@ -3,7 +3,7 @@ use std::path::Path;
use zeroize::Zeroizing;
use dalek_ff_group::Ristretto;
use ciphersuite::{group::ff::PrimeField, Ciphersuite};
use ciphersuite::{group::ff::PrimeField, WrappedGroup};
use crate::{Network, Os, mimalloc, os, build_serai_service, write_dockerfile};
@@ -12,8 +12,8 @@ pub fn processor(
orchestration_path: &Path,
network: Network,
coin: &'static str,
_coordinator_key: <Ristretto as Ciphersuite>::G,
processor_key: Zeroizing<<Ristretto as Ciphersuite>::F>,
_coordinator_key: <Ristretto as WrappedGroup>::G,
processor_key: Zeroizing<<Ristretto as WrappedGroup>::F>,
substrate_evrf_key: Zeroizing<Vec<u8>>,
network_evrf_key: Zeroizing<Vec<u8>>,
) {

View File

@@ -2,14 +2,14 @@ use std::path::Path;
use zeroize::Zeroizing;
use dalek_ff_group::Ristretto;
use ciphersuite::{group::ff::PrimeField, Ciphersuite};
use ciphersuite::{group::ff::PrimeField, WrappedGroup};
use crate::{Network, Os, mimalloc, os, build_serai_service, write_dockerfile};
pub fn serai(
orchestration_path: &Path,
network: Network,
serai_key: &Zeroizing<<Ristretto as Ciphersuite>::F>,
serai_key: &Zeroizing<<Ristretto as WrappedGroup>::F>,
) {
// Always builds in release for performance reasons
let setup = mimalloc(Os::Debian).to_string() + &build_serai_service("", true, "", "serai-node");

View File

@@ -16,13 +16,19 @@ rustdoc-args = ["--cfg", "docsrs"]
workspace = true
[dependencies]
std-shims = { path = "../../common/std-shims", version = "0.1.4", default-features = false, optional = true }
zeroize = { version = "1", default-features = false }
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false }
dalek-ff-group = { path = "../../crypto/dalek-ff-group", default-features = false, optional = true }
[features]
alloc = ["ciphersuite/alloc"]
alloc = ["std-shims", "zeroize/alloc", "ciphersuite/alloc", "dalek-ff-group/alloc"]
std = [
"alloc",
"std-shims/std",
"zeroize/std",
"ciphersuite/std",
"dalek-ff-group?/std",
]

View File

@@ -1,4 +1,4 @@
# Ciphersuite
Patch for the `crates.io` ciphersuite to use the in-tree ciphersuite, resolving
breaking changes made since.
Patch for the `crates.io` `ciphersuite` to use the in-tree `ciphersuite`,
resolving relevant breaking changes made since.

View File

@@ -1,5 +1,33 @@
#![cfg_attr(not(feature = "std"), no_std)]
pub use ciphersuite::*;
use std_shims::io;
use zeroize::Zeroize;
pub use ciphersuite::group;
use group::{*, ff::*, prime::PrimeGroup};
pub trait Ciphersuite: 'static + Send + Sync {
type F: PrimeField + PrimeFieldBits + Zeroize;
type G: Group<Scalar = Self::F> + GroupOps + PrimeGroup + Zeroize;
#[cfg(feature = "alloc")]
#[allow(non_snake_case)]
fn read_F<R: io::Read>(reader: &mut R) -> io::Result<Self::F>;
#[cfg(feature = "alloc")]
#[allow(non_snake_case)]
fn read_G<R: io::Read>(reader: &mut R) -> io::Result<Self::G>;
}
impl<C: ciphersuite::GroupIo> Ciphersuite for C {
type F = <C as ciphersuite::WrappedGroup>::F;
type G = <C as ciphersuite::WrappedGroup>::G;
#[cfg(feature = "alloc")]
fn read_F<R: io::Read>(reader: &mut R) -> io::Result<Self::F> {
<C as ciphersuite::GroupIo>::read_F(reader)
}
#[cfg(feature = "alloc")]
fn read_G<R: io::Read>(reader: &mut R) -> io::Result<Self::G> {
<C as ciphersuite::GroupIo>::read_G(reader)
}
}
#[cfg(feature = "ed25519")]
pub use dalek_ff_group::Ed25519;

View File

@@ -0,0 +1,29 @@
[package]
name = "dalek-ff-group"
version = "0.5.99"
description = "ff/group bindings around curve25519-dalek"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/dalek-ff-group"
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["curve25519", "ed25519", "ristretto", "dalek", "group"]
edition = "2021"
rust-version = "1.85"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[lints]
workspace = true
[dependencies]
dalek-ff-group = { path = "../../crypto/dalek-ff-group", default-features = false }
crypto-bigint-05 = { package = "crypto-bigint", version = "0.5", default-features = false, features = ["zeroize"] }
crypto-bigint = { version = "0.6", default-features = false, features = ["zeroize"] }
prime-field = { path = "../../crypto/prime-field", default-features = false }
[features]
alloc = ["dalek-ff-group/alloc", "crypto-bigint-05/alloc", "crypto-bigint/alloc", "prime-field/alloc"]
std = ["alloc", "dalek-ff-group/std", "prime-field/std"]
default = ["std"]

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022-2025 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.

View File

@@ -0,0 +1,4 @@
# Dalek FF/Group
Patch for the `crates.io` `dalek-ff-group` to use the in-tree `dalek-ff-group`,
resolving relevant breaking changes made since.

View File

@@ -0,0 +1,44 @@
#![allow(deprecated)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![no_std] // Prevents writing new code, in what should be a simple wrapper, which requires std
#![doc = include_str!("../README.md")]
#![allow(clippy::redundant_closure_call)]
pub use dalek_ff_group::{Scalar, EdwardsPoint, RistrettoPoint, Ed25519, Ristretto};
type ThirtyTwoArray = [u8; 32];
prime_field::odd_prime_field_with_specific_repr!(
FieldElement,
"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed",
"02",
false,
crate::ThirtyTwoArray
);
impl FieldElement {
/// Create a FieldElement from a `crypto_bigint::U256`.
///
/// This will reduce the `U256` by the modulus, into a member of the field.
#[deprecated]
pub const fn from_u256(u256: &crypto_bigint_05::U256) -> Self {
const MODULUS: crypto_bigint::U256 = crypto_bigint::U256::from_be_hex(
"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed",
);
let mut u256 = crypto_bigint::U256::from_words(*u256.as_words());
loop {
let result = FieldElement::from_bytes(&u256.to_le_bytes());
if let Some(result) = result {
return result;
}
u256 = u256.wrapping_sub(&MODULUS);
}
}
/// Create a `FieldElement` from the reduction of a 512-bit number.
///
/// The bytes are interpreted in little-endian format.
#[deprecated]
pub fn wide_reduce(value: [u8; 64]) -> Self {
<FieldElement as prime_field::ff::FromUniformBytes<_>>::from_uniform_bytes(&value)
}
}

View File

@@ -23,7 +23,7 @@ hex = { version = "0.4", default-features = false, features = ["std"] }
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std"] }
dkg = { package = "dkg-evrf", path = "../../crypto/dkg/evrf", default-features = false, features = ["std", "ristretto"] }
dkg = { package = "dkg-evrf", path = "../../crypto/dkg/evrf", default-features = false, features = ["std"] }
serai-primitives = { path = "../../substrate/primitives", default-features = false, features = ["std"] }
serai-cosign = { package = "serai-cosign-types", path = "../../coordinator/cosign/types" }

View File

@@ -4,9 +4,9 @@ use zeroize::{Zeroize, Zeroizing};
use ciphersuite::{
group::{ff::PrimeField, GroupEncoding},
Ciphersuite,
*,
};
use dkg::{Curves, Ristretto};
use dkg::Curves;
use serai_primitives::validator_sets::Session;
@@ -14,7 +14,7 @@ use serai_env as env;
use serai_db::{Get, DbTxn, Db as DbTrait, create_db, db_channel};
use primitives::EncodableG;
use ::key_gen::{KeyGenParams, KeyGen};
use ::key_gen::{Ristretto, KeyGenParams, KeyGen};
use scheduler::{SignableTransaction, TransactionFor};
use scanner::{ScannerFeed, Scanner, KeyFor, Scheduler};
use signers::{TransactionPublisher, Signers};
@@ -80,7 +80,7 @@ pub fn url() -> String {
}
fn key_gen<K: KeyGenParams>() -> KeyGen<K> {
fn read_key_from_env<C: Ciphersuite>(label: &'static str) -> Zeroizing<C::F> {
fn read_key_from_env<C: WrappedGroup>(label: &'static str) -> Zeroizing<C::F> {
let key_hex =
Zeroizing::new(env::var(label).unwrap_or_else(|| panic!("{label} wasn't provided")));
let bytes = Zeroizing::new(

View File

@@ -1,4 +1,4 @@
use ciphersuite::{group::GroupEncoding, Ciphersuite};
use ciphersuite::{group::GroupEncoding, *};
use dkg::{ThresholdKeys, Curves, Secp256k1};
use crate::{primitives::x_coord_to_even_point, scan::scanner};
@@ -18,7 +18,7 @@ impl key_gen::KeyGenParams for KeyGenParams {
}
fn encode_key(
key: <<Self::ExternalNetworkCiphersuite as Curves>::ToweringCurve as Ciphersuite>::G,
key: <<Self::ExternalNetworkCiphersuite as Curves>::ToweringCurve as WrappedGroup>::G,
) -> Vec<u8> {
let key = key.to_bytes();
let key: &[u8] = key.as_ref();
@@ -28,7 +28,7 @@ impl key_gen::KeyGenParams for KeyGenParams {
fn decode_key(
key: &[u8],
) -> Option<<<Self::ExternalNetworkCiphersuite as Curves>::ToweringCurve as Ciphersuite>::G> {
) -> Option<<<Self::ExternalNetworkCiphersuite as Curves>::ToweringCurve as WrappedGroup>::G> {
x_coord_to_even_point(key)
}
}

View File

@@ -1,7 +1,7 @@
use core::fmt;
use std::collections::HashMap;
use ciphersuite::Ciphersuite;
use ciphersuite::*;
use ciphersuite_kp256::Secp256k1;
use bitcoin_serai::bitcoin::block::{Header, Block as BBlock};
@@ -35,7 +35,7 @@ impl<D: Db> fmt::Debug for Block<D> {
impl<D: Db> primitives::Block for Block<D> {
type Header = BlockHeader;
type Key = <Secp256k1 as Ciphersuite>::G;
type Key = <Secp256k1 as WrappedGroup>::G;
type Address = Address;
type Output = Output;
type Eventuality = Eventuality;

View File

@@ -1,4 +1,4 @@
use ciphersuite::Ciphersuite;
use ciphersuite::*;
use ciphersuite_kp256::Secp256k1;
use bitcoin_serai::bitcoin::key::{Parity, XOnlyPublicKey};
@@ -7,7 +7,7 @@ pub(crate) mod output;
pub(crate) mod transaction;
pub(crate) mod block;
pub(crate) fn x_coord_to_even_point(key: &[u8]) -> Option<<Secp256k1 as Ciphersuite>::G> {
pub(crate) fn x_coord_to_even_point(key: &[u8]) -> Option<<Secp256k1 as WrappedGroup>::G> {
if key.len() != 32 {
None?
};

View File

@@ -1,6 +1,6 @@
use std::io;
use ciphersuite::Ciphersuite;
use ciphersuite::*;
use ciphersuite_kp256::Secp256k1;
use bitcoin_serai::{
@@ -58,7 +58,7 @@ pub(crate) struct Output {
impl Output {
pub(crate) fn new(
getter: &impl Get,
key: <Secp256k1 as Ciphersuite>::G,
key: <Secp256k1 as WrappedGroup>::G,
tx: &Transaction,
output: WalletOutput,
) -> Self {
@@ -74,7 +74,7 @@ impl Output {
}
pub(crate) fn new_with_presumed_origin(
key: <Secp256k1 as Ciphersuite>::G,
key: <Secp256k1 as WrappedGroup>::G,
tx: &Transaction,
presumed_origin: Option<Address>,
output: WalletOutput,
@@ -91,7 +91,7 @@ impl Output {
}
}
impl ReceivedOutput<<Secp256k1 as Ciphersuite>::G, Address> for Output {
impl ReceivedOutput<<Secp256k1 as WrappedGroup>::G, Address> for Output {
type Id = OutputId;
type TransactionId = [u8; 32];
@@ -111,7 +111,7 @@ impl ReceivedOutput<<Secp256k1 as Ciphersuite>::G, Address> for Output {
res
}
fn key(&self) -> <Secp256k1 as Ciphersuite>::G {
fn key(&self) -> <Secp256k1 as WrappedGroup>::G {
// We read the key from the script pubkey so we don't have to independently store it
let script = &self.output.output().script_pubkey;
@@ -124,7 +124,7 @@ impl ReceivedOutput<<Secp256k1 as Ciphersuite>::G, Address> for Output {
.expect("last item in scanned v1 Taproot script wasn't a valid x-only public key");
// The output's key minus the output's offset is the root key
key - (<Secp256k1 as Ciphersuite>::G::GENERATOR * self.output.offset())
key - (<Secp256k1 as WrappedGroup>::G::GENERATOR * self.output.offset())
}
fn presumed_origin(&self) -> Option<Address> {

View File

@@ -1,6 +1,6 @@
use std::{sync::LazyLock, collections::HashMap};
use ciphersuite::Ciphersuite;
use ciphersuite::*;
use ciphersuite_kp256::Secp256k1;
use bitcoin_serai::{
@@ -20,20 +20,20 @@ use primitives::OutputType;
use crate::hash_bytes;
// TODO: Bitcoin HD derivation, instead of these bespoke labels?
static BRANCH_BASE_OFFSET: LazyLock<<Secp256k1 as Ciphersuite>::F> =
static BRANCH_BASE_OFFSET: LazyLock<<Secp256k1 as WrappedGroup>::F> =
LazyLock::new(|| Secp256k1::hash_to_F(b"branch"));
static CHANGE_BASE_OFFSET: LazyLock<<Secp256k1 as Ciphersuite>::F> =
static CHANGE_BASE_OFFSET: LazyLock<<Secp256k1 as WrappedGroup>::F> =
LazyLock::new(|| Secp256k1::hash_to_F(b"change"));
static FORWARD_BASE_OFFSET: LazyLock<<Secp256k1 as Ciphersuite>::F> =
static FORWARD_BASE_OFFSET: LazyLock<<Secp256k1 as WrappedGroup>::F> =
LazyLock::new(|| Secp256k1::hash_to_F(b"forward"));
// Unfortunately, we have per-key offsets as it's the root key plus the base offset may not be
// even. While we could tweak the key until all derivations are even, that'd require significantly
// more tweaking. This algorithmic complexity is preferred.
pub(crate) fn offsets_for_key(
key: <Secp256k1 as Ciphersuite>::G,
) -> HashMap<OutputType, <Secp256k1 as Ciphersuite>::F> {
let mut offsets = HashMap::from([(OutputType::External, <Secp256k1 as Ciphersuite>::F::ZERO)]);
key: <Secp256k1 as WrappedGroup>::G,
) -> HashMap<OutputType, <Secp256k1 as WrappedGroup>::F> {
let mut offsets = HashMap::from([(OutputType::External, <Secp256k1 as WrappedGroup>::F::ZERO)]);
// We create an actual Bitcoin scanner as upon adding an offset, it yields the tweaked offset
// actually used
@@ -50,7 +50,7 @@ pub(crate) fn offsets_for_key(
offsets
}
pub(crate) fn scanner(key: <Secp256k1 as Ciphersuite>::G) -> Scanner {
pub(crate) fn scanner(key: <Secp256k1 as WrappedGroup>::G) -> Scanner {
let mut scanner = Scanner::new(key).unwrap();
for (_, offset) in offsets_for_key(key) {
let tweaked_offset = scanner.register_offset(offset).unwrap();

View File

@@ -1,6 +1,6 @@
use core::future::Future;
use ciphersuite::Ciphersuite;
use ciphersuite::*;
use ciphersuite_kp256::Secp256k1;
use bitcoin_serai::{
@@ -26,8 +26,8 @@ use crate::{
rpc::Rpc,
};
fn address_from_serai_key(key: <Secp256k1 as Ciphersuite>::G, kind: OutputType) -> Address {
let offset = <Secp256k1 as Ciphersuite>::G::GENERATOR * offsets_for_key(key)[&kind];
fn address_from_serai_key(key: <Secp256k1 as WrappedGroup>::G, kind: OutputType) -> Address {
let offset = <Secp256k1 as WrappedGroup>::G::GENERATOR * offsets_for_key(key)[&kind];
Address::new(
p2tr_script_buf(key + offset)
.expect("creating address from Serai key which wasn't properly tweaked"),
@@ -72,7 +72,7 @@ fn signable_transaction<D: Db>(
*/
payments.push((
// The generator is even so this is valid
p2tr_script_buf(<Secp256k1 as Ciphersuite>::G::GENERATOR).unwrap(),
p2tr_script_buf(<Secp256k1 as WrappedGroup>::G::GENERATOR).unwrap(),
// This uses the minimum output value allowed, as defined as a constant in bitcoin-serai
// TODO: Add a test for this comparing to bitcoin's `minimal_non_dust`
bitcoin_serai::wallet::DUST,

View File

@@ -1,4 +1,4 @@
use ciphersuite::Ciphersuite;
use ciphersuite::*;
use dkg::{ThresholdKeys, Curves, Secp256k1};
use ethereum_schnorr::PublicKey;
@@ -13,19 +13,19 @@ impl key_gen::KeyGenParams for KeyGenParams {
keys: &mut ThresholdKeys<<Self::ExternalNetworkCiphersuite as Curves>::ToweringCurve>,
) {
while PublicKey::new(keys.group_key()).is_none() {
*keys = keys.clone().offset(<<Secp256k1 as Curves>::ToweringCurve as Ciphersuite>::F::ONE);
*keys = keys.clone().offset(<<Secp256k1 as Curves>::ToweringCurve as WrappedGroup>::F::ONE);
}
}
fn encode_key(
key: <<Self::ExternalNetworkCiphersuite as Curves>::ToweringCurve as Ciphersuite>::G,
key: <<Self::ExternalNetworkCiphersuite as Curves>::ToweringCurve as WrappedGroup>::G,
) -> Vec<u8> {
PublicKey::new(key).unwrap().eth_repr().to_vec()
}
fn decode_key(
key: &[u8],
) -> Option<<<Self::ExternalNetworkCiphersuite as Curves>::ToweringCurve as Ciphersuite>::G> {
) -> Option<<<Self::ExternalNetworkCiphersuite as Curves>::ToweringCurve as WrappedGroup>::G> {
PublicKey::from_eth_repr(key.try_into().ok()?).map(|key| key.point())
}
}

View File

@@ -1,6 +1,6 @@
use std::collections::HashMap;
use ciphersuite::Ciphersuite;
use ciphersuite::*;
use ciphersuite_kp256::Secp256k1;
use serai_client::networks::ethereum::Address;
@@ -41,7 +41,7 @@ pub(crate) struct FullEpoch {
impl primitives::Block for FullEpoch {
type Header = Epoch;
type Key = <Secp256k1 as Ciphersuite>::G;
type Key = <Secp256k1 as WrappedGroup>::G;
type Address = Address;
type Output = Output;
type Eventuality = Eventuality;

View File

@@ -2,7 +2,7 @@ use std::{io, collections::HashMap};
use rand_core::{RngCore, CryptoRng};
use ciphersuite::Ciphersuite;
use ciphersuite::*;
use ciphersuite_kp256::Secp256k1;
use frost::{
dkg::{Participant, ThresholdKeys},
@@ -24,10 +24,10 @@ pub struct EthereumHram;
impl Hram<Secp256k1> for EthereumHram {
#[allow(non_snake_case)]
fn hram(
R: &<Secp256k1 as Ciphersuite>::G,
A: &<Secp256k1 as Ciphersuite>::G,
R: &<Secp256k1 as WrappedGroup>::G,
A: &<Secp256k1 as WrappedGroup>::G,
m: &[u8],
) -> <Secp256k1 as Ciphersuite>::F {
) -> <Secp256k1 as WrappedGroup>::F {
Signature::challenge(*R, &PublicKey::new(*A).unwrap(), m)
}
}

View File

@@ -1,6 +1,6 @@
use std::io;
use ciphersuite::{group::GroupEncoding, Ciphersuite};
use ciphersuite::{group::GroupEncoding, *};
use ciphersuite_kp256::Secp256k1;
use alloy_core::primitives::U256;
@@ -62,10 +62,10 @@ impl AsMut<[u8]> for OutputId {
#[derive(Clone, PartialEq, Eq, Debug)]
pub(crate) enum Output {
Output { key: <Secp256k1 as Ciphersuite>::G, instruction: EthereumInInstruction },
Eventuality { key: <Secp256k1 as Ciphersuite>::G, nonce: u64 },
Output { key: <Secp256k1 as WrappedGroup>::G, instruction: EthereumInInstruction },
Eventuality { key: <Secp256k1 as WrappedGroup>::G, nonce: u64 },
}
impl ReceivedOutput<<Secp256k1 as Ciphersuite>::G, Address> for Output {
impl ReceivedOutput<<Secp256k1 as WrappedGroup>::G, Address> for Output {
type Id = OutputId;
type TransactionId = [u8; 32];
@@ -108,7 +108,7 @@ impl ReceivedOutput<<Secp256k1 as Ciphersuite>::G, Address> for Output {
}
}
fn key(&self) -> <Secp256k1 as Ciphersuite>::G {
fn key(&self) -> <Secp256k1 as WrappedGroup>::G {
match self {
Output::Output { key, .. } | Output::Eventuality { key, .. } => *key,
}

View File

@@ -29,7 +29,9 @@ rand_chacha = { version = "0.3", default-features = false, features = ["std"] }
blake2 = { version = "0.11.0-rc.0", default-features = false, features = ["alloc"] }
transcript = { package = "flexible-transcript", path = "../../crypto/transcript", default-features = false, features = ["std"] }
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std"] }
dkg = { package = "dkg-evrf", path = "../../crypto/dkg/evrf", default-features = false, features = ["std", "ristretto"] }
embedwards25519 = { path = "../../crypto/embedwards25519", default-features = false, features = ["std"] }
dkg = { package = "dkg-evrf", path = "../../crypto/dkg/evrf", default-features = false, features = ["std"] }
frost = { package = "modular-frost", path = "../../crypto/frost", default-features = false, features = ["ristretto"] }
# Substrate
serai-primitives = { path = "../../substrate/primitives", default-features = false, features = ["std"] }

View File

@@ -3,7 +3,7 @@ use std::collections::HashMap;
use zeroize::Zeroizing;
use ciphersuite::{group::GroupEncoding, Ciphersuite};
use ciphersuite::{group::GroupEncoding, *};
use dkg::*;
use serai_primitives::validator_sets::Session;
@@ -11,15 +11,15 @@ use serai_primitives::validator_sets::Session;
use borsh::{BorshSerialize, BorshDeserialize};
use serai_db::{Get, DbTxn};
use crate::KeyGenParams;
use crate::{Ristretto, KeyGenParams};
pub(crate) struct Params<P: KeyGenParams> {
pub(crate) t: u16,
pub(crate) n: u16,
pub(crate) substrate_evrf_public_keys:
Vec<<<Ristretto as Curves>::EmbeddedCurve as Ciphersuite>::G>,
Vec<<<Ristretto as Curves>::EmbeddedCurve as WrappedGroup>::G>,
pub(crate) network_evrf_public_keys:
Vec<<<P::ExternalNetworkCiphersuite as Curves>::EmbeddedCurve as Ciphersuite>::G>,
Vec<<<P::ExternalNetworkCiphersuite as Curves>::EmbeddedCurve as WrappedGroup>::G>,
}
#[derive(BorshSerialize, BorshDeserialize)]
@@ -85,17 +85,16 @@ impl<P: KeyGenParams> KeyGenDb<P> {
.substrate_evrf_public_keys
.into_iter()
.map(|key| {
<<Ristretto as Curves>::EmbeddedCurve as Ciphersuite>::read_G(&mut key.as_slice())
.unwrap()
<<Ristretto as Curves>::EmbeddedCurve as GroupIo>::read_G(&mut key.as_slice()).unwrap()
})
.collect(),
network_evrf_public_keys: params
.network_evrf_public_keys
.into_iter()
.map(|key| {
<<P::ExternalNetworkCiphersuite as Curves>::EmbeddedCurve as Ciphersuite>::read_G::<
&[u8],
>(&mut key.as_ref())
<<P::ExternalNetworkCiphersuite as Curves>::EmbeddedCurve as GroupIo>::read_G::<&[u8]>(
&mut key.as_ref(),
)
.unwrap()
})
.collect(),

Some files were not shown because too many files have changed in this diff Show More