콘텐츠로 이동

v0.0

에어갭 통신 프로토콜 설계서

1. Executive Summary

본 문서는 D'CENT 엔터프라이즈 콜드월렛 커스터디 솔루션의 에어갭 통신 프로토콜을 바이트 수준으로 명세한다. Blockchain Commons UR(Uniform Resources) v2 표준을 기반으로 CBOR 스키마, Animated QR 프로토콜 파라미터, USB-C 통신 프로토콜을 정의하여 펌웨어/앱/백엔드 개발팀이 프로토콜 호환성을 보장하면서 독립 구현할 수 있게 한다.

설계 기반: - Phase 3 airgap-signing-flow.md 섹션 7 "Phase 5 설계 시 참조사항"의 상세화 범위 충족 - Blockchain Commons UR v2 표준 준수 - D'CENT X 기존 USB-C 통신 프로토콜과의 호환성 고려

통신 채널 구성: - 대시보드 ↔ 오프라인 앱: QR (Animated QR / Fountain Code) — 에어갭 경계 - 오프라인 앱 ↔ D'CENT X 콜드월렛: USB-C — 유선 직결 (에어갭이 아닌 SE 보안 경계) - D'CENT X ↔ 리커버리카드: NFC (ISO 14443) — 리커버리카드 전용, 본 문서 범위 외


2. UR (Uniform Resources) 프로토콜 스택

2.1. 계층 구조

┌─────────────────────────────────────┐
│  Layer 4: Transport                  │
│  QR (Animated QR / Fountain Code)   │
│  USB-C (HID / Bulk Transfer)        │
├─────────────────────────────────────┤
│  Layer 3: UR Encoding                │
│  UR v2 (Uniform Resources)          │
│  ur:<type>/<cbor-encoded-data>      │
├─────────────────────────────────────┤
│  Layer 2: Serialization              │
│  CBOR (RFC 8949)                     │
│  CDDL Schema Definition             │
├─────────────────────────────────────┤
│  Layer 1: Application Data           │
│  PSBT, EIP-712, PolicyUpdate, etc.  │
└─────────────────────────────────────┘

2.2. UR 버전

  • UR v2 (Blockchain Commons): 현재 표준
  • URI 형식: ur:<type>/<cbor-bytewords-encoded>
  • Bytewords 인코딩: CBOR 바이너리를 인간 판독 가능한 단어 시퀀스로 변환
  • Animated QR 분할 시 Fountain 코드 (LT 코드 기반)

2.3. UR Type Registry

UR Type CBOR 태그 용도 표준
crypto-psbt 310 BTC PSBT 전달 BC 표준
crypto-account 311 HD 계정 정보 BC 표준
crypto-hdkey 303 HD 키 정보 BC 표준
crypto-eth-sign-request 401 EVM 서명 요청 BC 확장
crypto-eth-signature 402 EVM 서명 응답 BC 확장
dcent-sign-request 45001 D'CENT 확장 서명 요청 D'CENT 커스텀
dcent-sign-response 45002 D'CENT 확장 서명 응답 D'CENT 커스텀
dcent-policy-update 45010 정책 업데이트 D'CENT 커스텀
dcent-policy-response 45011 정책 응답 D'CENT 커스텀
dcent-whitelist-verify 45020 화이트리스트 검증 D'CENT 커스텀
dcent-device-status 45031 디바이스 상태 D'CENT 커스텀

D'CENT 커스텀 태그 범위: 45000-45999 (IANA 미등록 Private Use 범위)


3. CBOR 스키마 정의 (CDDL)

3.1. BTC 서명 요청 (crypto-psbt 확장)

; D'CENT BTC 서명 요청
; UR Type: dcent-sign-request (chain_type = 0)
; CBOR Tag: 45001

