Skip to content

Commit

Permalink
Apply MASP signatures from the hardware wallet to the Transaction.
Browse files Browse the repository at this point in the history
  • Loading branch information
murisi committed Sep 4, 2024
1 parent 5d3c4c6 commit a938eb2
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 6 deletions.
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ libc = "0.2.97"
libloading = "0.7.2"
linkme = "0.3.24"
# branch = "tomas/arbitrary"
masp_primitives = { git = "https://github.com/anoma/masp", rev = "9bc6dec4d4b3f5a0344aa94b739a122c3e0989f3" }
masp_proofs = { git = "https://github.com/anoma/masp", rev = "9bc6dec4d4b3f5a0344aa94b739a122c3e0989f3", default-features = false, features = ["local-prover"] }
masp_primitives = { git = "https://github.com/anoma/masp", rev = "e6451ecf64d519409f9b1a67aa1d8322a9fe0717" }
masp_proofs = { git = "https://github.com/anoma/masp", rev = "e6451ecf64d519409f9b1a67aa1d8322a9fe0717", default-features = false, features = ["local-prover"] }
num256 = "0.3.5"
num_cpus = "1.13.0"
num-derive = "0.4"
Expand Down
103 changes: 103 additions & 0 deletions crates/apps_lib/src/client/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ use tokio::sync::RwLock;
use namada_sdk::masp::ExtendedViewingKey;
use masp_primitives::zip32::ExtendedFullViewingKey;
use masp_primitives::sapling::ProofGenerationKey;
use namada_sdk::collections::HashMap;
use masp_primitives::sapling::redjubjub;
use masp_primitives::transaction::components::sapling::fees::InputView;
use crate::masp_primitives::transaction::components::sapling;

