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 12, 2024
1 parent 054c3e0 commit c7f6410
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 27 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 = { path = "/Users/bogdan/wb/github/rustcrypto-crates/bip32", 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
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
76 changes: 58 additions & 18 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};

Expand Down Expand Up @@ -42,7 +44,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 @@ -169,31 +171,69 @@ 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(&self, path: &str) -> Vec<PrivateKeyBytes> {
let mut public_key = self.verifying_key();
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 path: DerivationPath = path.parse().unwrap();

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

tweaks
}

fn apply_tweaks_public(public_key: VerifyingKey, tweaks: &[PrivateKeyBytes]) -> VerifyingKey {
let mut public_key = public_key;
for tweak in tweaks {
public_key = public_key.derive_child(*tweak).unwrap();
}
public_key
}

fn apply_tweaks_private(private_key: SigningKey, tweaks: &[PrivateKeyBytes]) -> SigningKey {
let mut private_key = private_key;
for tweak in tweaks {
private_key = private_key.derive_child(*tweak).unwrap();
}
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(&self, path: &str) -> VerifyingKey {
let tweaks = self.derive_tweaks(path);
Self::apply_tweaks_public(self.verifying_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, path: &str) -> Self {
let tweaks = self.derive_tweaks(path);

let secret_share = self.secret_share.expose_secret().to_signing_key().unwrap();
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))
.map(|(id, point)| {
(id, {
let public_share = point.to_verifying_key().unwrap();
let public_share = Self::apply_tweaks_public(public_share, &tweaks);
Point::from_verifying_key(&public_share)
})
})
.collect();

Self {
Expand Down
14 changes: 7 additions & 7 deletions synedrion/tests/threshold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,14 +236,14 @@ 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";
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))
.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 = t_key_shares[0].derived_verifying_key_bip32(path);
assert_eq!(child_vkey, child_key_shares[0].verifying_key());

// Reshare to `n` nodes
Expand Down Expand Up @@ -313,7 +313,7 @@ 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 = new_t_key_shares[0].derived_verifying_key_bip32(path);
assert_eq!(child_vkey, child_vkey_after_resharing);

// Generate auxiliary data
Expand Down Expand Up @@ -341,13 +341,13 @@ 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)
.to_key_share(&selected_parties),
new_t_key_shares[2]
.derive(child_key_seed)
.derive_bip32(path)
.to_key_share(&selected_parties),
new_t_key_shares[4]
.derive(child_key_seed)
.derive_bip32(path)
.to_key_share(&selected_parties),
];
let selected_aux_infos = vec![
Expand Down

0 comments on commit c7f6410

Please sign in to comment.