v0.5
BTC MuSig2/Taproot 기반 2단계 서명 구현 설계
1. 설계 전제
1.1. 아키텍처 선정 결과
option-evaluation.md에서 옵션 A(오프체인 승인 + 온체인 마스터 서명 완전 분리) 가 최적안으로 선정되었다. BTC 구현은 이 결정을 전제로 한다.
개인 기기 = ECDSA secp256k1 오프체인 승인만 수행. BTC 온체인 서명(BIP-340 Schnorr)에 참여하지 않는다.
콜드월렛 = BIP-340 Schnorr 마스터 서명 전용. ApprovalBundle 검증 통과 후에만 서명을 실행한다.
키 독립성: approval_sk와 master_sk는 수학적으로 완전 독립 (공유 시드 없음, 독립 엔트로피).
1.2. 운용 모드
운용 모드
콜드월렛 수
BTC 서명 방식
MuSig2 필요 여부
단일 콜드월렛
1대
단일 Taproot key path 서명 (BIP-340)
불필요
복수 콜드월렛
2대 이상
MuSig2 N-of-N (BIP-327) 또는 Taproot Script Path M-of-N
필요
대부분의 엔터프라이즈 고객은 단일 콜드월렛 운용을 기본으로 하며, 대규모 기관은 복수 콜드월렛으로 추가 분산을 선택할 수 있다.
2. BTC 온체인 서명 구조
2.1. 단일 콜드월렛 운용
비트코인 주소 = Taproot key path (BIP-341)
P = master_pk (콜드월렛 SE의 x-only 공개 키)
주소 = bc1p + bech32m(P)
서명:
sig = Schnorr_Sign(master_sk, sighash) // BIP-340
witness = [sig] // key path spend
온체인에는 단일 Schnorr 서명만 노출된다.
MuSig2가 불필요하므로 QR 교환 문제가 근본적으로 해소된다.
Approval Verifier 통과가 SE 펌웨어 수준의 유일한 서명 전제 조건이다.
2.2. 복수 콜드월렛 운용
2.2.1. BIP-327 MuSig2 N-of-N (키 경로)
N개 콜드월렛의 공개 키: P_1, P_2, ..., P_N
MuSig2 키 집계:
L = H(P_1 || P_2 || ... || P_N) // key list hash
a_i = H(L || P_i) // key coefficient
P_agg = sum(a_i * P_i) // aggregated key
비트코인 주소 = bc1p + bech32m(P_agg)
서명 (2라운드):
Round 1: 각 콜드월렛이 nonce pair (R_i1, R_i2) 생성, 공유
Round 2: 각 콜드월렛이 partial signature s_i 생성
최종: s = sum(s_i), 검증: Schnorr_Verify(P_agg, msg, (R, s))
N-of-N만 지원: 모든 콜드월렛이 서명에 참여해야 한다.
콜드룸 직접 서명 폴백에서 사용되며, 이 경우 MuSig2 QR 최적화(Phase 27)가 적용된다.
2.2.2. Taproot Script Path M-of-N (스크립트 경로)
M-of-N 콜드월렛 구성 (예: 2-of-3):
Script Tree:
Leaf 1: OP_CHECKSIG(P_1) + OP_CHECKSIGADD(P_2) + OP_2 + OP_NUMEQUAL
Leaf 2: OP_CHECKSIG(P_1) + OP_CHECKSIGADD(P_3) + OP_2 + OP_NUMEQUAL
Leaf 3: OP_CHECKSIG(P_2) + OP_CHECKSIGADD(P_3) + OP_2 + OP_NUMEQUAL
조합 수: C(N, M) 리프
2-of-3: 3 리프
3-of-5: 10 리프
5-of-7: 21 리프 (관리 가능)
7-of-11: 330 리프 (비실용적 -> FROST 전환 필요)
M-of-N이 필요한 경우 Taproot Script Tree에 C(N,M) 조합을 배치한다.
5-of-7까지는 실용적이며, 그 이상은 FROST(BIP-445) 확정 후 전환을 권장한다.
2.3. 키 구조 요약
키
BIP 경로
용도
보관
master_sk (BTC)
m/86'/0'/account'/0/0
BIP-340 Schnorr 서명
콜드월렛 SE
approval_sk
m/45052'/0'/account'/0/index
ECDSA 오프체인 승인
개인 기기 SE
MuSig2 partial (복수)
m/86'/0'/account'/0/0 + MuSig2 context
N-of-N 부분 서명
각 콜드월렛 SE
3. 오프체인 승인 -> 온체인 서명 연결 흐름
3.1. 5단계 파이프라인 BTC 특화
(1) REQUEST (2) APPROVE (3) COLLECT
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 대시보드 │ │ 개인 기기 x M │ │ 오프라인 앱 │
│ │ │ │ │ │
│ PSBT 생성 │ │ sighash에 │ │ M개 승인 서명 │
│ (BIP-370) │ │ ECDSA 승인 │ │ 수집 → Bundle │
│ │ │ 서명 생성 │ │ │
└───────┬──────┘ └───────┬──────┘ └───────┬──────┘
│ │ │
[TB-3 QR] [TB-7 QR/NFC] [TB-4 QR/NFC]
│ │ │
▼ ▼ ▼
(4) SIGN (5) BROADCAST
┌──────────────┐ ┌──────────────┐
│ 콜드월렛 SE │ │ 대시보드 │
│ │ │ │
│ ApprovalBundle│ │ 서명된 TX를 │
│ 검증 → BIP-340│ │ 비트코인 노드 │
│ Schnorr 서명 │──────────────────────────▶│ 에 전파 │
└──────────────┘ [TB-4 → TB-3] └──────────────┘
3.2. 단계별 상세
(1) Request: PSBT 생성
항목
상세
실행 주체
Initiator (대시보드, Zone 1)
입력
출금 요청 (수신 주소, 금액, 수수료율)
출력
미서명 PSBT (BIP-370 v2)
데이터 포맷
BIP-370 PSBT: PSBT_GLOBAL_UNSIGNED_TX, 입력별 PSBT_IN_WITNESS_UTXO, PSBT_IN_TAP_BIP32_DERIVATION
QR 인코딩
UR type: crypto-psbt (CBOR 태그 310)
sighash 계산
BIP-341 Taproot sighash: SHA256(epoch || hash_type || ... || ext_flag || annex_hash)
정책 검증
대시보드 정책 엔진에서 1차 소프트 검증 (화이트리스트, 한도, 정족수 요구)
(2) Approve: M-of-N 개인 기기 승인
항목
상세
실행 주체
Approver x M (개인 기기, Zone 3+)
입력
ApprovalRequest (PSBT sighash + 메타데이터)
출력
ApprovalResponse (ECDSA 승인 서명)
전달 채널
TB-7 에어갭: QR (UR type: dcent-approval-request 45050) 또는 NFC APDU (INS 0x50)
서명 알고리즘
ECDSA secp256k1: sig = ECDSA_Sign(approval_sk, sighash)
WYSIWYS 표시
개인 기기 화면: 수신 주소, 전송 금액(BTC 단위), 수수료, 정책 요약
인증
생체(지문) 또는 PIN 인증 후에만 서명 수행
비동기 가능
각 Approver가 독립적으로 서명 (stateless, nonce 교환 불필요)
응답 포맷
UR type: dcent-approval-response (45051), QR 또는 NFC APDU (INS 0x51)
(3) Collect: ApprovalBundle 생성
항목
상세
실행 주체
Operator (오프라인 앱, Zone 2)
입력
M개 ApprovalResponse
출력
ApprovalBundle (CBOR 인코딩)
처리
(1) M개 승인 서명 수집 (비동기), (2) 형식 검증 (Approval Validator), (3) ApprovalBundle 구성
전달
TB-4 에어갭: QR (UR type: dcent-approval-bundle 45052) 또는 NFC APDU (INS 0x52)
Approval Validator 검증
서명 형식 DER 유효성, 공개 키 길이, device_id 존재 확인 (최종 암호학적 검증은 Zone 3 SE에서 수행)
(4) Sign: 콜드월렛 SE 검증 및 서명
항목
상세
실행 주체
Operator → 콜드월렛 SE (Zone 3)
입력
ApprovalBundle + 원본 PSBT
출력
서명된 PSBT (BIP-340 Schnorr witness 포함)
SE 내부 처리
(1) ApprovalBundle 디코딩, (2) M개 approval_sig 암호학적 검증, (3) M-of-N 정족수 확인, (4) PSBT sighash와 ApprovalBundle.tx_hash 일치 확인, (5) WYSIWYS 표시 + Operator 물리 확인, (6) HW 정책 엔진 4개 불변 규칙, (7) BIP-340 Schnorr 마스터 서명
WYSIWYS 표시
콜드월렛 화면: 수신 주소, 전송 금액, 수수료, 승인자 수(M/N), 정책 상태
서명 결과
PSBT에 PSBT_IN_TAP_KEY_SIG 필드 추가 (64바이트 Schnorr 서명)
응답 전달
TB-4 → TB-3: QR (UR type: crypto-psbt 310)
(5) Broadcast: 비트코인 네트워크 전파
항목
상세
실행 주체
System (대시보드, Zone 1)
입력
서명 완료 PSBT
출력
TX Hash (txid)
처리
PSBT finalizer가 witness 생성 → raw transaction 추출 → 비트코인 노드에 전파
모니터링
mempool 확인 → 블록 포함 확인 → 6-confirmation 최종 확정
4. ApprovalBundle BTC 스키마 (CBOR)
4.1. CDDL 정의
; ApprovalBundle - BTC/EVM 공통 구조
; UR type: dcent-approval-bundle (CBOR 태그 45052)
approval-bundle = {
1 : bytes .size 32 , ; tx_hash: PSBT sighash (BTC) 또는 safeTxHash (EVM)
2 : [ + approval-signature ], ; approval_signatures: M개 승인 서명 배열
3 : text , ; policy_id: 적용된 정책 ID
4 : quorum , ; quorum: 정족수 정보
? 5 : uint , ; chain_id: EVM 전용 (BTC에서는 생략)
6 : uint , ; timestamp: Bundle 생성 시간 (Unix epoch)
7 : bytes .size 16 , ; request_id: 승인 요청 고유 ID
}
approval-signature = {
1 : bytes .size 33 , ; pubkey: 승인자 공개 키 (compressed)
2 : bytes .size ( 64 .. 65 ), ; sig: ECDSA 서명 (64바이트 r||s 또는 65바이트 DER)
3 : text , ; device_id: 개인 기기 식별자
4 : uint , ; timestamp: 서명 생성 시간 (Unix epoch)
}
quorum = {
1 : uint , ; required: 필요 정족수 (M)
2 : uint , ; collected: 수집된 서명 수 (>= M)
}
4.2. BTC 특화 필드
필드
BTC 값
설명
tx_hash
BIP-341 sighash
SHA256(epoch \|\| hash_type \|\| ...) Taproot sighash 계산 결과
chain_id
생략 (optional)
BTC는 chain_id 개념 없음, EVM과 구분을 위해 필드 부재로 판별
sig
64바이트 (r || s)
ECDSA secp256k1 서명. DER 인코딩이 아닌 compact 형식 사용으로 페이로드 최소화
4.3. 예시 (CBOR 진단 표기)
45052({
1: h'a1b2c3...32bytes...sighash', ; tx_hash
2: [ ; approval_signatures
{
1: h'02abc...33bytes...pubkey', ; pubkey
2: h'3045...64bytes...sig', ; sig (r||s compact)
3: "dcent-bio-001", ; device_id
4: 1774745600, ; timestamp
},
{
1: h'03def...33bytes...pubkey',
2: h'3046...64bytes...sig',
3: "dcent-bio-002",
4: 1774745620,
},
],
3: "policy-daily-limit-v1", ; policy_id
4: { 1: 2, 2: 2 }, ; quorum: 2-of-3
6: 1774745650, ; bundle timestamp
7: h'aabbccdd...16bytes...request_id', ; request_id
})
4.4. UR 인코딩
QR 데이터: ur:dcent-approval-bundle/<cbor-bytewords>
예상 크기: ~300-500 바이트 (2-of-3 기준)
Animated QR: 단일 프레임 가능 (500B 미만)
NFC: 단일 APDU 전송 가능 (255B 제한 시 APDU chaining 사용)
5. 콜드월렛 SE Approval Verifier BTC 로직
5.1. 검증 흐름
┌─────────────────────────────────────────────────────────┐
│ Approval Verifier (콜드월렛 SE 내부) │
│ │
│ 입력: ApprovalBundle + PSBT │
│ │
│ Step 1: ApprovalBundle CBOR 디코딩 │
│ - 스키마 유효성 검증 │
│ - 필수 필드 존재 확인 │
│ │
│ Step 2: tx_hash 일치 검증 │
│ - PSBT에서 sighash 독립 계산 │
│ - ApprovalBundle.tx_hash == 계산된 sighash │
│ - 불일치 시: REJECT (ERROR_TX_HASH_MISMATCH) │
│ │
│ Step 3: 각 approval_sig 암호학적 검증 │
│ for each sig_i in approval_signatures: │
│ a. pubkey_i가 공개키 레지스트리에 등록되어 있는지 확인 │
│ - 미등록 시: REJECT (ERROR_UNREGISTERED_KEY) │
│ b. device_id_i가 활성 상태인지 확인 │
│ - 비활성 시: REJECT (ERROR_INACTIVE_DEVICE) │
│ c. ECDSA_Verify(pubkey_i, tx_hash, sig_i) == TRUE │
│ - 실패 시: REJECT (ERROR_INVALID_SIGNATURE) │
│ d. timestamp_i가 세션 타임아웃 내인지 확인 │
│ - 초과 시: REJECT (ERROR_TIMESTAMP_EXPIRED) │
│ │
│ Step 4: 정족수 검증 │
│ - count(valid_sigs) >= quorum.required │
│ - 미달 시: REJECT (ERROR_QUORUM_NOT_MET) │
│ │
│ Step 5: 정책 엔진 4개 불변 규칙 검증 │
│ Rule 1: 일일 한도 초과 여부 │
│ Rule 2: 수신 주소 화이트리스트 확인 │
│ Rule 3: 정족수 요구사항 확인 (Policy 수준) │
│ Rule 4: 시간 잠금 제한 확인 │
│ - 위반 시: REJECT (ERROR_POLICY_VIOLATION) │
│ │
│ Step 6: WYSIWYS 표시 + Operator 물리 확인 │
│ - 화면 표시: 수신 주소, 금액, 수수료, 승인 현황 │
│ - 물리 버튼 APPROVE/REJECT │
│ - REJECT 시: ABORT │
│ │
│ Step 7: BIP-340 Schnorr 마스터 서명 실행 │
│ sig = Schnorr_Sign(master_sk, sighash) │
│ PSBT에 PSBT_IN_TAP_KEY_SIG 기록 │
│ 감사 로그 기록: {request_id, approvers, timestamp, result}│
│ │
│ 출력: 서명된 PSBT + 감사 로그 엔트리 │
└─────────────────────────────────────────────────────────┘
5.2. 공개키 레지스트리
필드
크기
설명
pubkey
33 바이트
개인 기기 공개 키 (compressed)
device_id
16 바이트
기기 고유 식별자
status
1 바이트
0x01=활성, 0x02=비활성, 0x03=폐기
registered_at
4 바이트
등록 시간 (Unix epoch)
attestation_hash
32 바이트
SE attestation 체인 해시
저장 위치: 콜드월렛 SE 비휘발성 메모리
최대 용량: N=7 기기 기준 7 x 86바이트 = 602바이트 (SE 메모리 제약 내)
등록 권한: Super Admin + Admin 듀얼 서명 필요 (키 세레모니 시점)
수정 권한: Admin 단독 (기기 비활성화), Super Admin + Admin (기기 폐기/재등록)
5.3. 에러 코드
코드
이름
설명
심각도
0x01
ERROR_TX_HASH_MISMATCH
PSBT sighash와 ApprovalBundle tx_hash 불일치
Critical
0x02
ERROR_UNREGISTERED_KEY
미등록 공개키의 서명 포함
Critical
0x03
ERROR_INACTIVE_DEVICE
비활성화된 기기의 서명 포함
High
0x04
ERROR_INVALID_SIGNATURE
서명 암호학적 검증 실패
Critical
0x05
ERROR_TIMESTAMP_EXPIRED
승인 서명 세션 타임아웃 초과 (24시간)
Medium
0x06
ERROR_QUORUM_NOT_MET
정족수 미달
High
0x07
ERROR_POLICY_VIOLATION
HW 정책 엔진 규칙 위반
High
0x08
ERROR_CBOR_DECODE
ApprovalBundle CBOR 디코딩 실패
Medium
0x09
ERROR_OPERATOR_REJECT
Operator 물리 버튼 거부
Info
6. Nonce 관리 정책 (복수 콜드월렛 MuSig2 운용 시)
이 섹션은 복수 콜드월렛 간 MuSig2 서명 시에만 해당한다. 단일 콜드월렛 운용에서는 MuSig2가 불필요하므로 nonce 관리 이슈가 발생하지 않는다.
6.1. Nonce 재사용 위험성
BIP-327 MuSig2에서 동일 개인 키로 동일 nonce를 2회 이상 사용하면, 공격자가 두 서명에서 s_1 - s_2 = (c_1 - c_2) * sk 관계를 이용하여 개인 키를 추출할 수 있다 (Pitfall 1). 3회 재사용 시에는 Wagner 알고리즘 기반 동시 세션 공격으로 키 추출이 더 용이해진다.
6.2. SE 내부 Nonce 관리 규칙
규칙
구현
목적
원자적 Nonce 삭제 (Delete-on-Read)
SE가 nonce를 생성하면 비휘발성 메모리에 저장. 서명 Round 2에서 nonce를 읽으면 즉시 원자적으로 삭제. 전원 꺼짐/오류 시에도 nonce 상태가 일관성 유지
nonce 재사용 원천 차단
하드웨어 단조 카운터
SE 내부 monotonic counter가 nonce 생성마다 1 증가. 동일 카운터 값의 nonce 재생성 불가
롤백 공격 방어
24시간 세션 타임아웃
nonce 생성 후 24시간 내에 서명 완료되지 않으면 자동 폐기. 세션 타이머는 SE 내부 RTC 기준
장기간 미완료 세션의 nonce 누적 방지
결정론적 Nonce 사용 금지
결정론적 nonce(RFC 6979 방식)는 MuSig2 컨텍스트에서 상태 의존적 취약점을 생성할 수 있어 명시적으로 금지. SE TRNG 기반 랜덤 nonce만 허용
상태 기반 공격 방어
동시 세션 제한
SE가 동시에 유지할 수 있는 활성 nonce 세션을 최대 2개로 제한
Wagner 알고리즘 동시 세션 공격 표면 축소
6.3. Nonce 라이프사이클
1. GENERATE: SE TRNG → nonce (secnonce, pubnonce)
- secnonce: SE 비휘발성 메모리에 저장
- pubnonce: 외부 반환 (QR/NFC)
- counter++
2. USE: Round 2 서명 시 secnonce 읽기
- secnonce를 partial signature 계산에 사용
- 사용 즉시 비휘발성 메모리에서 원자적 삭제
- 삭제 확인 후에만 partial signature 외부 반환
3. EXPIRE: 24시간 타임아웃
- 미사용 secnonce 자동 폐기
- 세션 정리
4. ABORT: 서명 세션 취소
- secnonce 즉시 폐기
- 카운터는 롤백하지 않음
7. FROST 전환 대비 추상화 포인트
7.1. SE 서명 인터페이스 추상화
현재 MuSig2와 향후 FROST(BIP-445)가 동일한 SE 엔트리포인트를 공유할 수 있도록 "Schnorr 부분 서명" 추상화 계층을 설계한다.
┌──────────────────────────────────────┐
│ SE Signing Interface (추상화 계층) │
│ │
│ sign_partial( │
│ protocol: MUSIG2 | FROST, │
│ secnonce: bytes, │
│ session_context: bytes, │
│ msg: bytes(32) │
│ ) -> partial_sig: bytes(32) │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ MuSig2 │ │ FROST │ │
│ │ Backend │ │ Backend │ [미래] │
│ │ (BIP-327)│ │(BIP-445) │ │
│ └──────────┘ └──────────┘ │
│ │
│ 공통: secp256k1, BIP-340 호환 서명 │
│ 차이: 키 집계 방식, 라운드 구조 │
└──────────────────────────────────────┘
7.2. FROST 전환 시 최소 변경 지점
컴포넌트
MuSig2 현재
FROST 전환 시 변경
SE 서명 인터페이스
sign_partial(MUSIG2, ...)
sign_partial(FROST, ...) — protocol 파라미터만 변경
키 집계
BIP-327 KeyAgg
BIP-445 frost_keygen (DKG 필요)
Nonce 생성
MuSig2 NonceGen
FROST frost_nonce_generate
부분 서명
MuSig2 Sign
FROST frost_sign
집계
MuSig2 SigAgg
FROST frost_aggregate
변경 없음
Approval Verifier, WYSIWYS, 정책 엔진, ApprovalBundle
Approval Verifier, WYSIWYS, 정책 엔진, ApprovalBundle
핵심: 옵션 A의 2단계 분리 설계 덕분에 FROST 전환은 "콜드월렛 간 서명 프로토콜" 교체에만 영향을 미치며, 개인 기기 승인 흐름은 완전히 독립적이다.
7.3. FROST 전환 전제 조건
BIP-445가 BIP Final 상태로 승격
SE 환경에서 FROST 구현 검증 사례 확보
ChillDKG 또는 Trusted Dealer DKG의 에어갭 환경 실현 가능성 검증
기존 MuSig2 키에서 FROST 키로의 마이그레이션 프로토콜 정의
8. QR 교환 횟수 비교 (3-of-5 Approver 기준)
8.1. 현재 설계 vs 옵션 A 적용 후
구분
현재 (MuSig2 직접)
옵션 A (2단계 서명)
비고
(1) Request: 대시보드 → 앱
1회 QR
1회 QR
동일
(2) Approve: 개인 기기 승인
N/A
3명 x 2회 = 6회 QR
비동기 가능
(3) Collect: 앱 → 콜드월렛
3명 x 4회 = 12회 QR
1회 QR (ApprovalBundle)
12회 → 1회
(4) Sign: 콜드월렛 → 앱
포함
1회 QR (서명 PSBT)
단일 응답
(5) Broadcast: 앱 → 대시보드
1회 QR
1회 QR
동일
합계
14회+ QR
10회 QR
29% 절감
승인 6회 QR은 비동기이므로 콜드룸 외부에서 수행 가능하여, 콜드룸 내 QR 교환은 실질적으로 4회(Request 1 + Bundle 1 + Sign 1 + Broadcast 1)로 감소한다.
설계 기준: option-evaluation.md 옵션 A 채택 결과
참조: .planning/research/ARCHITECTURE.md 섹션 3.1, 4.1-4.5
BTC 프로토콜: BIP-327 (MuSig2), BIP-340 (Schnorr), BIP-341 (Taproot), BIP-370 (PSBTv2)
관련 문서