Skip to content

Commit

Permalink
Implement wallet/multisig/dkg/round3 RPC
Browse files Browse the repository at this point in the history
  • Loading branch information
andiflabs committed Apr 17, 2024
1 parent 60beae3 commit ed6f98f
Show file tree
Hide file tree
Showing 8 changed files with 493 additions and 28 deletions.
10 changes: 10 additions & 0 deletions ironfish-rust-nodejs/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,16 @@ export namespace multisig {
encryptedSecretPackage: string
publicPackages: Array<DkgRound2PublicPackage>
}
export function dkgRound3(secret: ParticipantSecret, round2SecretPackage: string, round1PublicPackages: Array<string>, round2PublicPackages: Array<string>): DkgRound3Packages
export interface DkgRound3Packages {
publicAddress: string
keyPackage: string
publicKeyPackage: string
viewKey: string
incomingViewKey: string
outgoingViewKey: string
proofAuthorizingKey: string
}
export function aggregateSignatureShares(publicKeyPackageStr: string, signingPackageStr: string, signatureSharesArr: Array<string>): Buffer
export class ParticipantSecret {
constructor(jsBytes: Buffer)
Expand Down
99 changes: 71 additions & 28 deletions ironfish-rust-nodejs/src/multisig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,22 @@
use crate::{structs::NativeUnsignedTransaction, to_napi_err};
use ironfish::{
frost::{keys::KeyPackage, round2, Randomizer},
frost_utils::{signing_package::SigningPackage, split_spender_key::split_spender_key},
frost_utils::{
account_keys::derive_account_keys, signing_package::SigningPackage,
split_spender_key::split_spender_key,
},
participant::{Identity, Secret},
serializing::{bytes_to_hex, fr::FrSerializable, hex_to_vec_bytes},
SaplingKey,
};
use ironfish_frost::{
dkg::round1::PublicPackage, keys::PublicKeyPackage, multienc,
nonces::deterministic_signing_nonces, signature_share::SignatureShare,
signing_commitment::SigningCommitment,
dkg, keys::PublicKeyPackage, multienc, nonces::deterministic_signing_nonces,
signature_share::SignatureShare, signing_commitment::SigningCommitment,
};
use napi::{bindgen_prelude::*, JsBuffer};
use napi_derive::napi;
use rand::thread_rng;
use std::io;
use std::ops::Deref;

#[napi(namespace = "multisig")]
Expand All @@ -26,41 +29,32 @@ pub const IDENTITY_LEN: u32 = ironfish::frost_utils::IDENTITY_LEN as u32;
#[napi(namespace = "multisig")]
pub const SECRET_LEN: u32 = ironfish_frost::participant::SECRET_LEN as u32;

fn try_deserialize_identities<I, S>(signers: I) -> Result<Vec<Identity>>
fn try_deserialize<I, S, F, T>(items: I, deserialize_item: F) -> Result<Vec<T>>
where
I: IntoIterator<Item = S>,
S: Deref<Target = str>,
F: for<'a> Fn(&'a [u8]) -> io::Result<T>,
{
signers
items
.into_iter()
.try_fold(Vec::new(), |mut signers, serialized_identity| {
let serialized_identity =
hex_to_vec_bytes(&serialized_identity).map_err(to_napi_err)?;
Identity::deserialize_from(&serialized_identity[..])
.map(|identity| {
signers.push(identity);
signers
.try_fold(Vec::new(), |mut items, serialized_item| {
let serialized_item = hex_to_vec_bytes(&serialized_item).map_err(to_napi_err)?;
deserialize_item(&serialized_item[..])
.map(|item| {
items.push(item);
items
})
.map_err(to_napi_err)
})
}

fn try_deserialize_public_packages<I, S>(public_packages: I) -> Result<Vec<PublicPackage>>
#[inline]
fn try_deserialize_identities<I, S>(signers: I) -> Result<Vec<Identity>>
where
I: IntoIterator<Item = S>,
S: Deref<Target = str>,
{
public_packages
.into_iter()
.try_fold(Vec::new(), |mut public_packages, serialized_package| {
let serialized_package = hex_to_vec_bytes(&serialized_package).map_err(to_napi_err)?;
PublicPackage::deserialize_from(&serialized_package[..])
.map(|public_package| {
public_packages.push(public_package);
public_packages
})
.map_err(to_napi_err)
})
try_deserialize(signers, |bytes| Identity::deserialize_from(bytes))
}

#[napi(namespace = "multisig")]
Expand Down Expand Up @@ -383,7 +377,7 @@ pub fn dkg_round1(
Identity::deserialize_from(&hex_to_vec_bytes(&self_identity).map_err(to_napi_err)?[..])?;
let participant_identities = try_deserialize_identities(participant_identities)?;

let (encrypted_secret_package, public_package) = ironfish_frost::dkg::round1::round1(
let (encrypted_secret_package, public_package) = dkg::round1::round1(
&self_identity,
min_signers,
&participant_identities,
Expand All @@ -410,11 +404,13 @@ pub fn dkg_round2(
public_packages: Vec<String>,
) -> Result<DkgRound2Packages> {
let secret = Secret::deserialize_from(&hex_to_vec_bytes(&secret).map_err(to_napi_err)?[..])?;
let public_packages = try_deserialize_public_packages(public_packages)?;
let public_packages = try_deserialize(public_packages, |bytes| {
dkg::round1::PublicPackage::deserialize_from(bytes)
})?;
let encrypted_secret_package =
hex_to_vec_bytes(&encrypted_secret_package).map_err(to_napi_err)?;

let (encrypted_secret_package, public_packages) = ironfish_frost::dkg::round2::round2(
let (encrypted_secret_package, public_packages) = dkg::round2::round2(
&secret,
&encrypted_secret_package,
&public_packages,
Expand Down Expand Up @@ -447,3 +443,50 @@ pub struct DkgRound2Packages {
pub encrypted_secret_package: String,
pub public_packages: Vec<DkgRound2PublicPackage>,
}

#[napi(object, namespace = "multisig")]
pub fn dkg_round3(
secret: &ParticipantSecret,
round2_secret_package: String,
round1_public_packages: Vec<String>,
round2_public_packages: Vec<String>,
) -> Result<DkgRound3Packages> {
let round2_secret_package = hex_to_vec_bytes(&round2_secret_package).map_err(to_napi_err)?;
let round1_public_packages = try_deserialize(round1_public_packages, |bytes| {
dkg::round1::PublicPackage::deserialize_from(bytes)
})?;
let round2_public_packages = try_deserialize(round2_public_packages, |bytes| {
dkg::round2::PublicPackage::deserialize_from(bytes)
})?;

let (key_package, public_key_package, group_secret_key) = dkg::round3::round3(
&secret.secret,
&round2_secret_package,
round1_public_packages.iter(),
round2_public_packages.iter(),
)
.map_err(to_napi_err)?;

let account_keys = derive_account_keys(public_key_package.verifying_key(), &group_secret_key);

Ok(DkgRound3Packages {
public_address: account_keys.public_address.hex_public_address(),
key_package: bytes_to_hex(&key_package.serialize().map_err(to_napi_err)?),
public_key_package: bytes_to_hex(&public_key_package.serialize()),
view_key: account_keys.view_key.hex_key(),
incoming_view_key: account_keys.incoming_viewing_key.hex_key(),
outgoing_view_key: account_keys.outgoing_viewing_key.hex_key(),
proof_authorizing_key: account_keys.proof_authorizing_key.hex_key(),
})
}

#[napi(object, namespace = "multisig")]
pub struct DkgRound3Packages {
pub public_address: String,
pub key_package: String,
pub public_key_package: String,
pub view_key: String,
pub incoming_view_key: String,
pub outgoing_view_key: String,
pub proof_authorizing_key: String,
}
70 changes: 70 additions & 0 deletions ironfish-rust/src/frost_utils/account_keys.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/* 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 crate::{IncomingViewKey, OutgoingViewKey, PublicAddress, SaplingKey, ViewKey};
use group::GroupEncoding;
use ironfish_frost::frost::VerifyingKey;
use ironfish_zkp::constants::PROOF_GENERATION_KEY_GENERATOR;
use jubjub::SubgroupPoint;

pub struct MultisigAccountKeys {
/// Equivalent to [`crate::keys::SaplingKey::proof_authorizing_key`]
pub proof_authorizing_key: jubjub::Fr,
/// Equivalent to [`crate::keys::SaplingKey::outgoing_viewing_key`]
pub outgoing_viewing_key: OutgoingViewKey,
/// Equivalent to [`crate::keys::SaplingKey::view_key`]
pub view_key: ViewKey,
/// Equivalent to [`crate::keys::SaplingKey::incoming_viewing_key`]
pub incoming_viewing_key: IncomingViewKey,
/// Equivalent to [`crate::keys::SaplingKey::public_address`]
pub public_address: PublicAddress,
}

/// Derives the account keys for a multisig account, realizing the following key hierarchy:
///
/// ```
/// ak ─┐
/// ├─ ivk ── pk
/// gsk ── nsk ── nk ─┘
/// ```
pub fn derive_account_keys(
authorizing_key: &VerifyingKey,
group_secret_key: &[u8; 32],
) -> MultisigAccountKeys {
// Group secret key (gsk), obtained from the multisig setup process
let group_secret_key =
SaplingKey::new(*group_secret_key).expect("failed to derive group secret key");

// Authorization key (ak), obtained from the multisig setup process
let authorizing_key = Option::from(SubgroupPoint::from_bytes(&authorizing_key.serialize()))
.expect("failied to derive authorizing key");

// Nullifier keys (nsk and nk), derived from the gsk
let proof_authorizing_key = group_secret_key.sapling_proof_generation_key().nsk;
let nullifier_deriving_key = *PROOF_GENERATION_KEY_GENERATOR * proof_authorizing_key;

// Incoming view key (ivk), derived from the ak and the nk
let view_key = ViewKey {
authorizing_key,
nullifier_deriving_key,
};
let incoming_viewing_key = IncomingViewKey {
view_key: SaplingKey::hash_viewing_key(&authorizing_key, &nullifier_deriving_key)
.expect("failed to derive view key"),
};

// Outgoing view key (ovk), derived from the gsk
let outgoing_viewing_key = group_secret_key.outgoing_view_key().clone();

// Public address (pk), derived from the ivk
let public_address = incoming_viewing_key.public_address();

MultisigAccountKeys {
proof_authorizing_key,
outgoing_viewing_key,
view_key,
incoming_viewing_key,
public_address,
}
}
2 changes: 2 additions & 0 deletions ironfish-rust/src/frost_utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
* 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/. */

pub mod account_keys;
pub mod signing_package;
pub mod split_secret;
pub mod split_spender_key;

pub use ironfish_frost::keys::PublicKeyPackage;
pub use ironfish_frost::participant::IDENTITY_LEN;
9 changes: 9 additions & 0 deletions ironfish/src/rpc/clients/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import type {
DkgRound1Response,
DkgRound2Request,
DkgRound2Response,
DkgRound3Request,
DkgRound3Response,
EstimateFeeRateRequest,
EstimateFeeRateResponse,
EstimateFeeRatesRequest,
Expand Down Expand Up @@ -290,6 +292,13 @@ export abstract class RpcClient {
params,
).waitForEnd()
},

round3: (params: DkgRound3Request): Promise<RpcResponseEnded<DkgRound3Response>> => {
return this.request<DkgRound3Response>(
`${ApiNamespace.wallet}/multisig/dkg/round3`,
params,
).waitForEnd()
},
},
},

Expand Down
1 change: 1 addition & 0 deletions ironfish/src/rpc/routes/wallet/multisig/dkg/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
export * from './round1'
export * from './round2'
export * from './round3'
Loading

0 comments on commit ed6f98f

Please sign in to comment.