use masp_primitives::transaction::components::sapling::builder::{
BuildParams, ConvertBuildParams, OutputBuildParams, RngBuildParams,
Expand Down Expand Up @@ -792,10 +796,41 @@ pub async fn submit_transparent_transfer(
Ok(())
}

// A mapper that replaces authorization signatures with those in a built-in map
struct MapSaplingSigAuth(
HashMap<usize, <sapling::Authorized as sapling::Authorization>::AuthSig>,
);

impl sapling::MapAuth<sapling::Authorized, sapling::Authorized> for MapSaplingSigAuth {
fn map_proof(
&self,
p: <sapling::Authorized as sapling::Authorization>::Proof,
_pos: usize,
) -> <sapling::Authorized as sapling::Authorization>::Proof {
p
}

fn map_auth_sig(
&self,
s: <sapling::Authorized as sapling::Authorization>::AuthSig,
pos: usize,
) -> <sapling::Authorized as sapling::Authorization>::AuthSig {
self.0.get(&pos).cloned().unwrap_or(s)
}

fn map_authorization(&self, a: sapling::Authorized) -> sapling::Authorized {
a
}
}

pub async fn submit_shielded_transfer(
namada: &impl Namada,
mut args: args::TxShieldedTransfer,
) -> Result<(), error::Error> {
// Records the shielded keys that are on the hardware wallet
let mut shielded_hw_keys = HashMap::new();
// Construct the build parameters that parameterized the Transaction
// authorizations
let mut bparams: Box<dyn BuildParams> = if args.tx.use_device {
let transport = WalletTransport::from_arg(args.tx.device_transport);
let app = NamadaApp::new(transport);
Expand Down Expand Up @@ -870,6 +905,7 @@ pub async fn submit_shielded_transfer(
"Proof generation key in response from the hardware wallet \
does not correspond to stored viewing key.",
)))?;
shielded_hw_keys.insert(path.path, viewing_key);
}
}
// Get randomness to aid in construction of various descriptors
Expand Down Expand Up @@ -925,6 +961,73 @@ pub async fn submit_shielded_transfer(
if args.tx.dump_tx {
tx::dump_tx(namada.io(), &args.tx, tx);
} else {
// Get the MASP section that is the target of our signing
if let Some(shielded_hash) = signing_data.shielded_hash {
let mut masp_tx = tx.get_masp_section(&shielded_hash)
.expect("Expected to find the indicated MASP Transaction")
.clone();

let masp_builder = tx.get_masp_builder(&shielded_hash)
.expect("Expected to find the indicated MASP Builder");

// Reverse the spend metadata to enable looking up construction
// material
let sapling_inputs = masp_builder.builder.sapling_inputs();
let mut descriptor_map = vec![0; sapling_inputs.len()];
for i in 0.. {
if let Some(pos) = masp_builder.metadata.spend_index(i) {
descriptor_map[pos] = i;
} else {
break;
};
}
// Sign the MASP Transaction using each relevant key in the
// hardware wallet
let transport = WalletTransport::from_arg(args.tx.device_transport);
let app = NamadaApp::new(transport);
for (path, vk) in shielded_hw_keys {
// Sign the MASP Transaction using the current viewing key
let path = BIP44Path { path: path.to_string() };
let response = app
.sign_masp(&path, &tx.serialize_to_vec())
.await
.map_err(|err| error::Error::Other(err.to_string()))?;
// Now prepare a new list of authorizations based on hardware
// wallet responses
let mut authorizations = HashMap::new();
for (tx_pos, builder_pos) in descriptor_map.iter().enumerate() {
// Read the next spend authorization signature from the
// hardware wallet
let response = app
.get_spend_signature()
.await
.map_err(|err| error::Error::Other(err.to_string()))?;
let signature = redjubjub::Signature::try_from_slice(
&[response.rbar, response.sbar].concat(),
).map_err(|err| error::Error::Other(format!(
"Unexpected spend authorization key in response from the \
hardware wallet: {}.",
err,
)))?;
if *sapling_inputs[*builder_pos].key() == ExtendedFullViewingKey::from(vk) {
// If this descriptor was produced by the current
// viewing key (which comes from the hardware wallet),
// then use the authorization from the hardware wallet
authorizations.insert(tx_pos, signature);
}
}
// Finally, patch the MASP Transaction with the fetched spend
// authorization signature
masp_tx = (*masp_tx).clone().map_authorization::<masp_primitives::transaction::Authorized>(
(),
MapSaplingSigAuth(authorizations),
).freeze().map_err(|err| error::Error::Other(format!(
"Unable to apply hardware walleet sourced authorization \
signatures to the transaction being constructed: {}.",
err,
)))?;
}
}
sign(namada, &mut tx, &args.tx, signing_data).await?;
namada.submit(tx, &args.tx).await?;
}
Expand Down
1 change: 1 addition & 0 deletions crates/apps_lib/src/config/genesis/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,7 @@ impl<T> Signed<T> {
public_keys: pks.clone(),
threshold,
fee_payer: genesis_fee_payer_pk(),
shielded_hash: None,
};

let mut tx = self.data.tx_to_sign();
Expand Down
3 changes: 3 additions & 0 deletions crates/sdk/src/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ pub struct SigningTxData {
pub account_public_keys_map: Option<AccountPublicKeysMap>,
/// The public keys of the fee payer
pub fee_payer: common::PublicKey,
/// ID of the Transaction needing signing
pub shielded_hash: Option<MaspTxId>,
}

/// Find the public key for the given address and try to load the keypair
Expand Down Expand Up @@ -354,6 +356,7 @@ pub async fn aux_signing_data(
threshold,
account_public_keys_map,
fee_payer,
shielded_hash: None,
})
}

Expand Down
3 changes: 2 additions & 1 deletion crates/sdk/src/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3014,7 +3014,7 @@ pub async fn build_shielded_transfer<N: Namada>(
args: &mut args::TxShieldedTransfer,
bparams: &mut impl BuildParams,
) -> Result<(Tx, SigningTxData)> {
let signing_data = signing::aux_signing_data(
let mut signing_data = signing::aux_signing_data(
context,
&args.tx,
Some(MASP),
Expand Down Expand Up @@ -3106,6 +3106,7 @@ pub async fn build_shielded_transfer<N: Namada>(
});

data.shielded_section_hash = Some(section_hash);
signing_data.shielded_hash = Some(section_hash);
tracing::debug!("Transfer data {data:?}");
Ok(())
};
Expand Down
12 changes: 12 additions & 0 deletions crates/tx/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1293,6 +1293,18 @@ impl Tx {
None
}

/// Get the MASP builder section with the given hash
pub fn get_masp_builder(&self, hash: &MaspTxId) -> Option<&MaspBuilder> {
for section in &self.sections {
if let Section::MaspBuilder(builder) = section {
if builder.target == *hash {
return Some(builder);
}
}
}
None
}

/// Set the last transaction memo hash stored in the header
pub fn set_memo_sechash(&mut self, hash: namada_core::hash::Hash) {
let item = match self.header.batch.pop() {
Expand Down

0 comments on commit a938eb2

Please sign in to comment.