Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid duplicated signatures when building transactions #4230

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Improved the transaction building process to avoid duplicated sections.
([\#4230](https://github.com/anoma/namada/pull/4230))
38 changes: 31 additions & 7 deletions crates/sdk/src/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,22 +83,42 @@ pub struct SigningTxData {

impl PartialEq for SigningTxData {
fn eq(&self, other: &Self) -> bool {
if !(self.owner == other.owner
&& self.threshold == other.threshold
&& self.account_public_keys_map == other.account_public_keys_map
&& self.fee_payer == other.fee_payer)
// Deconstruct the two instances to ensure we don't forget any new
// field
let SigningTxData {
owner,
public_keys,
threshold,
account_public_keys_map,
fee_payer,
shielded_hash,
} = self;
let SigningTxData {
owner: other_owner,
public_keys: other_public_keys,
threshold: other_threshold,
account_public_keys_map: other_account_public_keys_map,
fee_payer: other_fee_payer,
shielded_hash: other_shielded_hash,
} = other;

if !(owner == other_owner
&& threshold == other_threshold
&& account_public_keys_map == other_account_public_keys_map
&& fee_payer == other_fee_payer
&& shielded_hash == other_shielded_hash)
{
return false;
}

// Check equivalence of the public keys ignoring the specific ordering
if self.public_keys.len() != other.public_keys.len() {
if public_keys.len() != other_public_keys.len() {
return false;
}

self.public_keys
public_keys
.iter()
.all(|pubkey| other.public_keys.contains(pubkey))
.all(|pubkey| other_public_keys.contains(pubkey))
}
}

Expand Down Expand Up @@ -313,6 +333,10 @@ where
}
}

// Before signing the wrapper tx prune all the possible duplicated sections
// (including duplicated raw signatures)
tx.prune_duplicated_sections();

// Then try signing the wrapper header (fee payer). Check if there's a
// provided wrapper signature, otherwise sign with the software wallet or
// use the fallback
Expand Down
4 changes: 4 additions & 0 deletions crates/sdk/src/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,10 @@ pub fn dump_tx<IO: Io>(io: &IO, args: &args::Tx, mut tx: Tx) -> Result<()> {
));
}

// Remove duplicated sections before dumping. This is useful in case the
// dumped tx needed to be signed offline
tx.prune_duplicated_sections();

match args.output_folder.clone() {
Some(path) => {
let tx_path = path.join(format!(
Expand Down
34 changes: 33 additions & 1 deletion crates/tx/src/section.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,6 @@ pub enum Signer {
BorshSchema,
Serialize,
Deserialize,
PartialEq,
)]
pub struct Authorization {
/// The hash of the section being signed
Expand All @@ -442,6 +441,39 @@ pub struct Authorization {
pub signatures: BTreeMap<u8, common::Signature>,
}

impl PartialEq for Authorization {
fn eq(&self, other: &Self) -> bool {
// Deconstruct the two instances to ensure we don't forget any new
// field
let Authorization {
targets,
signer: _,
signatures,
} = self;
let Authorization {
targets: other_targets,
signer: _,
signatures: other_signatures,
} = other;

// Two authorizations are equal when they are computed over the same
// target(s) and the signatures match ,regardless of how the signer is
// expressed

// Check equivalence of the targets ignoring the specific ordering
if targets.len() != other_targets.len() {
return false;
}

if !targets.iter().all(|pubkey| other_targets.contains(pubkey)) {
return false;
}

// Check equivalence of the signatures
signatures == other_signatures
}
}

impl Authorization {
/// Sign the given section hash with the given key and return a section
pub fn new(
Expand Down
11 changes: 11 additions & 0 deletions crates/tx/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,17 @@ impl Tx {
self.header.batch.insert(cmt)
}

/// Remove duplicated sections from the transaction
pub fn prune_duplicated_sections(&mut self) {
let sections = std::mem::take(&mut self.sections);
let mut unique_sections = HashMap::with_capacity(sections.len());
for section in sections {
unique_sections.insert(section.get_hash(), section);
}

self.sections = unique_sections.into_values().collect();
}

/// Get the transaction header
pub fn header(&self) -> Header {
self.header.clone()
Expand Down
Loading