콘텐츠로 이동

v0.0

D'CENT X 펌웨어 서명 인터페이스 설계서

1. Executive Summary

본 문서는 D'CENT X 콜드월렛의 Secure Element(SE) 내부 앱릿 구조와 외부 통신 인터페이스를 설계한다. SignRequest/SignResponse 메시지의 SE 내부 처리 흐름, PolicyUpdate 인터페이스, WhitelistVerify 인터페이스, SE 상태 조회 인터페이스를 명세하여 펌웨어 개발팀이 구현에 착수할 수 있는 수준의 기술 명세를 제공한다.

설계 범위: - SE 앱릿 구조 (Signing, Policy, WYSIWYS) - SignRequest/SignResponse 메시지 처리 흐름 - PolicyUpdate 및 WhitelistVerify 인터페이스 - SE 상태 조회 인터페이스 - 에러 핸들링 및 복구 절차


2. SE 통신 인터페이스 개요

2.1. SE 앱릿 구조

┌──────────────────────────────────────────────────────┐
│                 D'CENT X Secure Element                │
│                                                        │
│  ┌──────────────────────────────────────────────────┐ │
│  │              Enterprise Applet Container          │ │
│  │                                                    │ │
│  │  ┌──────────────┐  ┌──────────────┐              │ │
│  │  │ Signing      │  │ WYSIWYS      │              │ │
│  │  │ Applet       │  │ Applet       │              │ │
│  │  │              │  │              │              │ │
│  │  │ ● ECDSA Sign │  │ ● PSBT Parse │              │ │
│  │  │ ● Schnorr    │  │ ● RLP/ABI    │              │ │
│  │  │ ● Key Derive │  │ ● Hash Gen   │              │ │
│  │  │ ● Nonce Gen  │  │ ● Display    │              │ │
│  │  │   (MuSig2)   │  │ ● Warning    │              │ │
│  │  └──────┬───────┘  └──────┬───────┘              │ │
│  │         │                  │                       │ │
│  │         │    Internal API  │                       │ │
│  │         │                  │                       │ │
│  │  ┌──────▼──────────────────▼──────┐               │ │
│  │  │         Policy Applet          │               │ │
│  │  │                                │               │ │
│  │  │  ● Amount Limit Check          │               │ │
│  │  │  ● Merkle Root Verify          │               │ │
│  │  │  ● Daily Counter               │               │ │
│  │  │  ● Rate Limit                  │               │ │
│  │  │  ● Cooldown Timer              │               │ │
│  │  │  ● Admin Sig Verify            │               │ │
│  │  └────────────────────────────────┘               │ │
│  │                                                    │ │
│  │  ┌────────────────────────────────────────────┐   │ │
│  │  │           Secure Storage                    │   │ │
│  │  │                                             │   │ │
│  │  │  ● Private Keys (HD derived)                │   │ │
│  │  │  ● Policy Data (256B)                       │   │ │
│  │  │  ● Merkle Root (32B)                        │   │ │
│  │  │  ● Admin Public Keys (512B)                 │   │ │
│  │  │  ● Sign Counter (4B)                        │   │ │
│  │  │  ● Token DB (2KB) + Selector DB (4KB)       │   │ │
│  │  └────────────────────────────────────────────┘   │ │
│  │                                                    │ │
│  └──────────────────────────────────────────────────┘ │
│                                                        │
│  ┌──────────────────────────────────────────────────┐ │
│  │              External Interface                   │ │
│  │  QR (카메라 입력 / 화면 출력)                      │ │
│  │  USB-C (HID)                                      │ │
│  │  물리 버튼 (승인 / 거부)                           │ │
│  │  디스플레이 (WYSIWYS 표시)                         │ │
│  └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘

2.2. 앱릿 간 통신 (Internal API)

SignRequest 수신
    │
    ▼
[WYSIWYS Applet]
    │ parse(raw_tx) → ParsedTransaction
    │ generateHash(parsed) → SE_PARSE_HASH
    │ compareHash(SE_PARSE_HASH, APP_HASH) → match/mismatch
    │ displayTransaction(parsed) → 화면 표시
    │
    ▼ (일치 + 사용자 물리 버튼 승인)
[Policy Applet]
    │ checkAmountLimit(tx.amount) → pass/reject
    │ verifyWhitelist(recipient, merkle_proof) → pass/reject
    │ checkDailyLimit(tx.amount) → pass/reject
    │ checkRateLimit() → pass/reject
    │
    ▼ (전체 통과)
