Skip to content

Commit

Permalink
Rahul/ifl 2044 split spender key (#4555)
Browse files Browse the repository at this point in the history
* split_spender_key

* test stub

* moving split_spender_key to frost_utils

* publishing hash_viewing_key

* removing unused import

* spender key config

* removing comment

* no need to recreate the incoming viewkey

* asserting keys are split correctly

* directly calling thread rng

* adding test for mismatch lengths

* changing test name

* adding partial eq to errors

* adding frost error kind

* split secret returns ironfish error type

* updating tests with new error type

* removing unwrap

* changed split_secret to pub crate

* variable name changes

* vec to array helper

* asserting error type

* sorting vectors

* fixing lint error

* moving identifiers after assignment

* adding test comment

* changing split_secret to map to an ironfish error
  • Loading branch information
patnir authored Jan 22, 2024
1 parent 1b010cf commit 54db84e
Show file tree
Hide file tree
Showing 4 changed files with 242 additions and 15 deletions.
10 changes: 9 additions & 1 deletion ironfish-rust/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ pub struct IronfishError {
/// in the code to reduce the cognitive load needed for using Result and Error
/// types. The second is to give a singular type to convert into NAPI errors to
/// be raised on the Javascript side.
#[derive(Debug)]
#[derive(Debug, PartialEq)]
pub enum IronfishErrorKind {
BellpersonSynthesis,
CryptoBox,
FrostLibError,
IllegalValue,
InconsistentWitness,
InvalidAssetIdentifier,
Expand All @@ -46,6 +47,7 @@ pub enum IronfishErrorKind {
InvalidOutputProof,
InvalidPaymentAddress,
InvalidPublicAddress,
InvalidSecret,
InvalidSignature,
InvalidSigningKey,
InvalidSpendProof,
Expand Down Expand Up @@ -129,3 +131,9 @@ impl From<num::TryFromIntError> for IronfishError {
IronfishError::new_with_source(IronfishErrorKind::TryFromInt, e)
}
}

impl From<ironfish_frost::frost::Error> for IronfishError {
fn from(e: ironfish_frost::frost::Error) -> IronfishError {
IronfishError::new_with_source(IronfishErrorKind::FrostLibError, e)
}
}
1 change: 1 addition & 0 deletions ironfish-rust/src/frost_utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
pub mod round_one;
pub mod round_two;
pub mod split_secret;
pub mod split_spender_key;
50 changes: 36 additions & 14 deletions ironfish-rust/src/frost_utils/split_secret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,36 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use ironfish_frost::frost;
use ironfish_frost::frost::Error;
use ironfish_frost::frost::{
frost::keys::split,
keys::{IdentifierList, KeyPackage, PublicKeyPackage},
Identifier, SigningKey,
};
use rand::rngs::ThreadRng;
use std::collections::HashMap;

use crate::errors::{IronfishError, IronfishErrorKind};

pub struct SecretShareConfig {
pub min_signers: u16,
pub max_signers: u16,
pub secret: Vec<u8>,
}

pub fn split_secret(
pub(crate) fn split_secret(
config: &SecretShareConfig,
identifiers: IdentifierList,
rng: &mut ThreadRng,
) -> Result<(HashMap<Identifier, KeyPackage>, PublicKeyPackage), Error> {
let secret_key = SigningKey::deserialize(
config
.secret
.clone()
.try_into()
.map_err(|_| Error::MalformedSigningKey)?,
)?;
) -> Result<(HashMap<Identifier, KeyPackage>, PublicKeyPackage), IronfishError> {
let secret_bytes: [u8; 32] = config
.secret
.clone()
.try_into()
.map_err(|_| IronfishError::new(IronfishErrorKind::InvalidSecret))?;

let (shares, pubkeys) = frost::keys::split(
let secret_key = SigningKey::deserialize(secret_bytes)?;

let (shares, pubkeys) = split(
&secret_key,
config.max_signers,
config.min_signers,
Expand All @@ -39,13 +40,13 @@ pub fn split_secret(
)?;

for (_k, v) in shares.clone() {
frost::keys::KeyPackage::try_from(v)?;
KeyPackage::try_from(v)?;
}

let mut key_packages: HashMap<_, _> = HashMap::new();

for (identifier, secret_share) in shares {
let key_package = frost::keys::KeyPackage::try_from(secret_share.clone()).unwrap();
let key_package = KeyPackage::try_from(secret_share.clone())?;
key_packages.insert(identifier, key_package);
}

Expand All @@ -58,6 +59,27 @@ mod test {
use crate::keys::SaplingKey;
use ironfish_frost::frost::{frost::keys::reconstruct, JubjubBlake2b512};

#[test]
fn test_invalid_secret() {
let vec = vec![1; 31];
let config = SecretShareConfig {
min_signers: 2,
max_signers: 3,
secret: vec,
};
let mut rng = rand::thread_rng();
let result = split_secret(
&config,
ironfish_frost::frost::keys::IdentifierList::Default,
&mut rng,
);
assert!(result.is_err());
assert!(
matches!(result.unwrap_err().kind, IronfishErrorKind::InvalidSecret),
"expected InvalidSecret error"
);
}

#[test]
fn test_split_secret() {
let mut rng = rand::thread_rng();
Expand Down
196 changes: 196 additions & 0 deletions ironfish-rust/src/frost_utils/split_spender_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use group::GroupEncoding;
use ironfish_frost::frost::{
keys::{IdentifierList, KeyPackage, PublicKeyPackage},
Identifier,
};
use ironfish_zkp::{constants::PROOF_GENERATION_KEY_GENERATOR, ProofGenerationKey};
use jubjub::SubgroupPoint;
use rand::thread_rng;
use std::collections::HashMap;

use crate::{
errors::{IronfishError, IronfishErrorKind},
IncomingViewKey, OutgoingViewKey, PublicAddress, SaplingKey, ViewKey,
};

use super::split_secret::{split_secret, SecretShareConfig};

type AuthorizingKey = [u8; 32];

pub struct TrustedDealerKeyPackages {
pub verifying_key: AuthorizingKey, // verifying_key is the name given to this field in the frost protocol
pub proof_generation_key: ProofGenerationKey,
pub view_key: ViewKey,
pub incoming_view_key: IncomingViewKey,
pub outgoing_view_key: OutgoingViewKey,
pub public_address: PublicAddress,
pub key_packages: HashMap<Identifier, KeyPackage>,
pub public_key_package: PublicKeyPackage,
}

pub fn split_spender_key(
coordinator_sapling_key: SaplingKey,
min_signers: u16,
max_signers: u16,
identifiers: Vec<Identifier>,
) -> Result<TrustedDealerKeyPackages, IronfishError> {
let secret = coordinator_sapling_key
.spend_authorizing_key
.to_bytes()
.to_vec();

let secret_config = SecretShareConfig {
min_signers,
max_signers,
secret,
};

let identifier_list = IdentifierList::Custom(&identifiers);

let mut rng: rand::prelude::ThreadRng = thread_rng();

let (key_packages, public_key_package) =
split_secret(&secret_config, identifier_list, &mut rng)?;

let authorizing_key_bytes = public_key_package.verifying_key().serialize();

let authorizing_key = Option::from(SubgroupPoint::from_bytes(&authorizing_key_bytes))
.ok_or_else(|| IronfishError::new(IronfishErrorKind::InvalidAuthorizingKey))?;

let proof_generation_key = ProofGenerationKey {
ak: authorizing_key,
nsk: coordinator_sapling_key.sapling_proof_generation_key().nsk,
};

let nullifier_deriving_key = *PROOF_GENERATION_KEY_GENERATOR
* coordinator_sapling_key.sapling_proof_generation_key().nsk;

let view_key = ViewKey {
authorizing_key,
nullifier_deriving_key,
};

let incoming_view_key = coordinator_sapling_key.incoming_view_key().clone();

let outgoing_view_key: OutgoingViewKey = coordinator_sapling_key.outgoing_view_key().clone();

let public_address = incoming_view_key.public_address();

Ok(TrustedDealerKeyPackages {
verifying_key: authorizing_key_bytes,
proof_generation_key,
view_key,
incoming_view_key,
outgoing_view_key,
public_address,
key_packages,
public_key_package,
})
}

#[cfg(test)]
mod test {
use super::*;
use ironfish_frost::{
frost::{frost::keys::reconstruct, JubjubBlake2b512},
participant::Secret,
};

#[test]
fn test_throws_error_with_mismatch_signer_and_identifiers_lengths() {
let mut identifiers = Vec::new();

for _ in 0..10 {
identifiers.push(
Secret::random(thread_rng())
.to_identity()
.to_frost_identifier(),
);
}

let sapling_key_1 = SaplingKey::generate_key();
// max signers > identifiers length
let result = split_spender_key(sapling_key_1, 5, 11, identifiers.clone());
assert!(result.is_err());
let err = result.err().unwrap();
assert_eq!(err.kind, IronfishErrorKind::FrostLibError);
assert!(err.to_string().contains("Incorrect number of identifiers."));

let sapling_key2 = SaplingKey::generate_key();
// max signers < identifiers length
let result = split_spender_key(sapling_key2, 5, 9, identifiers.clone());

assert!(result.is_err());
let err = result.err().unwrap();
assert_eq!(err.kind, IronfishErrorKind::FrostLibError);
assert!(err.to_string().contains("Incorrect number of identifiers."));
}

#[test]
fn test_split_spender_key_success() {
let mut identifiers = Vec::new();

for _ in 0..10 {
identifiers.push(
Secret::random(thread_rng())
.to_identity()
.to_frost_identifier(),
);
}
let mut cloned_identifiers = identifiers.clone();
cloned_identifiers.sort();

let sapling_key = SaplingKey::generate_key();

let trusted_dealer_key_packages =
split_spender_key(sapling_key.clone(), 5, 10, identifiers)
.expect("spender key split failed");

assert_eq!(
trusted_dealer_key_packages.key_packages.len(),
10,
"should have 10 key packages"
);

assert_eq!(
trusted_dealer_key_packages.view_key.to_bytes(),
sapling_key.view_key.to_bytes(),
"should have the same incoming viewing key"
);

assert_eq!(
trusted_dealer_key_packages.public_address,
sapling_key.public_address(),
"should have the same public address"
);

let spend_auth_key = sapling_key.spend_authorizing_key.to_bytes();

let key_parts: Vec<_> = trusted_dealer_key_packages
.key_packages
.values()
.cloned()
.collect();

let signing_key =
reconstruct::<JubjubBlake2b512>(&key_parts).expect("key reconstruction failed");

let scalar = signing_key.to_scalar();

assert_eq!(scalar.to_bytes(), spend_auth_key);

// assert identifiers and trusted_dealer_key_packages.key_packages.keys() are the same
let mut t_identifiers = trusted_dealer_key_packages
.key_packages
.keys()
.cloned()
.collect::<Vec<_>>();

t_identifiers.sort();
assert_eq!(t_identifiers, cloned_identifiers);
}
}

0 comments on commit 54db84e

Please sign in to comment.