跳到主要内容

UR 与 CBOR 入门

除纯地址和 PSBT 文本外,BitTool Signer 读写的每个二维码都是 Uniform Resource(BCR-2020-005)。你无需自己实现 UR(请使用现成的库),但了解其分层结构会让调试容易得多。

从 CBOR 到二维码文本​

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

该文本不区分大小写。生成二维码之前请将其转为大写:这样字符就能使用二维码的字母数字模式,其密度远高于字节模式。

多部分(动态)UR​

当载荷太大、一个二维码装不下时,编码器会将其拆分为等长的分片,并生成多个部分:

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 个部分。
  • 分片大小需要在二维码密度和帧数之间权衡。BitTool Signer 的钱包级配对使用 400 字节,签名响应使用 180 字节。

钱包级配对二维码的第 1 帧:

ur:bytes,第 1 部分,共 4 部分
UR:BYTES/1-4/LPADAACFAMCECYJYHYKIWKHKADLTHKAMCFOLIEJNJLIEIHIEIYKPJZJZIOJTIHJYKTJLJPJEIOJNHSINJTJTIHJYIOKOIHJPJKINJLJTADISHSIAIAJLKPJTJYJKLTONIEKSJOKPIDKSJLKSJOKPIDENFXHSJYHGIEINHTINJLIEJNGOIHGHFYJOETGSGHECJLJPETJTJNIDGRGLIAKPKKKOKNEMHGKKJEJKHFFGJEGRFWEEGMFDKTFXFYEOHDKKKPKOGDFEIDKOJSFPGYHKEOJPFPGDJKISHGIAGTGSJLGDEYIYGTFGGTGRFDGDGEEEHTIHHTHDHKHFGOISGSKOEHHFGTJPIMGDFXEMGDHGENHFIHIAISHSINJTIAIDJYIAIYIEIHJPINKOIHOEIYIAISHSJTIOIHIAEHDLDRIOJPIHIAIHINKOIHIADYDLDRIYJKIAJPINJOJYIYJOEYKTJOJEISJEHSIAIAJLKPJTJYGDHSJYISJEJNDLETEEDIDLDYDIDLDYDIONIEKSJOKPIDKSJLKSJOKPIDENFWIOFWIOJKIHJKJOHGKOFEGMFGEOGSFDGYKPENFXJTJSIEKOIYFEKOJYGTIAGYIMHKJPIAGMKNKSECEOGYGEIMGUKSHSJPIMEYHSIYHKHGIAGSJYIHJLFLHFJEKKEMFYEOGOGRFYGDESGYKKJPGSJOJPGYEOHFFXFEFXJLHKEEESKKIYIEFYFEFDFLFXJYGTGTIMESEYJOGMIHGOJKGYIHIAISHSINJTIAIDJYIAIYIEIHJPINKOIHOEIYIAISHSJTIOIHUYSWLYTA

这些载荷中的 CBOR​

只会出现 CBOR 的一个小子集:

主类型用途
0 unsigned int(无符号整数)map 键、数据类型、链 ID、指纹、路径索引
2 byte string(字节串)签名数据、密钥、链码、签名、PSBT
3 text string(文本串)origin、名称、配对 map 的键和值
4 array(数组)路径分量、描述符列表、配对账户
5 map所有请求、响应和密钥结构
6 tag(标签)带类型的数据项(见下文)
7 simple(简单值)true / false(is-private、硬化标志)

请求必须使用定长编码,不能有重复的 map 键,也不能有尾随字节。浮点数、负整数和不定长数据项在这些结构中都不应出现。

标签​

标签名称包裹的内容
37uuid请求 ID 字节
303crypto-hdkeyHD 密钥 map
304crypto-keypath路径 map {1: components, 2: source-fingerprint, 3: depth}
305crypto-coin-info{1: coin type, 2: network}
400script-hashsh(…)
401witness-script-hashwsh(…)
403public-key-hashpkh(…)
404witness-public-key-hashwpkh(…)
409taproottr(…)

区分字节串和文本很重要:请求 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

库​

语言URCBOR
JavaScript / TypeScript@ngraveio/bc-ur;Keystone 的 @keystonehq/bc-ur-registry* 提供带类型的类cborg
SwiftURKitURKit 的 DCBOR、SwiftCBOR
Kotlin / JavaHummingbirdHummingbird、com.upokecenter:cbor
Rusturminicbor、ciborium