Enhance multi-signature address functionality by adding support for sorting public keys and creating multi-sig addresses for Dogecoin. Update DOGEAddressEncoding to include P2SH prefix and implement corresponding formatting. Modify address creation methods to accommodate new features.
What changed, and why it matters
This commit adds Dogecoin support for multi-signature wallet addresses in a hardware wallet firmware. It lets the device create Dogecoin multi-sig addresses and sort public keys before building the address. There is also a trivial whitespace change in a UI file. The changes look like normal feature work, but adding new address handling always carries a small risk of address-generation mistakes that could make funds hard or impossible to spend.
Review the new multi-sig address path with Dogecoin-specific test vectors, verify that sorted vs unsorted public-key ordering matches the wallet coordinator's behavior, and confirm P2SH prefix constants are correct for Dogecoin mainnet. Treat as routine feature hardening rather than an incident response.
Security signals we found
New address-encoding path for Dogecoin P2SH added
Optional public-key sorting introduced for multi-sig address derivation
New multi-sig address creation helper accepts arbitrary public keys and network
No input validation changes visible in the diff
No explicit security or audit notes in commit message
Evidence from the diff
The patch extends Bitcoin-family address code to handle Dogecoin P2SH/P2WSH multi-sig addresses. It adds a P2SH prefix to DOGEAddressEncoding, allows p2sh-p2wpkh for Dogecoin, and introduces create_multi_sig_address_for_pubkeys_with_sorting() plus calculate_multi_address_with_network(). Public keys can optionally be sorted lexicographically before building the P2MS script. A unit test verifies a 2-of-3 Dogecoin P2SH address. The gui_status_bar.c change is only a trailing-space edit with no functional effect.
Changed components
rust/apps/bitcoin/src/addresses/address.rsrust/apps/bitcoin/src/addresses/encoding.rsrust/apps/bitcoin/src/multi_sig/address.rssrc/ui/gui_components/gui_status_bar.c (cosmetic only)Inspect captured patch +74 / −4
diff --git a/rust/apps/bitcoin/src/addresses/address.rs b/rust/apps/bitcoin/src/addresses/address.rs
index 352fa8f..cb3ea12 100644
--- a/rust/apps/bitcoin/src/addresses/address.rs
+++ b/rust/apps/bitcoin/src/addresses/address.rs
@@ -116,7 +116,7 @@ impl Address {
pub fn p2shp2wpkh(pk: &PublicKey, network: Network) -> Result<Address, BitcoinError> {
match network {
- Network::Bitcoin | Network::BitcoinTestnet | Network::Litecoin => {
+ Network::Bitcoin | Network::BitcoinTestnet | Network::Litecoin | Network::Dogecoin => {
let builder =
script::Builder::new()
.push_int(0)
@@ -243,6 +243,7 @@ impl fmt::Display for Address {
let encoding = DOGEAddressEncoding {
payload: &self.payload,
p2pkh_prefix: PUBKEY_ADDRESS_PREFIX_DOGE,
+ p2sh_prefix: SCRIPT_ADDRESS_PREFIX_DOGE,
};
encoding.fmt(fmt)
}
diff --git a/rust/apps/bitcoin/src/addresses/encoding.rs b/rust/apps/bitcoin/src/addresses/encoding.rs
index 7f060fd..fb24606 100644
--- a/rust/apps/bitcoin/src/addresses/encoding.rs
+++ b/rust/apps/bitcoin/src/addresses/encoding.rs
@@ -33,6 +33,7 @@ pub struct BCHAddressEncoding<'a> {
pub struct DOGEAddressEncoding<'a> {
pub payload: &'a Payload,
pub p2pkh_prefix: u8,
+ pub p2sh_prefix: u8,
}
struct UpperWriter<W: fmt::Write>(W);
@@ -164,6 +165,12 @@ impl<'a> fmt::Display for DOGEAddressEncoding<'a> {
prefixed[1..].copy_from_slice(&pubkey_hash[..]);
base58::encode_check_to_fmt(fmt, &prefixed[..])
}
+ Payload::P2sh { script_hash } => {
+ let mut prefixed = [0; 21];
+ prefixed[0] = self.p2sh_prefix;
+ prefixed[1..].copy_from_slice(&script_hash[..]);
+ base58::encode_check_to_fmt(fmt, &prefixed[..])
+ }
_ => {
write!(fmt, "invalid payload")
}
diff --git a/rust/apps/bitcoin/src/multi_sig/address.rs b/rust/apps/bitcoin/src/multi_sig/address.rs
index 2acc262..e6ddedf 100644
--- a/rust/apps/bitcoin/src/multi_sig/address.rs
+++ b/rust/apps/bitcoin/src/multi_sig/address.rs
@@ -32,6 +32,25 @@ pub fn create_multi_sig_address_for_wallet(
calculate_multi_address(&p2ms, format, wallet.get_network())
}
+pub fn create_multi_sig_address_for_pubkeys_with_sorting(
+ threshold: u8,
+ pub_keys: &[PublicKey],
+ format: MultiSigFormat,
+ network: crate::network::Network,
+ sort_keys: bool,
+) -> Result<String, BitcoinError> {
+ let p2ms = if sort_keys {
+ let ordered_pub_keys = pub_keys.iter().sorted().cloned().collect::<Vec<_>>();
+ let ordered_refs = ordered_pub_keys.iter().collect::<Vec<&PublicKey>>();
+ crate_p2ms_script(&ordered_refs, threshold as u32)
+ } else {
+ let ordered_refs = pub_keys.iter().collect::<Vec<&PublicKey>>();
+ crate_p2ms_script(&ordered_refs, threshold as u32)
+ };
+
+ calculate_multi_address_with_network(&p2ms, format, network)
+}
+
pub fn calculate_multi_address(
p2ms: &ScriptBuf,
format: MultiSigFormat,
@@ -54,6 +73,22 @@ pub fn calculate_multi_address(
Ok(Address::from_script(script.as_script(), network)?.to_string())
}
+pub fn calculate_multi_address_with_network(
+ p2ms: &ScriptBuf,
+ format: MultiSigFormat,
+ network: crate::network::Network,
+) -> Result<String, BitcoinError> {
+ let script = match format {
+ MultiSigFormat::P2sh => ScriptBuf::new_p2sh(&p2ms.script_hash()),
+ MultiSigFormat::P2wshP2sh => {
+ let p2wsh = ScriptBuf::new_p2wsh(&p2ms.wscript_hash());
+ ScriptBuf::new_p2sh(&p2wsh.script_hash())
+ }
+ MultiSigFormat::P2wsh => ScriptBuf::new_p2wsh(&p2ms.wscript_hash()),
+ };
+ Ok(Address::from_script(script.as_script(), network)?.to_string())
+}
+
fn derive_pub_key(xpub: &String, change: u32, account: u32) -> Result<PublicKey, BitcoinError> {
Ok(derive_public_key(
xpub,
@@ -77,9 +112,14 @@ fn crate_p2ms_script(pub_keys: &Vec<&PublicKey>, threshold: u32) -> ScriptBuf {
mod tests {
extern crate std;
- use crate::multi_sig::address::create_multi_sig_address_for_wallet;
+ use crate::multi_sig::address::{
+ create_multi_sig_address_for_pubkeys_with_sorting, create_multi_sig_address_for_wallet,
+ };
use crate::multi_sig::wallet::parse_wallet_config;
- use crate::multi_sig::Network;
+ use crate::multi_sig::MultiSigFormat;
+ use crate::network::Network;
+ use bitcoin::PublicKey;
+ use alloc::vec::Vec;
#[test]
fn test_create_multi_sig_address_for_wallet() {
@@ -163,4 +203,26 @@ mod tests {
assert_eq!("3A3vK8133WTMePMpPDmZSqSqK3gobohtG8", address);
}
}
+
+ #[test]
+ fn test_create_multi_sig_address_for_pubkeys() {
+ let pubkey_str = vec![
+ "03a0c95fd48f1a251c744629e19ad154dfe1d7fb992d6955d62c417ae4ac333340",
+ "0361769c55b3035962fd3267da5cc4efa03cb400fe1971f5ec1c686d6b301ccd60",
+ "021d24a7eda6ccbff4616d9965c9bb2a7871ce048b0161b71e91be83671be514d5",
+ ];
+ let pubkeys = pubkey_str
+ .iter()
+ .map(|s| PublicKey::from_slice(&hex::decode(s).unwrap()).unwrap())
+ .collect::<Vec<_>>();
+ let address = create_multi_sig_address_for_pubkeys_with_sorting(
+ 2,
+ &pubkeys,
+ MultiSigFormat::P2sh,
+ Network::Dogecoin,
+ false,
+ )
+ .unwrap();
+ assert_eq!(address, "A2nev5Fc7tFZ11oy1Ybz1kJRbebTWff8K6");
+ }
}
diff --git a/src/ui/gui_components/gui_status_bar.c b/src/ui/gui_components/gui_status_bar.c
index 0e0c308..51719da 100644
--- a/src/ui/gui_components/gui_status_bar.c
+++ b/src/ui/gui_components/gui_status_bar.c
@@ -172,7 +172,7 @@ const static WalletInfo_t g_walletBtn[] = {
{WALLET_LIST_TONKEEPER, "Tonkeeper", &walletTonkeeper},
{WALLET_LIST_BEGIN, "Begin", &walletBegin},
{WALLET_LIST_LEAP, "Leap", &walletLeap},
- {WALLET_LIST_NIGHTLY, "Nightly", &walletNightly},
+ {WALLET_LIST_NIGHTLY, "Nightly", &walletNightly},
{WALLET_LIST_SUIET, "Suiet", &walletSuiet},
// {WALLET_LIST_CAKE, "Cake Wallet", &walletCake},
{WALLET_LIST_FEATHER, "Feather Wallet", &walletFeather},
Why this scored 26/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.