mirror of
https://github.com/serai-dex/serai.git
synced 2025-02-03 03:36:35 +00:00
Merge branch 'develop' into tendermint
This commit is contained in:
commit
c9334ee694
46 changed files with 828 additions and 669 deletions
687
Cargo.lock
generated
687
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,7 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"common/zalloc",
|
||||
|
||||
"crypto/transcript",
|
||||
|
||||
"crypto/dalek-ff-group",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "monero-serai"
|
||||
version = "0.1.1-alpha"
|
||||
version = "0.1.2-alpha"
|
||||
description = "A modern Monero transaction library"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/coins/monero"
|
||||
|
@ -33,9 +33,9 @@ group = { version = "0.12" }
|
|||
dalek-ff-group = { path = "../../crypto/dalek-ff-group", version = "0.1" }
|
||||
multiexp = { path = "../../crypto/multiexp", version = "0.2", features = ["batch"] }
|
||||
|
||||
transcript = { package = "flexible-transcript", path = "../../crypto/transcript", version = "0.1", features = ["recommended"], optional = true }
|
||||
frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.4", features = ["ed25519"], optional = true }
|
||||
dleq = { path = "../../crypto/dleq", version = "0.1", features = ["serialize"], optional = true }
|
||||
transcript = { package = "flexible-transcript", path = "../../crypto/transcript", version = "0.2", features = ["recommended"], optional = true }
|
||||
frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.5", features = ["ed25519"], optional = true }
|
||||
dleq = { path = "../../crypto/dleq", version = "0.2", features = ["serialize"], optional = true }
|
||||
|
||||
monero-generators = { path = "generators", version = "0.1" }
|
||||
|
||||
|
@ -55,7 +55,7 @@ monero-generators = { path = "generators", version = "0.1" }
|
|||
[dev-dependencies]
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
|
||||
frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.4", features = ["ed25519", "tests"] }
|
||||
frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.5", features = ["ed25519", "tests"] }
|
||||
|
||||
[features]
|
||||
multisig = ["rand_chacha", "blake2", "transcript", "frost", "dleq"]
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
use core::ops::Deref;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use thiserror::Error;
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||
use subtle::{ConstantTimeEq, Choice, CtOption};
|
||||
|
||||
use curve25519_dalek::{
|
||||
|
@ -233,7 +235,7 @@ impl Clsag {
|
|||
/// sum_outputs is for the sum of the outputs' commitment masks.
|
||||
pub fn sign<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
mut inputs: Vec<(Scalar, EdwardsPoint, ClsagInput)>,
|
||||
mut inputs: Vec<(Zeroizing<Scalar>, EdwardsPoint, ClsagInput)>,
|
||||
sum_outputs: Scalar,
|
||||
msg: [u8; 32],
|
||||
) -> Vec<(Clsag, EdwardsPoint)> {
|
||||
|
@ -247,17 +249,19 @@ impl Clsag {
|
|||
sum_pseudo_outs += mask;
|
||||
}
|
||||
|
||||
let mut nonce = random_scalar(rng);
|
||||
let mut nonce = Zeroizing::new(random_scalar(rng));
|
||||
let (mut clsag, pseudo_out, p, c) = Clsag::sign_core(
|
||||
rng,
|
||||
&inputs[i].1,
|
||||
&inputs[i].2,
|
||||
mask,
|
||||
&msg,
|
||||
&nonce * &ED25519_BASEPOINT_TABLE,
|
||||
nonce * hash_to_point(inputs[i].2.decoys.ring[usize::from(inputs[i].2.decoys.i)][0]),
|
||||
nonce.deref() * &ED25519_BASEPOINT_TABLE,
|
||||
nonce.deref() *
|
||||
hash_to_point(inputs[i].2.decoys.ring[usize::from(inputs[i].2.decoys.i)][0]),
|
||||
);
|
||||
clsag.s[usize::from(inputs[i].2.decoys.i)] = nonce - ((p * inputs[i].0) + c);
|
||||
clsag.s[usize::from(inputs[i].2.decoys.i)] =
|
||||
(-((p * inputs[i].0.deref()) + c)) + nonce.deref();
|
||||
inputs[i].0.zeroize();
|
||||
nonce.zeroize();
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use core::fmt::Debug;
|
||||
use core::{ops::Deref, fmt::Debug};
|
||||
use std::{
|
||||
io::{self, Read, Write},
|
||||
sync::{Arc, RwLock},
|
||||
|
@ -7,7 +7,7 @@ use std::{
|
|||
use rand_core::{RngCore, CryptoRng, SeedableRng};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||
|
||||
use curve25519_dalek::{
|
||||
constants::ED25519_BASEPOINT_TABLE,
|
||||
|
@ -41,7 +41,7 @@ impl ClsagInput {
|
|||
// Doesn't domain separate as this is considered part of the larger CLSAG proof
|
||||
|
||||
// Ring index
|
||||
transcript.append_message(b"ring_index", &[self.decoys.i]);
|
||||
transcript.append_message(b"ring_index", [self.decoys.i]);
|
||||
|
||||
// Ring
|
||||
let mut ring = vec![];
|
||||
|
@ -52,7 +52,7 @@ impl ClsagInput {
|
|||
ring.extend(pair[0].compress().to_bytes());
|
||||
ring.extend(pair[1].compress().to_bytes());
|
||||
}
|
||||
transcript.append_message(b"ring", &ring);
|
||||
transcript.append_message(b"ring", ring);
|
||||
|
||||
// Doesn't include the commitment's parts as the above ring + index includes the commitment
|
||||
// The only potential malleability would be if the G/H relationship is known breaking the
|
||||
|
@ -157,7 +157,7 @@ impl Algorithm<Ed25519> for ClsagMultisig {
|
|||
view: &ThresholdView<Ed25519>,
|
||||
) -> ClsagAddendum {
|
||||
ClsagAddendum {
|
||||
key_image: dfg::EdwardsPoint(self.H * view.secret_share().0),
|
||||
key_image: dfg::EdwardsPoint(self.H) * view.secret_share().deref(),
|
||||
dleq: DLEqProof::prove(
|
||||
rng,
|
||||
// Doesn't take in a larger transcript object due to the usage of this
|
||||
|
@ -167,7 +167,7 @@ impl Algorithm<Ed25519> for ClsagMultisig {
|
|||
// try to merge later in some form, when it should instead just merge xH (as it does)
|
||||
&mut dleq_transcript(),
|
||||
&[dfg::EdwardsPoint::generator(), dfg::EdwardsPoint(self.H)],
|
||||
dfg::Scalar(view.secret_share().0),
|
||||
view.secret_share(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -195,10 +195,10 @@ impl Algorithm<Ed25519> for ClsagMultisig {
|
|||
if self.image.is_identity() {
|
||||
self.transcript.domain_separate(b"CLSAG");
|
||||
self.input().transcript(&mut self.transcript);
|
||||
self.transcript.append_message(b"mask", &self.mask().to_bytes());
|
||||
self.transcript.append_message(b"mask", self.mask().to_bytes());
|
||||
}
|
||||
|
||||
self.transcript.append_message(b"participant", &l.to_be_bytes());
|
||||
self.transcript.append_message(b"participant", l.to_be_bytes());
|
||||
|
||||
addendum
|
||||
.dleq
|
||||
|
@ -209,9 +209,7 @@ impl Algorithm<Ed25519> for ClsagMultisig {
|
|||
)
|
||||
.map_err(|_| FrostError::InvalidPreprocess(l))?;
|
||||
|
||||
self
|
||||
.transcript
|
||||
.append_message(b"key_image_share", addendum.key_image.compress().to_bytes().as_ref());
|
||||
self.transcript.append_message(b"key_image_share", addendum.key_image.compress().to_bytes());
|
||||
self.image += addendum.key_image.0;
|
||||
|
||||
Ok(())
|
||||
|
@ -225,7 +223,7 @@ impl Algorithm<Ed25519> for ClsagMultisig {
|
|||
&mut self,
|
||||
view: &ThresholdView<Ed25519>,
|
||||
nonce_sums: &[Vec<dfg::EdwardsPoint>],
|
||||
nonces: &[dfg::Scalar],
|
||||
nonces: Vec<Zeroizing<dfg::Scalar>>,
|
||||
msg: &[u8],
|
||||
) -> dfg::Scalar {
|
||||
// Use the transcript to get a seeded random number generator
|
||||
|
@ -249,7 +247,7 @@ impl Algorithm<Ed25519> for ClsagMultisig {
|
|||
);
|
||||
self.interim = Some(Interim { p, c, clsag, pseudo_out });
|
||||
|
||||
nonces[0] - (dfg::Scalar(p) * view.secret_share())
|
||||
(-(dfg::Scalar(p) * view.secret_share().deref())) + nonces[0].deref()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use zeroize::Zeroize;
|
||||
use core::ops::Deref;
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar, edwards::EdwardsPoint};
|
||||
|
||||
|
@ -17,10 +19,8 @@ use crate::{
|
|||
};
|
||||
|
||||
/// Generate a key image for a given key. Defined as `x * hash_to_point(xG)`.
|
||||
pub fn generate_key_image(mut secret: Scalar) -> EdwardsPoint {
|
||||
let res = secret * hash_to_point(&secret * &ED25519_BASEPOINT_TABLE);
|
||||
secret.zeroize();
|
||||
res
|
||||
pub fn generate_key_image(secret: &Zeroizing<Scalar>) -> EdwardsPoint {
|
||||
hash_to_point(&ED25519_BASEPOINT_TABLE * secret.deref()) * secret.deref()
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use core::ops::Deref;
|
||||
#[cfg(feature = "multisig")]
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
use rand_core::{RngCore, OsRng};
|
||||
|
||||
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar};
|
||||
|
@ -35,29 +37,30 @@ fn clsag() {
|
|||
for real in 0 .. RING_LEN {
|
||||
let msg = [1; 32];
|
||||
|
||||
let mut secrets = [Scalar::zero(), Scalar::zero()];
|
||||
let mut secrets = (Zeroizing::new(Scalar::zero()), Scalar::zero());
|
||||
let mut ring = vec![];
|
||||
for i in 0 .. RING_LEN {
|
||||
let dest = random_scalar(&mut OsRng);
|
||||
let dest = Zeroizing::new(random_scalar(&mut OsRng));
|
||||
let mask = random_scalar(&mut OsRng);
|
||||
let amount;
|
||||
if i == u64::from(real) {
|
||||
secrets = [dest, mask];
|
||||
secrets = (dest.clone(), mask);
|
||||
amount = AMOUNT;
|
||||
} else {
|
||||
amount = OsRng.next_u64();
|
||||
}
|
||||
ring.push([&dest * &ED25519_BASEPOINT_TABLE, Commitment::new(mask, amount).calculate()]);
|
||||
ring
|
||||
.push([dest.deref() * &ED25519_BASEPOINT_TABLE, Commitment::new(mask, amount).calculate()]);
|
||||
}
|
||||
|
||||
let image = generate_key_image(secrets[0]);
|
||||
let image = generate_key_image(&secrets.0);
|
||||
let (clsag, pseudo_out) = Clsag::sign(
|
||||
&mut OsRng,
|
||||
vec![(
|
||||
secrets[0],
|
||||
secrets.0,
|
||||
image,
|
||||
ClsagInput::new(
|
||||
Commitment::new(secrets[1], AMOUNT),
|
||||
Commitment::new(secrets.1, AMOUNT),
|
||||
Decoys {
|
||||
i: u8::try_from(real).unwrap(),
|
||||
offsets: (1 ..= RING_LEN).into_iter().collect(),
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use core::ops::Deref;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
use rand::seq::SliceRandom;
|
||||
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||
|
||||
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar, edwards::EdwardsPoint};
|
||||
|
||||
|
@ -108,9 +110,9 @@ async fn prepare_inputs<R: RngCore + CryptoRng>(
|
|||
rpc: &Rpc,
|
||||
ring_len: usize,
|
||||
inputs: &[SpendableOutput],
|
||||
spend: &Scalar,
|
||||
spend: &Zeroizing<Scalar>,
|
||||
tx: &mut Transaction,
|
||||
) -> Result<Vec<(Scalar, EdwardsPoint, ClsagInput)>, TransactionError> {
|
||||
) -> Result<Vec<(Zeroizing<Scalar>, EdwardsPoint, ClsagInput)>, TransactionError> {
|
||||
let mut signable = Vec::with_capacity(inputs.len());
|
||||
|
||||
// Select decoys
|
||||
|
@ -125,9 +127,11 @@ async fn prepare_inputs<R: RngCore + CryptoRng>(
|
|||
.map_err(TransactionError::RpcError)?;
|
||||
|
||||
for (i, input) in inputs.iter().enumerate() {
|
||||
let input_spend = Zeroizing::new(input.key_offset() + spend.deref());
|
||||
let image = generate_key_image(&input_spend);
|
||||
signable.push((
|
||||
spend + input.key_offset(),
|
||||
generate_key_image(spend + input.key_offset()),
|
||||
input_spend,
|
||||
image,
|
||||
ClsagInput::new(input.commitment().clone(), decoys[i].clone())
|
||||
.map_err(TransactionError::ClsagError)?,
|
||||
));
|
||||
|
@ -358,16 +362,16 @@ impl SignableTransaction {
|
|||
&mut self,
|
||||
rng: &mut R,
|
||||
rpc: &Rpc,
|
||||
spend: &Scalar,
|
||||
spend: &Zeroizing<Scalar>,
|
||||
) -> Result<Transaction, TransactionError> {
|
||||
let mut images = Vec::with_capacity(self.inputs.len());
|
||||
for input in &self.inputs {
|
||||
let mut offset = spend + input.key_offset();
|
||||
if (&offset * &ED25519_BASEPOINT_TABLE) != input.key() {
|
||||
let mut offset = Zeroizing::new(spend.deref() + input.key_offset());
|
||||
if (offset.deref() * &ED25519_BASEPOINT_TABLE) != input.key() {
|
||||
Err(TransactionError::WrongPrivateKey)?;
|
||||
}
|
||||
|
||||
images.push(generate_key_image(offset));
|
||||
images.push(generate_key_image(&offset));
|
||||
offset.zeroize();
|
||||
}
|
||||
images.sort_by(key_image_sort);
|
||||
|
|
|
@ -90,24 +90,24 @@ impl SignableTransaction {
|
|||
// Include the height we're using for our data
|
||||
// The data itself will be included, making this unnecessary, yet a lot of this is technically
|
||||
// unnecessary. Anything which further increases security at almost no cost should be followed
|
||||
transcript.append_message(b"height", &u64::try_from(height).unwrap().to_le_bytes());
|
||||
transcript.append_message(b"height", u64::try_from(height).unwrap().to_le_bytes());
|
||||
// Also include the spend_key as below only the key offset is included, so this transcripts the
|
||||
// sum product
|
||||
// Useful as transcripting the sum product effectively transcripts the key image, further
|
||||
// guaranteeing the one time properties noted below
|
||||
transcript.append_message(b"spend_key", &keys.group_key().0.compress().to_bytes());
|
||||
transcript.append_message(b"spend_key", keys.group_key().0.compress().to_bytes());
|
||||
for input in &self.inputs {
|
||||
// These outputs can only be spent once. Therefore, it forces all RNGs derived from this
|
||||
// transcript (such as the one used to create one time keys) to be unique
|
||||
transcript.append_message(b"input_hash", &input.output.absolute.tx);
|
||||
transcript.append_message(b"input_output_index", &[input.output.absolute.o]);
|
||||
transcript.append_message(b"input_hash", input.output.absolute.tx);
|
||||
transcript.append_message(b"input_output_index", [input.output.absolute.o]);
|
||||
// Not including this, with a doxxed list of payments, would allow brute forcing the inputs
|
||||
// to determine RNG seeds and therefore the true spends
|
||||
transcript.append_message(b"input_shared_key", &input.key_offset().to_bytes());
|
||||
transcript.append_message(b"input_shared_key", input.key_offset().to_bytes());
|
||||
}
|
||||
for payment in &self.payments {
|
||||
transcript.append_message(b"payment_address", payment.0.to_string().as_bytes());
|
||||
transcript.append_message(b"payment_amount", &payment.1.to_le_bytes());
|
||||
transcript.append_message(b"payment_amount", payment.1.to_le_bytes());
|
||||
}
|
||||
|
||||
// Sort included before cloning it around
|
||||
|
@ -243,7 +243,7 @@ impl SignMachine<Transaction> for TransactionSignMachine {
|
|||
// While each CLSAG will do this as they need to for security, they have their own
|
||||
// transcripts cloned from this TX's initial premise's transcript. For our TX
|
||||
// transcript to have the CLSAG data for entropy, it'll have to be added ourselves here
|
||||
self.transcript.append_message(b"participant", &(*l).to_be_bytes());
|
||||
self.transcript.append_message(b"participant", (*l).to_be_bytes());
|
||||
|
||||
let preprocess = if *l == self.i {
|
||||
self.our_preprocess[c].clone()
|
||||
|
@ -254,7 +254,7 @@ impl SignMachine<Transaction> for TransactionSignMachine {
|
|||
{
|
||||
let mut buf = vec![];
|
||||
preprocess.write(&mut buf).unwrap();
|
||||
self.transcript.append_message(b"preprocess", &buf);
|
||||
self.transcript.append_message(b"preprocess", buf);
|
||||
}
|
||||
|
||||
// While here, calculate the key image
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use core::ops::Deref;
|
||||
use std::{sync::Mutex, collections::HashSet};
|
||||
#[cfg(feature = "multisig")]
|
||||
use std::collections::HashMap;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
use rand_core::OsRng;
|
||||
|
||||
#[cfg(feature = "multisig")]
|
||||
|
@ -55,11 +56,11 @@ async fn send_core(test: usize, multisig: bool) {
|
|||
let rpc = rpc().await;
|
||||
|
||||
// Generate an address
|
||||
let spend = random_scalar(&mut OsRng);
|
||||
let spend = Zeroizing::new(random_scalar(&mut OsRng));
|
||||
#[allow(unused_mut)]
|
||||
let mut view = random_scalar(&mut OsRng);
|
||||
#[allow(unused_mut)]
|
||||
let mut spend_pub = &spend * &ED25519_BASEPOINT_TABLE;
|
||||
let mut spend_pub = spend.deref() * &ED25519_BASEPOINT_TABLE;
|
||||
|
||||
#[cfg(feature = "multisig")]
|
||||
let keys = key_gen::<_, Ed25519>(&mut OsRng);
|
||||
|
|
20
common/zalloc/Cargo.toml
Normal file
20
common/zalloc/Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "zalloc"
|
||||
version = "0.1.0"
|
||||
description = "An allocator wrapper which zeroizes memory on dealloc"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/common/zalloc"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
keywords = []
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[dependencies]
|
||||
zeroize = "1.5"
|
||||
|
||||
[features]
|
||||
# Commented for now as it requires nightly and we don't use nightly
|
||||
# allocator = []
|
21
common/zalloc/LICENSE
Normal file
21
common/zalloc/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Luke Parker
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
46
common/zalloc/src/lib.rs
Normal file
46
common/zalloc/src/lib.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![cfg_attr(feature = "allocator", feature(allocator_api))]
|
||||
|
||||
//! Implementation of a Zeroizing Allocator, enabling zeroizing memory on deallocation.
|
||||
//! This can either be used with Box (requires nightly and the "allocator" feature) to provide the
|
||||
//! functionality of zeroize on types which don't implement zeroize, or used as a wrapper around
|
||||
//! the global allocator to ensure *all* memory is zeroized.
|
||||
|
||||
use core::{
|
||||
slice,
|
||||
alloc::{Layout, GlobalAlloc},
|
||||
};
|
||||
|
||||
use zeroize::Zeroize;
|
||||
|
||||
/// An allocator wrapper which zeroizes its memory on dealloc.
|
||||
pub struct ZeroizingAlloc<T>(pub T);
|
||||
|
||||
#[cfg(feature = "allocator")]
|
||||
use core::{
|
||||
ptr::NonNull,
|
||||
alloc::{AllocError, Allocator},
|
||||
};
|
||||
#[cfg(feature = "allocator")]
|
||||
unsafe impl<T: Allocator> Allocator for ZeroizingAlloc<T> {
|
||||
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
|
||||
self.0.allocate(layout)
|
||||
}
|
||||
|
||||
unsafe fn deallocate(&self, mut ptr: NonNull<u8>, layout: Layout) {
|
||||
slice::from_raw_parts_mut(ptr.as_mut(), layout.size()).zeroize();
|
||||
self.0.deallocate(ptr, layout);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: GlobalAlloc> GlobalAlloc for ZeroizingAlloc<T> {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
self.0.alloc(layout)
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||||
slice::from_raw_parts_mut(ptr, layout.size()).zeroize();
|
||||
self.0.dealloc(ptr, layout);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "dkg"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
description = "Distributed key generation over ff/group"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/dkg"
|
||||
|
@ -31,12 +31,12 @@ group = "0.12"
|
|||
|
||||
ciphersuite = { path = "../ciphersuite", version = "0.1", features = ["std"] }
|
||||
|
||||
transcript = { package = "flexible-transcript", path = "../transcript", features = ["recommended"], version = "^0.1.3" }
|
||||
transcript = { package = "flexible-transcript", path = "../transcript", version = "0.2", features = ["recommended"] }
|
||||
|
||||
multiexp = { path = "../multiexp", version = "0.2", features = ["batch"] }
|
||||
|
||||
schnorr = { package = "schnorr-signatures", path = "../schnorr", version = "0.1.0" }
|
||||
dleq = { path = "../dleq", version = "^0.1.2", features = ["serialize"] }
|
||||
schnorr = { package = "schnorr-signatures", path = "../schnorr", version = "0.2" }
|
||||
dleq = { path = "../dleq", version = "0.2", features = ["serialize"] }
|
||||
|
||||
[features]
|
||||
tests = []
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
use std::{
|
||||
marker::PhantomData,
|
||||
ops::Deref,
|
||||
io::{self, Read, Write},
|
||||
collections::HashMap,
|
||||
};
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||
|
||||
use digest::Digest;
|
||||
use hkdf::{Hkdf, hmac::SimpleHmac};
|
||||
|
@ -48,12 +49,6 @@ pub struct Commitments<C: Ciphersuite> {
|
|||
cached_msg: Vec<u8>,
|
||||
sig: SchnorrSignature<C>,
|
||||
}
|
||||
impl<C: Ciphersuite> Drop for Commitments<C> {
|
||||
fn drop(&mut self) {
|
||||
self.zeroize();
|
||||
}
|
||||
}
|
||||
impl<C: Ciphersuite> ZeroizeOnDrop for Commitments<C> {}
|
||||
|
||||
impl<C: Ciphersuite> Commitments<C> {
|
||||
pub fn read<R: Read>(reader: &mut R, params: ThresholdParams) -> io::Result<Self> {
|
||||
|
@ -117,9 +112,9 @@ impl<C: Ciphersuite> KeyGenMachine<C> {
|
|||
|
||||
for i in 0 .. t {
|
||||
// Step 1: Generate t random values to form a polynomial with
|
||||
coefficients.push(C::random_nonzero_F(&mut *rng));
|
||||
coefficients.push(Zeroizing::new(C::random_nonzero_F(&mut *rng)));
|
||||
// Step 3: Generate public commitments
|
||||
commitments.push(C::generator() * coefficients[i]);
|
||||
commitments.push(C::generator() * coefficients[i].deref());
|
||||
cached_msg.extend(commitments[i].to_bytes().as_ref());
|
||||
}
|
||||
|
||||
|
@ -127,27 +122,22 @@ impl<C: Ciphersuite> KeyGenMachine<C> {
|
|||
// It would probably be perfectly fine to use one of our polynomial elements, yet doing so
|
||||
// puts the integrity of FROST at risk. While there's almost no way it could, as it's used in
|
||||
// an ECDH with validated group elemnents, better to avoid any questions on it
|
||||
let enc_key = C::random_nonzero_F(&mut *rng);
|
||||
let pub_enc_key = C::generator() * enc_key;
|
||||
let enc_key = Zeroizing::new(C::random_nonzero_F(&mut *rng));
|
||||
let pub_enc_key = C::generator() * enc_key.deref();
|
||||
cached_msg.extend(pub_enc_key.to_bytes().as_ref());
|
||||
|
||||
// Step 2: Provide a proof of knowledge
|
||||
let mut r = C::random_nonzero_F(rng);
|
||||
let r = Zeroizing::new(C::random_nonzero_F(rng));
|
||||
let nonce = C::generator() * r.deref();
|
||||
let sig = SchnorrSignature::<C>::sign(
|
||||
coefficients[0],
|
||||
&coefficients[0],
|
||||
// This could be deterministic as the PoK is a singleton never opened up to cooperative
|
||||
// discussion
|
||||
// There's no reason to spend the time and effort to make this deterministic besides a
|
||||
// general obsession with canonicity and determinism though
|
||||
r,
|
||||
challenge::<C>(
|
||||
&self.context,
|
||||
self.params.i(),
|
||||
(C::generator() * r).to_bytes().as_ref(),
|
||||
&cached_msg,
|
||||
),
|
||||
challenge::<C>(&self.context, self.params.i(), nonce.to_bytes().as_ref(), &cached_msg),
|
||||
);
|
||||
r.zeroize();
|
||||
|
||||
// Step 4: Broadcast
|
||||
(
|
||||
|
@ -157,19 +147,20 @@ impl<C: Ciphersuite> KeyGenMachine<C> {
|
|||
coefficients,
|
||||
our_commitments: commitments.clone(),
|
||||
enc_key,
|
||||
pub_enc_key,
|
||||
},
|
||||
Commitments { commitments, enc_key: pub_enc_key, cached_msg, sig },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn polynomial<F: PrimeField>(coefficients: &[F], l: u16) -> F {
|
||||
fn polynomial<F: PrimeField + Zeroize>(coefficients: &[Zeroizing<F>], l: u16) -> Zeroizing<F> {
|
||||
let l = F::from(u64::from(l));
|
||||
let mut share = F::zero();
|
||||
let mut share = Zeroizing::new(F::zero());
|
||||
for (idx, coefficient) in coefficients.iter().rev().enumerate() {
|
||||
share += coefficient;
|
||||
*share += coefficient.deref();
|
||||
if idx != (coefficients.len() - 1) {
|
||||
share *= l;
|
||||
*share *= l;
|
||||
}
|
||||
}
|
||||
share
|
||||
|
@ -250,16 +241,11 @@ fn create_ciphers<C: Ciphersuite>(
|
|||
pub struct SecretShareMachine<C: Ciphersuite> {
|
||||
params: ThresholdParams,
|
||||
context: String,
|
||||
coefficients: Vec<C::F>,
|
||||
coefficients: Vec<Zeroizing<C::F>>,
|
||||
our_commitments: Vec<C::G>,
|
||||
enc_key: C::F,
|
||||
enc_key: Zeroizing<C::F>,
|
||||
pub_enc_key: C::G,
|
||||
}
|
||||
impl<C: Ciphersuite> Drop for SecretShareMachine<C> {
|
||||
fn drop(&mut self) {
|
||||
self.zeroize()
|
||||
}
|
||||
}
|
||||
impl<C: Ciphersuite> ZeroizeOnDrop for SecretShareMachine<C> {}
|
||||
|
||||
impl<C: Ciphersuite> SecretShareMachine<C> {
|
||||
/// Verify the data from the previous round (canonicity, PoKs, message authenticity)
|
||||
|
@ -276,7 +262,6 @@ impl<C: Ciphersuite> SecretShareMachine<C> {
|
|||
.drain()
|
||||
.map(|(l, mut msg)| {
|
||||
enc_keys.insert(l, msg.enc_key);
|
||||
msg.enc_key.zeroize();
|
||||
|
||||
// Step 5: Validate each proof of knowledge
|
||||
// This is solely the prep step for the latter batch verification
|
||||
|
@ -309,7 +294,7 @@ impl<C: Ciphersuite> SecretShareMachine<C> {
|
|||
let (commitments, mut enc_keys) = self.verify_r1(&mut *rng, commitments)?;
|
||||
|
||||
// Step 1: Generate secret shares for all other parties
|
||||
let mut sender = (C::generator() * self.enc_key).to_bytes();
|
||||
let sender = self.pub_enc_key.to_bytes();
|
||||
let mut ciphers = HashMap::new();
|
||||
let mut res = HashMap::new();
|
||||
for l in 1 ..= self.params.n() {
|
||||
|
@ -321,7 +306,7 @@ impl<C: Ciphersuite> SecretShareMachine<C> {
|
|||
|
||||
let (mut cipher_send, cipher_recv) = {
|
||||
let receiver = enc_keys.get_mut(&l).unwrap();
|
||||
let mut ecdh = (*receiver * self.enc_key).to_bytes();
|
||||
let mut ecdh = (*receiver * self.enc_key.deref()).to_bytes();
|
||||
|
||||
create_ciphers::<C>(sender, &mut receiver.to_bytes(), &mut ecdh)
|
||||
};
|
||||
|
@ -338,11 +323,9 @@ impl<C: Ciphersuite> SecretShareMachine<C> {
|
|||
share_bytes.as_mut().zeroize();
|
||||
}
|
||||
self.enc_key.zeroize();
|
||||
sender.as_mut().zeroize();
|
||||
|
||||
// Calculate our own share
|
||||
let share = polynomial(&self.coefficients, self.params.i());
|
||||
|
||||
self.coefficients.zeroize();
|
||||
|
||||
Ok((KeyMachine { params: self.params, secret: share, commitments, ciphers }, res))
|
||||
|
@ -352,7 +335,7 @@ impl<C: Ciphersuite> SecretShareMachine<C> {
|
|||
/// Final step of the key generation protocol.
|
||||
pub struct KeyMachine<C: Ciphersuite> {
|
||||
params: ThresholdParams,
|
||||
secret: C::F,
|
||||
secret: Zeroizing<C::F>,
|
||||
ciphers: HashMap<u16, ChaCha20>,
|
||||
commitments: HashMap<u16, Vec<C::G>>,
|
||||
}
|
||||
|
@ -390,9 +373,6 @@ impl<C: Ciphersuite> KeyMachine<C> {
|
|||
rng: &mut R,
|
||||
mut shares: HashMap<u16, SecretShare<C::F>>,
|
||||
) -> Result<ThresholdCore<C>, DkgError> {
|
||||
let mut secret_share = self.secret;
|
||||
self.secret.zeroize();
|
||||
|
||||
validate_map(&shares, &(1 ..= self.params.n()).collect::<Vec<_>>(), self.params.i())?;
|
||||
|
||||
// Calculate the exponent for a given participant and apply it to a series of commitments
|
||||
|
@ -414,17 +394,19 @@ impl<C: Ciphersuite> KeyMachine<C> {
|
|||
cipher.apply_keystream(share_bytes.0.as_mut());
|
||||
drop(cipher);
|
||||
|
||||
let mut share: C::F =
|
||||
Option::from(C::F::from_repr(share_bytes.0)).ok_or(DkgError::InvalidShare(l))?;
|
||||
let mut share = Zeroizing::new(
|
||||
Option::<C::F>::from(C::F::from_repr(share_bytes.0)).ok_or(DkgError::InvalidShare(l))?,
|
||||
);
|
||||
share_bytes.zeroize();
|
||||
secret_share += share;
|
||||
*self.secret += share.deref();
|
||||
|
||||
// This can be insecurely linearized from n * t to just n using the below sums for a given
|
||||
// stripe. Doing so uses naive addition which is subject to malleability. The only way to
|
||||
// ensure that malleability isn't present is to use this n * t algorithm, which runs
|
||||
// per sender and not as an aggregate of all senders, which also enables blame
|
||||
let mut values = exponential(self.params.i, &self.commitments[&l]);
|
||||
values.push((-share, C::generator()));
|
||||
// multiexp will Zeroize this when it's done with it
|
||||
values.push((-*share.deref(), C::generator()));
|
||||
share.zeroize();
|
||||
|
||||
batch.queue(rng, l, values);
|
||||
|
@ -443,14 +425,19 @@ impl<C: Ciphersuite> KeyMachine<C> {
|
|||
// Calculate each user's verification share
|
||||
let mut verification_shares = HashMap::new();
|
||||
for i in 1 ..= self.params.n() {
|
||||
verification_shares.insert(i, multiexp_vartime(&exponential(i, &stripes)));
|
||||
verification_shares.insert(
|
||||
i,
|
||||
if i == self.params.i() {
|
||||
C::generator() * self.secret.deref()
|
||||
} else {
|
||||
multiexp_vartime(&exponential(i, &stripes))
|
||||
},
|
||||
);
|
||||
}
|
||||
// Removing this check would enable optimizing the above from t + (n * t) to t + ((n - 1) * t)
|
||||
debug_assert_eq!(C::generator() * secret_share, verification_shares[&self.params.i()]);
|
||||
|
||||
Ok(ThresholdCore {
|
||||
params: self.params,
|
||||
secret_share,
|
||||
secret_share: self.secret.clone(),
|
||||
group_key: stripes[0],
|
||||
verification_shares,
|
||||
})
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
//! Additional utilities around them, such as promotion from one generator to another, are also
|
||||
//! provided.
|
||||
|
||||
use core::fmt::Debug;
|
||||
use core::{fmt::Debug, ops::Deref};
|
||||
use std::{io::Read, sync::Arc, collections::HashMap};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
use zeroize::{Zeroize, Zeroizing};
|
||||
|
||||
use group::{
|
||||
ff::{Field, PrimeField},
|
||||
|
@ -153,7 +153,7 @@ pub struct ThresholdCore<C: Ciphersuite> {
|
|||
params: ThresholdParams,
|
||||
|
||||
/// Secret share key.
|
||||
secret_share: C::F,
|
||||
secret_share: Zeroizing<C::F>,
|
||||
/// Group key.
|
||||
group_key: C::G,
|
||||
/// Verification shares.
|
||||
|
@ -170,17 +170,11 @@ impl<C: Ciphersuite> Zeroize for ThresholdCore<C> {
|
|||
}
|
||||
}
|
||||
}
|
||||
impl<C: Ciphersuite> Drop for ThresholdCore<C> {
|
||||
fn drop(&mut self) {
|
||||
self.zeroize()
|
||||
}
|
||||
}
|
||||
impl<C: Ciphersuite> ZeroizeOnDrop for ThresholdCore<C> {}
|
||||
|
||||
impl<C: Ciphersuite> ThresholdCore<C> {
|
||||
pub(crate) fn new(
|
||||
params: ThresholdParams,
|
||||
secret_share: C::F,
|
||||
secret_share: Zeroizing<C::F>,
|
||||
verification_shares: HashMap<u16, C::G>,
|
||||
) -> ThresholdCore<C> {
|
||||
#[cfg(debug_assertions)]
|
||||
|
@ -198,8 +192,8 @@ impl<C: Ciphersuite> ThresholdCore<C> {
|
|||
self.params
|
||||
}
|
||||
|
||||
pub fn secret_share(&self) -> C::F {
|
||||
self.secret_share
|
||||
pub fn secret_share(&self) -> &Zeroizing<C::F> {
|
||||
&self.secret_share
|
||||
}
|
||||
|
||||
pub fn group_key(&self) -> C::G {
|
||||
|
@ -253,8 +247,9 @@ impl<C: Ciphersuite> ThresholdCore<C> {
|
|||
(read_u16()?, read_u16()?, read_u16()?)
|
||||
};
|
||||
|
||||
let secret_share =
|
||||
C::read_F(reader).map_err(|_| DkgError::InternalError("invalid secret share"))?;
|
||||
let secret_share = Zeroizing::new(
|
||||
C::read_F(reader).map_err(|_| DkgError::InternalError("invalid secret share"))?,
|
||||
);
|
||||
|
||||
let mut verification_shares = HashMap::new();
|
||||
for l in 1 ..= n {
|
||||
|
@ -284,32 +279,17 @@ pub struct ThresholdKeys<C: Ciphersuite> {
|
|||
pub(crate) offset: Option<C::F>,
|
||||
}
|
||||
|
||||
// Manually implement Drop due to https://github.com/RustCrypto/utils/issues/786
|
||||
impl<C: Ciphersuite> Drop for ThresholdKeys<C> {
|
||||
fn drop(&mut self) {
|
||||
self.zeroize()
|
||||
}
|
||||
}
|
||||
impl<C: Ciphersuite> ZeroizeOnDrop for ThresholdKeys<C> {}
|
||||
|
||||
/// View of keys passed to algorithm implementations.
|
||||
#[derive(Clone, Zeroize)]
|
||||
pub struct ThresholdView<C: Ciphersuite> {
|
||||
group_key: C::G,
|
||||
#[zeroize(skip)]
|
||||
included: Vec<u16>,
|
||||
secret_share: C::F,
|
||||
secret_share: Zeroizing<C::F>,
|
||||
#[zeroize(skip)]
|
||||
verification_shares: HashMap<u16, C::G>,
|
||||
}
|
||||
|
||||
impl<C: Ciphersuite> Drop for ThresholdView<C> {
|
||||
fn drop(&mut self) {
|
||||
self.zeroize()
|
||||
}
|
||||
}
|
||||
impl<C: Ciphersuite> ZeroizeOnDrop for ThresholdView<C> {}
|
||||
|
||||
impl<C: Ciphersuite> ThresholdKeys<C> {
|
||||
pub fn new(core: ThresholdCore<C>) -> ThresholdKeys<C> {
|
||||
ThresholdKeys { core: Arc::new(core), offset: None }
|
||||
|
@ -336,8 +316,8 @@ impl<C: Ciphersuite> ThresholdKeys<C> {
|
|||
self.core.params
|
||||
}
|
||||
|
||||
pub fn secret_share(&self) -> C::F {
|
||||
self.core.secret_share
|
||||
pub fn secret_share(&self) -> &Zeroizing<C::F> {
|
||||
&self.core.secret_share
|
||||
}
|
||||
|
||||
/// Returns the group key with any offset applied.
|
||||
|
@ -366,8 +346,9 @@ impl<C: Ciphersuite> ThresholdKeys<C> {
|
|||
|
||||
Ok(ThresholdView {
|
||||
group_key: self.group_key(),
|
||||
secret_share: (self.secret_share() * lagrange::<C::F>(self.params().i, included)) +
|
||||
offset_share,
|
||||
secret_share: Zeroizing::new(
|
||||
(lagrange::<C::F>(self.params().i, included) * self.secret_share().deref()) + offset_share,
|
||||
),
|
||||
verification_shares: self
|
||||
.verification_shares()
|
||||
.iter()
|
||||
|
@ -389,8 +370,8 @@ impl<C: Ciphersuite> ThresholdView<C> {
|
|||
self.included.clone()
|
||||
}
|
||||
|
||||
pub fn secret_share(&self) -> C::F {
|
||||
self.secret_share
|
||||
pub fn secret_share(&self) -> &Zeroizing<C::F> {
|
||||
&self.secret_share
|
||||
}
|
||||
|
||||
pub fn verification_share(&self, l: u16) -> C::G {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use core::{marker::PhantomData, ops::Deref};
|
||||
use std::{
|
||||
marker::PhantomData,
|
||||
io::{self, Read, Write},
|
||||
sync::Arc,
|
||||
collections::HashMap,
|
||||
|
@ -29,8 +29,8 @@ pub trait CiphersuitePromote<C2: Ciphersuite> {
|
|||
|
||||
fn transcript<G: GroupEncoding>(key: G, i: u16) -> RecommendedTranscript {
|
||||
let mut transcript = RecommendedTranscript::new(b"FROST Generator Update");
|
||||
transcript.append_message(b"group_key", key.to_bytes().as_ref());
|
||||
transcript.append_message(b"participant", &i.to_be_bytes());
|
||||
transcript.append_message(b"group_key", key.to_bytes());
|
||||
transcript.append_message(b"participant", i.to_be_bytes());
|
||||
transcript
|
||||
}
|
||||
|
||||
|
@ -82,7 +82,7 @@ where
|
|||
) -> (GeneratorPromotion<C1, C2>, GeneratorProof<C1>) {
|
||||
// Do a DLEqProof for the new generator
|
||||
let proof = GeneratorProof {
|
||||
share: C2::generator() * base.secret_share(),
|
||||
share: C2::generator() * base.secret_share().deref(),
|
||||
proof: DLEqProof::prove(
|
||||
rng,
|
||||
&mut transcript(base.core.group_key(), base.params().i),
|
||||
|
@ -120,7 +120,11 @@ where
|
|||
}
|
||||
|
||||
Ok(ThresholdKeys {
|
||||
core: Arc::new(ThresholdCore::new(params, self.base.secret_share(), verification_shares)),
|
||||
core: Arc::new(ThresholdCore::new(
|
||||
params,
|
||||
self.base.secret_share().clone(),
|
||||
verification_shares,
|
||||
)),
|
||||
offset: None,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use core::ops::Deref;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
@ -38,7 +39,7 @@ pub fn recover_key<C: Ciphersuite>(keys: &HashMap<u16, ThresholdKeys<C>>) -> C::
|
|||
let included = keys.keys().cloned().collect::<Vec<_>>();
|
||||
|
||||
let group_private = keys.iter().fold(C::F::zero(), |accum, (i, keys)| {
|
||||
accum + (keys.secret_share() * lagrange::<C::F>(*i, &included))
|
||||
accum + (lagrange::<C::F>(*i, &included) * keys.secret_share().deref())
|
||||
});
|
||||
assert_eq!(C::generator() * group_private, first.group_key(), "failed to recover keys");
|
||||
group_private
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use std::{marker::PhantomData, collections::HashMap};
|
||||
use core::{marker::PhantomData, ops::Deref};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
|
@ -54,7 +55,10 @@ pub(crate) fn test_generator_promotion<R: RngCore + CryptoRng, C: Ciphersuite>(r
|
|||
assert_eq!(keys[&i].secret_share(), promoted.secret_share());
|
||||
assert_eq!(new_group_key, promoted.group_key());
|
||||
for (l, verification_share) in promoted.verification_shares() {
|
||||
assert_eq!(AltGenerator::<C>::generator() * keys[&l].secret_share(), verification_share);
|
||||
assert_eq!(
|
||||
AltGenerator::<C>::generator() * keys[&l].secret_share().deref(),
|
||||
verification_share
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "dleq"
|
||||
version = "0.1.2"
|
||||
version = "0.2.0"
|
||||
description = "Implementation of single and cross-curve Discrete Log Equality proofs"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/dleq"
|
||||
|
@ -19,7 +19,7 @@ zeroize = { version = "1.3", features = ["zeroize_derive"] }
|
|||
|
||||
digest = "0.10"
|
||||
|
||||
transcript = { package = "flexible-transcript", path = "../transcript", version = "0.1" }
|
||||
transcript = { package = "flexible-transcript", path = "../transcript", version = "0.2" }
|
||||
|
||||
ff = "0.12"
|
||||
group = "0.12"
|
||||
|
|
|
@ -62,9 +62,9 @@ where
|
|||
#[allow(non_snake_case)]
|
||||
fn nonces<T: Transcript>(mut transcript: T, nonces: (G0, G1)) -> (G0::Scalar, G1::Scalar) {
|
||||
transcript.domain_separate(b"aos_membership_proof");
|
||||
transcript.append_message(b"ring_len", &u8::try_from(RING_LEN).unwrap().to_le_bytes());
|
||||
transcript.append_message(b"nonce_0", nonces.0.to_bytes().as_ref());
|
||||
transcript.append_message(b"nonce_1", nonces.1.to_bytes().as_ref());
|
||||
transcript.append_message(b"ring_len", u8::try_from(RING_LEN).unwrap().to_le_bytes());
|
||||
transcript.append_message(b"nonce_0", nonces.0.to_bytes());
|
||||
transcript.append_message(b"nonce_1", nonces.1.to_bytes());
|
||||
mutual_scalar_from_bytes(transcript.challenge(b"challenge").as_ref())
|
||||
}
|
||||
|
||||
|
|
|
@ -91,9 +91,9 @@ where
|
|||
{
|
||||
fn transcript<T: Transcript>(transcript: &mut T, i: usize, commitments: (G0, G1)) {
|
||||
transcript.domain_separate(b"bits");
|
||||
transcript.append_message(b"group", &u16::try_from(i).unwrap().to_le_bytes());
|
||||
transcript.append_message(b"commitment_0", commitments.0.to_bytes().as_ref());
|
||||
transcript.append_message(b"commitment_1", commitments.1.to_bytes().as_ref());
|
||||
transcript.append_message(b"group", u16::try_from(i).unwrap().to_le_bytes());
|
||||
transcript.append_message(b"commitment_0", commitments.0.to_bytes());
|
||||
transcript.append_message(b"commitment_1", commitments.1.to_bytes());
|
||||
}
|
||||
|
||||
fn ring(pow_2: (G0, G1), commitments: (G0, G1)) -> Vec<(G0, G1)> {
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
use core::ops::Deref;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use zeroize::Zeroize;
|
||||
use zeroize::{Zeroize, Zeroizing};
|
||||
|
||||
use digest::{Digest, HashMarker};
|
||||
|
||||
|
@ -18,7 +20,7 @@ pub mod scalar;
|
|||
use scalar::{scalar_convert, mutual_scalar_from_bytes};
|
||||
|
||||
pub(crate) mod schnorr;
|
||||
use schnorr::SchnorrPoK;
|
||||
use self::schnorr::SchnorrPoK;
|
||||
|
||||
pub(crate) mod aos;
|
||||
|
||||
|
@ -52,8 +54,8 @@ impl<G: PrimeGroup> Generators<G> {
|
|||
|
||||
fn transcript<T: Transcript>(&self, transcript: &mut T) {
|
||||
transcript.domain_separate(b"generators");
|
||||
transcript.append_message(b"primary", self.primary.to_bytes().as_ref());
|
||||
transcript.append_message(b"alternate", self.alt.to_bytes().as_ref());
|
||||
transcript.append_message(b"primary", self.primary.to_bytes());
|
||||
transcript.append_message(b"alternate", self.alt.to_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,8 +155,8 @@ where
|
|||
generators.0.transcript(transcript);
|
||||
generators.1.transcript(transcript);
|
||||
transcript.domain_separate(b"points");
|
||||
transcript.append_message(b"point_0", keys.0.to_bytes().as_ref());
|
||||
transcript.append_message(b"point_1", keys.1.to_bytes().as_ref());
|
||||
transcript.append_message(b"point_0", keys.0.to_bytes());
|
||||
transcript.append_message(b"point_1", keys.1.to_bytes());
|
||||
}
|
||||
|
||||
pub(crate) fn blinding_key<R: RngCore + CryptoRng, F: PrimeField>(
|
||||
|
@ -185,17 +187,17 @@ where
|
|||
rng: &mut R,
|
||||
transcript: &mut T,
|
||||
generators: (Generators<G0>, Generators<G1>),
|
||||
f: (G0::Scalar, G1::Scalar),
|
||||
) -> (Self, (G0::Scalar, G1::Scalar)) {
|
||||
f: (Zeroizing<G0::Scalar>, Zeroizing<G1::Scalar>),
|
||||
) -> (Self, (Zeroizing<G0::Scalar>, Zeroizing<G1::Scalar>)) {
|
||||
Self::transcript(
|
||||
transcript,
|
||||
generators,
|
||||
((generators.0.primary * f.0), (generators.1.primary * f.1)),
|
||||
((generators.0.primary * f.0.deref()), (generators.1.primary * f.1.deref())),
|
||||
);
|
||||
|
||||
let poks = (
|
||||
SchnorrPoK::<G0>::prove(rng, transcript, generators.0.primary, f.0),
|
||||
SchnorrPoK::<G1>::prove(rng, transcript, generators.1.primary, f.1),
|
||||
SchnorrPoK::<G0>::prove(rng, transcript, generators.0.primary, &f.0),
|
||||
SchnorrPoK::<G1>::prove(rng, transcript, generators.1.primary, &f.1),
|
||||
);
|
||||
|
||||
let mut blinding_key_total = (G0::Scalar::zero(), G1::Scalar::zero());
|
||||
|
@ -269,7 +271,7 @@ where
|
|||
let proof = __DLEqProof { bits, remainder, poks };
|
||||
debug_assert_eq!(
|
||||
proof.reconstruct_keys(),
|
||||
(generators.0.primary * f.0, generators.1.primary * f.1)
|
||||
(generators.0.primary * f.0.deref(), generators.1.primary * f.1.deref())
|
||||
);
|
||||
(proof, f)
|
||||
}
|
||||
|
@ -286,13 +288,17 @@ where
|
|||
transcript: &mut T,
|
||||
generators: (Generators<G0>, Generators<G1>),
|
||||
digest: D,
|
||||
) -> (Self, (G0::Scalar, G1::Scalar)) {
|
||||
Self::prove_internal(
|
||||
rng,
|
||||
transcript,
|
||||
generators,
|
||||
mutual_scalar_from_bytes(digest.finalize().as_ref()),
|
||||
)
|
||||
) -> (Self, (Zeroizing<G0::Scalar>, Zeroizing<G1::Scalar>)) {
|
||||
// This pattern theoretically prevents the compiler from moving it, so our protection against
|
||||
// a copy remaining un-zeroized is actually what's causing a copy. There's still a feeling of
|
||||
// safety granted by it, even if there's a loss in performance.
|
||||
let (mut f0, mut f1) =
|
||||
mutual_scalar_from_bytes::<G0::Scalar, G1::Scalar>(digest.finalize().as_ref());
|
||||
let f = (Zeroizing::new(f0), Zeroizing::new(f1));
|
||||
f0.zeroize();
|
||||
f1.zeroize();
|
||||
|
||||
Self::prove_internal(rng, transcript, generators, f)
|
||||
}
|
||||
|
||||
/// Prove the cross-Group Discrete Log Equality for the points derived from the scalar passed in,
|
||||
|
@ -302,9 +308,10 @@ where
|
|||
rng: &mut R,
|
||||
transcript: &mut T,
|
||||
generators: (Generators<G0>, Generators<G1>),
|
||||
f0: G0::Scalar,
|
||||
) -> Option<(Self, (G0::Scalar, G1::Scalar))> {
|
||||
scalar_convert(f0).map(|f1| Self::prove_internal(rng, transcript, generators, (f0, f1)))
|
||||
f0: Zeroizing<G0::Scalar>,
|
||||
) -> Option<(Self, (Zeroizing<G0::Scalar>, Zeroizing<G1::Scalar>))> {
|
||||
scalar_convert(*f0.deref()) // scalar_convert will zeroize it, though this is unfortunate
|
||||
.map(|f1| Self::prove_internal(rng, transcript, generators, (f0, Zeroizing::new(f1))))
|
||||
}
|
||||
|
||||
/// Verify a cross-Group Discrete Log Equality statement, returning the points proven for.
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use core::ops::Deref;
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use zeroize::Zeroize;
|
||||
use zeroize::{Zeroize, Zeroizing};
|
||||
|
||||
use transcript::Transcript;
|
||||
|
||||
|
@ -30,13 +32,13 @@ impl<G: PrimeGroup + Zeroize> SchnorrPoK<G>
|
|||
where
|
||||
G::Scalar: PrimeFieldBits + Zeroize,
|
||||
{
|
||||
// Not hram due to the lack of m
|
||||
// Not HRAm due to the lack of m
|
||||
#[allow(non_snake_case)]
|
||||
fn hra<T: Transcript>(transcript: &mut T, generator: G, R: G, A: G) -> G::Scalar {
|
||||
transcript.domain_separate(b"schnorr_proof_of_knowledge");
|
||||
transcript.append_message(b"generator", generator.to_bytes().as_ref());
|
||||
transcript.append_message(b"nonce", R.to_bytes().as_ref());
|
||||
transcript.append_message(b"public_key", A.to_bytes().as_ref());
|
||||
transcript.append_message(b"generator", generator.to_bytes());
|
||||
transcript.append_message(b"nonce", R.to_bytes());
|
||||
transcript.append_message(b"public_key", A.to_bytes());
|
||||
challenge(transcript)
|
||||
}
|
||||
|
||||
|
@ -44,18 +46,17 @@ where
|
|||
rng: &mut R,
|
||||
transcript: &mut T,
|
||||
generator: G,
|
||||
mut private_key: G::Scalar,
|
||||
private_key: &Zeroizing<G::Scalar>,
|
||||
) -> SchnorrPoK<G> {
|
||||
let mut nonce = G::Scalar::random(rng);
|
||||
let nonce = Zeroizing::new(G::Scalar::random(rng));
|
||||
#[allow(non_snake_case)]
|
||||
let R = generator * nonce;
|
||||
let res = SchnorrPoK {
|
||||
let R = generator * nonce.deref();
|
||||
SchnorrPoK {
|
||||
R,
|
||||
s: nonce + (private_key * SchnorrPoK::hra(transcript, generator, R, generator * private_key)),
|
||||
};
|
||||
private_key.zeroize();
|
||||
nonce.zeroize();
|
||||
res
|
||||
s: (SchnorrPoK::hra(transcript, generator, R, generator * private_key.deref()) *
|
||||
private_key.deref()) +
|
||||
nonce.deref(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn verify<R: RngCore + CryptoRng, T: Transcript>(
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use core::ops::Deref;
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use zeroize::Zeroize;
|
||||
use zeroize::{Zeroize, Zeroizing};
|
||||
|
||||
use transcript::Transcript;
|
||||
|
||||
|
@ -70,32 +72,29 @@ pub struct DLEqProof<G: PrimeGroup> {
|
|||
#[allow(non_snake_case)]
|
||||
impl<G: PrimeGroup> DLEqProof<G> {
|
||||
fn transcript<T: Transcript>(transcript: &mut T, generator: G, nonce: G, point: G) {
|
||||
transcript.append_message(b"generator", generator.to_bytes().as_ref());
|
||||
transcript.append_message(b"nonce", nonce.to_bytes().as_ref());
|
||||
transcript.append_message(b"point", point.to_bytes().as_ref());
|
||||
transcript.append_message(b"generator", generator.to_bytes());
|
||||
transcript.append_message(b"nonce", nonce.to_bytes());
|
||||
transcript.append_message(b"point", point.to_bytes());
|
||||
}
|
||||
|
||||
pub fn prove<R: RngCore + CryptoRng, T: Transcript>(
|
||||
rng: &mut R,
|
||||
transcript: &mut T,
|
||||
generators: &[G],
|
||||
mut scalar: G::Scalar,
|
||||
scalar: &Zeroizing<G::Scalar>,
|
||||
) -> DLEqProof<G>
|
||||
where
|
||||
G::Scalar: Zeroize,
|
||||
{
|
||||
let mut r = G::Scalar::random(rng);
|
||||
let r = Zeroizing::new(G::Scalar::random(rng));
|
||||
|
||||
transcript.domain_separate(b"dleq");
|
||||
for generator in generators {
|
||||
Self::transcript(transcript, *generator, *generator * r, *generator * scalar);
|
||||
Self::transcript(transcript, *generator, *generator * r.deref(), *generator * scalar.deref());
|
||||
}
|
||||
|
||||
let c = challenge(transcript);
|
||||
let s = r + (c * scalar);
|
||||
|
||||
scalar.zeroize();
|
||||
r.zeroize();
|
||||
let s = (c * scalar.deref()) + r.deref();
|
||||
|
||||
DLEqProof { c, s }
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
use core::ops::Deref;
|
||||
|
||||
use hex_literal::hex;
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
use rand_core::{RngCore, OsRng};
|
||||
|
||||
use ff::{Field, PrimeField};
|
||||
|
@ -19,7 +23,6 @@ use crate::{
|
|||
};
|
||||
|
||||
mod scalar;
|
||||
mod schnorr;
|
||||
mod aos;
|
||||
|
||||
type G0 = ProjectivePoint;
|
||||
|
@ -51,8 +54,8 @@ pub(crate) fn generators() -> (Generators<G0>, Generators<G1>) {
|
|||
macro_rules! verify_and_deserialize {
|
||||
($type: ty, $proof: ident, $generators: ident, $keys: ident) => {
|
||||
let public_keys = $proof.verify(&mut OsRng, &mut transcript(), $generators).unwrap();
|
||||
assert_eq!($generators.0.primary * $keys.0, public_keys.0);
|
||||
assert_eq!($generators.1.primary * $keys.1, public_keys.1);
|
||||
assert_eq!($generators.0.primary * $keys.0.deref(), public_keys.0);
|
||||
assert_eq!($generators.1.primary * $keys.1.deref(), public_keys.1);
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
{
|
||||
|
@ -117,8 +120,8 @@ macro_rules! test_dleq {
|
|||
let mut key;
|
||||
let mut res;
|
||||
while {
|
||||
key = Scalar::random(&mut OsRng);
|
||||
res = $type::prove_without_bias(&mut OsRng, &mut transcript(), generators, key);
|
||||
key = Zeroizing::new(Scalar::random(&mut OsRng));
|
||||
res = $type::prove_without_bias(&mut OsRng, &mut transcript(), generators, key.clone());
|
||||
res.is_none()
|
||||
} {}
|
||||
let res = res.unwrap();
|
||||
|
@ -156,8 +159,13 @@ fn test_rejection_sampling() {
|
|||
|
||||
assert!(
|
||||
// Either would work
|
||||
EfficientLinearDLEq::prove_without_bias(&mut OsRng, &mut transcript(), generators(), pow_2)
|
||||
.is_none()
|
||||
EfficientLinearDLEq::prove_without_bias(
|
||||
&mut OsRng,
|
||||
&mut transcript(),
|
||||
generators(),
|
||||
Zeroizing::new(pow_2)
|
||||
)
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -167,13 +175,18 @@ fn test_remainder() {
|
|||
assert_eq!(Scalar::CAPACITY, 255);
|
||||
let generators = (generators().0, generators().0);
|
||||
// This will ignore any unused bits, ensuring every remaining one is set
|
||||
let keys = mutual_scalar_from_bytes(&[0xFF; 32]);
|
||||
assert_eq!(keys.0 + Scalar::one(), Scalar::from(2u64).pow_vartime(&[255]));
|
||||
let keys = mutual_scalar_from_bytes::<Scalar, Scalar>(&[0xFF; 32]);
|
||||
let keys = (Zeroizing::new(keys.0), Zeroizing::new(keys.1));
|
||||
assert_eq!(Scalar::one() + keys.0.deref(), Scalar::from(2u64).pow_vartime(&[255]));
|
||||
assert_eq!(keys.0, keys.1);
|
||||
|
||||
let (proof, res) =
|
||||
ConciseLinearDLEq::prove_without_bias(&mut OsRng, &mut transcript(), generators, keys.0)
|
||||
.unwrap();
|
||||
let (proof, res) = ConciseLinearDLEq::prove_without_bias(
|
||||
&mut OsRng,
|
||||
&mut transcript(),
|
||||
generators,
|
||||
keys.0.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(keys, res);
|
||||
|
||||
verify_and_deserialize!(
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use core::ops::Deref;
|
||||
|
||||
use rand_core::OsRng;
|
||||
|
||||
use zeroize::Zeroize;
|
||||
|
@ -20,12 +22,12 @@ where
|
|||
|
||||
let mut batch = BatchVerifier::new(10);
|
||||
for _ in 0 .. 10 {
|
||||
let private = G::Scalar::random(&mut OsRng);
|
||||
SchnorrPoK::prove(&mut OsRng, &mut transcript.clone(), G::generator(), private).verify(
|
||||
let private = Zeroizing::new(G::Scalar::random(&mut OsRng));
|
||||
SchnorrPoK::prove(&mut OsRng, &mut transcript.clone(), G::generator(), &private).verify(
|
||||
&mut OsRng,
|
||||
&mut transcript.clone(),
|
||||
G::generator(),
|
||||
G::generator() * private,
|
||||
G::generator() * private.deref(),
|
||||
&mut batch,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
#[cfg(feature = "experimental")]
|
||||
mod cross_group;
|
||||
use core::ops::Deref;
|
||||
|
||||
use hex_literal::hex;
|
||||
|
||||
use rand_core::OsRng;
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use ff::Field;
|
||||
use group::GroupEncoding;
|
||||
|
||||
|
@ -13,6 +15,9 @@ use transcript::{Transcript, RecommendedTranscript};
|
|||
|
||||
use crate::DLEqProof;
|
||||
|
||||
#[cfg(feature = "experimental")]
|
||||
mod cross_group;
|
||||
|
||||
#[test]
|
||||
fn test_dleq() {
|
||||
let transcript = || RecommendedTranscript::new(b"DLEq Proof Test");
|
||||
|
@ -39,12 +44,12 @@ fn test_dleq() {
|
|||
];
|
||||
|
||||
for i in 0 .. 5 {
|
||||
let key = Scalar::random(&mut OsRng);
|
||||
let proof = DLEqProof::prove(&mut OsRng, &mut transcript(), &generators[.. i], key);
|
||||
let key = Zeroizing::new(Scalar::random(&mut OsRng));
|
||||
let proof = DLEqProof::prove(&mut OsRng, &mut transcript(), &generators[.. i], &key);
|
||||
|
||||
let mut keys = [ProjectivePoint::GENERATOR; 5];
|
||||
for k in 0 .. 5 {
|
||||
keys[k] = generators[k] * key;
|
||||
keys[k] = generators[k] * key.deref();
|
||||
}
|
||||
proof.verify(&mut transcript(), &generators[.. i], &keys[.. i]).unwrap();
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "modular-frost"
|
||||
version = "0.4.1"
|
||||
version = "0.5.0"
|
||||
description = "Modular implementation of FROST over ff/group"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/frost"
|
||||
|
@ -34,14 +34,14 @@ minimal-ed448 = { path = "../ed448", version = "^0.1.2", optional = true }
|
|||
|
||||
ciphersuite = { path = "../ciphersuite", version = "0.1", features = ["std"] }
|
||||
|
||||
transcript = { package = "flexible-transcript", path = "../transcript", features = ["recommended"], version = "^0.1.3" }
|
||||
transcript = { package = "flexible-transcript", path = "../transcript", version = "0.2", features = ["recommended"] }
|
||||
|
||||
multiexp = { path = "../multiexp", version = "0.2", features = ["batch"] }
|
||||
|
||||
schnorr = { package = "schnorr-signatures", path = "../schnorr", version = "0.1.0" }
|
||||
dleq = { path = "../dleq", version = "^0.1.2", features = ["serialize"] }
|
||||
schnorr = { package = "schnorr-signatures", path = "../schnorr", version = "0.2" }
|
||||
dleq = { path = "../dleq", version = "0.2", features = ["serialize"] }
|
||||
|
||||
dkg = { path = "../dkg", version = "0.1.0" }
|
||||
dkg = { path = "../dkg", version = "0.2" }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use core::{marker::PhantomData, fmt::Debug};
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use transcript::Transcript;
|
||||
|
@ -66,7 +67,7 @@ pub trait Algorithm<C: Curve>: Clone {
|
|||
&mut self,
|
||||
params: &ThresholdView<C>,
|
||||
nonce_sums: &[Vec<C::G>],
|
||||
nonces: &[C::F],
|
||||
nonces: Vec<Zeroizing<C::F>>,
|
||||
msg: &[u8],
|
||||
) -> C::F;
|
||||
|
||||
|
@ -93,8 +94,8 @@ impl Transcript for IetfTranscript {
|
|||
|
||||
fn domain_separate(&mut self, _: &[u8]) {}
|
||||
|
||||
fn append_message(&mut self, _: &'static [u8], message: &[u8]) {
|
||||
self.0.extend(message);
|
||||
fn append_message<M: AsRef<[u8]>>(&mut self, _: &'static [u8], message: M) {
|
||||
self.0.extend(message.as_ref());
|
||||
}
|
||||
|
||||
fn challenge(&mut self, _: &'static [u8]) -> Vec<u8> {
|
||||
|
@ -161,12 +162,12 @@ impl<C: Curve, H: Hram<C>> Algorithm<C> for Schnorr<C, H> {
|
|||
&mut self,
|
||||
params: &ThresholdView<C>,
|
||||
nonce_sums: &[Vec<C::G>],
|
||||
nonces: &[C::F],
|
||||
mut nonces: Vec<Zeroizing<C::F>>,
|
||||
msg: &[u8],
|
||||
) -> C::F {
|
||||
let c = H::hram(&nonce_sums[0][0], ¶ms.group_key(), msg);
|
||||
self.c = Some(c);
|
||||
SchnorrSignature::<C>::sign(params.secret_share(), nonces[0], c).s
|
||||
SchnorrSignature::<C>::sign(params.secret_share(), nonces.swap_remove(0), c).s
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use core::ops::Deref;
|
||||
use std::io::{self, Read};
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use zeroize::Zeroize;
|
||||
use zeroize::{Zeroize, Zeroizing};
|
||||
use subtle::ConstantTimeEq;
|
||||
|
||||
use digest::Digest;
|
||||
|
@ -67,26 +68,29 @@ pub trait Curve: Ciphersuite {
|
|||
}
|
||||
|
||||
/// Securely generate a random nonce. H3 from the IETF draft.
|
||||
fn random_nonce<R: RngCore + CryptoRng>(mut secret: Self::F, rng: &mut R) -> Self::F {
|
||||
let mut seed = vec![0; 32];
|
||||
rng.fill_bytes(&mut seed);
|
||||
fn random_nonce<R: RngCore + CryptoRng>(
|
||||
secret: &Zeroizing<Self::F>,
|
||||
rng: &mut R,
|
||||
) -> Zeroizing<Self::F> {
|
||||
let mut seed = Zeroizing::new(vec![0; 32]);
|
||||
rng.fill_bytes(seed.as_mut());
|
||||
|
||||
let mut repr = secret.to_repr();
|
||||
secret.zeroize();
|
||||
|
||||
let mut res;
|
||||
while {
|
||||
seed.extend(repr.as_ref());
|
||||
res = <Self as Curve>::hash_to_F(b"nonce", &seed);
|
||||
res = Zeroizing::new(<Self as Curve>::hash_to_F(b"nonce", seed.deref()));
|
||||
res.ct_eq(&Self::F::zero()).into()
|
||||
} {
|
||||
seed = Zeroizing::new(vec![0; 32]);
|
||||
rng.fill_bytes(&mut seed);
|
||||
}
|
||||
|
||||
for i in repr.as_mut() {
|
||||
i.zeroize();
|
||||
}
|
||||
seed.zeroize();
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
// Each nonce remains of the form (d, e) and made into a proper nonce with d + (e * b)
|
||||
// When multiple D, E pairs are provided, a DLEq proof is also provided to confirm their integrity
|
||||
|
||||
use core::ops::Deref;
|
||||
use std::{
|
||||
io::{self, Read, Write},
|
||||
collections::HashMap,
|
||||
|
@ -15,7 +16,7 @@ use std::{
|
|||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
use zeroize::{Zeroize, Zeroizing};
|
||||
|
||||
use transcript::Transcript;
|
||||
|
||||
|
@ -33,13 +34,7 @@ fn dleq_transcript<T: Transcript>() -> T {
|
|||
// Each nonce is actually a pair of random scalars, notated as d, e under the FROST paper
|
||||
// This is considered a single nonce as r = d + be
|
||||
#[derive(Clone, Zeroize)]
|
||||
pub(crate) struct Nonce<C: Curve>(pub(crate) [C::F; 2]);
|
||||
impl<C: Curve> Drop for Nonce<C> {
|
||||
fn drop(&mut self) {
|
||||
self.zeroize();
|
||||
}
|
||||
}
|
||||
impl<C: Curve> ZeroizeOnDrop for Nonce<C> {}
|
||||
pub(crate) struct Nonce<C: Curve>(pub(crate) [Zeroizing<C::F>; 2]);
|
||||
|
||||
// Commitments to a specific generator for this nonce
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
|
@ -70,16 +65,20 @@ pub(crate) struct NonceCommitments<C: Curve> {
|
|||
impl<C: Curve> NonceCommitments<C> {
|
||||
pub(crate) fn new<R: RngCore + CryptoRng, T: Transcript>(
|
||||
rng: &mut R,
|
||||
mut secret_share: C::F,
|
||||
secret_share: &Zeroizing<C::F>,
|
||||
generators: &[C::G],
|
||||
) -> (Nonce<C>, NonceCommitments<C>) {
|
||||
let nonce =
|
||||
Nonce([C::random_nonce(secret_share, &mut *rng), C::random_nonce(secret_share, &mut *rng)]);
|
||||
secret_share.zeroize();
|
||||
let nonce = Nonce::<C>([
|
||||
C::random_nonce(secret_share, &mut *rng),
|
||||
C::random_nonce(secret_share, &mut *rng),
|
||||
]);
|
||||
|
||||
let mut commitments = Vec::with_capacity(generators.len());
|
||||
for generator in generators {
|
||||
commitments.push(GeneratorCommitments([*generator * nonce.0[0], *generator * nonce.0[1]]));
|
||||
commitments.push(GeneratorCommitments([
|
||||
*generator * nonce.0[0].deref(),
|
||||
*generator * nonce.0[1].deref(),
|
||||
]));
|
||||
}
|
||||
|
||||
let mut dleqs = None;
|
||||
|
@ -91,7 +90,7 @@ impl<C: Curve> NonceCommitments<C> {
|
|||
// TODO: At least include a challenge from the existing transcript
|
||||
DLEqProof::prove(&mut *rng, &mut dleq_transcript::<T>(), generators, nonce)
|
||||
};
|
||||
dleqs = Some([dleq(nonce.0[0]), dleq(nonce.0[1])]);
|
||||
dleqs = Some([dleq(&nonce.0[0]), dleq(&nonce.0[1])]);
|
||||
}
|
||||
|
||||
(nonce, NonceCommitments { generators: commitments, dleqs })
|
||||
|
@ -145,7 +144,7 @@ pub(crate) struct Commitments<C: Curve> {
|
|||
impl<C: Curve> Commitments<C> {
|
||||
pub(crate) fn new<R: RngCore + CryptoRng, T: Transcript>(
|
||||
rng: &mut R,
|
||||
secret_share: C::F,
|
||||
secret_share: &Zeroizing<C::F>,
|
||||
planned_nonces: &[Vec<C::G>],
|
||||
) -> (Vec<Nonce<C>>, Commitments<C>) {
|
||||
let mut nonces = vec![];
|
||||
|
@ -162,8 +161,8 @@ impl<C: Curve> Commitments<C> {
|
|||
pub(crate) fn transcript<T: Transcript>(&self, t: &mut T) {
|
||||
for nonce in &self.nonces {
|
||||
for commitments in &nonce.generators {
|
||||
t.append_message(b"commitment_D", commitments.0[0].to_bytes().as_ref());
|
||||
t.append_message(b"commitment_E", commitments.0[1].to_bytes().as_ref());
|
||||
t.append_message(b"commitment_D", commitments.0[0].to_bytes());
|
||||
t.append_message(b"commitment_E", commitments.0[1].to_bytes());
|
||||
}
|
||||
|
||||
// Transcripting the DLEqs implicitly transcripts the exact generators used for this nonce
|
||||
|
@ -215,7 +214,7 @@ impl<C: Curve> BindingFactor<C> {
|
|||
pub(crate) fn calculate_binding_factors<T: Clone + Transcript>(&mut self, transcript: &mut T) {
|
||||
for (l, binding) in self.0.iter_mut() {
|
||||
let mut transcript = transcript.clone();
|
||||
transcript.append_message(b"participant", C::F::from(u64::from(*l)).to_repr().as_ref());
|
||||
transcript.append_message(b"participant", C::F::from(u64::from(*l)).to_repr());
|
||||
// It *should* be perfectly fine to reuse a binding factor for multiple nonces
|
||||
// This generates a binding factor per nonce just to ensure it never comes up as a question
|
||||
binding.binding_factors = Some(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use core::fmt;
|
||||
use core::{ops::Deref, fmt::Debug};
|
||||
use std::{
|
||||
io::{self, Read, Write},
|
||||
collections::HashMap,
|
||||
|
@ -6,7 +6,7 @@ use std::{
|
|||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use transcript::Transcript;
|
||||
|
||||
|
@ -49,12 +49,6 @@ pub struct Params<C: Curve, A: Algorithm<C>> {
|
|||
keys: ThresholdKeys<C>,
|
||||
view: ThresholdView<C>,
|
||||
}
|
||||
impl<C: Curve, A: Algorithm<C>> Drop for Params<C, A> {
|
||||
fn drop(&mut self) {
|
||||
self.zeroize()
|
||||
}
|
||||
}
|
||||
impl<C: Curve, A: Algorithm<C>> ZeroizeOnDrop for Params<C, A> {}
|
||||
|
||||
impl<C: Curve, A: Algorithm<C>> Params<C, A> {
|
||||
pub fn new(
|
||||
|
@ -122,7 +116,7 @@ pub trait PreprocessMachine {
|
|||
/// Preprocess message for this machine.
|
||||
type Preprocess: Clone + PartialEq + Writable;
|
||||
/// Signature produced by this machine.
|
||||
type Signature: Clone + PartialEq + fmt::Debug;
|
||||
type Signature: Clone + PartialEq + Debug;
|
||||
/// SignMachine this PreprocessMachine turns into.
|
||||
type SignMachine: SignMachine<Self::Signature, Preprocess = Self::Preprocess>;
|
||||
|
||||
|
@ -213,22 +207,13 @@ pub trait SignMachine<S> {
|
|||
}
|
||||
|
||||
/// Next step of the state machine for the signing process.
|
||||
#[derive(Zeroize)]
|
||||
pub struct AlgorithmSignMachine<C: Curve, A: Algorithm<C>> {
|
||||
params: Params<C, A>,
|
||||
pub(crate) nonces: Vec<Nonce<C>>,
|
||||
#[zeroize(skip)]
|
||||
pub(crate) preprocess: Preprocess<C, A::Addendum>,
|
||||
}
|
||||
impl<C: Curve, A: Algorithm<C>> Zeroize for AlgorithmSignMachine<C, A> {
|
||||
fn zeroize(&mut self) {
|
||||
self.nonces.zeroize()
|
||||
}
|
||||
}
|
||||
impl<C: Curve, A: Algorithm<C>> Drop for AlgorithmSignMachine<C, A> {
|
||||
fn drop(&mut self) {
|
||||
self.zeroize()
|
||||
}
|
||||
}
|
||||
impl<C: Curve, A: Algorithm<C>> ZeroizeOnDrop for AlgorithmSignMachine<C, A> {}
|
||||
|
||||
impl<C: Curve, A: Algorithm<C>> SignMachine<A::Signature> for AlgorithmSignMachine<C, A> {
|
||||
type Preprocess = Preprocess<C, A::Addendum>;
|
||||
|
@ -266,7 +251,7 @@ impl<C: Curve, A: Algorithm<C>> SignMachine<A::Signature> for AlgorithmSignMachi
|
|||
.params
|
||||
.algorithm
|
||||
.transcript()
|
||||
.append_message(b"participant", C::F::from(u64::from(*l)).to_repr().as_ref());
|
||||
.append_message(b"participant", C::F::from(u64::from(*l)).to_repr());
|
||||
}
|
||||
|
||||
if *l == self.params.keys.params().i() {
|
||||
|
@ -277,7 +262,7 @@ impl<C: Curve, A: Algorithm<C>> SignMachine<A::Signature> for AlgorithmSignMachi
|
|||
{
|
||||
let mut buf = vec![];
|
||||
addendum.write(&mut buf).unwrap();
|
||||
self.params.algorithm.transcript().append_message(b"addendum", &buf);
|
||||
self.params.algorithm.transcript().append_message(b"addendum", buf);
|
||||
}
|
||||
|
||||
B.insert(*l, commitments);
|
||||
|
@ -288,7 +273,7 @@ impl<C: Curve, A: Algorithm<C>> SignMachine<A::Signature> for AlgorithmSignMachi
|
|||
{
|
||||
let mut buf = vec![];
|
||||
preprocess.addendum.write(&mut buf).unwrap();
|
||||
self.params.algorithm.transcript().append_message(b"addendum", &buf);
|
||||
self.params.algorithm.transcript().append_message(b"addendum", buf);
|
||||
}
|
||||
|
||||
B.insert(*l, preprocess.commitments);
|
||||
|
@ -298,7 +283,7 @@ impl<C: Curve, A: Algorithm<C>> SignMachine<A::Signature> for AlgorithmSignMachi
|
|||
|
||||
// Re-format into the FROST-expected rho transcript
|
||||
let mut rho_transcript = A::Transcript::new(b"FROST_rho");
|
||||
rho_transcript.append_message(b"message", &C::hash_msg(msg));
|
||||
rho_transcript.append_message(b"message", C::hash_msg(msg));
|
||||
rho_transcript.append_message(
|
||||
b"preprocesses",
|
||||
&C::hash_commitments(
|
||||
|
@ -317,7 +302,7 @@ impl<C: Curve, A: Algorithm<C>> SignMachine<A::Signature> for AlgorithmSignMachi
|
|||
// While further code edits would still be required for such a model (having the offset
|
||||
// communicated as a point along with only a single party applying the offset), this means
|
||||
// it wouldn't require a transcript change as well
|
||||
rho_transcript.append_message(b"offset", (C::generator() * offset).to_bytes().as_ref());
|
||||
rho_transcript.append_message(b"offset", (C::generator() * offset).to_bytes());
|
||||
}
|
||||
|
||||
// Generate the per-signer binding factors
|
||||
|
@ -329,23 +314,26 @@ impl<C: Curve, A: Algorithm<C>> SignMachine<A::Signature> for AlgorithmSignMachi
|
|||
.params
|
||||
.algorithm
|
||||
.transcript()
|
||||
.append_message(b"rho_transcript", rho_transcript.challenge(b"merge").as_ref());
|
||||
.append_message(b"rho_transcript", rho_transcript.challenge(b"merge"));
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
let Rs = B.nonces(&nonces);
|
||||
|
||||
let our_binding_factors = B.binding_factors(multisig_params.i());
|
||||
let mut nonces = self
|
||||
let nonces = self
|
||||
.nonces
|
||||
.iter()
|
||||
.drain(..)
|
||||
.enumerate()
|
||||
.map(|(n, nonces)| nonces.0[0] + (nonces.0[1] * our_binding_factors[n]))
|
||||
.map(|(n, nonces)| {
|
||||
let [base, mut actual] = nonces.0;
|
||||
*actual *= our_binding_factors[n];
|
||||
*actual += base.deref();
|
||||
actual
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
self.nonces.zeroize();
|
||||
|
||||
let share = self.params.algorithm.sign_share(&self.params.view, &Rs, &nonces, msg);
|
||||
nonces.zeroize();
|
||||
let share = self.params.algorithm.sign_share(&self.params.view, &Rs, nonces, msg);
|
||||
|
||||
Ok((
|
||||
AlgorithmSignatureMachine { params: self.params.clone(), B, Rs, share },
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use core::ops::Deref;
|
||||
|
||||
use std::collections::HashMap;
|
||||
#[cfg(test)]
|
||||
use std::str::FromStr;
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use group::{ff::PrimeField, GroupEncoding};
|
||||
|
@ -103,7 +106,7 @@ fn vectors_to_multisig_keys<C: Curve>(vectors: &Vectors) -> HashMap<u16, Thresho
|
|||
assert_eq!(these_keys.params().t(), vectors.threshold);
|
||||
assert_eq!(usize::from(these_keys.params().n()), shares.len());
|
||||
assert_eq!(these_keys.params().i(), i);
|
||||
assert_eq!(these_keys.secret_share(), shares[usize::from(i - 1)]);
|
||||
assert_eq!(these_keys.secret_share().deref(), &shares[usize::from(i - 1)]);
|
||||
assert_eq!(hex::encode(these_keys.group_key().to_bytes().as_ref()), vectors.group_key);
|
||||
keys.insert(i, ThresholdKeys::new(these_keys));
|
||||
}
|
||||
|
@ -148,12 +151,15 @@ pub fn test_with_vectors<R: RngCore + CryptoRng, C: Curve, H: Hram<C>>(
|
|||
let mut machines = machines
|
||||
.drain(..)
|
||||
.map(|(i, machine)| {
|
||||
let nonces = [
|
||||
C::read_F::<&[u8]>(&mut hex::decode(&vectors.nonces[c][0]).unwrap().as_ref()).unwrap(),
|
||||
C::read_F::<&[u8]>(&mut hex::decode(&vectors.nonces[c][1]).unwrap().as_ref()).unwrap(),
|
||||
];
|
||||
let nonce = |i| {
|
||||
Zeroizing::new(
|
||||
C::read_F::<&[u8]>(&mut hex::decode(&vectors.nonces[c][i]).unwrap().as_ref()).unwrap(),
|
||||
)
|
||||
};
|
||||
let nonces = [nonce(0), nonce(1)];
|
||||
c += 1;
|
||||
let these_commitments = [C::generator() * nonces[0], C::generator() * nonces[1]];
|
||||
let these_commitments =
|
||||
[C::generator() * nonces[0].deref(), C::generator() * nonces[1].deref()];
|
||||
let machine = machine.unsafe_override_preprocess(
|
||||
vec![Nonce(nonces)],
|
||||
Preprocess {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "multiexp"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
description = "Multiexponentation algorithms for ff/group"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/multiexp"
|
||||
|
|
|
@ -7,19 +7,21 @@ use group::Group;
|
|||
|
||||
use crate::{multiexp, multiexp_vartime};
|
||||
|
||||
#[cfg(feature = "batch")]
|
||||
/// A batch verifier intended to verify a series of statements are each equivalent to zero.
|
||||
#[derive(Clone, Zeroize)]
|
||||
pub struct BatchVerifier<Id: Copy + Zeroize, G: Group + Zeroize>(Vec<(Id, Vec<(G::Scalar, G)>)>);
|
||||
|
||||
#[cfg(feature = "batch")]
|
||||
impl<Id: Copy + Zeroize, G: Group + Zeroize> BatchVerifier<Id, G>
|
||||
where
|
||||
<G as Group>::Scalar: PrimeFieldBits + Zeroize,
|
||||
{
|
||||
/// Create a new batch verifier, expected to verify the following amount of statements.
|
||||
/// This is a size hint and is not required to be accurate.
|
||||
pub fn new(capacity: usize) -> BatchVerifier<Id, G> {
|
||||
BatchVerifier(Vec::with_capacity(capacity))
|
||||
}
|
||||
|
||||
/// Queue a statement for batch verification.
|
||||
pub fn queue<R: RngCore + CryptoRng, I: IntoIterator<Item = (G::Scalar, G)>>(
|
||||
&mut self,
|
||||
rng: &mut R,
|
||||
|
@ -71,6 +73,7 @@ where
|
|||
self.0.push((id, pairs.into_iter().map(|(scalar, point)| (scalar * u, point)).collect()));
|
||||
}
|
||||
|
||||
/// Perform batch verification, returning a boolean of if the statements equaled zero.
|
||||
#[must_use]
|
||||
pub fn verify_core(&self) -> bool {
|
||||
let mut flat = self.0.iter().flat_map(|pairs| pairs.1.iter()).cloned().collect::<Vec<_>>();
|
||||
|
@ -79,12 +82,14 @@ where
|
|||
res
|
||||
}
|
||||
|
||||
/// Perform batch verification, zeroizing the statements verified.
|
||||
pub fn verify(mut self) -> bool {
|
||||
let res = self.verify_core();
|
||||
self.zeroize();
|
||||
res
|
||||
}
|
||||
|
||||
/// Perform batch verification in variable time.
|
||||
#[must_use]
|
||||
pub fn verify_vartime(&self) -> bool {
|
||||
multiexp_vartime(&self.0.iter().flat_map(|pairs| pairs.1.iter()).cloned().collect::<Vec<_>>())
|
||||
|
@ -92,6 +97,9 @@ where
|
|||
.into()
|
||||
}
|
||||
|
||||
/// Perform a binary search to identify which statement does not equal 0, returning None if all
|
||||
/// statements do. This function will only return the ID of one invalid statement, even if
|
||||
/// multiple are invalid.
|
||||
// A constant time variant may be beneficial for robust protocols
|
||||
pub fn blame_vartime(&self) -> Option<Id> {
|
||||
let mut slice = self.0.as_slice();
|
||||
|
@ -115,12 +123,16 @@ where
|
|||
.map(|(id, _)| *id)
|
||||
}
|
||||
|
||||
/// Perform constant time batch verification, and if verification fails, identify one faulty
|
||||
/// statement in variable time.
|
||||
pub fn verify_with_vartime_blame(mut self) -> Result<(), Id> {
|
||||
let res = if self.verify_core() { Ok(()) } else { Err(self.blame_vartime().unwrap()) };
|
||||
self.zeroize();
|
||||
res
|
||||
}
|
||||
|
||||
/// Perform variable time batch verification, and if verification fails, identify one faulty
|
||||
/// statement in variable time.
|
||||
pub fn verify_vartime_with_vartime_blame(&self) -> Result<(), Id> {
|
||||
if self.verify_vartime() {
|
||||
Ok(())
|
||||
|
|
|
@ -160,7 +160,8 @@ fn algorithm(len: usize) -> Algorithm {
|
|||
}
|
||||
}
|
||||
|
||||
// Performs a multiexp, automatically selecting the optimal algorithm based on amount of pairs
|
||||
/// Performs a multiexponentation, automatically selecting the optimal algorithm based on the
|
||||
/// amount of pairs.
|
||||
pub fn multiexp<G: Group>(pairs: &[(G::Scalar, G)]) -> G
|
||||
where
|
||||
G::Scalar: PrimeFieldBits + Zeroize,
|
||||
|
@ -173,6 +174,8 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Performs a multiexponentation in variable time, automatically selecting the optimal algorithm
|
||||
/// based on the amount of pairs.
|
||||
pub fn multiexp_vartime<G: Group>(pairs: &[(G::Scalar, G)]) -> G
|
||||
where
|
||||
G::Scalar: PrimeFieldBits,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "schnorr-signatures"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
description = "Minimal Schnorr signatures crate hosting common code"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/schnorr"
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use core::ops::Deref;
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use zeroize::Zeroize;
|
||||
use zeroize::{Zeroize, Zeroizing};
|
||||
|
||||
use group::{
|
||||
ff::{Field, PrimeField},
|
||||
|
@ -46,11 +47,16 @@ impl<C: Ciphersuite> SchnorrSignature<C> {
|
|||
}
|
||||
|
||||
/// Sign a Schnorr signature with the given nonce for the specified challenge.
|
||||
pub fn sign(mut private_key: C::F, mut nonce: C::F, challenge: C::F) -> SchnorrSignature<C> {
|
||||
let res = SchnorrSignature { R: C::generator() * nonce, s: nonce + (private_key * challenge) };
|
||||
private_key.zeroize();
|
||||
nonce.zeroize();
|
||||
res
|
||||
pub fn sign(
|
||||
private_key: &Zeroizing<C::F>,
|
||||
nonce: Zeroizing<C::F>,
|
||||
challenge: C::F,
|
||||
) -> SchnorrSignature<C> {
|
||||
SchnorrSignature {
|
||||
// Uses deref instead of * as * returns C::F yet deref returns &C::F, preventing a copy
|
||||
R: C::generator() * nonce.deref(),
|
||||
s: (challenge * private_key.deref()) + nonce.deref(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify a Schnorr signature for the given key with the specified challenge.
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
use core::ops::Deref;
|
||||
|
||||
use rand_core::OsRng;
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use blake2::{digest::typenum::U32, Blake2b};
|
||||
type Blake2b256 = Blake2b<U32>;
|
||||
|
||||
|
@ -14,11 +18,11 @@ use crate::{
|
|||
};
|
||||
|
||||
pub(crate) fn sign<C: Ciphersuite>() {
|
||||
let private_key = C::random_nonzero_F(&mut OsRng);
|
||||
let nonce = C::random_nonzero_F(&mut OsRng);
|
||||
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
|
||||
assert!(SchnorrSignature::<C>::sign(private_key, nonce, challenge)
|
||||
.verify(C::generator() * private_key, challenge));
|
||||
assert!(SchnorrSignature::<C>::sign(&private_key, nonce, challenge)
|
||||
.verify(C::generator() * private_key.deref(), challenge));
|
||||
}
|
||||
|
||||
// The above sign function verifies signing works
|
||||
|
@ -35,16 +39,20 @@ pub(crate) fn batch_verify<C: Ciphersuite>() {
|
|||
let mut challenges = vec![];
|
||||
let mut sigs = vec![];
|
||||
for i in 0 .. 5 {
|
||||
keys.push(C::random_nonzero_F(&mut OsRng));
|
||||
keys.push(Zeroizing::new(C::random_nonzero_F(&mut OsRng)));
|
||||
challenges.push(C::random_nonzero_F(&mut OsRng));
|
||||
sigs.push(SchnorrSignature::<C>::sign(keys[i], C::random_nonzero_F(&mut OsRng), challenges[i]));
|
||||
sigs.push(SchnorrSignature::<C>::sign(
|
||||
&keys[i],
|
||||
Zeroizing::new(C::random_nonzero_F(&mut OsRng)),
|
||||
challenges[i],
|
||||
));
|
||||
}
|
||||
|
||||
// Batch verify
|
||||
{
|
||||
let mut batch = BatchVerifier::new(5);
|
||||
for (i, sig) in sigs.iter().enumerate() {
|
||||
sig.batch_verify(&mut OsRng, &mut batch, i, C::generator() * keys[i], challenges[i]);
|
||||
sig.batch_verify(&mut OsRng, &mut batch, i, C::generator() * keys[i].deref(), challenges[i]);
|
||||
}
|
||||
batch.verify_with_vartime_blame().unwrap();
|
||||
}
|
||||
|
@ -60,7 +68,7 @@ pub(crate) fn batch_verify<C: Ciphersuite>() {
|
|||
if i == 2 {
|
||||
sig.s -= C::F::one();
|
||||
}
|
||||
sig.batch_verify(&mut OsRng, &mut batch, i, C::generator() * keys[i], challenges[i]);
|
||||
sig.batch_verify(&mut OsRng, &mut batch, i, C::generator() * keys[i].deref(), challenges[i]);
|
||||
}
|
||||
if let Err(blame) = batch.verify_with_vartime_blame() {
|
||||
assert!((blame == 1) || (blame == 2));
|
||||
|
@ -76,12 +84,16 @@ pub(crate) fn aggregate<C: Ciphersuite>() {
|
|||
let mut challenges = vec![];
|
||||
let mut aggregator = SchnorrAggregator::<Blake2b256, C>::new();
|
||||
for i in 0 .. 5 {
|
||||
keys.push(C::random_nonzero_F(&mut OsRng));
|
||||
keys.push(Zeroizing::new(C::random_nonzero_F(&mut OsRng)));
|
||||
challenges.push(C::random_nonzero_F(&mut OsRng));
|
||||
aggregator.aggregate(
|
||||
C::generator() * keys[i],
|
||||
C::generator() * keys[i].deref(),
|
||||
challenges[i],
|
||||
SchnorrSignature::<C>::sign(keys[i], C::random_nonzero_F(&mut OsRng), challenges[i]),
|
||||
SchnorrSignature::<C>::sign(
|
||||
&keys[i],
|
||||
Zeroizing::new(C::random_nonzero_F(&mut OsRng)),
|
||||
challenges[i],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -91,7 +103,7 @@ pub(crate) fn aggregate<C: Ciphersuite>() {
|
|||
assert!(aggregate.verify::<Blake2b256>(
|
||||
keys
|
||||
.iter()
|
||||
.map(|key| C::generator() * key)
|
||||
.map(|key| C::generator() * key.deref())
|
||||
.zip(challenges.iter().cloned())
|
||||
.collect::<Vec<_>>()
|
||||
.as_ref()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "flexible-transcript"
|
||||
version = "0.1.3"
|
||||
version = "0.2.0"
|
||||
description = "A simple transcript trait definition, along with viable options"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/transcript"
|
||||
|
|
|
@ -18,7 +18,7 @@ pub trait Transcript {
|
|||
fn domain_separate(&mut self, label: &'static [u8]);
|
||||
|
||||
/// Append a message to the transcript.
|
||||
fn append_message(&mut self, label: &'static [u8], message: &[u8]);
|
||||
fn append_message<M: AsRef<[u8]>>(&mut self, label: &'static [u8], message: M);
|
||||
|
||||
/// Produce a challenge. This MUST update the transcript as it does so, preventing the same
|
||||
/// challenge from being generated multiple times.
|
||||
|
@ -77,13 +77,13 @@ impl<D: Clone + SecureDigest> Transcript for DigestTranscript<D> {
|
|||
res
|
||||
}
|
||||
|
||||
fn domain_separate(&mut self, label: &[u8]) {
|
||||
fn domain_separate(&mut self, label: &'static [u8]) {
|
||||
self.append(DigestTranscriptMember::Domain, label);
|
||||
}
|
||||
|
||||
fn append_message(&mut self, label: &'static [u8], message: &[u8]) {
|
||||
fn append_message<M: AsRef<[u8]>>(&mut self, label: &'static [u8], message: M) {
|
||||
self.append(DigestTranscriptMember::Label, label);
|
||||
self.append(DigestTranscriptMember::Value, message);
|
||||
self.append(DigestTranscriptMember::Value, message.as_ref());
|
||||
}
|
||||
|
||||
fn challenge(&mut self, label: &'static [u8]) -> Self::Challenge {
|
||||
|
|
|
@ -27,8 +27,8 @@ impl Transcript for MerlinTranscript {
|
|||
self.append_message(b"dom-sep", label);
|
||||
}
|
||||
|
||||
fn append_message(&mut self, label: &'static [u8], message: &[u8]) {
|
||||
self.0.append_message(label, message);
|
||||
fn append_message<M: AsRef<[u8]>>(&mut self, label: &'static [u8], message: M) {
|
||||
self.0.append_message(label, message.as_ref());
|
||||
}
|
||||
|
||||
fn challenge(&mut self, label: &'static [u8]) -> Self::Challenge {
|
||||
|
|
|
@ -15,8 +15,9 @@ rustdoc-args = ["--cfg", "docsrs"]
|
|||
|
||||
[dependencies]
|
||||
async-trait = "0.1"
|
||||
rand_core = "0.6"
|
||||
zeroize = "1.5"
|
||||
thiserror = "1"
|
||||
rand_core = "0.6"
|
||||
|
||||
group = "0.12"
|
||||
|
||||
|
|
|
@ -236,6 +236,7 @@ impl Coin for Monero {
|
|||
|
||||
#[cfg(test)]
|
||||
async fn test_send(&self, address: Self::Address) {
|
||||
use zeroize::Zeroizing;
|
||||
use rand_core::OsRng;
|
||||
|
||||
let new_block = self.get_latest_block_number().await.unwrap() + 1;
|
||||
|
@ -263,7 +264,7 @@ impl Coin for Monero {
|
|||
self.rpc.get_fee().await.unwrap(),
|
||||
)
|
||||
.unwrap()
|
||||
.sign(&mut OsRng, &self.rpc, &Scalar::one())
|
||||
.sign(&mut OsRng, &self.rpc, &Zeroizing::new(Scalar::one()))
|
||||
.await
|
||||
.unwrap();
|
||||
self.rpc.publish_transaction(&tx).await.unwrap();
|
||||
|
|
|
@ -39,7 +39,7 @@ impl<C: Curve> WalletKeys<C> {
|
|||
let mut transcript = RecommendedTranscript::new(DST);
|
||||
transcript.append_message(b"chain", chain);
|
||||
transcript.append_message(b"curve", C::ID);
|
||||
transcript.append_message(b"group_key", self.keys.group_key().to_bytes().as_ref());
|
||||
transcript.append_message(b"group_key", self.keys.group_key().to_bytes());
|
||||
self.keys.offset(<C as Ciphersuite>::hash_to_F(DST, &transcript.challenge(b"offset")))
|
||||
}
|
||||
}
|
||||
|
@ -314,12 +314,12 @@ impl<D: CoinDb, C: Coin> Wallet<D, C> {
|
|||
// Create the transcript for this transaction
|
||||
let mut transcript = RecommendedTranscript::new(b"Serai Processor Wallet Send");
|
||||
transcript
|
||||
.append_message(b"canonical_block", &u64::try_from(canonical).unwrap().to_le_bytes());
|
||||
.append_message(b"canonical_block", u64::try_from(canonical).unwrap().to_le_bytes());
|
||||
transcript.append_message(
|
||||
b"acknowledged_block",
|
||||
&u64::try_from(acknowledged_block).unwrap().to_le_bytes(),
|
||||
u64::try_from(acknowledged_block).unwrap().to_le_bytes(),
|
||||
);
|
||||
transcript.append_message(b"index", &u64::try_from(txs.len()).unwrap().to_le_bytes());
|
||||
transcript.append_message(b"index", u64::try_from(txs.len()).unwrap().to_le_bytes());
|
||||
|
||||
let tx = self
|
||||
.coin
|
||||
|
|
Loading…
Reference in a new issue