From eed9226971265706ed2a8d1147451281b2554284 Mon Sep 17 00:00:00 2001 From: andrea Date: Mon, 25 Nov 2024 16:20:57 -0800 Subject: [PATCH] WASM: add bindings for the Sapling keys --- ironfish-rust-wasm/src/keys/mod.rs | 6 + .../src/keys/proof_generation_key.rs | 36 ++++++ ironfish-rust-wasm/src/keys/sapling_key.rs | 105 ++++++++++++++++ ironfish-rust-wasm/src/keys/view_keys.rs | 119 ++++++++++++++++++ ironfish-rust-wasm/src/primitives.rs | 12 ++ ironfish-rust/src/keys/mod.rs | 13 +- ironfish-rust/src/keys/view_keys.rs | 66 ++++++++-- .../src/primitives/proof_generation_key.rs | 15 +++ 8 files changed, 358 insertions(+), 14 deletions(-) create mode 100644 ironfish-rust-wasm/src/keys/proof_generation_key.rs create mode 100644 ironfish-rust-wasm/src/keys/sapling_key.rs create mode 100644 ironfish-rust-wasm/src/keys/view_keys.rs diff --git a/ironfish-rust-wasm/src/keys/mod.rs b/ironfish-rust-wasm/src/keys/mod.rs index 54b4364b7b..5d67846bdb 100644 --- a/ironfish-rust-wasm/src/keys/mod.rs +++ b/ironfish-rust-wasm/src/keys/mod.rs @@ -2,6 +2,12 @@ * 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/. */ +mod proof_generation_key; mod public_address; +mod sapling_key; +mod view_keys; +pub use proof_generation_key::ProofGenerationKey; pub use public_address::PublicAddress; +pub use sapling_key::SaplingKey; +pub use view_keys::{IncomingViewKey, OutgoingViewKey, ViewKey}; diff --git a/ironfish-rust-wasm/src/keys/proof_generation_key.rs b/ironfish-rust-wasm/src/keys/proof_generation_key.rs new file mode 100644 index 0000000000..cdde330c97 --- /dev/null +++ b/ironfish-rust-wasm/src/keys/proof_generation_key.rs @@ -0,0 +1,36 @@ +/* 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::{ + errors::IronfishError, + primitives::{Fr, SubgroupPoint}, + wasm_bindgen_wrapper, +}; +use wasm_bindgen::prelude::*; + +wasm_bindgen_wrapper! { + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct ProofGenerationKey(ironfish::keys::ProofGenerationKey); +} + +#[wasm_bindgen] +impl ProofGenerationKey { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self(ironfish::keys::ProofGenerationKey::read(bytes)?)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + self.0.to_bytes().to_vec() + } + + #[wasm_bindgen(js_name = fromParts)] + pub fn from_parts(ak: SubgroupPoint, nsk: Fr) -> Self { + Self(ironfish::keys::ProofGenerationKey::new( + ak.into(), + nsk.into(), + )) + } +} diff --git a/ironfish-rust-wasm/src/keys/sapling_key.rs b/ironfish-rust-wasm/src/keys/sapling_key.rs new file mode 100644 index 0000000000..ce357af063 --- /dev/null +++ b/ironfish-rust-wasm/src/keys/sapling_key.rs @@ -0,0 +1,105 @@ +/* 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::{ + errors::IronfishError, + keys::{IncomingViewKey, OutgoingViewKey, ProofGenerationKey, PublicAddress, ViewKey}, + wasm_bindgen_wrapper, +}; +use wasm_bindgen::prelude::*; + +wasm_bindgen_wrapper! { + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct SaplingKey(ironfish::keys::SaplingKey); +} + +#[wasm_bindgen] +impl SaplingKey { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self(ironfish::keys::SaplingKey::read(bytes)?)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + let mut buf = Vec::new(); + self.0 + .write(&mut buf) + .expect("failed to serialize sapling key"); + buf + } + + #[wasm_bindgen] + pub fn random() -> Self { + Self(ironfish::keys::SaplingKey::generate_key()) + } + + #[wasm_bindgen(js_name = fromHex)] + pub fn from_hex(hex: &str) -> Result { + Ok(Self(ironfish::keys::SaplingKey::from_hex(hex)?)) + } + + #[wasm_bindgen(js_name = toHex)] + pub fn to_hex(&self) -> String { + self.0.hex_spending_key() + } + + // TODO: to/fromWords + + #[wasm_bindgen(getter, js_name = publicAddress)] + pub fn public_address(&self) -> PublicAddress { + self.0.public_address().into() + } + + #[wasm_bindgen(getter, js_name = spendingKey)] + pub fn spending_key(&self) -> Vec { + self.0.spending_key().to_vec() + } + + #[wasm_bindgen(getter, js_name = incomingViewKey)] + pub fn incoming_view_key(&self) -> IncomingViewKey { + self.0.incoming_view_key().to_owned().into() + } + + #[wasm_bindgen(getter, js_name = outgoingViewKey)] + pub fn outgoing_view_key(&self) -> OutgoingViewKey { + self.0.outgoing_view_key().to_owned().into() + } + + #[wasm_bindgen(getter, js_name = viewKey)] + pub fn view_key(&self) -> ViewKey { + self.0.view_key().to_owned().into() + } + + #[wasm_bindgen(getter, js_name = proofGenerationKey)] + pub fn proof_generation_key(&self) -> ProofGenerationKey { + self.0.sapling_proof_generation_key().into() + } +} + +#[cfg(test)] +mod tests { + use crate::keys::{IncomingViewKey, OutgoingViewKey, ProofGenerationKey, SaplingKey, ViewKey}; + use wasm_bindgen_test::wasm_bindgen_test; + + macro_rules! assert_serde_ok { + ( $type:ty, $key:expr ) => { + assert_eq!( + $key, + <$type>::deserialize($key.serialize().as_slice()).expect("deserialization failed") + ) + }; + } + + #[test] + #[wasm_bindgen_test] + fn serialization_roundtrip() { + let key = SaplingKey::random(); + assert_serde_ok!(SaplingKey, key); + assert_serde_ok!(IncomingViewKey, key.incoming_view_key()); + assert_serde_ok!(OutgoingViewKey, key.outgoing_view_key()); + assert_serde_ok!(ViewKey, key.view_key()); + assert_serde_ok!(ProofGenerationKey, key.proof_generation_key()); + } +} diff --git a/ironfish-rust-wasm/src/keys/view_keys.rs b/ironfish-rust-wasm/src/keys/view_keys.rs new file mode 100644 index 0000000000..6166bfd85f --- /dev/null +++ b/ironfish-rust-wasm/src/keys/view_keys.rs @@ -0,0 +1,119 @@ +/* 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::{ + errors::IronfishError, keys::PublicAddress, primitives::PublicKey, wasm_bindgen_wrapper, +}; +use wasm_bindgen::prelude::*; + +wasm_bindgen_wrapper! { + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct IncomingViewKey(ironfish::keys::IncomingViewKey); +} + +#[wasm_bindgen] +impl IncomingViewKey { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self(ironfish::keys::IncomingViewKey::read(bytes)?)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + self.0.to_bytes().to_vec() + } + + #[wasm_bindgen(js_name = fromHex)] + pub fn from_hex(hex: &str) -> Result { + Ok(Self(ironfish::keys::IncomingViewKey::from_hex(hex)?)) + } + + #[wasm_bindgen(js_name = toHex)] + pub fn to_hex(&self) -> String { + self.0.hex_key() + } + + // TODO: to/fromWords + + #[wasm_bindgen(getter, js_name = publicAddress)] + pub fn public_address(&self) -> PublicAddress { + self.0.public_address().into() + } +} + +wasm_bindgen_wrapper! { + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct OutgoingViewKey(ironfish::keys::OutgoingViewKey); +} + +#[wasm_bindgen] +impl OutgoingViewKey { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self(ironfish::keys::OutgoingViewKey::read(bytes)?)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + self.0.to_bytes().to_vec() + } + + #[wasm_bindgen(js_name = fromHex)] + pub fn from_hex(hex: &str) -> Result { + Ok(Self(ironfish::keys::OutgoingViewKey::from_hex(hex)?)) + } + + #[wasm_bindgen(js_name = toHex)] + pub fn to_hex(&self) -> String { + self.0.hex_key() + } + + // TODO: to/fromWords +} + +wasm_bindgen_wrapper! { + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct ViewKey(ironfish::keys::ViewKey); +} + +#[wasm_bindgen] +impl ViewKey { + #[wasm_bindgen(constructor)] + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self(ironfish::keys::ViewKey::read(bytes)?)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Vec { + self.0.to_bytes().to_vec() + } + + #[wasm_bindgen(js_name = fromHex)] + pub fn from_hex(hex: &str) -> Result { + Ok(Self(ironfish::keys::ViewKey::from_hex(hex)?)) + } + + #[wasm_bindgen(js_name = toHex)] + pub fn to_hex(&self) -> String { + self.0.hex_key() + } + + #[wasm_bindgen(getter, js_name = publicAddress)] + pub fn public_address(&self) -> Result { + self.0 + .public_address() + .map(|a| a.into()) + .map_err(|e| e.into()) + } + + #[wasm_bindgen(getter, js_name = authorizingKey)] + pub fn authorizing_key(&self) -> PublicKey { + self.0.authorizing_key.into() + } + + #[wasm_bindgen(getter, js_name = nullifierDerivingKey)] + pub fn nullifier_deriving_key(&self) -> PublicKey { + self.0.nullifier_deriving_key.into() + } +} diff --git a/ironfish-rust-wasm/src/primitives.rs b/ironfish-rust-wasm/src/primitives.rs index 8eb38fc640..ba1c1c5088 100644 --- a/ironfish-rust-wasm/src/primitives.rs +++ b/ironfish-rust-wasm/src/primitives.rs @@ -153,6 +153,18 @@ impl PublicKey { } } +impl From for PublicKey { + fn from(p: ironfish_jubjub::ExtendedPoint) -> Self { + Self(redjubjub::PublicKey(p)) + } +} + +impl From for PublicKey { + fn from(p: ironfish_jubjub::SubgroupPoint) -> Self { + Self(redjubjub::PublicKey(p.into())) + } +} + wasm_bindgen_wrapper! { #[derive(Clone, Debug)] pub struct Signature(redjubjub::Signature); diff --git a/ironfish-rust/src/keys/mod.rs b/ironfish-rust/src/keys/mod.rs index 92c1e7cd9c..ecb5eb3b18 100644 --- a/ironfish-rust/src/keys/mod.rs +++ b/ironfish-rust/src/keys/mod.rs @@ -17,7 +17,7 @@ use ironfish_zkp::constants::{ pub use ironfish_zkp::ProofGenerationKey; use rand::prelude::*; -use std::io; +use std::{fmt, io}; mod ephemeral; pub use ephemeral::EphemeralKeyPair; @@ -41,7 +41,7 @@ pub const SPEND_KEY_SIZE: usize = 32; /// While the key parts are all represented as 256 bit keys to the outside /// world, inside the API they map to Edwards points or scalar values /// on the JubJub curve. -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] pub struct SaplingKey { /// The private (secret) key from which all the other key parts are derived. /// The expanded form of this key is required before a note can be spent. @@ -116,7 +116,7 @@ impl SaplingKey { } /// Load a new key from a Read implementation (e.g: socket, file) - pub fn read(reader: &mut R) -> Result { + pub fn read(mut reader: R) -> Result { let mut spending_key = [0; SPEND_KEY_SIZE]; reader.read_exact(&mut spending_key)?; Self::new(spending_key) @@ -266,3 +266,10 @@ impl SaplingKey { Ok(scalar) } } + +impl fmt::Debug for SaplingKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Hide all private keys + f.debug_struct("SaplingKey").finish_non_exhaustive() + } +} diff --git a/ironfish-rust/src/keys/view_keys.rs b/ironfish-rust/src/keys/view_keys.rs index 0a039381a2..ad220e8841 100644 --- a/ironfish-rust/src/keys/view_keys.rs +++ b/ironfish-rust/src/keys/view_keys.rs @@ -20,17 +20,19 @@ use crate::{ }; use bip39::{Language, Mnemonic}; use blake2b_simd::Params as Blake2b; +use ff::Field; use group::GroupEncoding; use ironfish_jubjub::SubgroupPoint; - -use std::io; +use ironfish_zkp::{constants::SPENDING_KEY_GENERATOR, redjubjub}; +use rand::RngCore; +use std::{fmt, io}; const DIFFIE_HELLMAN_PERSONALIZATION: &[u8; 16] = b"Iron Fish shared"; /// Key that allows someone to view a transaction that you have received. /// /// Referred to as `ivk` in the literature. -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] pub struct IncomingViewKey { pub(crate) view_key: ironfish_jubjub::Fr, } @@ -42,6 +44,10 @@ impl IncomingViewKey { Ok(IncomingViewKey { view_key }) } + pub fn to_bytes(&self) -> [u8; 32] { + self.view_key.to_bytes() + } + /// Load a key from a string of hexadecimal digits pub fn from_hex(value: &str) -> Result { match hex_to_bytes::<32>(value) { @@ -102,10 +108,17 @@ impl IncomingViewKey { } } +impl fmt::Debug for IncomingViewKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Hide all private keys + f.debug_struct("IncomingViewKey").finish_non_exhaustive() + } +} + /// Contains two keys that are required (along with outgoing view key) /// to have full view access to an account. /// Referred to as `ViewingKey` in the literature. -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] pub struct ViewKey { /// Part of the full viewing key. Generally referred to as /// `ak` in the literature. Derived from spend_authorizing_key using scalar @@ -118,15 +131,12 @@ pub struct ViewKey { } impl ViewKey { - /// Load a key from a string of hexadecimal digits - pub fn from_hex(value: &str) -> Result { - let bytes: [u8; 64] = hex_to_bytes(value)?; - + pub fn read(mut reader: R) -> Result { let mut authorizing_key_bytes = [0; 32]; let mut nullifier_deriving_key_bytes = [0; 32]; - authorizing_key_bytes.clone_from_slice(&bytes[..32]); - nullifier_deriving_key_bytes.clone_from_slice(&bytes[32..]); + reader.read_exact(&mut authorizing_key_bytes)?; + reader.read_exact(&mut nullifier_deriving_key_bytes)?; let authorizing_key = Option::from(SubgroupPoint::from_bytes(&authorizing_key_bytes)) .ok_or_else(|| IronfishError::new(IronfishErrorKind::InvalidAuthorizingKey))?; @@ -141,6 +151,12 @@ impl ViewKey { }) } + /// Load a key from a string of hexadecimal digits + pub fn from_hex(value: &str) -> Result { + let bytes: [u8; 64] = hex_to_bytes(value)?; + Self::read(&bytes[..]) + } + /// Viewing key as hexadecimal, for readability. pub fn hex_key(&self) -> String { bytes_to_hex(&self.to_bytes()) @@ -163,12 +179,29 @@ impl ViewKey { Ok(ivk.public_address()) } + + pub fn randomized_public_key( + &self, + rng: R, + ) -> (ironfish_jubjub::Fr, redjubjub::PublicKey) { + let public_key_randomness = ironfish_jubjub::Fr::random(rng); + let randomized_public_key = redjubjub::PublicKey(self.authorizing_key.into()) + .randomize(public_key_randomness, *SPENDING_KEY_GENERATOR); + (public_key_randomness, randomized_public_key) + } +} + +impl fmt::Debug for ViewKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Hide all private keys + f.debug_struct("ViewKey").finish_non_exhaustive() + } } /// Key that allows someone to view a transaction that you have spent. /// /// Referred to as `ovk` in the literature. -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] pub struct OutgoingViewKey { pub(crate) view_key: [u8; 32], } @@ -181,6 +214,10 @@ impl OutgoingViewKey { Ok(OutgoingViewKey { view_key }) } + pub fn to_bytes(&self) -> [u8; 32] { + self.view_key + } + /// Load a key from a string of hexadecimal digits pub fn from_hex(value: &str) -> Result { match hex_to_bytes(value) { @@ -215,6 +252,13 @@ impl OutgoingViewKey { } } +impl fmt::Debug for OutgoingViewKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Hide all private keys + f.debug_struct("OutgoingViewKey").finish_non_exhaustive() + } +} + /// Derive a shared secret key from a secret key and the other person's public /// key. /// diff --git a/ironfish-zkp/src/primitives/proof_generation_key.rs b/ironfish-zkp/src/primitives/proof_generation_key.rs index a46f045ab0..ac3c98a73f 100644 --- a/ironfish-zkp/src/primitives/proof_generation_key.rs +++ b/ironfish-zkp/src/primitives/proof_generation_key.rs @@ -100,6 +100,21 @@ impl From for ProofGenerationKey { } } +impl PartialEq for ProofGenerationKey { + fn eq(&self, other: &Self) -> bool { + self.to_bytes() == other.to_bytes() + } +} + +impl Eq for ProofGenerationKey {} + +impl fmt::Debug for ProofGenerationKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Hide all private keys + f.debug_struct("ProofGenerationKey").finish_non_exhaustive() + } +} + #[cfg(test)] mod test { use ff::Field;