탭루트 스크립트 경로(script path)의 제어 블록(control block)은 첫 바이트에 출력 키의 홀짝(parity)과 리프 버전(leaf version)을 담습니다. 다음 32바이트에는 내부 x좌표 전용 키가, 그 뒤에는 선택한 리프에서 루트까지 올라가는 32바이트 머클(Merkle) 형제 해시들이 이어집니다. 검증 노드는 탭스크립트(tapscript)와 리프 버전으로 TapLeaf 해시를 만들고, 형제 해시를 정렬해 TapBranch 해시를 계산합니다. 그런 다음 내부 키와 루트로 TapTweak 출력 점을 다시 만듭니다. 이 점의 x 좌표와 홀짝이 실제 P2TR 출력 키와 맞아야 스크립트 실행으로 넘어갑니다.
증인 데이터의 마지막 항목이 제어 블록입니다
BIP341 스크립트 경로에서는 부록(annex)을 뺀 증인(witness) 데이터의 끝에서 두 번째 항목을 스크립트 s로, 마지막 항목을 제어 블록 c로 읽습니다. c의 길이는 정확히 33+32m이어야 하고, m은 0부터 128 사이입니다. 나머지 증인 항목은 탭스크립트의 초기 스택이 됩니다.
c[0]의 가장 낮은 비트는 출력 점 Q의 y 홀짝이고, c[0]&0xfe는 리프 버전입니다. 현재 탭스크립트의 리프 버전은 0xc0입니다. c[1:33]은 내부 키 P의 x 좌표이며, lift_x가 실패하면 지출 전체가 실패합니다.
제어 블록은 스크립트 내용을 감추는 열쇠가 아닙니다. 공개한 리프가 원래 출력 키 안에 들어 있었다는 짧은 머클 증명입니다.
JOBCOIN 해설

