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

View file

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