dcent-btc-sign-request = {
    1: uint,                    ; version (프로토콜 버전)
    2: uint,                    ; message_type (0x01)
    3: bytes .size 16,          ; request_id (UUID)
    4: uint,                    ; timestamp_ref
    5: bytes .size 32,          ; payload_hash (SHA-256)
    6: uint,                    ; chain_type (0x00 = BTC)
    7: bytes .size 16,          ; wallet_id
    8: bytes,                   ; psbt (PSBT 바이너리)
    9: [+ derivation-entry],    ; derivation_paths
    ? 10: musig2-context,       ; musig2_context (MuSig2 시)
    11: uint,                   ; policy_version
    12: uint,                   ; whitelist_version
    13: bytes .size 32,         ; app_hash (WYSIWYS)
    ? 14: whitelist-proof       ; whitelist_proof
}

derivation-entry = {
    1: text,                    ; path (e.g., "m/86'/0'/0'/0/0")
    2: uint                     ; signer_index
}

musig2-context = {
    1: uint,                    ; round (1 또는 2)
    ? 2: bytes .size 66,        ; aggregated_nonce (Round 2)
    3: [+ bytes .size 33]       ; participant_pubkeys
}

whitelist-proof = {
    1: bytes,                   ; recipient_address
    2: [+ bytes .size 32]       ; merkle_proof (최대 20개)
}

3.2. EVM 서명 요청

; D'CENT EVM 서명 요청
; UR Type: dcent-sign-request (chain_type = 1)
; CBOR Tag: 45001

dcent-evm-sign-request = {
    1: uint,                    ; version
    2: uint,                    ; message_type (0x02)
    3: bytes .size 16,          ; request_id
    4: uint,                    ; timestamp_ref
    5: bytes .size 32,          ; payload_hash
    6: uint,                    ; chain_type (0x01 = EVM)
    7: bytes .size 16,          ; wallet_id
    8: uint,                    ; chain_id (EIP-155)
    9: evm-transaction,         ; transaction
    ? 10: eip712-data,          ; eip712_data (Safe 등)
    11: text,                   ; derivation_path
    12: uint,                   ; policy_version
    13: uint,                   ; whitelist_version
    14: bytes .size 32,         ; app_hash
    ? 15: token-info,           ; token_info (ERC-20)
    ? 16: whitelist-proof       ; whitelist_proof
}

evm-transaction = {
    1: bytes .size 20,          ; to
    2: bytes .size 32,          ; value (big-endian wei)
    3: bytes,                   ; data (calldata)
    4: uint,                    ; gas_limit
    5: uint,                    ; max_fee_per_gas
    6: uint,                    ; max_priority_fee_per_gas
    7: uint,                    ; nonce
    ? 8: [+ access-list-entry]  ; access_list
}

access-list-entry = {
    1: bytes .size 20,          ; address
    2: [+ bytes .size 32]       ; storage_keys
}

eip712-data = {
    1: bytes .size 32,          ; domain_separator
    2: bytes .size 32,          ; message_hash
    3: text                     ; typed_data_json
}

token-info = {
    1: text,                    ; symbol
    2: uint,                    ; decimals
    3: bytes .size 20           ; contract_address
}

3.3. 서명 응답

; D'CENT 서명 응답
; UR Type: dcent-sign-response
; CBOR Tag: 45002

dcent-sign-response = {
    1: uint,                    ; version
    2: uint,                    ; message_type (0x03)
    3: bytes .size 16,          ; request_id
    4: uint,                    ; timestamp_ref
    5: bytes .size 32,          ; payload_hash
    6: uint,                    ; status_code
    ? 7: signature-data,        ; signature (성공 시)
    8: bytes .size 32,          ; se_parse_hash
    9: uint,                    ; sign_counter
    ? 10: musig2-nonce-data,    ; musig2_nonce (Round 1 응답)
    ? 11: error-detail          ; error (실패 시)
}

signature-data = {
    1: uint,                    ; chain_type
    2: bytes,                   ; sig_bytes (64B Schnorr / 65B ECDSA)
    3: bytes .size 33,          ; pubkey
    4: bytes                    ; derivation_path (바이너리)
}

