Skip to content

Commit

Permalink
Use bip32 for key derivation
Browse files Browse the repository at this point in the history
  • Loading branch information
fjarri committed Jul 18, 2024
1 parent 3be1339 commit 8ac2d36
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 31 deletions.
1 change: 1 addition & 0 deletions synedrion/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ base64 = { version = "0.21", default-features = false, features = ["alloc"] }
hashing-serializer = { version = "0.1", default-features = false }
secrecy = { version = "0.8", default-features = false, features = ["alloc", "serde"] }
zeroize = { version = "1.8", default-features = false, features = ["alloc", "zeroize_derive"] }
bip32 = { version = "0.5.2", default-features = false, features = ["alloc", "secp256k1"] }

# Note: `alloc` is needed for `crytpto-bigint`'s dependency `serdect` to be able
# to serialize Uints in human-readable formats.
Expand Down
14 changes: 13 additions & 1 deletion synedrion/src/curve/arithmetic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ use k256::elliptic_curve::{
FieldBytesSize,
NonZeroScalar,
};
use k256::{ecdsa::VerifyingKey, Secp256k1};
use k256::{
ecdsa::{SigningKey, VerifyingKey},
Secp256k1,
};
use rand_core::CryptoRngCore;
use secrecy::{CloneableSecret, DebugSecret, SerializableSecret};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
Expand Down Expand Up @@ -94,6 +97,15 @@ impl Scalar {
self.0
}

pub fn to_signing_key(self) -> Option<SigningKey> {
let scalar: Option<NonZeroScalar<Secp256k1>> = NonZeroScalar::new(self.0).into();
Some(SigningKey::from(scalar?))
}

pub fn from_signing_key(sk: &SigningKey) -> Self {
Self(*sk.as_nonzero_scalar().as_ref())
}

