1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//! HD (Hierarchical Deterministic) wallet functionality
//!
//! This module implements secure hierarchical deterministic key generation
//! for Webcash wallets, following the pattern used in the C++ reference implementation.
use crate::crypto;
use sha2::Digest;
/// Key types for domain separation in HD wallet
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyType {
/// Keys for receiving payments (mine=false, sweep=true)
Receive = 0,
/// Keys for making payments (mine=false, sweep=false)
Pay = 1,
/// Keys for transaction change (mine=true, sweep=false)
Change = 2,
/// Keys for mining rewards (mine=true, sweep=true)
Mining = 3,
}
impl KeyType {
/// Convert key type to the encoding used in derivation
pub fn to_encoding(self) -> u8 {
match self {
KeyType::Receive => 0,
KeyType::Pay => 1,
KeyType::Change => 2,
KeyType::Mining => 3,
}
}
}
/// HD wallet implementation with secure key derivation
pub struct HDWallet {
/// Master secret key (32 bytes)
master_secret: [u8; 32],
}
impl HDWallet {
/// Create a new HD wallet with a cryptographically secure random master secret
pub fn new() -> std::result::Result<Self, Box<dyn std::error::Error>> {
let master_secret = crypto::generate_random_bytes();
Ok(HDWallet { master_secret })
}
/// Create HD wallet from existing master secret
pub fn from_master_secret(master_secret: [u8; 32]) -> Self {
HDWallet { master_secret }
}
/// Derive a secret using the exact algorithm from Python reference implementation
pub fn derive_secret(&self, chain_code: u64, depth: u64) -> std::result::Result<String, Box<dyn std::error::Error>> {
// Domain tag for Webcash wallet (matches Python exactly)
let tag_str = "webcashwalletv1";
let tag = crypto::sha256(tag_str.as_bytes());
// Create incremental hasher and initialize with tag + tag (matches Python)
let mut hasher = crypto::sha256_incremental();
hasher.update(tag);
hasher.update(tag);
// Update with master secret (matches Python)
hasher.update(self.master_secret);
// Update with chain code (big-endian 8 bytes, matches Python struct.pack(">Q", CHAIN_CODES[chain_code]))
let chaincode_bytes = chain_code.to_be_bytes();
hasher.update(chaincode_bytes);
// Update with depth (big-endian 8 bytes, matches Python struct.pack(">Q", walletdepth))
let depth_bytes = depth.to_be_bytes();
hasher.update(depth_bytes);
// Finalize and convert to hex string (matches Python)
let final_hash = crypto::sha256_finalize(hasher);
Ok(hex::encode(final_hash))
}
/// Generate a new secret for receiving payments (matches Python generate_new_secret with chain_code="RECEIVE")
pub fn generate_receive_secret(&mut self, depth: u64) -> std::result::Result<String, Box<dyn std::error::Error>> {
self.derive_secret(0, depth) // CHAIN_CODES["RECEIVE"] = 0
}
/// Generate a new secret for making payments (matches Python generate_new_secret with chain_code="PAY")
pub fn generate_pay_secret(&mut self, depth: u64) -> std::result::Result<String, Box<dyn std::error::Error>> {
self.derive_secret(1, depth) // CHAIN_CODES["PAY"] = 1
}
/// Generate a new secret for transaction change (matches Python generate_new_secret with chain_code="CHANGE")
pub fn generate_change_secret(&mut self, depth: u64) -> std::result::Result<String, Box<dyn std::error::Error>> {
self.derive_secret(2, depth) // CHAIN_CODES["CHANGE"] = 2
}
/// Generate a new secret for mining rewards (matches Python generate_new_secret with chain_code="MINING")
pub fn generate_mining_secret(&mut self, depth: u64) -> std::result::Result<String, Box<dyn std::error::Error>> {
self.derive_secret(3, depth) // CHAIN_CODES["MINING"] = 3
}
/// Get the master secret (use with caution - only for backup/recovery)
pub fn master_secret(&self) -> &[u8; 32] {
&self.master_secret
}
/// Get master secret as hex string for backup
pub fn master_secret_hex(&self) -> String {
hex::encode(self.master_secret)
}
}
impl Drop for HDWallet {
fn drop(&mut self) {
// Secure cleanup - zeroize the master secret
self.master_secret = [0u8; 32];
}
}