그림은 이 주제의 공통 개념을 단순화한 설명입니다. 아래 항목에서 이 글의 구체적인 조건과 예외를 함께 읽어보세요.
- 증인 데이터의 마지막 항목이 제어 블록입니다
BIP341 스크립트 경로에서는 부록(annex)을 뺀 증인(witness) 데이터의 끝에서 두 번째 항목을 스크립트 s로, 마지막 항목을 제어 블록 c로 읽습니다.
- 리프에서 루트까지 거슬러 올라갑니다
먼저 k0=hashTapLeaf(v || compact_size(len(s)) || s)를 계산합니다.
- 내부 키와 루트로 출력 키를 되살립니다
t=hashTapTweak(p || k_m)를 secp256k1 위수와 비교하고 Q=P+tG를 계산합니다.
설명을 위한 개념도 · 실제 가격·거래 내역·통계가 아닙니다.
리프에서 루트까지 거슬러 올라갑니다
먼저 k0=hashTapLeaf(v || compact_size(len(s)) || s)를 계산합니다. 스크립트 길이는 CompactSize로 인코딩되므로 원본 바이트와 길이 접두어가 정확해야 합니다. 사람이 읽는 옵코드 문자열로 다시 조립하면 원래 바이트 배열과 달라질 수 있습니다. 그래서 증인에 담긴 원본 스크립트를 사용합니다.
각 형제 해시 e_j마다 현재 값 k_j와 e_j를 사전순으로 비교해 작은 값부터 이어 붙이고 TapBranch 태그 해시를 만듭니다. 제어 블록에 왼쪽·오른쪽 방향 비트를 따로 넣지 않는 이유가 여기에 있습니다. 형제 해시를 모두 처리한 k_m이 약속된 머클 루트가 됩니다.
| 구간 | 길이 | 의미 |
|---|---|---|
| c[0] | 1바이트 | 홀짝 + 리프 버전 |
| c[1:33] | 32바이트 | 내부 x좌표 전용 키 |
| c[33:] | 32m바이트 | 머클 형제 해시 |
| 스크립트 s | 별도 증인 항목 | TapLeaf 계산 대상 |
| 초기 스택 | 앞쪽 증인 항목 | 탭스크립트 입력 |
내부 키와 루트로 출력 키를 되살립니다
t=hashTapTweak(p || k_m)를 secp256k1 위수와 비교하고 Q=P+tG를 계산합니다. t가 곡선 위수 이상이면 실패입니다. 계산한 x(Q)가 scriptPubKey의 32바이트 프로그램 q와 같고, y(Q)의 홀짝이 c[0] 비트와 같아야 합니다. 둘 중 하나라도 다르면 다른 트리의 리프입니다.
머클 트리에 리프가 하나뿐이면 m=0이라 형제 해시가 없지만, 이때 루트는 TapLeaf 해시 자체입니다. 스크립트 경로가 아예 없는 키 전용 출력과 헷갈리면 안 됩니다. 제어 블록이 33바이트라고 해서 빈 트리라는 뜻은 아니며, 리프 하나짜리 증명일 수 있습니다.
- 부록이 있는지부터 처리합니다.
- 제어 블록 길이가 33+32m인지 검사합니다.
- 리프 버전과 원본 스크립트로 TapLeaf를 계산합니다.
- 형제 해시를 사전순으로 묶어 해시합니다.
- TapTweak로 얻은 Q의 x와 홀짝을 출력과 대조합니다.
- 마지막으로 탭스크립트를 실행합니다.
증명은 다른 가지의 내용을 드러내지 않습니다
형제 해시는 선택하지 않은 스크립트의 원문을 보여 주지 않습니다. 관찰자가 아는 것은 공개된 리프, 트리 깊이, 형제 해시 약속값뿐입니다. 다만 같은 형제 해시나 내부 키를 여러 출력에서 다시 쓰면 지갑 구조를 잇는 단서가 되므로, 키와 정책을 재사용하지 않습니다.
자주 쓸 리프를 얕은 곳에 두면 제어 블록이 짧아져 증인 무게를 줄일 수 있습니다. 그렇다고 단순한 균형 트리가 늘 최선은 아닙니다. 복구 경로별 사용 확률, 공개됐을 때의 민감도, 최악의 수수료를 함께 따져 보고, 디스크립터(descriptor) 백업에 트리 구조를 남겨 둡니다.
디코더는 합의 규칙의 순서를 그대로 따라야 합니다
탐색기가 제어 블록을 보기 좋은 필드로 풀어 보여 주더라도 원본 증인 바이트와 블록 높이, 리프 버전은 따로 기록합니다. 앞으로 나올 리프 버전에는 지금의 탭스크립트와 다른 의미가 붙을 수 있으니, 0xc0 규칙을 모든 버전에 강요하지 않습니다. 알 수 없는 버전은 BIP341의 업그레이드 규칙대로 처리해야 합니다.
테스트에는 올바른 증명뿐 아니라 잘못된 길이, 무효한 내부 키, 형제 해시 순서 오류, 트윅 범위 초과, 홀짝 불일치, 스크립트 실패를 하나씩 넣습니다. 증명 재구성 성공과 스크립트 실행 성공을 참거짓 값 하나 뒤에 숨기면 장애 원인을 찾기 어렵습니다.
자주 묻는 질문
제어 블록에 머클 방향 비트가 왜 없나요?
단계마다 두 해시를 사전순으로 정렬해 TapBranch를 계산하므로, 좌우 방향을 따로 저장할 필요가 없습니다.
33바이트 제어 블록이면 스크립트 트리가 없는 건가요?
아닙니다. 형제 해시가 없는, 리프 하나짜리 스크립트 경로일 수 있습니다.
출력 키를 다시 만들면 지출이 곧바로 유효한가요?
아닙니다. 증명을 확인한 뒤 초기 스택으로 돌린 탭스크립트 실행까지 성공해야 합니다.
더 깊이 읽기
본문에서 다룬 개념과 확인 절차를 다음 글에서 이어서 살펴보세요.



