UR 與 CBOR 入門
除了純地址與 PSBT 文字之外,BitTool Signer 讀取或寫出的每個 QR Code 都是 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 Code 前請將它轉為大寫:這樣字元就能使用 QR 的 英數字 模式,其密度遠高於位元組模式。
多部分(動態)UR
當酬載太大、一個 QR Code 放不下時,編碼器會將它拆分為等長的片段,並產生多個部分:
ur:<type>/<seq>-<count>/<bytewords of CBOR [seq, count, message-length, checksum, fragment]>
- 部分
1…count依序承載各片段(「純」部分)。 count之後的部分是 fountain 部分:對一組偽隨機選取的片段計算 XOR,選取依據是由seq與校驗碼衍生的種子。解碼器可以利用它們重建遺漏的片段。- 同一工作階段中的每個部分,其
count、message-length與checksum(整則訊息的 CRC-32)都相同。BitTool Signer 會拒絕混合兩個工作階段的掃描。
實務上的影響:
- 以任意順序掃描各部分;忽略重複的部分。
- 顯示時:在迴圈中持續以
nextPart()產生新的部分,而不是重播前count個部分。 - 片段大小是 QR 密度與幀數之間的取捨。BitTool Signer 在錢包層級配對使用 400 位元組,簽名回應則使用 180 位元組。
錢包層級配對 QR Code 的第 1 幀:
ur:bytes,第 1 部分(共 4 部分)
UR:BYTES/1-4/LPADAACFAMCECYJYHYKIWKHKADLTHKAMCFOLIEJNJLIEIHIEIYKPJZJZIOJTIHJYKTJLJPJEIOJNHSINJTJTIHJYIOKOIHJPJKINJLJTADISHSIAIAJLKPJTJYJKLTONIEKSJOKPIDKSJLKSJOKPIDENFXHSJYHGIEINHTINJLIEJNGOIHGHFYJOETGSGHECJLJPETJTJNIDGRGLIAKPKKKOKNEMHGKKJEJKHFFGJEGRFWEEGMFDKTFXFYEOHDKKKPKOGDFEIDKOJSFPGYHKEOJPFPGDJKISHGIAGTGSJLGDEYIYGTFGGTGRFDGDGEEEHTIHHTHDHKHFGOISGSKOEHHFGTJPIMGDFXEMGDHGENHFIHIAISHSINJTIAIDJYIAIYIEIHJPINKOIHOEIYIAISHSJTIOIHIAEHDLDRIOJPIHIAIHINKOIHIADYDLDRIYJKIAJPINJOJYIYJOEYKTJOJEISJEHSIAIAJLKPJTJYGDHSJYISJEJNDLETEEDIDLDYDIDLDYDIONIEKSJOKPIDKSJLKSJOKPIDENFWIOFWIOJKIHJKJOHGKOFEGMFGEOGSFDGYKPENFXJTJSIEKOIYFEKOJYGTIAGYIMHKJPIAGMKNKSECEOGYGEIMGUKSHSJPIMEYHSIYHKHGIAGSJYIHJLFLHFJEKKEMFYEOGOGRFYGDESGYKKJPGSJOJPGYEOHFFXFEFXJLHKEEESKKIYIEFYFEFDFLFXJYGTGTIMESEYJOGMIHGOJKGYIHIAISHSINJTIAIDJYIAIYIEIHJPINKOIHOEIYIAISHSJTIOIHUYSWLYTA
這些酬載中的 CBOR
只會出現 CBOR 的一小部分子集:
| 主要類型 | 用途 |
|---|---|
| 0 unsigned int | map 鍵、資料類型、chain ID、指紋、路徑索引 |
| 2 byte string | 簽名資料、金鑰、chain code、簽名、PSBT |
| 3 text string | origin、名稱、配對 map 的鍵與值 |
| 4 array | 路徑元件、描述元清單、配對帳戶 |
| 5 map | 所有請求、回應與金鑰結構 |
| 6 tag | 具型別的項目(見下方) |
| 7 simple | true / false(is-private、強化旗標) |
請求必須使用 確定長度,不得有重複的 map 鍵,也不得有多餘的尾端位元組。浮點數、負整數與不定長度項目不會出現在這些結構中。
標籤
| 標籤 | 名稱 | 包裝內容 |
|---|---|---|
| 37 | uuid | 請求 ID 位元組 |
| 303 | crypto-hdkey | HD 金鑰 map |
| 304 | crypto-keypath | 路徑 map {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 是標籤 37 內的位元組,而不是文字形式的 UUID;鍵 6 中的 EVM 地址是 20 個原始位元組,而不是 "0x…"。
手動解讀請求
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 |