v0.6
PolicyUpdateBundle UR 타입 및 에어갭 정책 동기화 프로토콜¶
1. 개요¶
1.1. 설계 목적¶
본 문서는 쿼럼 승인된 정책을 에어갭을 통해 SE(Secure Element, Zone 3)에 안전하게 전달하는 PolicyUpdateBundle UR 타입과 동기화 프로토콜을 설계한다. 이를 통해 정책 변경이 하드웨어 수준에서 검증되고, 변조 불가한 방식으로 적용된다.
핵심 가치: - 정책 변경은 반드시 쿼럼 승인을 거쳐야 SE에 적용 가능 - 에어갭 원칙을 준수하여 QR/NFC를 통해서만 정책 전달 - 정책 적용의 원자성 보장: 전체 성공 또는 전체 롤백 - Phase 30 AuditSyncPayload 패턴과 Phase 27 UR 패턴을 준수
1.2. 규제 근거¶
| 요건 | 근거 | 본 문서 대응 |
|---|---|---|
| POL-02 | PolicyUpdateBundle UR 타입 정의 + 에어갭 전달 프로토콜 | CDDL 스키마 + 8단계 프로토콜 |
| CORE-09 | 정책 변경 이력 관리 | 쿼럼 승인 이력 + POLICY_CHANGE 이벤트 |
| REG-EU-01 | MiCA 거버넌스 체계 | 쿼럼 기반 정책 승인 프로세스 |
| CORE-05 | 접근 통제 (RBAC + MFA) | 정책 변경 권한 = Policy Admin + 쿼럼 |
1.3. Phase 30 연결점¶
- AuditSyncPayload (
ur:dcent-audit-sync): 정책 변경 이벤트가 CompactAuditRecord로 동기화 - SEAuditDigest.policy_hash: PolicyUpdateBundle 적용 시 갱신
- AUDIT APDU (CLA=0x80, INS=0xA0): Phase 30에서 확립한 APDU 패턴을 POLICY 클래스로 확장
2. PolicyUpdateBundle CDDL 스키마¶
2.1. UR 타입 등록¶
- UR 타입:
ur:dcent-policy-update - 네임스페이스: D'CENT Enterprise 전용 UR 타입 (Phase 27 패턴 준수)
- CBOR 인코딩: RFC 8949 준수
- UR 프레임워크: BC-UR (Blockchain Commons Uniform Resources) 기반 멀티파트 QR 지원
2.2. CDDL 정의¶
; ====================================================================
; D'CENT Enterprise PolicyUpdateBundle Schema
; UR Type: ur:dcent-policy-update
; CBOR (RFC 8949) encoded
; Phase: 31-hardware-policy-engine
; ====================================================================
PolicyUpdateBundle = {
version: uint .size 1, ; 스키마 버전 (1 byte)
bundle_id: bstr .size 16, ; UUID v7 (시간 정렬 가능)
policy_version: uint .size 4, ; 정책 버전 (단조 증가, uint32)
target_device_id: bstr .size 8, ; 대상 기기 식별자
jurisdiction_id: JurisdictionCode, ; 관할 코드
rules: [+ PolicyRule], ; 정책 규칙 배열 (1개 이상)
reg_tags: RegulationTagBitmap, ; 번들 전체의 규제 태그 (8B)
quorum_sigs: [+ QuorumSignature], ; 쿼럼 승인 서명 배열
prev_policy_hash: bstr .size 32, ; 이전 정책 해시 (연결 체인)
created_at: uint .size 4, ; 생성 시간 (epoch seconds)
bundle_sig: bstr .size 64, ; 번들 전체 서명 (대시보드)
}
JurisdictionCode = &(
jurisdiction-global: 0, ; Core만 적용 (관할 미지정)
jurisdiction-kr: 1, ; 한국
jurisdiction-eu: 2, ; EU
jurisdiction-jp: 3, ; 일본
jurisdiction-sg: 4, ; 싱가포르
jurisdiction-us: 5, ; 미국 (예약)
jurisdiction-multi: 255, ; 멀티 관할 (병합)
)
PolicyRule = {
rule_id: uint .size 2, ; 규칙 식별자
rule_type: PolicyRuleType, ; 규칙 유형
parameters: PolicyRuleParameters, ; 규칙 파라미터
reg_tags: RegulationTagBitmap, ; 이 규칙이 충족하는 규제 태그
enabled: bool, ; 활성/비활성
? description: tstr, ; 규칙 설명 (선택, SE에는 미전달)
}
PolicyRuleType = &(
rule-amount-limit: 1, ; 금액 한도
rule-time-window: 2, ; 시간 제한
rule-quorum: 3, ; 쿼럼 요건
rule-cold-ratio: 4, ; 콜드 보관 비율
rule-whitelist: 5, ; 화이트리스트 주소
rule-audit-enforce: 6, ; 감사 강제
rule-role-restrict: 7, ; 역할 기반 제한
rule-velocity: 8, ; 거래 속도 제한
rule-retention: 9, ; 보존 기간
rule-jurisdiction: 10, ; 관할 특화 규칙
)
PolicyRuleParameters = {
? amount_limit: uint, ; 단건 금액 한도 (satoshi)
? daily_limit: uint, ; 일일 누적 한도 (satoshi)
? time_start: uint .size 2, ; 시작 시간 (HHMM)
? time_end: uint .size 2, ; 종료 시간 (HHMM)
? quorum_m: uint .size 1, ; M-of-N 중 M
? quorum_n: uint .size 1, ; M-of-N 중 N
? cold_ratio: uint .size 1, ; 콜드 비율 % (0~100)
? whitelist: [* bstr], ; 주소 해시 목록
? retention_days: uint .size 4, ; 보존 기간 (일)
? velocity_count: uint .size 2, ; 시간당 최대 거래 수
? velocity_window: uint .size 4, ; 속도 제한 윈도우 (초)
}
QuorumSignature = {
signer_device_id: bstr .size 8, ; 서명자 기기 ID
signer_role: RBACRole, ; 서명자 역할
signature: bstr .size 64, ; ECDSA P-256 서명
signed_at: uint .size 4, ; 서명 시간 (epoch seconds)
}
; RegulationTagBitmap: regulatory-tag-bitmap.md에서 정의
RegulationTagBitmap = bstr .size 8
; RBACRole: audit-log-record-schema.md에서 정의
RBACRole = &(
role-org-owner: 1,
role-vault-admin: 2,
role-policy-admin: 3,
role-tx-initiator: 4,
role-approver: 5,
role-viewer: 6,
)
2.3. 크기 추정¶
| 필드 | 크기 | 비고 |
|---|---|---|
| version | 1B | 고정 |
| bundle_id | 16B | UUID v7 |
| policy_version | 4B | uint32 |
| target_device_id | 8B | 기기 ID |
| jurisdiction_id | 1B | 열거형 |
| rules (10개 기준) | ~800B | ~80B/rule |
| reg_tags | 8B | 비트맵 |
| quorum_sigs (3개 기준) | ~240B | ~80B/sig |
| prev_policy_hash | 32B | SHA-256 |
| created_at | 4B | epoch |
| bundle_sig | 64B | ECDSA P-256 |
| CBOR 오버헤드 | ~50B | 맵/배열 헤더 |
| 합계 (10규칙, 3서명) | ~1,228B | ~1.2KB |
| 최대 (20규칙, 5서명) | ~2,500B | ~2.5KB |
QR v40 단일 프레임(~4,296B)으로 전송 가능. NFC 32KB 세션 한도 내에도 충분한 여유.
3. 쿼럼 승인 프로토콜 흐름 (8단계)¶
3.1. 전체 흐름도¶
Zone 1 (대시보드) Zone 2 (오프라인 앱) Zone 3 (SE)
────────────────── ────────────────── ──────────────
Step 1: Policy Admin이
정책 변경 초안 생성
───────────┐
v
Step 2: 쿼럼 서명 수집
(N-of-M 개인 서명 기기)
───────────┐
v
Step 3: PolicyUpdateBundle 조립
(quorum_sigs 포함)
───────────┐
v
Step 4: QR 코드 생성 ─────────> QR 스캔 수신
(ur:dcent-policy-update) │
v
Step 5: 쿼럼 서명 1차 검증
policy_version 확인
│
v
Step 6: ──────────> APPLY_POLICY APDU
쿼럼 서명 2차 검증
policy_version 단조 증가 확인
정책 적용
───────────┐
v
Step 7: <────────── policy_hash 갱신
POLICY_CHANGE AuditRecord
갱신된 SEAuditDigest 반환
│
v
Step 8: POLICY_CHANGE 이벤트
AuditSyncPayload에 포함
──────────> 다음 동기화 시 전달
3.2. 각 단계 상세¶
Step 1: 정책 변경 초안 생성 - 주체: Policy Admin (role-policy-admin, RBAC v2) - 동작: 대시보드에서 규칙 추가/수정/삭제, 관할 선택, 파라미터 입력 - 산출물: PolicyUpdateBundle 초안 (quorum_sigs 미포함) - 검증: 초안의 규칙 충돌 검사, 규제 태그 자동 산정
Step 2: 쿼럼 서명 수집
- 주체: 쿼럼 요건에 따른 서명자들 (개인 서명 기기 사용)
- 동작: 각 서명자가 정책 변경 내용을 검토한 후 개인 서명 기기로 서명
- 쿼럼 요건: 기본 2-of-3, 관할에 따라 3-of-5까지 설정 가능
- 서명 대상: SHA-256(version || bundle_id || policy_version || rules || reg_tags || prev_policy_hash)
- 산출물: QuorumSignature 배열
Step 3: PolicyUpdateBundle 조립
- 주체: 대시보드 서버
- 동작: 초안 + 쿼럼 서명 → 완전한 PolicyUpdateBundle 조립
- bundle_sig: 대시보드 서버 키로 전체 번들 서명 (무결성 보장)
- prev_policy_hash: SE에 현재 적용된 정책의 해시 (체인 연결)
- 산출물: 완성된 PolicyUpdateBundle CBOR 바이너리
Step 4: QR/NFC 전달
- 주체: 대시보드 → 오프라인 앱
- 채널: QR 코드 (기본), NFC (대안)
- UR 타입: ur:dcent-policy-update
- QR: ~1.2KB 기준 QR v40 단일 프레임 전송
- NFC: ISO 14443 기반 전송
- 에어갭 원칙: 네트워크 연결 절대 불가
Step 5: 오프라인 앱 1차 검증
- 주체: 오프라인 앱 (Zone 2)
- 검증 항목:
1. bundle_sig 서명 검증 (대시보드 공개키)
2. quorum_sigs 각 서명 검증 (서명자 공개키)
3. 쿼럼 요건 충족 확인 (M-of-N)
4. policy_version > 로컬 캐시의 현재 버전 확인
5. prev_policy_hash == 로컬 캐시의 현재 policy_hash 확인
6. target_device_id == 현재 연결된 SE의 device_id 확인
- 검증 실패 시: 사용자에게 거부 사유 표시, APDU 전송하지 않음
Step 6: SE 2차 검증 및 정책 적용
- 주체: SE (Zone 3)
- APDU: APPLY_POLICY (3.3절 상세)
- SE 내부 검증:
1. 쿼럼 서명 재검증 (SE 내 공개키로 독립 검증)
2. policy_version > SE 내부 저장 policy_version 확인 (단조 증가)
3. prev_policy_hash == SE 내부 policy_hash 확인 (체인 연속성)
4. 규칙 충돌 검사 (상호 모순 규칙 없음)
5. NVM 크기 제한 확인
- 적용: 모든 검증 통과 시 원자적 정책 교체
Step 7: 정책 적용 완료 및 다이제스트 갱신
- SE 내부 처리:
1. policy_hash = SHA-256(PolicyUpdateBundle의 rules + reg_tags)
2. policy_version = 수신된 policy_version
3. active_jurisdictions 갱신 (reserved[0:2])
4. reg_tag_version 확인/갱신 (reserved[2])
5. digest_sig 재계산
6. POLICY_CHANGE AuditRecord 생성 (PolicyChangePayload 포함)
- 응답: 갱신된 SEAuditDigest + 적용 결과
Step 8: 감사 동기화 - 주체: 오프라인 앱 → 대시보드 - POLICY_CHANGE 이벤트가 다음 AuditSyncPayload에 CompactAuditRecord로 포함 - 대시보드에서 정책 변경 이력 기록 및 PolicyVersionRecord 추가 (Plan 02)
3.3. APPLY_POLICY APDU 명세¶
3.3.1. APPLY_POLICY (정책 적용)¶
Command APDU:
CLA: 0x80
INS: 0xB0 ; POLICY 클래스
P1: 0x01 ; APPLY 서브커맨드
P2: 0x00 ; 예약
Lc: <data length>
Data: PolicyUpdateBundle의 핵심 필드:
- policy_version (4B)
- rules (가변)
- reg_tags (8B)
- quorum_sigs (가변)
- prev_policy_hash (32B)
Le: 0x80 (128 bytes 응답 요청)
Response APDU:
Data: 갱신된 SEAuditDigest 전체 (128 bytes)
SW1-SW2:
0x9000: 성공 (정책 적용 완료)
0x6982: 인증 필요 (PIN/서명 세션 미인증)
0x6A80: 잘못된 데이터 (파싱 오류)
0x6A82: 쿼럼 부족 (서명 수 < M)
0x6A83: 버전 역행 (policy_version <= 현재 버전)
0x6A84: 크기 초과 (NVM 용량 초과)
0x6A85: 규칙 충돌 (상호 모순 규칙 감지)
0x6A86: 체인 불일치 (prev_policy_hash != 현재 policy_hash)
0x6985: 대상 불일치 (target_device_id != SE device_id)
접근 제어: PIN 인증 + Policy Admin 역할 확인 필수. Org Owner는 긴급 정책에 한해 단독 적용 가능.
3.3.2. READ_POLICY (정책 읽기)¶
Command APDU:
CLA: 0x80
INS: 0xB0 ; POLICY 클래스
P1: 0x02 ; READ 서브커맨드
P2: <read_mode>
0x00: 정책 해시만 (32B)
0x01: 정책 버전 + 해시 (36B)
0x02: 전체 정책 규칙 덤프 (가변)
0x03: 규제 태그 비트맵만 (8B)
Lc: 0x00
Le: <expected length>
Response APDU:
Data: P2에 따른 응답 데이터
P2=0x00: policy_hash (32B)
P2=0x01: policy_version(4B) + policy_hash(32B) = 36B
P2=0x02: 직렬화된 규칙 배열 (가변, 최대 NVM 규칙 크기)
P2=0x03: RegulationTagBitmap (8B)
SW1-SW2:
0x9000: 성공
0x6982: 인증 필요
0x6A86: 정책 미설정 (초기 상태)
접근 제어: PIN 인증 후 접근 가능. 모든 RBAC 역할에서 읽기 가능.
3.3.3. APDU 커맨드 요약¶
| INS | P1 | 명칭 | 동작 | 접근 제어 |
|---|---|---|---|---|
| 0xA0 | 0x01 | UPDATE_AUDIT_DIGEST | 감사 다이제스트 갱신 (Phase 30) | PIN |
| 0xA0 | 0x02 | READ_AUDIT_DIGEST | 감사 다이제스트 읽기 (Phase 30) | PIN |
| 0xB0 | 0x01 | APPLY_POLICY | 정책 적용 | PIN + Policy Admin/Org Owner |
| 0xB0 | 0x02 | READ_POLICY | 정책 읽기 | PIN |
CLA=0x80으로 통일. INS=0xA0은 AUDIT 클래스(Phase 30), INS=0xB0은 POLICY 클래스(Phase 31).
4. 보안 고려사항¶
4.1. 정책 다운그레이드 공격 방지¶
| 공격 벡터 | 방어 메커니즘 | 에러 코드 |
|---|---|---|
| 이전 버전 PolicyUpdateBundle 재전송 | policy_version 단조 증가 강제 (SE NVM) |
0x6A83 |
| 중간자가 정책 변조 | bundle_sig + quorum_sigs 이중 서명 검증 |
0x6A82 |
| 다른 기기의 정책 번들 전달 | target_device_id 확인 |
0x6985 |
| 체인 분기 (중간 정책 건너뛰기) | prev_policy_hash 체인 연속성 확인 |
0x6A86 |
| NVM 과부하 공격 | 규칙 수 상한 + NVM 크기 사전 검사 | 0x6A84 |
4.2. 쿼럼 서명 이중 검증¶
정책 적용의 신뢰성을 보장하기 위해 쿼럼 서명을 두 지점에서 독립 검증한다:
| 검증 지점 | 주체 | 공개키 출처 | 실패 시 동작 |
|---|---|---|---|
| 1차 검증 | 오프라인 앱 (Zone 2) | 로컬 캐시된 공개키 | 사용자에게 거부 표시, APDU 미전송 |
| 2차 검증 | SE (Zone 3) | SE NVM 저장 공개키 | 0x6A82 에러 반환 |
이중 검증의 가치: 오프라인 앱이 해킹되어 1차 검증을 우회하더라도, SE의 2차 검증에서 차단된다.
4.3. 정책 적용 원자성¶
APPLY_POLICY 트랜잭션 모델:
1. SE가 모든 검증 수행 (쿼럼, 버전, 체인, 충돌, 크기)
2. 검증 통과 시:
a. NVM 임시 영역에 새 규칙 기록
b. 모든 규칙 기록 성공 확인
c. 포인터를 새 규칙으로 전환 (atomic swap)
d. policy_hash 갱신
e. POLICY_CHANGE AuditRecord 생성
3. 어느 단계에서든 실패 시:
a. 임시 영역 폐기
b. 기존 정책 유지
c. 에러 코드 반환
결과: 부분 적용 상태는 존재하지 않음. 전체 성공 또는 전체 롤백.
4.4. 긴급 정책 (Emergency Override)¶
특수 상황에서 쿼럼 수집이 불가능할 때를 위한 긴급 정책 메커니즘:
| 항목 | 상세 |
|---|---|
| 적용 조건 | Org Owner 단독 서명으로 즉시 적용 |
| 허용 범위 | 제한적: 서명 동결(freeze), 한도 축소만 가능. 한도 확대, 쿼럼 변경 불가 |
| 감사 추적 | POLICY_OVERRIDE AuditRecord 강제 생성 (일반 POLICY_CHANGE와 별도 추적) |
| 유효 기간 | 72시간 자동 만료. 만료 전 정상 쿼럼 승인 정책으로 교체 필요 |
| policy_version | 정상 버전 체인과 동일 단조 증가 (별도 채널 아님) |
| 규제 태그 | CORE-05(접근 통제) + CORE-06(리스크 관리) 태그 자동 부여 |
; 긴급 정책 확장 필드
EmergencyOverride = {
emergency: bool, ; true = 긴급 정책
expires_at: uint .size 4, ; 만료 시간 (epoch, 72시간 이내)
override_scope: OverrideScope, ; 허용 범위
reason: tstr, ; 긴급 사유 (감사 기록용)
}
OverrideScope = &(
scope-freeze: 1, ; 전체 서명 동결
scope-limit-reduce: 2, ; 한도 축소만
scope-whitelist-lock: 3, ; 화이트리스트 고정 (추가/삭제 불가)
)
5. QR/NFC 전송 최적화¶
5.1. Phase 27 UR 멀티파트 QR 패턴 준수¶
| 항목 | 사양 | PolicyUpdateBundle 적합성 |
|---|---|---|
| UR 인코딩 | BC-UR (Blockchain Commons) | ur:dcent-policy-update 등록 |
| CBOR 직렬화 | RFC 8949 | PolicyUpdateBundle 전체 CBOR 인코딩 |
| QR 바이너리 | ISO 18004, 오류 정정 L | v40 기준 ~4,296B 수용 |
| 멀티파트 | BC-UR 멀티파트 프레임 | 번들 > 4KB 시 자동 분할 (미예상) |
5.2. 채널별 전송 시나리오¶
| 시나리오 | 번들 크기 | 채널 | 프레임 수 | 전송 시간 |
|---|---|---|---|---|
| 기본 정책 (10규칙, 3서명) | ~1.2KB | QR v40 | 1프레임 | ~2초 (스캔) |
| 대규모 정책 (20규칙, 5서명) | ~2.5KB | QR v40 | 1프레임 | ~3초 (스캔) |
| 최대 정책 (30규칙, 7서명) | ~3.8KB | QR v40 | 1프레임 | ~4초 (스캔) |
| NFC 전송 | ~1.2KB | NFC | N/A | ~1초 |
모든 실용적 시나리오에서 QR 단일 프레임 전송이 가능하다.
5.3. PolicyUpdateBundle 전용 최적화¶
Phase 27에서 확립한 MuSig2 QR 최적화 원칙을 정책 번들에도 적용:
- CBOR 정수 키: 필드명 대신 정수 키 사용으로 ~30% 크기 절감
- description 필드 제외: SE 전달 시
description필드는 오프라인 앱에서 제거 (표시 전용) - 서명 압축: QuorumSignature에서
signed_at은 오프라인 앱 검증용만. SE 전달 시 제외 가능
6. Phase 30 연동점¶
6.1. POLICY_CHANGE AuditRecord 연동¶
PolicyUpdateBundle 적용 시 Phase 30에서 정의한 AuditRecord가 생성된다:
AuditRecord (POLICY_CHANGE) = {
record_id: UUID v7,
event_type: POLICY_CHANGE (8),
timestamp: 적용 시점,
actor: {role: policy-admin, device_id: SE ID},
zone: zone-se (3),
result: result-success (1) 또는 result-failure (2),
prev_hash: 이전 AuditRecord 해시,
payload: PolicyChangePayload {
change_type: 정상 (1) 또는 긴급 (2),
policy_version: 적용된 버전,
prev_policy_hash: 이전 정책 해시,
new_policy_hash: 신규 정책 해시,
changed_rules: 변경된 규칙 목록,
reg_tags_before: 변경 전 규제 태그,
reg_tags_after: 변경 후 규제 태그,
jurisdictions_before: 변경 전 관할,
jurisdictions_after: 변경 후 관할,
},
signature: SE 서명,
}
6.2. AuditSyncPayload 포함¶
정책 변경 이벤트는 Phase 30의 AuditSyncPayload(ur:dcent-audit-sync)에 CompactAuditRecord로 포함되어 대시보드에 전달된다:
- 동기화 시점: 다음 정기 동기화 또는 긴급 동기화(
sync-emergency) - 긴급 정책 적용 시:
sync-emergency유형으로 즉시 동기화 권장 - 대시보드: POLICY_CHANGE 레코드 수신 시 PolicyVersionRecord 추가 (Plan 02)
6.3. SEAuditDigest 갱신¶
PolicyUpdateBundle 적용 후 SEAuditDigest의 다음 필드가 갱신된다:
| 필드 | 갱신 내용 |
|---|---|
policy_hash |
SHA-256(rules + reg_tags) |
timestamp |
적용 시점의 epoch seconds |
active_jurisdictions (reserved[0:2]) |
번들의 jurisdiction_id에 따라 갱신 |
reg_tag_version (reserved[2]) |
태그 스키마 변경 시 갱신 |
digest_sig |
전체 다이제스트 재서명 |
7. 설계 제약 및 확장점¶
7.1. SE NVM 규칙 저장 한도¶
| 항목 | 제한 | 근거 |
|---|---|---|
| 최대 규칙 수 | 30개 | NVM ~632B 정책 테이블 / ~21B(최소)/rule |
| 최대 쿼럼 서명자 | 7명 | 관리 복잡성 + SE 검증 시간 |
| 최대 화이트리스트 | 50주소 | NVM 별도 영역 할당 (~1.6KB) |
| 긴급 정책 유효기간 | 72시간 | REG-US-04 보고 시한 기준 |
7.2. Phase 32 확장점¶
- Compliance-as-Code 매핑 엔진: PolicyUpdateBundle의
reg_tags를 입력으로 컴플라이언스 체크리스트 자동 생성 - AML/KYT 연동 트리거: 정책 변경 시 AML/KYT 규칙 동기화 (Zone 1에서만 처리)
- 보고서 포맷 변환: POLICY_CHANGE 이벤트를 OCSF/ESMA JSON 포맷으로 변환
Phase: 31-hardware-policy-engine, Plan: 01 Version: 1.0 UR 타입: ur:dcent-policy-update APDU 클래스: CLA=0x80, INS=0xB0 (POLICY) 번들 크기: 기본 ~1.2KB, 최대 ~3.8KB (QR v40 1프레임)
관련 문서¶
- 규제 관할별 사전 구성 정책 프로파일 설계 -- 펌웨어 요구사항
- SE 정책 버전 관리 및 정책 변경 이력 해시 체인 설계 -- 펌웨어 요구사항
- 규제 태그 비트맵 CDDL 스키마 및 SE 불변 규칙 매핑 -- 펌웨어 요구사항