musig2-nonce-data = {
    1: bytes .size 66           ; public_nonce
}

error-detail = {
    1: uint,                    ; error_code
    ? 2: text,                  ; error_message
    ? 3: policy-violation-info  ; policy_info
}

policy-violation-info = {
    1: uint,                    ; violated_policy (0x01-0x04)
    2: bytes .size 32,          ; current_value
    3: bytes .size 32           ; requested_value
}

3.4. 정책 업데이트

; D'CENT 정책 업데이트
; UR Type: dcent-policy-update
; CBOR Tag: 45010

dcent-policy-update = {
    1: uint,                    ; version
    2: uint,                    ; message_type (0x10)
    3: bytes .size 16,          ; request_id
    4: uint,                    ; timestamp_ref
    5: bytes .size 32,          ; payload_hash
    6: policy-data,             ; policy_data
    7: [+ admin-signature],     ; admin_signatures
    8: uint                     ; quorum_threshold
}

policy-data = {
    1: uint,                    ; version (단조 증가)
    2: uint,                    ; target_policy (0x01-0x04)
    3: bytes .size 32,          ; previous_value
    4: bytes .size 32,          ; new_value
    5: uint                     ; cooldown_seconds
}

admin-signature = {
    1: bytes .size 33,          ; pubkey
    2: bytes .size 64           ; signature (ECDSA)
}

4. Animated QR 프로토콜 상세

4.1. Fountain 코드 기반 멀티파트 분할

Animated QR은 Blockchain Commons의 UR v2 Fountain 코드를 사용하여 대용량 데이터를 여러 QR 프레임으로 분할한다.

Fountain 코드 (LT Code) 특성: - Rateless: 수신측이 충분한 프레임을 수신하면 원본 복원 가능 - 순서 무관: 어떤 프레임부터 수신해도 복원 가능 - 오류 복구: 일부 프레임 손실 시에도 추가 프레임으로 복구

4.2. QR 프레임 구조

┌──────────────────────────────────────┐
│ QR Frame                             │
│                                      │
│  UR Part Header:                     │
│    sequence_number: uint32           │
│    sequence_count: uint32            │
│    message_length: uint32            │
│    checksum: uint32 (CRC32)          │
│                                      │
│  Fragment Data:                      │
│    fragment_bytes: bytes             │
│    (XOR-mixed for Fountain coding)   │
│                                      │
│  UR Encoding:                        │
│    ur:<type>/[seq-num]-[seq-count]/  │
│    <bytewords-encoded-fragment>      │
└──────────────────────────────────────┘

4.3. QR 코드 파라미터

파라미터 권장 값 범위 근거
QR 버전 12-15 1-40 카메라 해상도와 인식률 최적 밸런스
에러 보정 Level L (7%) L/M/Q/H 에어갭 환경에서 육안 확인 가능, L로 최대 데이터 용량 확보
모듈 크기 8px 이상 4-16px 카메라 초점 거리 30-50cm 기준
프레임당 데이터 ~500 bytes 100-1000B 인식률 99%+ 유지 범위
프레임 전환 속도 100ms 50-500ms 기본 100ms, 사용자 조절 가능
프레임 표시 시간 무제한 반복 - Fountain 코드이므로 무한 반복 표시

4.4. 페이로드 크기별 QR 파라미터

페이로드 크기 예상 프레임 수 전송 시간 (100ms) 적합 사용
< 500B 1 (정적 QR) 즉시 EVM 단순 전송 응답
500B - 2KB 4-8 프레임 0.4-0.8초 EVM 서명 요청, BTC 단순 PSBT
2KB - 10KB 8-40 프레임 0.8-4초 BTC 다중 입력 PSBT
10KB - 50KB 40-200 프레임 4-20초 MuSig2 멀티 서명자 PSBT
> 50KB 200+ 프레임 20초+ 배치 트랜잭션 (QR 필수)

4.5. Animated QR 구현 사양

