UR과 CBOR 입문
일반 주소와 PSBT 텍스트를 제외하면, BitTool Signer가 읽거나 쓰는 모든 QR 코드는 Uniform Resource(BCR-2020-005)입니다. UR을 직접 구현할 필요는 없지만(라이브러리를 사용하세요), 계층 구조를 알면 디버깅이 훨씬 쉬워집니다.
CBOR에서 QR 텍스트까지
CBOR bytes
→ append CRC-32 (4 bytes)
→ Bytewords "minimal": each byte becomes 2 letters (first + last letter of a 4-letter word)
→ "ur:" + type + "/" + bytewords single part
예를 들어 짧은 EVM 서명 응답은 다음과 같습니다.
ur:eth-signature (단일 파트)
UR:ETH-SIGNATURE/OEADTPDAGDHECKNDDWKIFTGLJLLEBDCEDPFMGWHTADAOHDFPJNBYWYMUYLLTIMKSWDCFWKYTSPVWGYVEGMDNJNWMLSGAOLHGDPATJEGLIOTOQZFPJNQDYNMHQDESRSIHLEHHLGDRTDYLHHUREYGMWTLBMWBKLFCTIHRKJZRHCKMSLFOYADCWJNLUAY
텍스트는 대소문자를 구분하지 않습니다. QR 코드를 만들기 전에 대문자로 바꾸세요. 그러면 문자가 QR 영숫자(alphanumeric) 모드에 맞게 되어, 바이트 모드보다 훨씬 밀도가 높아집니다.
멀티파트(애니메이션) UR
페이로드가 QR 코드 하나에 담기지 않으면, 인코더는 페이로드를 같은 길이의 프래그먼트로 나누어 파트를 생성합니다.
ur:<type>/<seq>-<count>/<bytewords of CBOR [seq, count, message-length, checksum, fragment]>
- 파트
1…count는 프래그먼트를 순서대로 전달합니다("순수" 파트). count이후의 파트는 파운틴(fountain) 파트입니다.seq와 체크섬에서 파생한 시드로 의사 난수 방식으로 고른 프래그먼트들을 XOR한 값이며, 디코더는 이를 이용해 누락된 프래그먼트를 복원할 수 있습니다.count,message-length,checksum(메시지 전체의 CRC-32)은 한 세션의 모든 파트에서 같습니다. BitTool Signer는 두 세션이 섞인 스캔을 거부합니다.
실무상 의미:
- 파트는 어떤 순서로 스캔해도 되며, 반복된 파트는 무시합니다.
- 표시할 때는 처음
count개 파트를 반복 재생하지 말고, 루프에서nextPart()로 파트를 계속 생성하세요. - 프래그먼트 크기는 QR 밀도와 프레임 수 사이의 절충입니다. BitTool Signer는 지갑 레벨 페어링에 400바이트, 서명 응답에 180바이트를 사용합니다.
지갑 레벨 페어링 QR 코드의 1번 프레임:
ur:bytes, 4개 중 1번 파트
UR:BYTES/1-4/LPADAACFAMCECYJYHYKIWKHKADLTHKAMCFOLIEJNJLIEIHIEIYKPJZJZIOJTIHJYKTJLJPJEIOJNHSINJTJTIHJYIOKOIHJPJKINJLJTADISHSIAIAJLKPJTJYJKLTONIEKSJOKPIDKSJLKSJOKPIDENFXHSJYHGIEINHTINJLIEJNGOIHGHFYJOETGSGHECJLJPETJTJNIDGRGLIAKPKKKOKNEMHGKKJEJKHFFGJEGRFWEEGMFDKTFXFYEOHDKKKPKOGDFEIDKOJSFPGYHKEOJPFPGDJKISHGIAGTGSJLGDEYIYGTFGGTGRFDGDGEEEHTIHHTHDHKHFGOISGSKOEHHFGTJPIMGDFXEMGDHGENHFIHIAISHSINJTIAIDJYIAIYIEIHJPINKOIHOEIYIAISHSJTIOIHIAEHDLDRIOJPIHIAIHINKOIHIADYDLDRIYJKIAJPINJOJYIYJOEYKTJOJEISJEHSIAIAJLKPJTJYGDHSJYISJEJNDLETEEDIDLDYDIDLDYDIONIEKSJOKPIDKSJLKSJOKPIDENFWIOFWIOJKIHJKJOHGKOFEGMFGEOGSFDGYKPENFXJTJSIEKOIYFEKOJYGTIAGYIMHKJPIAGMKNKSECEOGYGEIMGUKSHSJPIMEYHSIYHKHGIAGSJYIHJLFLHFJEKKEMFYEOGOGRFYGDESGYKKJPGSJOJPGYEOHFFXFEFXJLHKEEESKKIYIEFYFEFDFLFXJYGTGTIMESEYJOGMIHGOJKGYIHIAISHSINJTIAIDJYIAIYIEIHJPINKOIHOEIYIAISHSJTIOIHUYSWLYTA
이 페이로드에 쓰이는 CBOR
CBOR의 작은 부분집합만 사용합니다.
| 메이저 타입 | 용도 |
|---|---|
| 0 unsigned int | 맵 키, 데이터 타입, 체인 ID, 지문, 경로 인덱스 |
| 2 byte string | 서명 데이터, 키, 체인 코드, 서명, PSBT |
| 3 text string | origin, 이름, 페어링 맵의 키와 값 |
| 4 array | 경로 구성 요소, 디스크립터 목록, 페어링 계정 |
| 5 map | 모든 요청, 응답, 키 구조 |
| 6 tag | 타입이 지정된 항목(아래 참고) |
| 7 simple | true / false (is-private, 강화 플래그) |
요청은 확정 길이를 사용해야 하며, 중복 맵 키와 뒤따르는 잉여 바이트가 없어야 합니다. 부동소수점, 음의 정수, 비확정 길이 항목은 이 구조 어디에도 쓰이지 않습니다.
태그
| 태그 | 이름 | 감싸는 대상 |
|---|---|---|
| 37 | uuid | 요청 ID 바이트 |
| 303 | crypto-hdkey | HD 키 맵 |
| 304 | crypto-keypath | 경로 맵 {1: components, 2: source-fingerprint, 3: depth} |
| 305 | crypto-coin-info | {1: coin type, 2: network} |
| 400 | script-hash | sh(…) |
| 401 | witness-script-hash | wsh(…) |
| 403 | public-key-hash | pkh(…) |
| 404 | witness-public-key-hash | wpkh(…) |
| 409 | taproot | tr(…) |
바이트 문자열과 텍스트의 구분은 중요합니다. 요청 ID는 UUID 텍스트가 아니라 태그 37 안의 바이트이며, 키 6의 EVM 주소는 "0x…"가 아니라 20바이트 원시 데이터입니다.
요청 직접 읽어 보기
EIP-1559 예제의 CBOR에 주석을 단 것입니다.
a7 map, 7 entries
01 d8 25 50 5f1e9b2c…5a01 1: tag 37 (uuid), 16 bytes
02 58 31 02ef010c…80c0 2: 49 bytes: 0x02 ‖ rlp(unsigned EIP-1559 tx)
03 04 3: data-type 4
04 01 4: chain-id 1
05 d9 0130 a2 5: tag 304 (crypto-keypath), map 2
01 8a 18 2c f5 18 3c f5 00 f5 00 f4 00 f4 components [44,true,60,true,0,true,0,false,0,false]
02 1a 73c5da0a source-fingerprint 0x73c5da0a
06 54 9858effd…eda94 6: 20-byte address
07 6e "BitTool Wallet" 7: origin
같은 바이트 전체
a701d825505f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0102583102ef010c843b9aca008506fc23ac0082520894000000000000000000000000000000000000dead872386f26fc1000080c00304040105d90130a2018a182cf5183cf500f500f400f4021a73c5da0a06549858effd232b4033e47d90003d41ec34ecaeda94076e426974546f6f6c2057616c6c6574
라이브러리
| 언어 | UR | CBOR |
|---|---|---|
| JavaScript / TypeScript | @ngraveio/bc-ur, 타입 클래스가 필요하면 Keystone @keystonehq/bc-ur-registry* | cborg |
| Swift | URKit | URKit의 DCBOR, SwiftCBOR |
| Kotlin / Java | Hummingbird | Hummingbird, com.upokecenter:cbor |
| Rust | ur | minicbor, ciborium |