pub(crate) fn try_from_bytes(bytes: &[u8]) -> Result<Self, String> {
let arr =
GenericArray::<u8, FieldBytesSize<Secp256k1>>::from_exact_iter(bytes.iter().cloned())
Expand Down
1 change: 1 addition & 0 deletions synedrion/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ mod uint;
mod www02;

// Some re-exports to avoid the need for version-matching
pub use bip32;
pub use k256;
pub use k256::ecdsa;
pub use signature;
Expand Down
2 changes: 1 addition & 1 deletion synedrion/src/tools/hashing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ impl Chain for FofHasher {
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct HashOutput(
// Length of the BackendDigest output. Unfortunately we can't get it in compile-time.
#[serde(with = "serde_bytes::as_hex")] [u8; 32],
#[serde(with = "serde_bytes::as_hex")] pub(crate) [u8; 32],
);

impl AsRef<[u8]> for HashOutput {
Expand Down
99 changes: 77 additions & 22 deletions synedrion/src/www02/entities.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::vec::Vec;
use core::fmt::Debug;
use core::marker::PhantomData;

use k256::ecdsa::VerifyingKey;
use bip32::{DerivationPath, PrivateKey, PrivateKeyBytes, PublicKey};
use k256::ecdsa::{SigningKey, VerifyingKey};
use rand_core::CryptoRngCore;
use secrecy::{ExposeSecret, Secret};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -43,7 +45,7 @@ impl<P: SchemeParams, I: Clone + Ord + PartialEq + Debug> ThresholdKeyShare<P, I
rng: &mut impl CryptoRngCore,
ids: &BTreeSet<I>,
threshold: usize,
signing_key: Option<&k256::ecdsa::SigningKey>,
signing_key: Option<&SigningKey>,
) -> BTreeMap<I, Self> {
debug_assert!(threshold <= ids.len()); // TODO (#68): make the method fallible

Expand Down Expand Up @@ -170,41 +172,94 @@ impl<P: SchemeParams, I: Clone + Ord + PartialEq + Debug> ThresholdKeyShare<P, I
}
}

fn derive_tweak(seed: &[u8]) -> Scalar {
FofHasher::new_with_dst(b"key-derivation")
.chain_bytes(seed)
.finalize_to_scalar()
fn derive_tweaks(
public_key: VerifyingKey,
derivation_path: &DerivationPath,
) -> Result<Vec<PrivateKeyBytes>, bip32::Error> {
let mut public_key = public_key;

// Note: deriving the initial chain code from public information. Is this okay?
let mut chain_code = FofHasher::new_with_dst(b"chain-code-derivation")
.chain_bytes(&Point::from_verifying_key(&public_key).to_compressed_array())
.finalize()
.0;

let mut tweaks = Vec::new();
for child_number in derivation_path.iter() {
let (tweak, new_chain_code) = public_key.derive_tweak(&chain_code, child_number)?;
public_key = public_key.derive_child(tweak)?;
tweaks.push(tweak);
chain_code = new_chain_code;
}

Ok(tweaks)
}

fn apply_tweaks_public(
public_key: VerifyingKey,
tweaks: &[PrivateKeyBytes],
) -> Result<VerifyingKey, bip32::Error> {
let mut public_key = public_key;
for tweak in tweaks {
public_key = public_key.derive_child(*tweak)?;
}
Ok(public_key)
}

fn apply_tweaks_private(
private_key: SigningKey,
tweaks: &[PrivateKeyBytes],
) -> Result<SigningKey, bip32::Error> {
let mut private_key = private_key;
for tweak in tweaks {
private_key = private_key.derive_child(*tweak)?;
}
Ok(private_key)
}

/// Return the verifying key to which the derive set of shares will correspond.
pub fn derived_verifying_key(&self, seed: &[u8]) -> VerifyingKey {
let parent = self.verifying_key_as_point();
let tweak = Self::derive_tweak(seed);
(parent + tweak.mul_by_generator())
.to_verifying_key()
.unwrap()
pub fn derived_verifying_key_bip32(
public_key: &VerifyingKey,
derivation_path: &DerivationPath,
) -> Result<VerifyingKey, bip32::Error> {
let tweaks = Self::derive_tweaks(*public_key, derivation_path)?;
Self::apply_tweaks_public(*public_key, &tweaks)
}

/// Deterministically derives a child share.
pub fn derive(&self, seed: &[u8]) -> Self {
let tweak = Self::derive_tweak(seed);
let tweak_point = tweak.mul_by_generator();
let secret_share = Secret::new(self.secret_share.expose_secret() + &tweak);
/// Deterministically derives a child share using BIP-32 standard.
pub fn derive_bip32(&self, derivation_path: &DerivationPath) -> Result<Self, bip32::Error> {
let tweaks = Self::derive_tweaks(self.verifying_key(), derivation_path)?;

// Will fail here if secret share is zero
let secret_share = self
.secret_share
.expose_secret()
.to_signing_key()
.ok_or(bip32::Error::Crypto)?;
let secret_share = Secret::new(Scalar::from_signing_key(&Self::apply_tweaks_private(
secret_share,
&tweaks,
)?));

let public_shares = self
.public_shares
.clone()
.into_iter()
.map(|(id, point)| (id, point + tweak_point))
.collect();

Self {
.map(|(id, point)|
// Will fail here if the final or one of the intermediate points is an identity
point.to_verifying_key().ok_or(bip32::Error::Crypto)
.and_then(|vkey| Self::apply_tweaks_public(vkey, &tweaks))
.map(|vkey| (id, Point::from_verifying_key(&vkey))))
.collect::<Result<_, _>>()?;

Ok(Self {
owner: self.owner.clone(),
threshold: self.threshold,
share_ids: self.share_ids.clone(),
secret_share,
public_shares,
phantom: PhantomData,
}
})
}
}

Expand Down
26 changes: 19 additions & 7 deletions synedrion/tests/threshold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,14 +236,18 @@ async fn full_sequence() {
.collect::<Vec<_>>();

// Derive child shares
let child_key_seed = b"some derivation path";
let path = "m/0/2/1/4/2".parse().unwrap();
let child_key_shares = t_key_shares
.iter()
.map(|key_share| key_share.derive(child_key_seed))
.map(|key_share| key_share.derive_bip32(&path).unwrap())
.collect::<Vec<_>>();

// The full verifying key can be obtained both from the original key shares and child key shares
let child_vkey = t_key_shares[0].derived_verifying_key(child_key_seed);
let child_vkey = ThresholdKeyShare::<TestParams, VerifyingKey>::derived_verifying_key_bip32(
&t_key_shares[0].verifying_key(),
&path,
)
.unwrap();
assert_eq!(child_vkey, child_key_shares[0].verifying_key());

// Reshare to `n` nodes
Expand Down Expand Up @@ -313,7 +317,12 @@ async fn full_sequence() {
);

// Check that resharing did not change the derived child key
let child_vkey_after_resharing = new_t_key_shares[0].derived_verifying_key(child_key_seed);
let child_vkey_after_resharing =
ThresholdKeyShare::<TestParams, VerifyingKey>::derived_verifying_key_bip32(
&new_t_key_shares[0].verifying_key(),
&path,
)
.unwrap();
assert_eq!(child_vkey, child_vkey_after_resharing);

// Generate auxiliary data
Expand Down Expand Up @@ -341,13 +350,16 @@ async fn full_sequence() {
let selected_parties = BTreeSet::from([verifiers[0], verifiers[2], verifiers[4]]);
let selected_key_shares = vec![
new_t_key_shares[0]
.derive(child_key_seed)
.derive_bip32(&path)
.unwrap()
.to_key_share(&selected_parties),
new_t_key_shares[2]
.derive(child_key_seed)
.derive_bip32(&path)
.unwrap()
.to_key_share(&selected_parties),
new_t_key_shares[4]
.derive(child_key_seed)
.derive_bip32(&path)
.unwrap()
.to_key_share(&selected_parties),
];
let selected_aux_infos = vec![
Expand Down

0 comments on commit 8ac2d36

Please sign in to comment.