Skip to content

Commit

Permalink
Second attempt to implement HD wallets
Browse files Browse the repository at this point in the history
Signed-off-by: Denis Varlakov <[email protected]>
  • Loading branch information
survived committed Jul 17, 2024
1 parent 41d3391 commit b7f727a
Showing 1 changed file with 47 additions and 305 deletions.
352 changes: 47 additions & 305 deletions givre/src/key_share.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
//!
//! This module re-exports type definitions from [`key_share`] crate.
#![allow(non_snake_case)]
#![allow(missing_docs)] // TODO: remove

use generic_ec::{Curve, NonZero, Point, Scalar, SecretScalar};
use generic_ec::Curve;

#[doc(inline)]
pub use key_share::{
Expand All @@ -16,332 +16,74 @@ pub use key_share::{
#[doc(inline)]
pub use key_share::reconstruct_secret_key;

use crate::ciphersuite::NormalizedPoint;

mod sealed {
pub trait Sealed {}
}

/// Any data structure that provides the same information as validated [`KeyInfo`]
pub trait KeyInfoLike: sealed::Sealed {
/// Curve type
type Curve: Curve;

/// Public key corresponding to shared secret key
///
/// Corresponds to [`KeyInfo::shared_public_key`]
fn shared_public_key(&self) -> Point<Self::Curve>;

/// Returns public share of j-th signer
///
/// Corresponds to [`DirtyKeyInfo::public_shares`]
fn public_share(&self, j: u16) -> Option<Point<Self::Curve>>;
// /// Returns iterator over public shares of all signers, from signer with index
// /// `0` to signer with index `n-1`
// fn public_shares(&self) -> impl Iterator<Item = NonZero<Point<Self::Curve>>>;

/// Returns signing threshold
///
/// Corresponds to [`KeyInfo::min_signers`]
fn min_signers(&self) -> u16;
/// Returns share preimage of j-th signer
///
/// Corresponds to [`DirtyKeyInfo::share_preimage`]
fn share_preimage(&self, j: u16) -> Option<NonZero<Scalar<Self::Curve>>>;

/// Indicates if it's an additive key share
///
/// Returns:
/// * `true` if it's additive key share (implies that `min_signers == n`)
/// * `false` if it's polynomial key share (threshold can be anything in range `2 <= min_signers <= n`)
fn is_additive(&self) -> bool;

/// Returns the chain code
///
/// Corresponds to [`DirtyKeyInfo::chain_code`]
#[cfg(feature = "hd-wallets")]
fn chain_code(&self) -> Option<slip_10::ChainCode>;

/// Returns a key share corresponding to the derived child key
///
/// Returns error if the key share is not HD-capable, or if path is invalid
#[cfg(feature = "hd-wallets")]
fn with_hd_path<Index>(
self,
path: impl IntoIterator<Item = Index>,
) -> Result<
WithAdditiveShift<Self>,
HdError<<Index as TryInto<slip_10::NonHardenedIndex>>::Error>,
>
where
slip_10::NonHardenedIndex: TryFrom<Index>,
Self: Sized,
{
let epub = self.extended_public_key().ok_or(HdError::DisabledHd)?;
let shift = derive_additive_shift(epub, path).map_err(HdError::InvalidPath)?;
Ok(WithAdditiveShift::new(self, shift))
}
pub struct With<T: transformations::TryTransform<K>, K> {
inner: T::Out,
}

/// Any data structure that provides the same information as validated [`KeyShare`]
///
/// This trait provides read-only access to key share fields. [`KeyShare`] obviously is
/// the core structure that implements this trait. It may be composed with other wrappers
/// like [`HdChild`] to "modify" the key share.
pub trait KeyShareLike: KeyInfoLike {
/// Index of local party in key generation protocol
///
/// Corresponds to [`DirtyKeyShare::i`]
fn i(&self) -> u16;

/// Secret share
///
/// Corresponds to [`DirtyKeyShare::x`]
fn x(&self) -> &SecretScalar<Self::Curve>;

/// Returns the extended public key, if HD support was enabled
#[cfg(feature = "hd-wallets")]
fn extended_public_key(&self) -> Option<slip_10::ExtendedPublicKey<Self::Curve>> {
Some(slip_10::ExtendedPublicKey {
public_key: self.shared_public_key(),
chain_code: self.chain_code()?,
})
}
}

#[cfg(feature = "hd-wallets")]
fn derive_additive_shift<E: Curve, Index>(
mut epub: slip_10::ExtendedPublicKey<E>,
path: impl IntoIterator<Item = Index>,
) -> Result<Scalar<E>, <Index as TryInto<slip_10::NonHardenedIndex>>::Error>
where
slip_10::NonHardenedIndex: TryFrom<Index>,
{
let mut additive_shift = Scalar::<E>::zero();

for child_index in path {
let child_index: slip_10::NonHardenedIndex = child_index.try_into()?;
let shift = slip_10::derive_public_shift(&epub, child_index);

additive_shift += shift.shift;
epub = shift.child_public_key;
impl<T: transformations::TryTransform<K>, K> core::ops::Deref for With<T, K> {
type Target = T::Out;
fn deref(&self) -> &Self::Target {
&self.inner
}

Ok(additive_shift)
}

impl<E: Curve> sealed::Sealed for KeyShare<E> {}
impl<E: Curve> KeyInfoLike for KeyShare<E> {
type Curve = E;
pub mod transformations {
use generic_ec::Curve;

fn shared_public_key(&self) -> Point<Self::Curve> {
*self.shared_public_key
}
fn public_share(&self, j: u16) -> Option<Point<Self::Curve>> {
self.public_shares.get(usize::from(j)).map(|s| **s)
}
use super::{DirtyKeyShare, KeyShare};

fn min_signers(&self) -> u16 {
self.min_signers()
pub trait TryTransform<K>: super::sealed::Sealed {
type Out;
type Err;
fn transform(self, value: K) -> Result<Self::Out, Self::Err>;
}

fn share_preimage(&self, j: u16) -> Option<NonZero<Scalar<Self::Curve>>> {
(**self).share_preimage(j)
pub struct HdDerivation<P> {
pub path: P,
}
impl<P> super::sealed::Sealed for HdDerivation<P> {}
impl<E, P, Index> TryTransform<KeyShare<E>> for HdDerivation<P>
where
E: Curve,
P: IntoIterator<Item = Index>,
slip_10::NonHardenedIndex: TryFrom<Index>,
{
type Out = DirtyKeyShare<E>;
type Err = DeriveChildError<<slip_10::NonHardenedIndex as TryFrom<Index>>::Error>;

fn is_additive(&self) -> bool {
self.vss_setup.is_none()
fn transform(self, value: KeyShare<E>) -> Result<Self::Out, Self::Err> {
todo!()
}
}

fn chain_code(&self) -> Option<slip_10::ChainCode> {
self.chain_code
}
}
impl<E: Curve> KeyShareLike for KeyShare<E> {
fn i(&self) -> u16 {
self.i
pub struct DeriveChildError<E>(DeriveChildReason<E>);
enum DeriveChildReason<E> {
DisabledHd,
InvalidPath(E),
InvalidChildKey,
}

fn x(&self) -> &SecretScalar<Self::Curve> {
&self.x
}
pub(crate) struct Normalization;
}

/// Normalizes the key share
///
/// Makes sure that shared public key is normalized. Negates the shared secret
/// key if necessary.
///
/// It's not exposed in public API, it's rather used internally during the
/// signing process.
pub(crate) struct WithNormalization<C: crate::Ciphersuite, K> {
key_share: K,
/// Indicates whether the shared secret key needs to be negated
needs_negation: bool,
normalized_pk: NormalizedPoint<C, Point<C::Curve>>,
normalized_share: Option<SecretScalar<C::Curve>>,
mod sealed {
pub trait Sealed {}
}

impl<E, C, K> WithNormalization<C, K>
where
E: generic_ec::Curve,
C: crate::Ciphersuite<Curve = E>,
K: KeyShareLike<Curve = E>,
/// Any valid key share
pub trait AnyKeyShare<E: Curve>:
core::ops::Deref<Target = DirtyKeyShare<E>> + sealed::Sealed
{
pub fn new(key_share: K) -> Self {
let (pk, needs_negation) =
match NormalizedPoint::try_normalize(key_share.shared_public_key()) {
Ok(pk) => (pk, false),
Err(pk) => (pk, true),
};
let share = if needs_negation {
Some(SecretScalar::new(&mut -key_share.x().as_ref()))
} else {
None
};
Self {
key_share,
normalized_pk: pk,
normalized_share: share,
needs_negation,
}
}

pub fn normalized_pk(&self) -> NormalizedPoint<C, Point<E>> {
self.normalized_pk
}
}

impl<C, K> sealed::Sealed for WithNormalization<C, K> where C: crate::Ciphersuite {}
impl<E, C, K> KeyInfoLike for WithNormalization<C, K>
where
E: generic_ec::Curve,
C: crate::Ciphersuite<Curve = E>,
K: KeyShareLike<Curve = E>,
{
type Curve = E;

fn shared_public_key(&self) -> Point<E> {
*self.normalized_pk()
}

fn public_share(&self, j: u16) -> Option<Point<E>> {
if !self.needs_negation {
self.key_share.public_share(j)
} else {
self.key_share.public_share(j).map(|s| -s)
}
}

fn min_signers(&self) -> u16 {
self.key_share.min_signers()
}

fn share_preimage(&self, j: u16) -> Option<NonZero<Scalar<E>>> {
self.key_share.share_preimage(j)
}

fn is_additive(&self) -> bool {
self.key_share.is_additive()
}
impl<E: Curve> sealed::Sealed for KeyShare<E> {}
impl<E: Curve> AnyKeyShare<E> for KeyShare<E> {}

fn chain_code(&self) -> Option<slip_10::ChainCode> {
self.key_share.chain_code()
}
}
impl<E, C, K> KeyShareLike for WithNormalization<C, K>
impl<T: transformations::TryTransform<K>, K> sealed::Sealed for With<T, K> {}
impl<E, T, K> AnyKeyShare<E> for With<T, K>
where
E: generic_ec::Curve,
C: crate::Ciphersuite<Curve = E>,
K: KeyShareLike<Curve = E>,
E: Curve,
T: transformations::TryTransform<K, Out = DirtyKeyShare<E>>,
K: AnyKeyShare<E>,
{
fn i(&self) -> u16 {
self.key_share.i()
}

fn x(&self) -> &SecretScalar<E> {
self.normalized_share
.as_ref()
.unwrap_or_else(|| self.key_share.x())
}
}

/// Adds additive shift to the key share
pub struct WithAdditiveShift<K: KeyInfoLike> {
shift: Scalar<K::Curve>,
Shift: Point<K::Curve>,
shifted_x: Option<SecretScalar<K::Curve>>,
/// When it's `None`, chain code is inherited from the `key_share`
new_chain_code: Option<slip_10::ChainCode>,
key_share: K,
}

impl<E: Curve, K: KeyInfoLike<Curve = E>> WithAdditiveShift<K> {
fn new(key_share: K, shift: Scalar<E>) -> Self {
let Shift = Point::generator() * shift;
let shifted_x = if !key_share.is_additive() || key_share.i() == 0 {
// * For polynomial key share, we add an additive shift to each share
// * For additive key share, we add the shift only to the first key share
Some(SecretScalar::new(&mut (key_share.x() + shift)))
} else {
None
};
Self {
shift,
Shift,
shifted_x,
new_chain_code: None,
key_share,
}
}

fn set_chain_code(mut self, chain_code: slip_10::ChainCode) -> Self {
self.new_chain_code = Some(chain_code);
self
}
}

impl<K: KeyShareLike> sealed::Sealed for WithAdditiveShift<K> {}
impl<E: Curve, K: KeyShareLike<Curve = E>> KeyShareLike for WithAdditiveShift<K> {
type Curve = E;

fn shared_public_key(&self) -> Point<E> {
self.key_share.shared_public_key() + self.Shift
}

fn public_share(&self, j: u16) -> Option<Point<E>> {
// * For polynomial key share, we add an additive shift to each share
// * For additive key share, we add the shift only to the first key share
if !self.key_share.is_additive() || j == 0 {
self.key_share.public_share(j).map(|s| s + self.Shift)
} else {
self.key_share.public_share(j)
}
}

fn min_signers(&self) -> u16 {
self.key_share.min_signers()
}

fn share_preimage(&self, j: u16) -> Option<NonZero<Scalar<E>>> {
self.key_share.share_preimage(j)
}

fn is_additive(&self) -> bool {
self.key_share.is_additive()
}

fn chain_code(&self) -> Option<slip_10::ChainCode> {
self.new_chain_code.or_else(|| self.key_share.chain_code())
}

fn i(&self) -> u16 {
self.key_share.i()
}

fn x(&self) -> &SecretScalar<E> {
self.shifted_x
.as_ref()
.unwrap_or_else(|| self.key_share.x())
}
}

0 comments on commit b7f727a

Please sign in to comment.