송신측 (QR 생성): 1. 원본 데이터를 CBOR 인코딩 2. UR 래핑 (type + CBOR) 3. Fountain 코드로 프래그먼트 생성 (원본 프래그먼트 수의 2-3배 여분) 4. 각 프래그먼트를 Bytewords로 인코딩 5. QR 코드로 렌더링 → 순차 표시 (100ms 간격) 6. 전체 프래그먼트를 무한 반복

수신측 (QR 스캔): 1. 카메라로 QR 프레임 연속 캡처 2. Bytewords 디코딩 → Fountain 프래그먼트 추출 3. 충분한 프래그먼트 수집 시 LT 디코딩으로 원본 복원 4. UR 파싱 → CBOR 디코딩 → 원본 데이터 추출 5. payload_hash로 무결성 검증


5. USB-C 통신 프로토콜 명세

D'CENT X 콜드월렛은 오프라인 서명 앱(태블릿/PC)과 USB-C로 통신한다. D'CENT 기존 지문인증형 월렛의 USB 통신 프로토콜을 기반으로 엔터프라이즈 확장 커맨드를 추가한다.

참고: D'CENT X의 NFC 인터페이스는 리커버리카드 전용이며, 서명 채널로 사용하지 않는다.

5.1. USB HID 프로토콜 구조

D'CENT X는 USB HID(Human Interface Device) 클래스로 동작하여 별도 드라이버 없이 연결된다.

USB HID Report 구조:

┌──────────┬──────────┬──────┬────────┐
│ Report ID│ Cmd Type │ Len  │ Data   │
│ 1B       │ 1B       │ 2B   │ 0-60B  │
└──────────┴──────────┴──────┴────────┘

HID Report 크기: 64 bytes (USB Full-Speed HID 표준)

대용량 페이로드 전송: 64B 패킷 단위로 분할 전송. 기존 D'CENT USB 프로토콜의 청킹 메커니즘을 재사용한다.

[패킷 1] Report=01 Cmd=SIGN_REQ Len=총길이 Data=[60B]  → ACK
[패킷 2] Report=01 Cmd=CONTINUE  Len=0     Data=[60B]  → ACK
[패킷 N] Report=01 Cmd=END       Len=0     Data=[나머지] → Processing
[응답]   Report=01 Cmd=RESPONSE  Len=응답길이 Data=[...]  → Complete

5.2. D'CENT Enterprise 커맨드 정의

Cmd Type 설명 페이로드
SIGN_REQUEST 0x10 서명 요청 전달 CBOR 인코딩 SignRequest
GET_SIGNATURE 0x20 서명 결과 수신 -
POLICY_UPDATE 0x30 정책 업데이트 CBOR 인코딩 PolicyUpdate
GET_STATUS 0x40 디바이스 상태 조회 -
VERIFY_WHITELIST 0x50 화이트리스트 검증 CBOR 인코딩 WhitelistVerifyRequest

5.3. 응답 상태 코드

코드 의미 처리
0x00 성공 정상 처리
0x01 데이터 수신 중 추가 패킷 대기
0x10 사용자 거부 물리 버튼 거부
0x20 정책 위반 정책 위반 상세 포함
0x30 WYSIWYS 불일치 서명 거부됨
0x31 WYSIWYS 잠금 (24h) 잠금 해제 대기
0x40 파싱 에러 데이터 포맷 확인
0x82 보안 조건 미충족 PIN 입력 필요
0xF0 내부 오류 SE 상태 확인

5.4. USB-C vs 기존 NFC APDU 비교

항목 USB-C (현재) NFC APDU (미사용)
패킷 크기 64B HID Report 255B APDU
대용량 전송 분할 전송 (사실상 무제한) APDU 체이닝 (~4KB 한계)
연결 안정성 높음 (유선) 낮음 (접촉 유지 필요)
세션 보안 물리적 격리 (USB 케이블) ECDH + AES-128-CCM 필요
드라이버 불필요 (HID 클래스) NFC 리더 필요
속도 12 Mbps (Full-Speed) ~424 kbps

5.5. USB-C 보안 고려사항

USB-C는 물리적 유선 연결이므로 NFC의 도청/릴레이 위협이 존재하지 않는다. 다만:

  • 악성 USB 디바이스: MDM으로 오프라인 앱 태블릿에서 허용 USB 디바이스를 D'CENT X로 제한
  • USB 데이터 유출: 오프라인 앱이 USB 통신 대상을 D'CENT X Vendor ID/Product ID로 검증
  • 세션 암호화: 물리적 격리 환경에서는 선택적. 추가 보안이 필요한 경우 ECDH 세션 키 교환 적용 가능

6. 채널 구성 및 데이터 경로

6.1. 구간별 채널 정의

대시보드 ◄──── QR (UR Animated) ────► 오프라인 앱 ◄──── USB-C ────► D'CENT X
                에어갭 경계                              유선 직결
구간 채널 역할
대시보드 ↔ 오프라인 앱 QR (Animated QR / Fountain Code) 에어갭 경계. 네트워크 격리 유지.
오프라인 앱 ↔ D'CENT X USB-C (HID) 유선 직결. SE 보안 경계 (키는 SE 외부 미노출).

6.2. 데이터 경로별 채널

방향 경로 채널
대시보드 → 오프라인 앱 앱 카메라로 대시보드 QR 촬영
오프라인 앱 → D'CENT X USB-C 커맨드 전송
D'CENT X → 오프라인 앱 USB-C 응답 수신
오프라인 앱 → 대시보드 앱 화면에 QR 표시 → 대시보드 스캐너 촬영

6.3. 보안 모델

에어갭 경계는 대시보드 ↔ 오프라인 서명 유닛(앱+디바이스) 사이의 QR 채널 하나이다. 오프라인 앱과 D'CENT X는 USB-C로 직결된 하나의 오프라인 서명 유닛을 구성한다.

핵심 보안 보장: - 네트워크 격리: QR 에어갭으로 대시보드 침해가 서명 유닛에 전파 불가 - 키 격리: USB-C 연결에서도 프라이빗 키는 SE 외부로 나가지 않음 - WYSIWYS: SE가 TX를 독립 파싱하여 앱 변조를 감지


7. 에어갭 데이터 암호화 및 무결성

7.1. 무결성 보장

모든 에어갭 메시지는 payload_hash 필드를 통해 무결성을 검증한다:

payload_hash = SHA-256(header.version || header.message_type ||
                       header.request_id || payload_body)

수신측 검증: 1. 메시지 수신 → payload_body에서 SHA-256 해시 계산 2. 계산 결과와 header.payload_hash 대조 3. 불일치 시 메시지 폐기 + 에러 반환

7.2. 리플레이 방지

세션 관리:
    request_id (UUID v4) — 각 요청에 고유 ID
    SE 서명 카운터 — 단조 증가, 이전 카운터의 요청 거부

리플레이 공격 방어:
    1. request_id 중복 확인 (오프라인 앱 레벨)
    2. sign_counter 검증 (SE 레벨)
    3. 세션 타임아웃 (4시간 기본)

7.3. 채널 암호화

채널 암호화 근거
QR (대시보드↔앱) 선택적 시각적 채널이므로 숄더 서핑 외 중간자 공격 어려움. PSBT는 공개 데이터 위주. WYSIWYS 해시 비교가 무결성 보장.
USB-C (앱↔디바이스) 선택적 물리적 유선 연결이므로 도청/릴레이 위협 없음. 오프라인 환경에서는 암호화 불필요. 추가 보안 시 ECDH 세션 키 교환 적용 가능.

8. 프로토콜 테스트 벡터

8.1. BTC 단순 전송 서명 요청

시나리오: 1 BTC를 bc1q...addr로 전송하는 단일 입력 PSBT

