构建可投入生产的盲签名 eCash 系统(Rust)

发布: (2025年12月7日 GMT+8 08:34)
6 min read
原文: Dev.to

Source: Dev.to

简介

匿名数字现金可以在不使用区块链的情况下通过 盲签名 实现,这一概念由 David Chaum 于 1983 年提出。该协议支撑了世界上第一个数字现金系统 DigiCash。
如今,Chaum 协议的完整、可投入生产的实现已经在 Rust 中可用。

资源
GitHub Repository | Live Demo | Crates.io

协议概述

传统数字签名

Alice → Message → Bob signs → Alice receives signature

问题:Bob 能看到消息内容。

盲签名协议

Alice → Blinded Message → Bob signs → Alice unblinds → Valid signature

Bob 永远看不到原始消息。

RSA 盲签名步骤

  1. 盲化(客户端)

    let message = hash(serial_number);
    let blinding_factor = random() % n;
    let blinded = (message * blinding_factor.pow(e)) % n;
  2. 签名(服务器)

    let blind_signature = blinded.pow(d) % n;
  3. 去盲化(客户端)

    let signature = (blind_signature * blinding_factor.inverse()) % n;

结果是对原始消息的有效 RSA 签名,而服务器从未看到该消息本身。

系统架构

┌──────────┐      ┌────────┐      ┌────────────┐
│  Client  │─────▶│ Nginx │────▶ │ API Server │
│ (Wallet) │      │  :80   │      │   :8080    │
└──────────┘      └────────┘      └──────┬─────┘

                        ┌────────────────┼────────────┐
                        ▼                ▼            ▼
                   ┌─────────┐    ┌─────────┐   ┌──────┐
                   │  Redis  │    │Postgres │   │ HSM  │
                   │ (Cache) │    │ (Audit) │   │(Keys)│
                   └─────────┘    └─────────┘   └──────┘

加密原语(Rust)

pub struct BlindUser {
    public_key: RsaPublicKey,
}

impl BlindUser {
    pub fn blind_message(&self, message: &[u8]) -> Result {
        let n = self.public_key.n();
        let e = self.public_key.e();

        // Hash the message
        let m = BigUint::from_bytes_be(&Sha256::digest(message));

        // Generate blinding factor
        let r = loop {
            let candidate = random_biguint(n.bits());
            if candidate > 1 && gcd(&candidate, n) == 1 {
                break candidate;
            }
        };

        // Blind: m' = m * r^e mod n
        let r_e = r.modpow(e, n);
        let blinded = (&m * &r_e) % n;

        Ok((blinded, r))
    }

    pub fn unblind_signature(
        &self,
        blind_sig: &BigUint,
        blinding_factor: &BigUint,
    ) -> Result {
        let n = self.public_key.n();
        let r_inv = mod_inverse(blinding_factor, n)?;

        // Unblind: s = s' * r^-1 mod n
        Ok((blind_sig * r_inv) % n)
    }
}

双重支付防止

pub async fn redeem_token(&self, token: &Token) -> Result {
    // Fast check in Redis
    if self.cache.exists(&token.serial).await? {
        return Err(Error::TokenAlreadySpent);
    }

    // Reliable check in PostgreSQL
    if self.db.is_token_spent(&token.serial).await? {
        return Err(Error::TokenAlreadySpent);
    }

    // Atomic transaction
    let tx_id = Uuid::new_v4();

    sqlx::query!(
        "INSERT INTO redeemed_tokens (serial, tx_id, timestamp) 
         VALUES ($1, $2, NOW())",
        token.serial,
        tx_id
    )
    .execute(&self.db.pool)
    .await?;

    self.cache.set(&token.serial, tx_id.to_string()).await?;

    Ok(tx_id.to_string())
}

HTTP 处理程序(Axum)

async fn withdraw(
    State(state): State>,
    Json(req): Json,
) -> Result> {
    // Verify denomination
    if !state.is_valid_denomination(req.denomination) {
        return Err(ApiError::InvalidDenomination(req.denomination));
    }

    // Sign each blinded token
    let signatures = req.blinded_tokens
        .iter()
        .map(|blinded| {
            state.institution
                .sign_blinded(&BigUint::from_str(blinded)?)
        })
        .collect::>>()?;

    Ok(Json(WithdrawResponse {
        transaction_id: Uuid::new_v4().to_string(),
        blind_signatures: signatures,
        expires_at: Utc::now() + Duration::days(90),
    }))
}

性能基准

操作p50 延迟p99 延迟吞吐量
提现45 ms120 ms150 req/s
兑换25 ms80 ms600 req/s
验证5 ms15 ms2 000 req/s

主要瓶颈是 RSA 计算(CPU 受限)。由于 API 服务器是无状态的,水平扩展非常直接。

部署

git clone https://github.com/ChronoCoders/ecash-protocol.git
cd ecash-protocol
docker-compose up -d

该技术栈包括:

  • API 服务器 (localhost:8080)
  • PostgreSQL 数据库
  • Redis 缓存
  • 带速率限制的 Nginx 反向代理

将客户端库添加到你的项目中:

[dependencies]
ecash-client = "0.1.0"

客户端使用示例

use ecash_client::Wallet;

#[tokio::main]
async fn main() -> Result {
    // Initialize wallet
    let mut wallet = Wallet::new(
        "http://localhost:8080".to_string(),
        "wallet.db".to_string(),
    )?;

    wallet.initialize().await?;

    // Withdraw $100 in $10 denominations
    let tokens = wallet.withdraw(100, 10).await?;
    println!("Withdrew {} tokens", tokens.len());

    // Check balance
    let balance = wallet.get_balance()?;
    println!("Balance: ${}", balance);

    // Spend $20
    let tx_id = wallet.spend(20).await?;
    println!("Transaction: {}", tx_id);

    Ok(())
}

安全属性

  • 不可关联性 – 服务器无法将一次提现与后续的兑换关联起来。
  • 不可伪造性 – 有效的代币必须使用服务器的私有 RSA‑3072 密钥(128 位安全)进行签名。
  • 双重支付防止 – 在 Redis 和 PostgreSQL 中的原子检查确保代币不会被二次消费,即使在并发请求下也是如此。

生产环境考虑

  • HSM 用于私钥存储(永不将密钥存放在磁盘上)。
  • TLS/HTTPS 用于所有网络流量。
  • 速率限制(在 Nginx 中配置)以缓解 DoS 攻击。
  • 监控 验证失败和双重支付尝试。
  • 密钥轮换 – 实现定期轮换(尚未包含)。

扩展策略

部署规模近似吞吐量
单节点~1 000 req/s
3 节点集群~3 000 req/s
10 节点集群~10 000 req/s

当吞吐量接近 ~5 000 req/s 时,数据库会成为瓶颈;此时应添加读副本并增加连接池大小。

参考文献

  • Chaum, D. (1983). 盲签名用于不可追踪的支付
  • 完整的学术白皮书(164 KB)在仓库中 – 包含安全性证明、协议规范和性能分析。

技术栈

  • 语言:Rust 1.91+
  • Web 框架:Axum 0.7
  • 数据库:PostgreSQL 16
  • 缓存:Redis 7
  • 加密rsa crate,使用 3072 位密钥
  • 部署:Docker,Kubernetes

路线图

  • 实现自动密钥轮换机制
  • 为单个交易添加多种面额支持

生产就绪 v1.0.0

Back to Blog

相关文章

阅读更多 »