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:
parent
ce5c996b35
commit
f23e7928ea
8 changed files with 27 additions and 37 deletions
BIN
apps/desktop-wallet/src-tauri/icons/128x128.png
Normal file
BIN
apps/desktop-wallet/src-tauri/icons/128x128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 130 B |
BIN
apps/desktop-wallet/src-tauri/icons/128x128@2x.png
Normal file
BIN
apps/desktop-wallet/src-tauri/icons/128x128@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 130 B |
BIN
apps/desktop-wallet/src-tauri/icons/32x32.png
Normal file
BIN
apps/desktop-wallet/src-tauri/icons/32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 130 B |
0
apps/desktop-wallet/src-tauri/icons/icon.icns
Normal file
0
apps/desktop-wallet/src-tauri/icons/icon.icns
Normal file
0
apps/desktop-wallet/src-tauri/icons/icon.ico
Normal file
0
apps/desktop-wallet/src-tauri/icons/icon.ico
Normal file
BIN
apps/desktop-wallet/src-tauri/icons/icon.png
Normal file
BIN
apps/desktop-wallet/src-tauri/icons/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 130 B |
|
|
@ -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)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue