Introducción a UR y CBOR
Todos los códigos QR que BitTool Signer lee o escribe, excepto las direcciones simples y el texto PSBT, son un Uniform Resource (BCR-2020-005). No necesitas implementar UR por tu cuenta (usa una biblioteca), pero conocer las capas facilita mucho la depuración.
Del CBOR al texto del 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
Por ejemplo, una respuesta de firma EVM corta:
UR:ETH-SIGNATURE/OEADTPDAGDHECKNDDWKIFTGLJLLEBDCEDPFMGWHTADAOHDFPJNBYWYMUYLLTIMKSWDCFWKYTSPVWGYVEGMDNJNWMLSGAOLHGDPATJEGLIOTOQZFPJNQDYNMHQDESRSIHLEHHLGDRTDYLHHUREYGMWTLBMWBKLFCTIHRKJZRHCKMSLFOYADCWJNLUAY
El texto no distingue entre mayúsculas y minúsculas. Pásalo a mayúsculas antes de generar el código QR: así los caracteres caben en el modo alfanumérico de QR, que es mucho más denso que el modo byte.
UR multiparte (animados)
Cuando una carga útil es demasiado grande para un solo código QR, el codificador la divide en fragmentos de igual longitud y genera partes:
ur:<type>/<seq>-<count>/<bytewords of CBOR [seq, count, message-length, checksum, fragment]>
- Las partes
1…counttransportan los fragmentos en orden (partes «puras»). - Las partes posteriores a
countson partes fountain: el XOR de una selección pseudoaleatoria de fragmentos, elegida a partir de una semilla derivada deseqy del checksum. Un decodificador puede reconstruir con ellas los fragmentos que falten. count,message-lengthychecksum(CRC-32 de todo el mensaje) son iguales en todas las partes de una sesión. BitTool Signer rechaza un escaneo que mezcle dos sesiones.
Consecuencias prácticas:
- Escanea las partes en cualquier orden; ignora las repeticiones.
- Visualización: sigue generando partes con
nextPart()en bucle en lugar de repetir las primerascountpartes. - El tamaño de fragmento es un equilibrio entre la densidad del QR y el número de fotogramas. BitTool Signer usa 400 bytes para la vinculación a nivel de cartera y 180 bytes para sus respuestas de firma.
Fotograma 1 del código QR de vinculación a nivel de cartera:
UR:BYTES/1-4/LPADAACFAMCECYJYHYKIWKHKADLTHKAMCFOLIEJNJLIEIHIEIYKPJZJZIOJTIHJYKTJLJPJEIOJNHSINJTJTIHJYIOKOIHJPJKINJLJTADISHSIAIAJLKPJTJYJKLTONIEKSJOKPIDKSJLKSJOKPIDENFXHSJYHGIEINHTINJLIEJNGOIHGHFYJOETGSGHECJLJPETJTJNIDGRGLIAKPKKKOKNEMHGKKJEJKHFFGJEGRFWEEGMFDKTFXFYEOHDKKKPKOGDFEIDKOJSFPGYHKEOJPFPGDJKISHGIAGTGSJLGDEYIYGTFGGTGRFDGDGEEEHTIHHTHDHKHFGOISGSKOEHHFGTJPIMGDFXEMGDHGENHFIHIAISHSINJTIAIDJYIAIYIEIHJPINKOIHOEIYIAISHSJTIOIHIAEHDLDRIOJPIHIAIHINKOIHIADYDLDRIYJKIAJPINJOJYIYJOEYKTJOJEISJEHSIAIAJLKPJTJYGDHSJYISJEJNDLETEEDIDLDYDIDLDYDIONIEKSJOKPIDKSJLKSJOKPIDENFWIOFWIOJKIHJKJOHGKOFEGMFGEOGSFDGYKPENFXJTJSIEKOIYFEKOJYGTIAGYIMHKJPIAGMKNKSECEOGYGEIMGUKSHSJPIMEYHSIYHKHGIAGSJYIHJLFLHFJEKKEMFYEOGOGRFYGDESGYKKJPGSJOJPGYEOHFFXFEFXJLHKEEESKKIYIEFYFEFDFLFXJYGTGTIMESEYJOGMIHGOJKGYIHIAISHSINJTIAIDJYIAIYIEIHJPINKOIHOEIYIAISHSJTIOIHUYSWLYTA
El CBOR de estas cargas útiles
Solo aparece un pequeño subconjunto de CBOR:
| Tipo principal | Se usa para |
|---|---|
| 0 unsigned int | Claves de mapa, tipos de datos, chain IDs, huellas, índices de ruta |
| 2 byte string | Datos de firma, claves, chain codes, firmas, PSBT |
| 3 text string | Origen, nombres, las claves y los valores del mapa de vinculación |
| 4 array | Componentes de ruta, listas de descriptores, cuentas de la vinculación |
| 5 map | Todas las estructuras de solicitud, respuesta y clave |
| 6 tag | Elementos tipados (abajo) |
| 7 simple | true / false (is-private, indicadores hardened) |
Las solicitudes deben usar longitudes definidas, sin claves de mapa duplicadas y sin bytes sobrantes al final. Los números de coma flotante, los enteros negativos y los elementos de longitud indefinida no tienen cabida en ninguna de estas estructuras.
Tags
| Tag | Nombre | Envuelve |
|---|---|---|
| 37 | uuid | Bytes del ID de solicitud |
| 303 | crypto-hdkey | Mapa de clave HD |
| 304 | crypto-keypath | Mapa de ruta {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(…) |
La diferencia entre cadena de bytes y texto importa: un ID de solicitud son bytes dentro del tag 37, no el UUID como texto; una dirección EVM en la clave 6 son 20 bytes sin procesar, no "0x…".
Leer una solicitud a mano
El CBOR del ejemplo EIP-1559, con anotaciones:
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
Bibliotecas
| Lenguaje | UR | CBOR |
|---|---|---|
| JavaScript / TypeScript | @ngraveio/bc-ur; @keystonehq/bc-ur-registry* de Keystone para clases tipadas | cborg |
| Swift | URKit | DCBOR de URKit, SwiftCBOR |
| Kotlin / Java | Hummingbird | Hummingbird, com.upokecenter:cbor |
| Rust | ur | minicbor, ciborium |