Unified Address (ZIP-316) 검증
이 문서는 학습 가이드이며, 패키지형 디코더나 복사-붙여넣기용 결제 라이브러리가 아닙니다. 유지 관리되는 라이브러리가 내부적으로 수행하는 작업을 이해할 수 있도록 Unified Address의 구조를 설명합니다. 실제 자금을 처리하는 모든 작업에는 ZIP-316 명세 및 아래에 연결된 공식 구현을 따르세요.
전체 개요
Unified Address(UA)는 여러 수신자 유형, 즉 Transparent, Sapling, Orchard 또는 그 조합을 담는 단일 주소 문자열입니다. 결제 지갑은 지원하는 수신자 풀 중 가장 적합한 것을 자동으로 선택합니다.
UA를 여러 장의 라벨이 붙은 카드를 담은 봉인된 봉투라고 생각해 보세요. 각 카드는 여러분에게 도달하는 서로 다른 방법을 나타냅니다. 주소를 확인하려면 애플리케이션은 다음을 수행해야 합니다.
- 봉투 열기: 텍스트 문자열을 디코딩합니다.
- 내용물 다시 정렬하기: 보호용 뒤섞기(F4Jumble)를 되돌립니다.
- 각 카드 읽기: 개별 수신자를 추출합니다.
- 프로토콜 규칙 적용: typecode 범위에 따라 항목을 무시하거나 거부합니다.
Bech32m만 “디코딩”하는 것으로는 충분하지 않은 이유
UA는 Bech32m 텍스트 인코딩을 사용하지만, Bech32m만 디코딩해서는 사용할 수 있는 수신자를 알 수 없습니다.
ZIP-316은 인코딩 전에 F4Jumble을 사용해 페이로드를 의도적으로 뒤섞습니다. F4Jumble은 주소의 문자 하나만 변경해도 디코딩 결과 전체가 달라지도록 합니다. 이는 주소의 접두사와 접미사는 유효해 보이도록 남겨 둔 채 공격자가 주소 중간의 바이트를 바꾸는 주소 가변성 공격을 방지합니다.
핵심 규칙: 가변성 보호는 애플리케이션이 전체 디코딩 및 검증 파이프라인을 실행할 때만 작동합니다. 부분 디코딩은 안전성을 제거하면서 모든 위험은 그대로 남깁니다.
단계별 디코딩 파이프라인
1단계: Bech32m 디코딩 및 네트워크 확인
- 사람이 읽을 수 있는 부분(HRP):
u은 메인넷을 나타내고, utest은 테스트넷을 나타냅니다. (메인넷 UA는 u1로 시작하며, 여기서 1은 Bech32 구분자입니다.)
- 길이 제한: 표준 Bech32m은 90자 제한을 적용합니다. UA는 일반적으로 이 제한을 초과하므로 디코더에서 표준 길이 검사를 비활성화해야 합니다.
- 5비트 Bech32m 워드를 다시 표준 8비트 바이트로 변환합니다.
2단계: F4Jumble 역변환
F4Jumble은 BLAKE2b 기반의 4라운드 Feistel 네트워크입니다.
- 왼쪽 절반 길이:
min(64, floor(length / 2))바이트입니다. 64바이트 상한은 BLAKE2b의 최대 출력 크기에 해당합니다. 오른쪽 절반에는 나머지 페이로드가 포함됩니다.
- 해시 함수: 고정된 개인화 레이블(
UA_F4Jumble_G 및 UA_F4Jumble_H)을 사용하여 G와 H를 번갈아 적용합니다.
- 라운드 순서: 순방향 인코딩은 G(0) → H(0) → G(1) → H(1) 순으로 실행됩니다. 역방향 처리(뒤섞기 해제)는 H(1) → G(1) → H(0) → G(0) 순으로 실행됩니다.
- 범위 검사: ZIP-316 페이로드 크기 제한을 벗어난 입력은 거부합니다.
3단계: 패딩 제거 및 HRP 확인
뒤섞기 전에 인코더는 HRP가 포함되고 0으로 패딩된 16바이트를 추가합니다.
- 뒤섞기를 해제한 후 마지막 16바이트를 제거합니다.
- 포함된 HRP가 예상 네트워크(
u 또는 utest)와 일치하는지 확인합니다. 이렇게 하면 테스트넷 주소가 메인넷에서 실수로 허용되는 것을 방지합니다.
4단계: 수신자 추출
남은 페이로드는 (typecode, length, content) 항목으로 구성되며, typecode와 길이는 compact-size 정수(작은 값의 경우 단일 바이트)로 저장됩니다. 알려진 수신자 typecode는 다음과 같습니다.
| Typecode | 수신자 유형 | 콘텐츠 길이 |
|---|
0x00 | Transparent (P2PKH) | 20바이트 |
0x01 | Transparent (P2SH) | 20바이트 |
0x02 | Sapling | 43바이트 |
0x03 | Orchard | 43바이트 |
이 외에도 ZIP-316은 향후 호환성을 위해 두 가지 추가 범위를 예약합니다.
0xC0–0xDF(MUST-understand가 아닌 메타데이터): 소비자는 이 범위에서 인식하지 못하는 메타데이터 항목을 무시해야 합니다.
0xE0 및 0xE1(할당된 MUST-understand 만료 메타데이터): 현재 ZIP-316 레지스트리는 이를 주소 만료 높이와 시간에 할당합니다. 소비자는 이 항목을 이해하거나 주소를 거부해야 합니다.
0xE2–0xFC(할당되지 않은 MUST-understand 메타데이터): 소비자는 이 범위에서 인식하지 못하는 항목을 발견하면 주소를 거부해야 합니다.
알려진 수신자 유형의 경우 인코딩된 길이가 해당 유형에 지정된 콘텐츠 길이와 일치하는지 확인합니다. 메타데이터 항목의 경우 인코딩된 compact-size 길이를 사용하여 콘텐츠 길이를 결정합니다. 잘린 항목이나 후행 바이트는 거부합니다.
선호 수신자 순서. 주소 파싱에 성공하면 지갑 또는 결제 도구는 다음 순서로 가장 적합한 수신자를 선택해야 합니다. Orchard, 그다음 Sapling, 그다음 transparent입니다.
필수 ZIP-316 거부 규칙
성공적으로 디코딩되었다고 해서 주소가 유효한 것은 아닙니다. 공식 Zcash 지갑은 다음 규칙을 위반하는 주소를 엄격히 거부합니다. 웹 도구도 결제 실패를 방지하기 위해 이를 거부해야 합니다.
- 차폐 수신자 누락: 주소에는 최소 하나의 Sapling 또는 Orchard 수신자가 반드시 있어야 합니다. transparent 수신자만 있는 UA는 ZIP-316에 따라 유효하지 않습니다.
- 중복 typecode: 각 수신자 유형은 최대 한 번만 나타날 수 있습니다.
- 정렬되지 않은 typecode: 수신자는 엄격히 오름차순 typecode 순서로 나타나야 합니다.
- 충돌하는 transparent 수신자: UA는 P2PKH 또는 P2SH 중 하나를 포함할 수 있지만, 둘 다 포함해서는 안 됩니다.
- 잘못된 형식의 항목 또는 패딩: 일치하지 않는 네트워크 접두사, 잘린 페이로드 또는 길이 불일치는 즉시 거부되어야 합니다.
- 인식되지 않은 typecode: 소비자는 MUST-understand 메타데이터 범위(
0xE0–0xFC)의 항목을 제외한 인식되지 않은 항목을 무시해야 하며, 이 범위의 항목은 인식하지 못할 경우 거부해야 합니다. 현재 레지스트리에서 0xE0 및 0xE1은 할당된 만료 유형이고, 0xE2–0xFC은 할당되지 않았습니다. 이와 별개로, Sapling 또는 Orchard 수신자 요구 사항을 포함하여 위의 필수 유효성 규칙을 충족하지 못하는 모든 주소를 거부합니다.
개발자를 위한 모범 사례
- 원시 문자열이 아닌 파싱된 수신자를 비교하세요. 동등성을 확인하기 전에 먼저 주소를 디코딩하세요.
- 자금을 처리하는 모든 작업에는 유지 관리되는 라이브러리를 사용하세요. 사용자 정의 JavaScript 디코더를 배포하는 대신 공식 Rust 크레이트(예:
zcash_address)를 WebAssembly로 컴파일하세요.
- 직접 작성한 파서에 주의하세요. 학습을 위해 작성한다면 학습 프로젝트로 취급하고, 무엇이든 신뢰하기 전에 아래의 공식 벡터로 테스트하세요.
공식 명세 및 참조 구현
용어집
| 용어 | 의미 |
|---|
| Unified Address (UA) | 여러 수신자 풀을 묶는 단일 주소 문자열입니다. |
| 수신자 | 특정 결제 목적지 유형(transparent, Sapling 또는 Orchard)입니다. |
| Bech32m | UA 문자열에 사용되는 텍스트 인코딩 방식입니다. |
| HRP | 사람이 읽을 수 있는 부분 또는 네트워크 접두사(u 또는 utest)입니다. |
| F4Jumble | 주소 무결성을 보장하는 가역적 난독화 알고리즘입니다. |
| Typecode | 페이로드에서 수신자 유형을 정의하는 각 항목의 번호입니다. |
| 가변성 | 탐지되지 않은 상태에서 주소 바이트를 무단으로 수정하는 것입니다. |
참고: Viewing Keys