mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 04:09:23 +00:00
Support taking arbitrary linear combinations of signing keys, not just additive offsets
This commit is contained in:
@@ -412,14 +412,17 @@ mod lib {
|
|||||||
#[zeroize(skip)]
|
#[zeroize(skip)]
|
||||||
pub(crate) core: Arc<ThresholdCore<C>>,
|
pub(crate) core: Arc<ThresholdCore<C>>,
|
||||||
|
|
||||||
|
// Scalar applied to these keys.
|
||||||
|
pub(crate) scalar: C::F,
|
||||||
// Offset applied to these keys.
|
// Offset applied to these keys.
|
||||||
pub(crate) offset: Option<C::F>,
|
pub(crate) offset: C::F,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// View of keys, interpolated and offset for usage.
|
/// View of keys, interpolated and with the expected linear combination taken for usage.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ThresholdView<C: Ciphersuite> {
|
pub struct ThresholdView<C: Ciphersuite> {
|
||||||
interpolation: Interpolation<C::F>,
|
interpolation: Interpolation<C::F>,
|
||||||
|
scalar: C::F,
|
||||||
offset: C::F,
|
offset: C::F,
|
||||||
group_key: C::G,
|
group_key: C::G,
|
||||||
included: Vec<Participant>,
|
included: Vec<Participant>,
|
||||||
@@ -433,6 +436,7 @@ mod lib {
|
|||||||
fmt
|
fmt
|
||||||
.debug_struct("ThresholdView")
|
.debug_struct("ThresholdView")
|
||||||
.field("interpolation", &self.interpolation)
|
.field("interpolation", &self.interpolation)
|
||||||
|
.field("scalar", &self.scalar)
|
||||||
.field("offset", &self.offset)
|
.field("offset", &self.offset)
|
||||||
.field("group_key", &self.group_key)
|
.field("group_key", &self.group_key)
|
||||||
.field("included", &self.included)
|
.field("included", &self.included)
|
||||||
@@ -444,6 +448,7 @@ mod lib {
|
|||||||
|
|
||||||
impl<C: Ciphersuite> Zeroize for ThresholdView<C> {
|
impl<C: Ciphersuite> Zeroize for ThresholdView<C> {
|
||||||
fn zeroize(&mut self) {
|
fn zeroize(&mut self) {
|
||||||
|
self.scalar.zeroize();
|
||||||
self.offset.zeroize();
|
self.offset.zeroize();
|
||||||
self.group_key.zeroize();
|
self.group_key.zeroize();
|
||||||
self.included.zeroize();
|
self.included.zeroize();
|
||||||
@@ -460,25 +465,42 @@ mod lib {
|
|||||||
impl<C: Ciphersuite> ThresholdKeys<C> {
|
impl<C: Ciphersuite> ThresholdKeys<C> {
|
||||||
/// Create a new set of ThresholdKeys from a ThresholdCore.
|
/// Create a new set of ThresholdKeys from a ThresholdCore.
|
||||||
pub fn new(core: ThresholdCore<C>) -> ThresholdKeys<C> {
|
pub fn new(core: ThresholdCore<C>) -> ThresholdKeys<C> {
|
||||||
ThresholdKeys { core: Arc::new(core), offset: None }
|
ThresholdKeys { core: Arc::new(core), scalar: C::F::ONE, offset: C::F::ZERO }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scale the keys by a given scalar to allow for various account and privacy schemes.
|
||||||
|
///
|
||||||
|
/// This scalar is ephemeral and will not be included when these keys are serialized. The
|
||||||
|
/// scalar is applied on top of any already-existing scalar/offset.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the scalar is equal to `0`.
|
||||||
|
#[must_use]
|
||||||
|
pub fn scale(mut self, scalar: C::F) -> Option<ThresholdKeys<C>> {
|
||||||
|
if bool::from(scalar.is_zero()) {
|
||||||
|
None?;
|
||||||
|
}
|
||||||
|
self.scalar *= scalar;
|
||||||
|
self.offset *= scalar;
|
||||||
|
Some(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Offset the keys by a given scalar to allow for various account and privacy schemes.
|
/// Offset the keys by a given scalar to allow for various account and privacy schemes.
|
||||||
///
|
///
|
||||||
/// This offset is ephemeral and will not be included when these keys are serialized. It also
|
/// This offset is ephemeral and will not be included when these keys are serialized. The
|
||||||
/// accumulates, so calling offset multiple times will produce a offset of the offsets' sum.
|
/// offset is applied on top of any already-existing scalar/offset.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn offset(&self, offset: C::F) -> ThresholdKeys<C> {
|
pub fn offset(mut self, offset: C::F) -> ThresholdKeys<C> {
|
||||||
let mut res = self.clone();
|
self.offset += offset;
|
||||||
// Carry any existing offset
|
self
|
||||||
// Enables schemes like Monero's subaddresses which have a per-subaddress offset and then a
|
}
|
||||||
// one-time-key offset
|
|
||||||
res.offset = Some(offset + res.offset.unwrap_or(C::F::ZERO));
|
/// Return the current scalar in-use for these keys.
|
||||||
res
|
pub fn current_scalar(&self) -> C::F {
|
||||||
|
self.scalar
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the current offset in-use for these keys.
|
/// Return the current offset in-use for these keys.
|
||||||
pub fn current_offset(&self) -> Option<C::F> {
|
pub fn current_offset(&self) -> C::F {
|
||||||
self.offset
|
self.offset
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,9 +514,9 @@ mod lib {
|
|||||||
&self.core.secret_share
|
&self.core.secret_share
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the group key, with any offset applied.
|
/// Return the group key, with the expected linear combination taken.
|
||||||
pub fn group_key(&self) -> C::G {
|
pub fn group_key(&self) -> C::G {
|
||||||
self.core.group_key + (C::generator() * self.offset.unwrap_or(C::F::ZERO))
|
(self.core.group_key * self.scalar) + (C::generator() * self.offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return all participants' verification shares without any offsetting.
|
/// Return all participants' verification shares without any offsetting.
|
||||||
@@ -507,8 +529,8 @@ mod lib {
|
|||||||
self.core.serialize()
|
self.core.serialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Obtain a view of these keys, with any offset applied, interpolated for the specified signing
|
/// Obtain a view of these keys, interpolated for the specified signing set, with the specified
|
||||||
/// set.
|
/// linear combination taken.
|
||||||
pub fn view(&self, mut included: Vec<Participant>) -> Result<ThresholdView<C>, DkgError<()>> {
|
pub fn view(&self, mut included: Vec<Participant>) -> Result<ThresholdView<C>, DkgError<()>> {
|
||||||
if (included.len() < self.params().t.into()) ||
|
if (included.len() < self.params().t.into()) ||
|
||||||
(usize::from(self.params().n()) < included.len())
|
(usize::from(self.params().n()) < included.len())
|
||||||
@@ -517,26 +539,36 @@ mod lib {
|
|||||||
}
|
}
|
||||||
included.sort();
|
included.sort();
|
||||||
|
|
||||||
|
// The interpolation occurs multiplicatively, letting us scale by the scalar now
|
||||||
|
let secret_share_scaled = Zeroizing::new(self.scalar * self.secret_share().deref());
|
||||||
let mut secret_share = Zeroizing::new(
|
let mut secret_share = Zeroizing::new(
|
||||||
self.core.interpolation.interpolation_factor(self.params().i(), &included) *
|
self.core.interpolation.interpolation_factor(self.params().i(), &included) *
|
||||||
self.secret_share().deref(),
|
secret_share_scaled.deref(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut verification_shares = self.verification_shares();
|
let mut verification_shares = self.verification_shares();
|
||||||
for (i, share) in &mut verification_shares {
|
for (i, share) in &mut verification_shares {
|
||||||
*share *= self.core.interpolation.interpolation_factor(*i, &included);
|
*share *= self.scalar * self.core.interpolation.interpolation_factor(*i, &included);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The offset is included by adding it to the participant with the lowest ID
|
/*
|
||||||
let offset = self.offset.unwrap_or(C::F::ZERO);
|
The offset is included by adding it to the participant with the lowest ID.
|
||||||
|
|
||||||
|
This is done after interpolating to ensure, regardless of the method of interpolation, that
|
||||||
|
the method of interpolation does not scale the offset. For Lagrange interpolation, we could
|
||||||
|
add the offset to every key share before interpolating, yet for Constant interpolation, we
|
||||||
|
_have_ to add it as we do here (which also works even when we intend to perform Lagrange
|
||||||
|
interpolation).
|
||||||
|
*/
|
||||||
if included[0] == self.params().i() {
|
if included[0] == self.params().i() {
|
||||||
*secret_share += offset;
|
*secret_share += self.offset;
|
||||||
}
|
}
|
||||||
*verification_shares.get_mut(&included[0]).unwrap() += C::generator() * offset;
|
*verification_shares.get_mut(&included[0]).unwrap() += C::generator() * self.offset;
|
||||||
|
|
||||||
Ok(ThresholdView {
|
Ok(ThresholdView {
|
||||||
interpolation: self.core.interpolation.clone(),
|
interpolation: self.core.interpolation.clone(),
|
||||||
offset,
|
scalar: self.scalar,
|
||||||
|
offset: self.offset,
|
||||||
group_key: self.group_key(),
|
group_key: self.group_key(),
|
||||||
secret_share,
|
secret_share,
|
||||||
original_verification_shares: self.verification_shares(),
|
original_verification_shares: self.verification_shares(),
|
||||||
@@ -553,7 +585,12 @@ mod lib {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<C: Ciphersuite> ThresholdView<C> {
|
impl<C: Ciphersuite> ThresholdView<C> {
|
||||||
/// Return the offset for this view.
|
/// Return the scalar applied to this view.
|
||||||
|
pub fn scalar(&self) -> C::F {
|
||||||
|
self.scalar
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the offset applied to this view.
|
||||||
pub fn offset(&self) -> C::F {
|
pub fn offset(&self) -> C::F {
|
||||||
self.offset
|
self.offset
|
||||||
}
|
}
|
||||||
@@ -576,7 +613,7 @@ mod lib {
|
|||||||
Some(self.interpolation.interpolation_factor(participant, &self.included))
|
Some(self.interpolation.interpolation_factor(participant, &self.included))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the interpolated, offset secret share.
|
/// Return the interpolated secret share, with the expected linear combination taken.
|
||||||
pub fn secret_share(&self) -> &Zeroizing<C::F> {
|
pub fn secret_share(&self) -> &Zeroizing<C::F> {
|
||||||
&self.secret_share
|
&self.secret_share
|
||||||
}
|
}
|
||||||
@@ -586,7 +623,8 @@ mod lib {
|
|||||||
self.original_verification_shares[&l]
|
self.original_verification_shares[&l]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the interpolated, offset verification share for the specified participant.
|
/// Return the interpolated verification share, with the expected linear combination taken,
|
||||||
|
/// for the specified participant.
|
||||||
pub fn verification_share(&self, l: Participant) -> C::G {
|
pub fn verification_share(&self, l: Participant) -> C::G {
|
||||||
self.verification_shares[&l]
|
self.verification_shares[&l]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ pub fn musig<C: Ciphersuite>(
|
|||||||
let mut group_key = C::G::identity();
|
let mut group_key = C::G::identity();
|
||||||
for l in 1 ..= keys_len {
|
for l in 1 ..= keys_len {
|
||||||
let key = keys[usize::from(l) - 1];
|
let key = keys[usize::from(l) - 1];
|
||||||
|
// TODO: Use a multiexp for this
|
||||||
group_key += key * binding[usize::from(l - 1)];
|
group_key += key * binding[usize::from(l - 1)];
|
||||||
|
|
||||||
// These errors also shouldn't be possible, for the same reasons as documented above
|
// These errors also shouldn't be possible, for the same reasons as documented above
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ use std::{
|
|||||||
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
use ciphersuite::{group::GroupEncoding, Ciphersuite};
|
use ciphersuite::{
|
||||||
|
group::{ff::Field, GroupEncoding},
|
||||||
|
Ciphersuite,
|
||||||
|
};
|
||||||
|
|
||||||
use transcript::{Transcript, RecommendedTranscript};
|
use transcript::{Transcript, RecommendedTranscript};
|
||||||
use dleq::DLEqProof;
|
use dleq::DLEqProof;
|
||||||
@@ -117,7 +120,8 @@ impl<C1: Ciphersuite, C2: Ciphersuite<F = C1::F, G = C1::G>> GeneratorPromotion<
|
|||||||
self.base.secret_share().clone(),
|
self.base.secret_share().clone(),
|
||||||
verification_shares,
|
verification_shares,
|
||||||
)),
|
)),
|
||||||
offset: None,
|
scalar: C2::F::ONE,
|
||||||
|
offset: C2::F::ZERO,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user