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.
De CBOR a texto 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 corta de firma EVM:
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 de varias partes (animados)
Cuando una carga útil es demasiado grande para un código QR, el codificador la divide en fragmentos de igual longitud y produce partes:
ur:<type>/<seq>-<count>/<bytewords of CBOR [seq, count, message-length, checksum, fragment]>
- Las partes
1…countllevan 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. Con ellas, un decodificador puede reconstruir los fragmentos faltantes. count,message-lengthychecksum(CRC-32 de todo el mensaje) son iguales en todas las partes de una misma sesión. BitTool Signer rechaza un escaneo que mezcle dos sesiones.
Consecuencias prácticas:
- Escanea las partes en cualquier orden; ignora las repetidas.
- Visualización: sigue generando partes con
nextPart()en un bucle en lugar de repetir las primerascountpartes. - El tamaño del fragmento es un equilibrio entre la densidad del QR y la cantidad de fotogramas. BitTool Signer usa 400 bytes para el emparejamiento a nivel de billetera y 180 bytes para sus respuestas de firma.
Fotograma 1 del código QR de emparejamiento a nivel de billetera:
UR:BYTES/1-4/LPADAACFAMCECYJYHYKIWKHKADLTHKAMCFOLIEJNJLIEIHIEIYKPJZJZIOJTIHJYKTJLJPJEIOJNHSINJTJTIHJYIOKOIHJPJKINJLJTADISHSIAIAJLKPJTJYJKLTONIEKSJOKPIDKSJLKSJOKPIDENFXHSJYHGIEINHTINJLIEJNGOIHGHFYJOETGSGHECJLJPETJTJNIDGRGLIAKPKKKOKNEMHGKKJEJKHFFGJEGRFWEEGMFDKTFXFYEOHDKKKPKOGDFEIDKOJSFPGYHKEOJPFPGDJKISHGIAGTGSJLGDEYIYGTFGGTGRFDGDGEEEHTIHHTHDHKHFGOISGSKOEHHFGTJPIMGDFXEMGDHGENHFIHIAISHSINJTIAIDJYIAIYIEIHJPINKOIHOEIYIAISHSJTIOIHIAEHDLDRIOJPIHIAIHINKOIHIADYDLDRIYJKIAJPINJOJYIYJOEYKTJOJEISJEHSIAIAJLKPJTJYGDHSJYISJEJNDLETEEDIDLDYDIDLDYDIONIEKSJOKPIDKSJLKSJOKPIDENFWIOFWIOJKIHJKJOHGKOFEGMFGEOGSFDGYKPENFXJTJSIEKOIYFEKOJYGTIAGYIMHKJPIAGMKNKSECEOGYGEIMGUKSHSJPIMEYHSIYHKHGIAGSJYIHJLFLHFJEKKEMFYEOGOGRFYGDESGYKKJPGSJOJPGYEOHFFXFEFXJLHKEEESKKIYIEFYFEFDFLFXJYGTGTIMESEYJOGMIHGOJKGYIHIAISHSINJTIAIDJYIAIYIEIHJPINKOIHOEIYIAISHSJTIOIHUYSWLYTA
CBOR en estas cargas útiles
Solo aparece un pequeño subconjunto de CBOR:
| Tipo mayor | Se usa para |
|---|---|
| 0 entero sin signo | Claves de mapa, tipos de datos, chain IDs, huellas digitales, índices de ruta |
| 2 cadena de bytes | Datos de firma, claves, chain codes, firmas, PSBT |
| 3 cadena de texto | Origen, nombres, las claves y los valores del mapa de emparejamiento |
| 4 arreglo | Componentes de ruta, listas de descriptores, cuentas de emparejamiento |
| 5 mapa | Todas las solicitudes, respuestas y estructuras de claves |
| 6 tag | Elementos tipados (ver abajo) |
| 7 simple | true / false (is-private, indicadores de endurecimiento) |
Las solicitudes deben usar longitudes definidas, sin claves de mapa duplicadas y sin bytes sobrantes. Los flotantes, 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, anotado:
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 |