NAC_Blockchain/nac_wallet_service/src/wallet_service.rs

283 lines
8.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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"),
}),
}
}