diff --git a/ironfish-rust/src/errors.rs b/ironfish-rust/src/errors.rs index 90c809def3..0717b88156 100644 --- a/ironfish-rust/src/errors.rs +++ b/ironfish-rust/src/errors.rs @@ -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, @@ -46,6 +47,7 @@ pub enum IronfishErrorKind { InvalidOutputProof, InvalidPaymentAddress, InvalidPublicAddress, + InvalidSecret, InvalidSignature, InvalidSigningKey, InvalidSpendProof, @@ -129,3 +131,9 @@ impl From for IronfishError { IronfishError::new_with_source(IronfishErrorKind::TryFromInt, e) } } + +impl From for IronfishError { + fn from(e: ironfish_frost::frost::Error) -> IronfishError { + IronfishError::new_with_source(IronfishErrorKind::FrostLibError, e) + } +} diff --git a/ironfish-rust/src/frost_utils/mod.rs b/ironfish-rust/src/frost_utils/mod.rs index fdc67c6e20..559dd5588e 100644 --- a/ironfish-rust/src/frost_utils/mod.rs +++ b/ironfish-rust/src/frost_utils/mod.rs @@ -5,3 +5,4 @@ pub mod round_one; pub mod round_two; pub mod split_secret; +pub mod split_spender_key; diff --git a/ironfish-rust/src/frost_utils/split_secret.rs b/ironfish-rust/src/frost_utils/split_secret.rs index 81c4788c79..42271de691 100644 --- a/ironfish-rust/src/frost_utils/split_secret.rs +++ b/ironfish-rust/src/frost_utils/split_secret.rs @@ -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, } -pub fn split_secret( +pub(crate) fn split_secret( config: &SecretShareConfig, identifiers: IdentifierList, rng: &mut ThreadRng, -) -> Result<(HashMap, PublicKeyPackage), Error> { - let secret_key = SigningKey::deserialize( - config - .secret - .clone() - .try_into() - .map_err(|_| Error::MalformedSigningKey)?, - )?; +) -> Result<(HashMap, 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, @@ -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); } @@ -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(); diff --git a/ironfish-rust/src/frost_utils/split_spender_key.rs b/ironfish-rust/src/frost_utils/split_spender_key.rs new file mode 100644 index 0000000000..4f425770c5 --- /dev/null +++ b/ironfish-rust/src/frost_utils/split_spender_key.rs @@ -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, + pub public_key_package: PublicKeyPackage, +} + +pub fn split_spender_key( + coordinator_sapling_key: SaplingKey, + min_signers: u16, + max_signers: u16, + identifiers: Vec, +) -> Result { + 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::(&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::>(); + + t_identifiers.sort(); + assert_eq!(t_identifiers, cloned_identifiers); + } +}