283 lines
8.3 KiB
Rust
283 lines
8.3 KiB
Rust
use deadpool_postgres::Pool;
|
||
use sha3::{Sha3_384, Digest};
|
||
use sha2::Sha256;
|
||
use hmac::{Hmac, Mac};
|
||
use pbkdf2::pbkdf2_hmac;
|
||
use aes_gcm::{
|
||
aead::{Aead, AeadCore, KeyInit, OsRng},
|
||
Aes256Gcm, Key, Nonce,
|
||
};
|
||
use rand::RngCore;
|
||
use zeroize::Zeroize;
|
||
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
|
||
use tracing::{info, warn};
|
||
|
||
use crate::errors::AppError;
|
||
use crate::models::wallet::{CreateWalletRequest, WalletResponse, AssetInfo};
|
||
|
||
/// NAC原生地址类型 (32字节)
|
||
pub struct NacAddress([u8; 32]);
|
||
|
||
impl NacAddress {
|
||
/// 从公钥派生NAC原生地址
|
||
/// 使用SHA3-384哈希后取前32字节,符合NAC原生类型系统
|
||
pub fn from_public_key(public_key: &[u8]) -> Self {
|
||
let mut hasher = Sha3_384::new();
|
||
hasher.update(b"NAC_ADDRESS_V1"); // 域分隔符,防止哈希碰撞
|
||
hasher.update(public_key);
|
||
let hash = hasher.finalize();
|
||
let mut addr = [0u8; 32];
|
||
addr.copy_from_slice(&hash[..32]);
|
||
NacAddress(addr)
|
||
}
|
||
|
||
pub fn to_hex(&self) -> String {
|
||
format!("0x{}", hex::encode(self.0))
|
||
}
|
||
|
||
pub fn as_bytes(&self) -> &[u8; 32] {
|
||
&self.0
|
||
}
|
||
}
|
||
|
||
/// 助记词加密结果
|
||
struct EncryptedMnemonic {
|
||
ciphertext: Vec<u8>,
|
||
salt: Vec<u8>,
|
||
iv: Vec<u8>,
|
||
}
|
||
|
||
/// 使用用户密码加密助记词
|
||
/// 算法: PBKDF2(SHA256, password, salt, 100000次) -> AES-256-GCM
|
||
fn encrypt_mnemonic(mnemonic: &str, password: &str) -> Result<EncryptedMnemonic, AppError> {
|
||
// 生成随机盐值 (32字节)
|
||
let mut salt = vec![0u8; 32];
|
||
OsRng.fill_bytes(&mut salt);
|
||
|
||
// PBKDF2派生密钥 (100000次迭代,符合NIST推荐)
|
||
let mut key_bytes = [0u8; 32];
|
||
pbkdf2_hmac::<Sha256>(
|
||
password.as_bytes(),
|
||
&salt,
|
||
100_000,
|
||
&mut key_bytes,
|
||
);
|
||
|
||
// AES-256-GCM加密
|
||
let key = Key::<Aes256Gcm>::from_slice(&key_bytes);
|
||
let cipher = Aes256Gcm::new(key);
|
||
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
|
||
|
||
let ciphertext = cipher
|
||
.encrypt(&nonce, mnemonic.as_bytes())
|
||
.map_err(|_| AppError::Internal)?;
|
||
|
||
// 清零密钥材料(安全要求)
|
||
let mut key_bytes_mut = key_bytes;
|
||
key_bytes_mut.zeroize();
|
||
|
||
Ok(EncryptedMnemonic {
|
||
ciphertext,
|
||
salt,
|
||
iv: nonce.to_vec(),
|
||
})
|
||
}
|
||
|
||
/// 生成12个单词的BIP39助记词
|
||
fn generate_mnemonic() -> Result<String, AppError> {
|
||
// 生成128位熵 (对应12个单词)
|
||
let mut entropy = [0u8; 16];
|
||
OsRng.fill_bytes(&mut entropy);
|
||
|
||
// 使用bip39库生成助记词
|
||
let mnemonic = bip39::Mnemonic::from_entropy(&entropy)
|
||
.map_err(|_| AppError::Internal)?;
|
||
|
||
Ok(mnemonic.to_string())
|
||
}
|
||
|
||
/// 从助记词派生NAC原生密钥对
|
||
fn derive_nac_keypair(mnemonic_str: &str) -> Result<(Vec<u8>, Vec<u8>), AppError> {
|
||
let mnemonic = bip39::Mnemonic::parse_normalized(mnemonic_str)
|
||
.map_err(|_| AppError::Internal)?;
|
||
|
||
// 生成种子
|
||
let seed = mnemonic.to_seed("");
|
||
|
||
// NAC原生派生路径: m/44'/9999'/0'/0/0
|
||
// 9999是NAC公链的BIP44 coin type
|
||
let path = "m/44'/9999'/0'/0/0";
|
||
|
||
// 使用HMAC-SHA3-384进行密钥派生(NAC原生,不使用HMAC-SHA512)
|
||
use hmac::Mac;
|
||
let mut mac = <Hmac::<sha3::Sha3_384> as Mac>::new_from_slice(b"NAC seed")
|
||
.map_err(|_| AppError::Internal)?;
|
||
mac.update(&seed);
|
||
let result = mac.finalize().into_bytes();
|
||
|
||
// 私钥取前32字节
|
||
let mut private_key = vec![0u8; 32];
|
||
private_key.copy_from_slice(&result[..32]);
|
||
|
||
// 使用ed25519-dalek生成公钥
|
||
let signing_key = ed25519_dalek::SigningKey::from_bytes(
|
||
private_key.as_slice().try_into().map_err(|_| AppError::Internal)?
|
||
);
|
||
let public_key = signing_key.verifying_key().to_bytes().to_vec();
|
||
|
||
// 清零私钥(公钥派生完成后)
|
||
private_key.zeroize();
|
||
|
||
// 返回公钥和签名密钥的字节(加密后存储)
|
||
Ok((public_key, signing_key.to_bytes().to_vec()))
|
||
}
|
||
|
||
/// 创建新钱包
|
||
pub async fn create_wallet(
|
||
pool: &Pool,
|
||
req: &CreateWalletRequest,
|
||
) -> Result<WalletResponse, AppError> {
|
||
let client = pool.get().await.map_err(|e| {
|
||
tracing::error!("数据库连接获取失败: {}", e);
|
||
AppError::Database
|
||
})?;
|
||
|
||
// 检查用户是否已有钱包
|
||
let existing = client
|
||
.query_opt(
|
||
"SELECT id FROM wallets WHERE user_id = $1",
|
||
&[&req.user_id],
|
||
)
|
||
.await
|
||
.map_err(|_| AppError::Database)?;
|
||
|
||
if existing.is_some() {
|
||
return Err(AppError::WalletAlreadyExists);
|
||
}
|
||
|
||
// 生成助记词
|
||
let mnemonic = generate_mnemonic()?;
|
||
|
||
// 派生密钥对
|
||
let (public_key, mut signing_key_bytes) = derive_nac_keypair(&mnemonic)?;
|
||
|
||
// 派生NAC原生地址
|
||
let nac_address = NacAddress::from_public_key(&public_key);
|
||
|
||
// 加密助记词
|
||
let encrypted = encrypt_mnemonic(&mnemonic, &req.encryption_password)?;
|
||
|
||
// 存储到数据库
|
||
let row = client
|
||
.query_one(
|
||
r#"
|
||
INSERT INTO wallets (
|
||
user_id, address, address_hex, public_key,
|
||
encrypted_mnemonic, encryption_salt, encryption_iv,
|
||
wallet_type, kyc_level
|
||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||
RETURNING id, address_hex, created_at
|
||
"#,
|
||
&[
|
||
&req.user_id,
|
||
&nac_address.as_bytes().as_slice(),
|
||
&nac_address.to_hex(),
|
||
&public_key.as_slice(),
|
||
&BASE64.encode(&encrypted.ciphertext),
|
||
&encrypted.salt.as_slice(),
|
||
&encrypted.iv.as_slice(),
|
||
&"standard",
|
||
&"none",
|
||
],
|
||
)
|
||
.await
|
||
.map_err(|e| {
|
||
tracing::error!("钱包创建数据库写入失败: {}", e);
|
||
AppError::Database
|
||
})?;
|
||
|
||
// 初始化NAC原生双币资产记录
|
||
client
|
||
.execute(
|
||
r#"
|
||
INSERT INTO assets (wallet_id, chain, asset_symbol, asset_type, balance)
|
||
VALUES
|
||
($1, 'nac', 'XIC', 'native', 0),
|
||
($1, 'nac', 'XTZH', 'native', 0)
|
||
"#,
|
||
&[&row.get::<_, i64>("id")],
|
||
)
|
||
.await
|
||
.map_err(|_| AppError::Database)?;
|
||
|
||
// 记录审计日志
|
||
client
|
||
.execute(
|
||
r#"
|
||
INSERT INTO audit_logs (wallet_id, action, actor, actor_type, details, result)
|
||
VALUES ($1, 'create_wallet', $2, 'user', $3, 'success')
|
||
"#,
|
||
&[
|
||
&row.get::<_, i64>("id"),
|
||
&req.user_id.to_string(),
|
||
&serde_json::json!({
|
||
"address": nac_address.to_hex(),
|
||
"wallet_type": "standard"
|
||
}),
|
||
],
|
||
)
|
||
.await
|
||
.map_err(|_| AppError::Database)?;
|
||
|
||
// 清零签名密钥
|
||
signing_key_bytes.zeroize();
|
||
|
||
info!("钱包创建成功: user_id={}, address={}", req.user_id, nac_address.to_hex());
|
||
|
||
Ok(WalletResponse {
|
||
wallet_id: row.get("id"),
|
||
address: row.get("address_hex"),
|
||
// 助记词仅在创建时返回一次,之后不可再获取
|
||
mnemonic: Some(mnemonic),
|
||
created_at: row.get("created_at"),
|
||
})
|
||
}
|
||
|
||
/// 获取钱包信息(不含敏感数据)
|
||
pub async fn get_wallet(
|
||
pool: &Pool,
|
||
user_id: i64,
|
||
) -> Result<WalletResponse, AppError> {
|
||
let client = pool.get().await.map_err(|_| AppError::Database)?;
|
||
|
||
let row = client
|
||
.query_opt(
|
||
r#"
|
||
SELECT w.id, w.address_hex, w.kyc_level, w.vip_level, w.created_at,
|
||
json_agg(json_build_object(
|
||
'chain', a.chain,
|
||
'symbol', a.asset_symbol,
|
||
'balance', a.balance::text,
|
||
'frozen', a.frozen_balance::text
|
||
)) as assets
|
||
FROM wallets w
|
||
LEFT JOIN assets a ON a.wallet_id = w.id
|
||
WHERE w.user_id = $1 AND w.is_active = true
|
||
GROUP BY w.id
|
||
"#,
|
||
&[&user_id],
|
||
)
|
||
.await
|
||
.map_err(|_| AppError::Database)?;
|
||
|
||
match row {
|
||
None => Err(AppError::NotFound),
|
||
Some(r) => Ok(WalletResponse {
|
||
wallet_id: r.get("id"),
|
||
address: r.get("address_hex"),
|
||
mnemonic: None, // 查询时不返回助记词
|
||
created_at: r.get("created_at"),
|
||
}),
|
||
}
|
||
}
|