v0.0
오프라인 서명 앱 아키텍처 설계서¶
1. Executive Summary¶
본 문서는 D'CENT 엔터프라이즈 콜드월렛 커스터디 솔루션의 오프라인 서명 앱 아키텍처를 설계한다. 오프라인 앱은 3-Zone 보안 아키텍처에서 Zone 2(Offline App Zone)에 해당하며, Zone 1(대시보드)과 Zone 3(콜드월렛 SE) 사이의 에어갭 브릿지 역할을 수행한다.
핵심 역할: - 에어갭 브릿지: 대시보드 ↔ 콜드월렛 간 데이터 중계 - 2차 정책 검증: 캐시된 정책 데이터로 핵심 규칙 재확인 - M-of-N 서명 수집: 여러 서명자의 부분 서명을 로컬에서 수집/관리 - WYSIWYS 보조: TX 내용 표시로 사용자 확인 유도
2. 플랫폼 선택 및 근거¶
2.1. 후보 플랫폼 비교¶
| 항목 | 태블릿 앱 (iOS/Android) | 데스크톱 앱 (Electron/Tauri) | 전용 Linux 디바이스 |
|---|---|---|---|
| 카메라 | 내장 (QR 스캔 즉시 가능) | 외장 웹캠 필요 | 외장 또는 내장 |
| USB-C | 내장 (USB-C 포트) | 내장 (USB-C 포트) | 내장 (USB-C 포트) |
| 에어갭 강제 | 비행기 모드 + MDM | 네트워크 비활성화 (SW) | NIC 물리적 제거 가능 |
| 이동성 | 높음 | 낮음 | 중간 |
| 공급망 신뢰 | Apple/Google 앱스토어 | 자체 배포 | 자체 빌드 |
| 보안 수준 | 중간-높음 | 중간 | 최고 |
| 배포 용이성 | 높음 (앱스토어) | 중간 | 낮음 (하드웨어 포함) |
2.2. 추천: 태블릿 앱 (iOS + Android)¶
선택 근거: 1. 카메라 내장 + USB-C 포트: QR 스캔에 별도 장비 불필요, 콜드월렛 USB-C 직결 2. 에어갭 강제 가능: MDM(Mobile Device Management)으로 네트워크 완전 차단 3. 이동성: 서명자가 콜드월렛과 함께 휴대 가능 4. 앱스토어 배포: 코드 서명 검증 자동화 5. D'CENT 기존 역량: 기존 D'CENT 모바일 앱 개발 경험 활용
2.3. 에어갭 강제 방안¶
| 방안 | 설명 | 보안 수준 |
|---|---|---|
| MDM 프로필 | 기업 MDM으로 WiFi/셀룰러/BT 영구 차단 | 높음 |
| 비행기 모드 + 앱 검증 | 앱 시작 시 네트워크 상태 감지 → 활성 시 경고/차단 | 중간 |
| Supervised Mode (iOS) | iOS 감독 모드에서 네트워크 완전 비활성화 | 높음 |
| Work Profile (Android) | Android Enterprise 정책으로 네트워크 차단 | 높음 |
2.4. 대안: 전용 Linux 디바이스 (최고 보안 환경)¶
네트워크 하드웨어를 물리적으로 제거한 전용 디바이스: - WiFi/BT 모듈 물리적 제거 - 셀룰러 모뎀 없음 - USB-C 포트는 D'CENT X 연결 전용으로 제한 (MDM 정책) - 카메라 + USB-C 포트만 장착 - 적합: 최고 보안이 필요한 금융기관, 국방 관련 기관
3. 앱 내부 모듈 구조¶
┌──────────────────────────────────────────────────────┐
│ Offline Signing App │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ UI Layer │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ TX Viewer │ │ Signature │ │ Settings │ │ │
│ │ │ Screen │ │ Status │ │ Screen │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Service Layer │ │
│ │ ┌──────────────┐ ┌──────────────────────────┐ │ │
│ │ │ QR Scanner │ │ Signature Collector │ │ │
│ │ │ Module │ │ │ │ │
│ │ │ │ │ ● 부분 서명 수집 │ │ │
│ │ │ ● UR Parser │ │ ● M-of-N 상태 추적 │ │ │
│ │ │ ● Fountain │ │ ● 세션 관리 │ │ │
│ │ │ Decoder │ │ ● 릴레이/중앙 수집 │ │ │
│ │ └──────────────┘ └──────────────────────────┘ │ │
│ │ ┌──────────────┐ ┌──────────────────────────┐ │ │
│ │ │ USB Bridge │ │ Policy Checker │ │ │
│ │ │ Module │ │ │ │ │
│ │ │ │ │ ● 캐시 정책 검증 │ │ │
│ │ │ ● USB HID 관리│ │ ● 버전 대조 │ │ │
│ │ │ ● 디바이스 인증│ │ ● 경고 표시 │ │ │
│ │ │ ● 패킷 분할 │ │ │ │ │
│ │ └──────────────┘ └──────────────────────────┘ │ │
│ │ ┌──────────────┐ ┌──────────────────────────┐ │ │
│ │ │ Transaction │ │ APP_HASH │ │ │
│ │ │ Viewer │ │ Generator │ │ │
│ │ │ │ │ │ │ │
│ │ │ ● TX 파싱 │ │ ● WYSIWYS 해시 생성 │ │ │
│ │ │ ● 표시 │ │ ● 필드 추출 │ │ │
│ │ └──────────────┘ └──────────────────────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Data Layer │ │
│ │ ┌──────────────────────────────────────────────┐│ │
│ │ │ Local Storage (AES-256 암호화) ││ │
│ │ │ ││ │
│ │ │ ● 진행 중 서명 세션 ││ │
│ │ │ ● 정책 캐시 ││ │
│ │ │ ● 화이트리스트 캐시 ││ │
│ │ │ ● 앱 설정 ││ │
│ │ │ ● 감사 로그 (로컬) ││ │
│ │ └──────────────────────────────────────────────┘│ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Security Layer │ │
│ │ ● 앱 무결성 검증 (코드 서명 해시) │ │
│ │ ● PIN/생체인증 │ │
│ │ ● 화면 캡처 방지 │ │
│ │ ● 네트워크 상태 감지 (활성 시 경고) │ │
│ │ ● 데이터 자동 만료 (24시간) │ │
│ └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
4. QR/USB-C 브릿지 구현 방향¶
4.1. 데이터 흐름별 채널¶
| 방향 | 경로 | 앱 동작 | 채널 |
|---|---|---|---|
| → | 대시보드 → 앱 | 앱 카메라로 대시보드 화면의 QR 촬영 | QR 스캔 |
| → | 앱 → 콜드월렛 | 앱이 USB-C로 콜드월렛에 전송 | USB-C 전송 |
| ← | 콜드월렛 → 앱 | 앱이 USB-C로 콜드월렛에서 수신 | USB-C 수신 |
| ← | 앱 → 대시보드 | 앱 화면에 QR 표시 → 대시보드 스캐너 촬영 | QR 표시 |
4.2. QR Scanner Module¶
QRScannerModule {
// Animated QR 디코딩
startScan(onFrame: callback): ScanSession
stopScan(session: ScanSession): void
// UR 파서
parseURFrame(qrData: string): URFragment
assembleFountain(fragments: [URFragment]): bytes?
// 진행률 표시
getProgress(): {received: uint, total: uint, percentage: float}
// 파라미터 조정
adjustScanSpeed(fps: uint): void // 카메라 FPS
}
4.3. USB Bridge Module¶
USBBridgeModule {
// USB 디바이스 관리
connectDevice(): USBDevice
disconnectDevice(device: USBDevice): void
// USB HID 통신
initEnterprise(deviceId: bytes): USBResponse
sendUSBCommand(command: bytes): USBResponse
sendChunkedUSB(data: bytes, commandType: uint8): USBResponse
// 에러 핸들링
onConnectionLost(callback): void
retry(maxAttempts: uint): void
}
5. M-of-N 서명 수집 흐름¶
5.1. 서명 수집 모드¶
중앙 수집 (권장):
서명 요청 수신 (대시보드 → 앱)
│
▼
세션 생성 (session_id, M, N, 타임아웃)
│
├──▶ 서명자 1에게 USB-C 전달 → 콜드월렛1 서명 → 부분 서명1 수신
│ └─ 세션에 부분 서명1 저장
│
├──▶ 서명자 2에게 USB-C 전달 → 콜드월렛2 서명 → 부분 서명2 수신
│ └─ 세션에 부분 서명2 저장
│
└──▶ ... (M명까지)
│
▼
M개 서명 도달 → 서명 결과 QR 생성 → 대시보드 반환
릴레이 수집 (세레모니 환경):
서명 요청 + 빈 서명 목록으로 시작
│
▼
서명자 1 콜드월렛 → 부분 서명1 추가 → 앱에 저장
│
▼
서명자 2 콜드월렛 → 부분 서명2 추가 → 앱에 저장
│
▼
M개 도달 → 대시보드 반환
5.2. 서명 세션 관리¶
SigningSession {
session_id: bytes(16) // UUID
request_id: bytes(16) // 원본 서명 요청 ID
chain_type: uint8
unsigned_tx: bytes // 미서명 TX
required_signatures: uint8 // M (필요 서명 수)
total_signers: uint8 // N (전체 서명자 수)
collected_signatures: [{
signer_index: uint8
signer_pubkey: bytes(33)
sig_bytes: bytes
timestamp: uint32
}]
status: "collecting" | "complete" | "expired" | "failed"
created_at: uint32
expires_at: uint32 // 기본 4시간
musig2_state: { // MuSig2 시 상태 관리
round: uint8
nonces: [{signer_index, public_nonce}]
partial_sigs: [{signer_index, partial_sig}]
}?
}
5.3. 다중 오프라인 앱 환경¶
서명자가 각자 자신의 오프라인 앱을 사용하는 분산 비동기 환경:
대시보드: 서명 요청 QR 표시 (request_id 포함)
│
├──▶ 서명자 1 (앱A): QR 스캔 → 콜드월렛1 서명 → 부분 서명 QR → 대시보드
│
├──▶ 서명자 2 (앱B): QR 스캔 → 콜드월렛2 서명 → 부분 서명 QR → 대시보드
│
└──▶ 서명자 3 (앱C): QR 스캔 → 콜드월렛3 서명 → 부분 서명 QR → 대시보드
대시보드: request_id로 부분 서명 매칭 → M개 도달 시 취합
이 경우 서명 세션 관리는 대시보드에서 수행하며, 각 오프라인 앱은 단일 서명 세션만 처리한다.
6. 보안 요구사항¶
| 항목 | 구현 방향 |
|---|---|
| 앱 무결성 검증 | 앱 시작 시 코드 서명 해시 확인. 변조 감지 시 실행 차단. |
| 로컬 데이터 암호화 | AES-256-GCM. 키는 디바이스 Keychain/Keystore에서 파생. |
| PIN/생체인증 | 앱 접근 시 PIN(최소 6자리) 또는 생체인증(Face ID/지문) 필수. |
| 화면 캡처 방지 | iOS: UIScreen.captured 감지. Android: FLAG_SECURE. |
| 데이터 자동 만료 | 서명 세션 데이터 24시간 후 자동 삭제. 앱 종료 시 메모리 클리어. |
| 네트워크 감지 | 앱 실행 중 네트워크 활성 감지 시 경고 배너 + 서명 기능 비활성화. |
| Jailbreak/Root 감지 | 탈옥/루팅 디바이스 감지 시 앱 실행 차단 (보안 무결성). |
| 디버그 모드 차단 | 프로덕션 빌드에서 디버거 연결 감지 시 즉시 종료. |
7. 오프라인 정책 캐시¶
7.1. 캐시 구조¶
PolicyCache {
policy_version: uint32 // 대시보드 정책 버전
last_synced: uint32 // 마지막 동기화 타임스탬프
rules: [{
rule_type: uint8 // 정책 규칙 유형
value: bytes(32) // 정책 값
}]
whitelist: [{
address: string
chain_type: uint8
label: string
}]
whitelist_merkle_root: bytes(32)
whitelist_version: uint32
}
7.2. 동기화 방식¶
정책 캐시는 에어갭을 통해 동기화된다:
대시보드: 정책 동기화 QR 생성 (현재 정책 + 화이트리스트 스냅샷)
│
▼
앱: QR 스캔 → 캐시 업데이트
│
▼
앱: policy_version 저장 → 이후 서명 요청 시 버전 대조
7.3. 캐시 미스 시 동작¶
| 상황 | 동작 |
|---|---|
| 캐시 없음 (첫 사용) | 2차 정책 검증 스킵 + "정책 미동기화" 경고 표시. SE(Zone 3)가 최종 검증. |
| 캐시 버전 불일치 | 경고 표시 + 서명 진행 허용. SE가 최종 검증 수행. |
| 캐시 만료 (30일 초과) | 강한 경고 + 동기화 권고. 서명 진행은 허용 (SE 의존). |
설계 철학: 오프라인 앱의 정책 검증은 보조적 역할이다. 최종 정책 강제는 항상 SE(Zone 3)에서 수행되므로, 캐시 부재가 보안 취약점이 되지 않는다.
본 문서는 Phase 5 System Architecture Design의 일부로, Phase 3 에어갭 서명 플로우(airgap-signing-flow.md)의 오프라인 앱 역할을 아키텍처 수준으로 설계한다. three-zone-security-architecture.md의 Zone 2 컴포넌트 책임을 구현 수준으로 구체화한다.
관련 문서¶
- 에어갭 통신 프로토콜 설계서 -- 시스템 아키텍처
- 체인 추상화 레이어 인터페이스 정의서 -- 시스템 아키텍처
- 컴포넌트 간 데이터 흐름 및 인터페이스 명세 -- 시스템 아키텍처
- 온라인 대시보드 백엔드 아키텍처 설계서 -- 시스템 아키텍처
- D'CENT X 펌웨어 서명 인터페이스 설계서 -- 시스템 아키텍처
- STRIDE 기반 보안 위협 모델 -- 시스템 아키텍처
- 3-Zone 보안 아키텍처 설계서 -- 시스템 아키텍처