Rust로 프로덕션 수준 블라인드 서명 eCash 시스템 구축
Source: Dev.to
소개
블록체인 없이도 블라인드 서명을 이용해 익명 디지털 현금을 구현할 수 있습니다. 이 개념은 1983년 David Chaum이 제안했으며, 세계 최초 디지털 현금 시스템인 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 블라인드 서명 단계
-
블라인딩 (클라이언트)
let message = hash(serial_number); let blinding_factor = random() % n; let blinded = (message * blinding_factor.pow(e)) % n; -
서명 (서버)
let blind_signature = blinded.pow(d) % n; -
언블라인딩 (클라이언트)
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 지연시간 | 처리량 |
|---|---|---|---|
| 인출 (Withdrawal) | 45 ms | 120 ms | 150 req/s |
| 교환 (Redemption) | 25 ms | 80 ms | 600 req/s |
| 검증 (Verification) | 5 ms | 15 ms | 2 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(())
}
보안 속성
- 연결 불가능성 (Unlinkability) – 서버는 인출과 이후 교환을 연결시킬 수 없습니다.
- 위조 방지 (Unforgeability) – 유효 토큰은 서버의 개인 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). Blind Signatures for Untraceable Payments.
- 저장소에 포함된 전체 학술 백서(164 KB) – 보안 증명, 프로토콜 사양, 성능 분석 포함.
기술 스택
- 언어: Rust 1.91+
- 웹 프레임워크: Axum 0.7
- 데이터베이스: PostgreSQL 16
- 캐시: Redis 7
- 암호화:
rsa크레이트와 3072‑비트 키 - 배포: Docker, Kubernetes
로드맵
- 자동 키 회전 메커니즘 구현
- 단일 트랜잭션에서 다중 금액 단위 지원 추가
Production‑Ready v1.0.0