[Signing Applet]
    │ deriveKey(derivation_path) → private_key
    │ sign(data_to_sign, private_key) → signature
    │ incrementCounter()
    │
    ▼
SignResponse 생성 + 반환

3. SignRequest 메시지 구조

3.1. 공통 헤더

SignRequestHeader {
    version: uint8              // 프로토콜 버전 (0x01)
    request_id: bytes(16)       // UUID v4
    chain_type: uint8           // 0x00=BTC, 0x01=EVM, 0x02=SOL(향후)
    timestamp_ref: uint32       // 참조용 타임스탬프 (SE 미신뢰)
    payload_hash: bytes(32)     // SHA-256 페이로드 해시
}

3.2. BTC SignRequest

BTCSignRequest {
    header: SignRequestHeader
    psbt: bytes                 // PSBT 바이너리 (BIP-174/370)
    derivation_path: bytes      // BIP-32 경로 (바이너리: 4B × depth)
    signer_index: uint8         // MuSig2 참여자 인덱스
    wallet_id: bytes(16)        // 월렛 식별자
    policy_version: uint32      // 정책 버전 (SE 저장값과 대조)
    whitelist_version: uint32   // 화이트리스트 버전
    app_hash: bytes(32)         // WYSIWYS 검증용 해시
    musig2_data: {              // MuSig2 라운드 데이터
        round: uint8            // 1=nonce, 2=partial_sig
        aggregated_nonce: bytes(66)?  // Round 2
        participant_pubkeys: [bytes(33)]
    }?
    whitelist_proof: {
        recipient: bytes        // 수신 주소
        proof: [bytes(32)]      // Merkle Proof
    }?
}

3.3. EVM SignRequest

EVMSignRequest {
    header: SignRequestHeader
    chain_id: uint64            // EIP-155 Chain ID
    rlp_tx: bytes               // RLP 인코딩된 미서명 TX
    derivation_path: bytes
    wallet_id: bytes(16)
    policy_version: uint32
    whitelist_version: uint32
    app_hash: bytes(32)
    eip712_domain: bytes(32)?   // EIP-712 도메인 세퍼레이터
    eip712_hash: bytes(32)?     // EIP-712 메시지 해시
    whitelist_proof: {...}?
}

3.4. SE 내부 처리 흐름

[1] SignRequest CBOR 수신 및 디코딩
     │
     ▼
[2] 헤더 검증
     ├─ version 지원 확인
     ├─ payload_hash 무결성 확인
     └─ policy_version/whitelist_version 대조
        ├─ 불일치 → POLICY_VERSION_MISMATCH (0x24) 반환
        └─ 일치 → 계속
     │
     ▼
[3] WYSIWYS Applet 호출
     ├─ raw_tx 독립 파싱 (PSBT 디코딩 또는 RLP+ABI 디코딩)
     ├─ ParsedTransaction 생성
     ├─ SE_PARSE_HASH = SHA-256(recipients || amounts || fee || chain)
     ├─ APP_HASH 대조
     │   ├─ 불일치 → WYSIWYS_HASH_MISMATCH (0x30) 반환 + 잠금 카운터 증가
     │   │           3회 연속 → WYSIWYS_LOCKOUT (0x32)
     │   └─ 일치 → 계속
     ├─ 화면에 TX 내용 표시 (수신 주소, 금액, 수수료, 체인)
     └─ 물리 버튼 대기
        ├─ 거부 → REJECTED_BY_USER (0x10) 반환
        ├─ 타임아웃(60초) → CANCELLED_BY_TIMEOUT (0x11) 반환
        └─ 승인 → 계속
     │
     ▼
[4] Policy Applet 호출
     ├─ 화이트리스트 검증 (Merkle Proof → Root 대조)
     │   └─ 실패 → POLICY_WHITELIST_FAILED (0x21)
     ├─ 건당 한도 검증 (tx.amount vs max_per_tx)
     │   └─ 초과 → POLICY_AMOUNT_EXCEEDED (0x20)
     ├─ 일일 누적 검증 (daily_spent + tx.amount vs daily_max)
     │   └─ 초과 → POLICY_DAILY_LIMIT_EXCEEDED (0x22)
     └─ 서명 횟수 검증 (window_signs vs max_signs)
         └─ 초과 → POLICY_RATE_LIMIT_EXCEEDED (0x23)
     │
     ▼
