fix(desktop-wallet): update crypto APIs for bech32 v0.11 and bip39 v2

- Update bech32 encoding to use Hrp struct and segwit::encode
- Update bip39 to use from_entropy() instead of generate_in()
- Update Mnemonic::parse() instead of parse_in(Language)
- Fix HMAC ambiguity with explicit type annotation
- Add Debug and Clone derives to EncryptedWallet
- Use show_menu_on_left_click() (deprecation fix)
- Add placeholder icons for development builds
This commit is contained in:
Gulshan Yadav 2026-01-10 07:08:47 +05:30
parent ce5c996b35
commit f23e7928ea
8 changed files with 27 additions and 37 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 B

View file

@ -5,26 +5,29 @@
//! - Argon2id password-based key derivation
//! - ChaCha20-Poly1305 authenticated encryption
//! - Ed25519 key derivation from seed
//! - Bech32 address encoding
//! - Bech32m address encoding
use argon2::{
password_hash::{rand_core::OsRng, PasswordHasher, SaltString},
password_hash::SaltString,
Argon2, Params,
};
use bip39::{Language, Mnemonic};
use bip39::Mnemonic;
use chacha20poly1305::{
aead::{Aead, KeyInit},
ChaCha20Poly1305, Nonce,
};
use hmac::{Hmac, Mac};
use rand::RngCore;
use hmac::Mac;
use rand::{rngs::OsRng, RngCore};
use sha2::Sha512;
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::{Error, Result};
/// Type alias for HMAC-SHA512 to avoid ambiguity
type HmacSha512 = hmac::Hmac<Sha512>;
/// Encrypted wallet data stored on disk
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct EncryptedWallet {
/// Argon2 salt (22 bytes, base64 encoded)
pub salt: String,
@ -46,7 +49,10 @@ pub struct SeedData {
/// Generate a new random 24-word BIP39 mnemonic
pub fn generate_mnemonic() -> Result<String> {
// Generate 256 bits of entropy for 24 words
let mnemonic = Mnemonic::generate_in(Language::English, 24)
let mut entropy = [0u8; 32]; // 256 bits for 24 words
OsRng.fill_bytes(&mut entropy);
let mnemonic = Mnemonic::from_entropy(&entropy)
.map_err(|e| Error::Crypto(format!("Failed to generate mnemonic: {}", e)))?;
Ok(mnemonic.to_string())
@ -54,15 +60,15 @@ pub fn generate_mnemonic() -> Result<String> {
/// Validate a BIP39 mnemonic phrase
pub fn validate_mnemonic(phrase: &str) -> Result<()> {
Mnemonic::parse_in(Language::English, phrase)
.map_err(|e| Error::InvalidMnemonic)?;
Mnemonic::parse(phrase)
.map_err(|_| Error::InvalidMnemonic)?;
Ok(())
}
/// Derive a 64-byte seed from mnemonic using BIP39
/// The passphrase is optional (empty string if not used)
pub fn mnemonic_to_seed(mnemonic: &str, passphrase: &str) -> Result<SeedData> {
let mnemonic = Mnemonic::parse_in(Language::English, mnemonic)
let mnemonic = Mnemonic::parse(mnemonic)
.map_err(|_| Error::InvalidMnemonic)?;
let seed = mnemonic.to_seed(passphrase);
@ -169,14 +175,13 @@ pub fn decrypt_seed(wallet: &EncryptedWallet, password: &str) -> Result<SeedData
pub fn derive_ed25519_keypair(seed: &[u8; 64], index: u32) -> ([u8; 32], [u8; 32]) {
// Use HMAC-SHA512 for deterministic key derivation
// Path: m/44'/synor'/0'/0/index
type HmacSha512 = Hmac<Sha512>;
let mut mac = HmacSha512::new_from_slice(b"ed25519 seed").unwrap();
let mut mac: HmacSha512 = Mac::new_from_slice(b"ed25519 seed").unwrap();
mac.update(seed);
let master = mac.finalize().into_bytes();
// Derive child key for index
let mut mac = HmacSha512::new_from_slice(&master[32..]).unwrap();
let mut mac: HmacSha512 = Mac::new_from_slice(&master[32..]).unwrap();
mac.update(&master[..32]);
mac.update(&index.to_be_bytes());
let derived = mac.finalize().into_bytes();
@ -193,36 +198,21 @@ pub fn derive_ed25519_keypair(seed: &[u8; 64], index: u32) -> ([u8; 32], [u8; 32
}
/// Generate a Synor address from a public key
/// Format: synor1<bech32-encoded-pubkey-hash>
/// Format: synor1<bech32m-encoded-pubkey-hash>
pub fn pubkey_to_address(pubkey: &[u8; 32], testnet: bool) -> Result<String> {
use bech32::Hrp;
use sha2::{Digest, Sha256};
// Hash the public key (SHA256 then take first 20 bytes like Bitcoin)
let hash = Sha256::digest(pubkey);
let pubkey_hash: Vec<u8> = hash[..20].to_vec();
let pubkey_hash: &[u8] = &hash[..20];
// Convert to 5-bit groups for bech32
let mut data5 = Vec::with_capacity(33);
data5.push(bech32::u5::try_from_u8(0).unwrap()); // version byte
let hrp_str = if testnet { "tsynor" } else { "synor" };
let hrp = Hrp::parse(hrp_str)
.map_err(|e| Error::Crypto(format!("Invalid HRP: {}", e)))?;
// Convert 8-bit to 5-bit
let mut acc = 0u32;
let mut bits = 0u32;
for byte in &pubkey_hash {
acc = (acc << 8) | (*byte as u32);
bits += 8;
while bits >= 5 {
bits -= 5;
data5.push(bech32::u5::try_from_u8(((acc >> bits) & 31) as u8).unwrap());
}
}
if bits > 0 {
data5.push(bech32::u5::try_from_u8(((acc << (5 - bits)) & 31) as u8).unwrap());
}
let hrp = if testnet { "tsynor" } else { "synor" };
bech32::encode(hrp, data5, bech32::Variant::Bech32)
// Encode using bech32 segwit encoding (version 0)
bech32::segwit::encode(hrp, bech32::segwit::VERSION_0, pubkey_hash)
.map_err(|e| Error::Crypto(format!("Bech32 encoding failed: {}", e)))
}

View file

@ -120,7 +120,7 @@ pub fn run() {
let _tray = TrayIconBuilder::new()
.icon(app.default_window_icon().unwrap().clone())
.menu(&menu)
.menu_on_left_click(false)
.show_menu_on_left_click(false)
.on_tray_icon_event(|tray, event| {
handle_tray_event(tray.app_handle(), event);
})