Request (CBOR Hex):
A8                          ; map(8)
  01                        ; key: version
  01                        ; value: 1
  02                        ; key: message_type
  01                        ; value: 0x01 (BTC_SIGN_REQUEST)
  03                        ; key: request_id
  50                        ; bytes(16)
  550E8400E29B41D4A716446655440000
  06                        ; key: chain_type
  00                        ; value: 0x00 (BTC)
  08                        ; key: psbt
  59 01F4                   ; bytes(500) — PSBT 바이너리
  [PSBT binary data...]
  0D                        ; key: app_hash
  58 20                     ; bytes(32)
  [SHA-256 hash 32 bytes]
  0B                        ; key: policy_version
  19 0005                   ; value: 5
  0C                        ; key: whitelist_version
  19 000A                   ; value: 10

UR Encoding:
  ur:dcent-sign-request/[bytewords...]

Response (성공, CBOR Hex):
A5                          ; map(5)
  06                        ; key: status_code
  00                        ; value: 0x00 (SUCCESS)
  07                        ; key: signature
  A4                        ; map(4) — signature-data
    01 00                   ; chain_type: BTC
    02 58 40                ; sig_bytes: 64 bytes (Schnorr)
    [64 bytes signature]
    03 58 21                ; pubkey: 33 bytes
    [33 bytes compressed pubkey]
    04 46                   ; derivation_path: 6 bytes
    8680000080000000...
  08 58 20                  ; se_parse_hash: 32 bytes
  [32 bytes hash]
  09 19 012C                ; sign_counter: 300

8.2. EVM ERC-20 전송 서명 요청

시나리오: 1000 USDT를 0x742d...로 전송

Request (핵심 필드):
  message_type: 0x02 (EVM_SIGN_REQUEST)
  chain_type: 0x01 (EVM)
  chain_id: 1 (Ethereum)
  transaction:
    to: 0xdAC17F958D2ee523a2206206994597C13D831ec7 (USDT)
    value: 0x00 (토큰 전송이므로 ETH value = 0)
    data: 0xa9059cbb                              (transfer selector)
          000000000000000000000000742d35Cc6634...  (recipient, padded)
          00000000000000000000000000000000003B9ACA00 (amount: 1000 * 10^6)
    gas_limit: 80000
    max_fee_per_gas: 30000000000 (30 Gwei)
    nonce: 42
  token_info:
    symbol: "USDT"
    decimals: 6
    contract_address: 0xdAC17F958D2ee523a2206206994597C13D831ec7
  app_hash: [SHA-256 of parsed fields]

8.3. 정책 업데이트 메시지

시나리오: 건당 한도를 10 BTC에서 20 BTC로 변경 (Admin 3-of-5 서명)

PolicyUpdate:
  policy_data:
    version: 6 (현재 5에서 증가)
    target_policy: 0x01 (AMOUNT_LIMIT)
    previous_value: 0x00000000 3B9ACA00 (10 BTC = 10^9 sat, 32B padded)
    new_value: 0x00000000 77359400 (20 BTC = 2*10^9 sat, 32B padded)
    cooldown_seconds: 86400 (24시간)
  admin_signatures: [
    {pubkey: [33B], signature: [64B]},  ; Admin 1
    {pubkey: [33B], signature: [64B]},  ; Admin 2
    {pubkey: [33B], signature: [64B]}   ; Admin 3
  ]
  quorum_threshold: 3

SE 처리:
  1. Admin 서명 3개 검증 (공개키 매칭)
  2. version == current + 1 확인 (6 == 5+1)
  3. previous_value == 현재 SE 저장값 확인
  4. 물리 버튼 2회 확인
  5. 쿨다운 시작 (24시간 후 적용)

본 문서는 Phase 5 System Architecture Design의 일부로, Phase 3 에어갭 서명 플로우의 프로토콜 수준 상세화이다. component-data-flow.md의 인터페이스 메시지 구조를 UR/CBOR/APDU 바이트 수준으로 구체화한다. D'CENT X 기존 USB-C 통신 프로토콜을 기반으로 엔터프라이즈 확장 커맨드를 추가한다. NFC는 리커버리카드 전용으로 사용한다.


관련 문서