[5] Signing Applet 호출
     ├─ BTC: deriveKey(path) → Schnorr sign(sighash)
     │   ├─ MuSig2 Round 1: generateNonce() → public_nonce 반환
     │   └─ MuSig2 Round 2: partialSign(sighash, agg_nonce) → partial_sig 반환
     ├─ EVM: deriveKey(path) → ECDSA sign(tx_hash)
     ├─ 서명 카운터 증가
     └─ 일일 누적 금액 업데이트
     │
     ▼
[6] SignResponse 생성 + 반환

4. SignResponse 메시지 구조

4.1. 성공 응답

SignResponse_Success {
    header: {
        version: uint8
        request_id: bytes(16)     // 원본 요청 ID
        message_type: 0x03
    }
    status_code: 0x00             // SUCCESS
    signature: {
        chain_type: uint8
        sig_bytes: bytes          // BTC Schnorr: 64B, EVM ECDSA: 65B (r,s,v)
        pubkey: bytes(33)         // 서명한 공개키
        derivation_path: bytes
    }
    se_parse_hash: bytes(32)      // WYSIWYS 검증 해시
    sign_counter: uint32          // 현재 서명 카운터
    musig2_data: {                // MuSig2 Round 1 응답 (해당 시)
        public_nonce: bytes(66)
    }?
}

4.2. 실패 응답

SignResponse_Failure {
    header: {...}
    status_code: uint8            // 0x10-0xFF
    se_parse_hash: bytes(32)      // 파싱 성공 시에만 포함
    sign_counter: uint32
    error_detail: {
        error_code: uint8
        error_message: string?
        policy_info: {            // 정책 위반 시
            violated_policy: uint8
            current_value: bytes(32)
            requested_value: bytes(32)
        }?
    }
}

4.3. 상태 코드 체계

(component-data-flow.md 섹션 5.2의 상태 코드 체계를 SE 내부에서 그대로 사용)

범위 카테고리 설명
0x00 성공 서명 완료
0x10-0x1F 사용자 거부 물리 버튼 거부, 타임아웃
0x20-0x2F 정책 위반 HW 정책 엔진 거부 (6종 코드)
0x30-0x3F WYSIWYS 해시 불일치, 파싱 실패, 잠금
0x40-0x4F 파싱 에러 데이터 파싱 불가, 미지원 체인
0xF0-0xFF 시스템 내부 오류, 미초기화

5. PolicyUpdate 인터페이스

5.1. PolicyUpdate 처리 흐름

[1] PolicyUpdate CBOR 수신
     │
     ▼
[2] 기본 검증
     ├─ version == current_version + 1 (롤백 방지)
     │   └─ 불일치 → POLICY_VERSION_MISMATCH
     ├─ previous_value == SE 현재 저장값
     │   └─ 불일치 → 거부 (데이터 무결성)
     └─ 쿨다운 상태 확인
         └─ 쿨다운 중 → POLICY_COOLDOWN_ACTIVE (0x25)
     │
     ▼
[3] Admin 쿼럼 서명 검증
     ├─ admin_signatures 각각의 pubkey가 SE 저장 Admin 목록에 포함
     ├─ 각 서명의 ECDSA 검증 (payload_hash 기준)
     └─ 유효 서명 수 >= quorum_threshold
        └─ 미달 → POLICY_SIGNATURE_INVALID (0x06)
     │
     ▼
[4] 화면 표시
     ├─ "정책 변경: [항목] [이전값] → [신규값]"
     └─ 물리 버튼 2회 확인
        ├─ 1회: "정책 변경을 인지합니까?"
        └─ 2회: "변경을 최종 승인합니까?"
     │
     ▼
[5] 쿨다운 적용
     ├─ 보안 강화 방향(한도 감소): 즉시 적용
     └─ 보안 약화 방향(한도 증가): 쿨다운 시작
        └─ cooldown_remaining = cooldown_seconds
        └─ 쿨다운 완료 시 신규 값 활성화
     │
     ▼
[6] PolicyResponse 반환
     ├─ status: SUCCESS / PENDING_COOLDOWN
     ├─ new_version: uint32
     └─ cooldown_remaining: uint32

5.2. Admin 공개키 관리

SE에 저장되는 Admin 공개키 세트:
    admin_keys: [bytes(33)]    // 최대 8개
    admin_threshold: uint8     // Admin 쿼럼 임계값

Admin 키 세트 변경:
    - 기존 Admin 전체 서명 필수 (N-of-N)
    - 키 세레모니 수준의 절차 요구
    - 변경 후 72시간 쿨다운

6. WhitelistVerify 인터페이스

6.1. Merkle Proof 검증 흐름

[1] WhitelistVerifyRequest 수신
     ├─ recipient: 수신 주소
     ├─ proof: [bytes(32)] (Merkle Proof, 최대 20 depth)
     └─ whitelist_version: uint32
     │
     ▼
[2] 버전 확인
     └─ whitelist_version == SE 저장 버전
        └─ 불일치 → 거부 (동기화 필요)
     │
     ▼
[3] Merkle 검증
     ├─ leaf = SHA-256(recipient)
     ├─ for each sibling in proof:
     │      current = SHA-256(min(current, sibling) || max(current, sibling))
     └─ computed_root == SE_stored_merkle_root ?
        ├─ 일치 → PASS
        └─ 불일치 → REJECT (POLICY_WHITELIST_FAILED)
     │
     ▼
[4] 결과 반환 (1 byte: 0x00=통과, 0x01=실패)

6.2. 성능

항목 수치
Proof depth 20 1,048,576 주소 지원
SHA-256 × 20 ~50ms
SE 저장: Merkle Root 32 bytes 고정
Proof 전송 크기 depth × 32 = 640 bytes

7. SE 상태 조회 인터페이스

7.1. GetDeviceInfo

Request: INS=0x40, P1=0x00
Response:
    DeviceInfo {
        se_version: string          // SE 펌웨어 버전 "1.2.0"
        protocol_version: uint8     // 프로토콜 버전
        supported_chains: [uint8]   // [0x00(BTC), 0x01(EVM)]
        max_psbt_size: uint32       // 최대 PSBT 크기 (bytes)
        max_recipients: uint8       // 최대 수신자 수 (WYSIWYS 페이지)
        selector_db_count: uint16   // 등록 함수 셀렉터 수
        token_db_count: uint16      // 등록 토큰 수
    }

7.2. GetPolicyStatus

Request: INS=0x40, P1=0x01
Response:
    PolicyStatus {
        policy_version: uint32      // 현재 정책 버전
        policies: [{
            id: uint8               // 0x01-0x04
            enabled: bool
            current_value: bytes(32)
        }]
        daily_spent: uint64         // 일일 누적 금액
        daily_reset_counter: uint32
        cooldown_active: bool
        cooldown_remaining: uint32  // 초
        whitelist_version: uint32
        whitelist_root: bytes(32)   // Merkle Root
    }

7.3. GetSignCounter

Request: INS=0x40, P1=0x02
Response:
    SignCounterInfo {
        global_counter: uint32      // 전체 서명 횟수
        window_counter: uint16      // 현재 윈도우 내 서명 수
        window_start: uint32        // 윈도우 시작 카운터
        last_sign_timestamp_ref: uint32  // 마지막 서명 참조 타임스탬프
    }

8. 에러 핸들링 및 복구

8.1. 통신 중단 시 세션 복구

상황 처리
QR 스캔 중단 오프라인 앱에서 재스캔. SE는 상태 없음 (각 요청 독립 처리)
USB-C 연결 끊김 (패킷 전송 중) USB HID 패킷 첫 패킷부터 재전송. SE는 불완전 데이터 폐기
서명 후 응답 전달 실패 SE 서명 카운터는 이미 증가. 오프라인 앱이 GET_SIGNATURE로 재요청
WYSIWYS 표시 중 전원 차단 SE 상태 초기화. 서명 미수행. 처음부터 재시작

8.2. 부분 전송 데이터 처리

USB-C 패킷 전송 중 데이터 불완전 수신 시: - SE는 END 커맨드 수신 전까지 데이터 버퍼에 누적 - 30초 내 마지막 청크 미수신 → 버퍼 폐기 - 새 SIGN_REQ 커맨드 수신 시 이전 버퍼 자동 폐기

8.3. SE 잠금 상태 해제

잠금 유형 원인 해제 방식
PIN 잠금 PIN 연속 오류 (5회) PUK 코드 입력 (별도 제공)
WYSIWYS 잠금 해시 불일치 3회 연속 24시간 자동 해제
정책 쿨다운 정책 변경 대기 쿨다운 시간 경과 후 자동
디바이스 잠금 Admin 원격 잠금 요청 Admin 쿼럼 서명 + 에어갭 해제 명령

본 문서는 Phase 5 System Architecture Design의 일부로, Phase 4 WYSIWYS(wysiwys-design.md) 및 HW 정책 엔진(hardware-policy-engine.md)의 SE 구현 인터페이스를 명세한다. airgap-communication-protocol.md의 USB-C 통신 프로토콜과 연계하여 SE 외부 인터페이스를 정의한다. Phase 6 펌웨어 요구사항의 입력 문서로 활용된다.


관련 문서