Saltar al contenido principal

Firma mediante QR

Después de la vinculación, cada firma es un único viaje de ida y vuelta: tu cartera muestra un código QR de solicitud y BitTool Signer muestra un código QR de respuesta.

CadenaSolicitud (tú → firmante)Respuesta (firmante → tú)Página
Bitcoinur:crypto-psbt (también ur:psbt, BBQr, texto Base64/hex)El mismo tipo, con las firmas añadidasBitcoin
EVMur:eth-sign-requestur:eth-signatureEVM
TRONur:tron-sign-requestur:tron-signatureTRON
Solanaur:sol-sign-requestur:sol-signatureSolana

Estos son los tipos del registro público de UR de Keystone. El formato de vinculación a nivel de cartera sirve solo para importar cuentas; no existe ningún envoltorio de firma propio de BitTool, y no hay que inventar ninguno.

Componentes básicos de una solicitud​

Las solicitudes de EVM, TRON y Solana son mapas CBOR con claves enteras, transportados directamente como cuerpo del UR (una sola capa CBOR, a diferencia de la vinculación a nivel de cartera). Comparten tres componentes básicos.

ID de solicitud: 37(bytes)​

Un identificador aleatorio: el tag CBOR 37 alrededor de una cadena de bytes. BitTool Signer lo copia sin cambios en la respuesta, para que puedas asociar la respuesta con tu solicitud pendiente.

  • Genera 16 bytes aleatorios para cada solicitud (los bytes de un UUID, no su texto).
  • sol-sign-request y tron-sign-request exigen exactamente 16 bytes. eth-sign-request también acepta de 1 a 64 bytes, por compatibilidad con carteras que usan IDs opacos más largos.
  • Compara los IDs como bytes. Que el ID coincida no hace válida una firma; aun así tienes que verificarla.

Ruta de derivación: 304(crypto-keypath)​

La ruta completa de la clave que debe firmar, más la huella maestra:

304({
1: [44, true, 60, true, 0, true, 0, false, 0, false], ; m/44'/60'/0'/0/0: index, hardened, index, hardened …
2: 1942346250 ; master fingerprint 0x73c5da0a as a big-endian uint32
})
  • Los componentes alternan index, hardened?. No sumes además 0x80000000 a los índices hardened.
  • Usa la ruta concreta hasta la hoja (…/0/5), nunca una ruta de cuenta ni un comodín *.
  • Incluye siempre la huella (clave 2). Sin ella, la identificación de la cartera es más débil y algunas solicitudes no se pueden asociar a ninguna.

Una función auxiliar que la construye con cborg:

examples/keypath.mjs
import { Tagged } from 'cborg';

/** crypto-keypath (tag 304): "m/44'/195'/0'/0/0" + "73c5da0a" →
* 304({1: [44, true, 195, true, 0, true, 0, false, 0, false], 2: 1942346250}) */
export function keypath(path, masterFingerprint) {
const components = path.replace(/^m\//, '').split('/')
.flatMap((part) => [Number.parseInt(part, 10), part.endsWith("'")]);
return new Tagged(304, new Map([[1, components], [2, Number.parseInt(masterFingerprint, 16)]]));
}

Origen: texto​

Una etiqueta breve que se muestra al usuario como origen de la solicitud, por ejemplo "BitTool Wallet". Como máximo 256 caracteres. Se muestra, pero no se autentica: BitTool Signer nunca la trata como prueba de nada.

Sin campos adicionales​

Solo se aceptan las claves definidas para cada tipo: 1–7 para eth-sign-request y 1–6 para sol- y tron-sign-request. Una clave desconocida invalida toda la solicitud, así que no añadas tus propios metadatos.

Códigos QR animados​

Las solicitudes suelen ser más grandes de lo que cabe en un código QR. Codifícalas como UR multiparte y reproduce los fotogramas en bucle:

  • Pasa el texto del UR a mayúsculas: UR:ETH-SIGN-REQUEST/… cabe en el modo alfanumérico de QR, que necesita alrededor de un 30 % menos de bits que el modo byte.
  • Entre 200 y 400 bytes por fragmento y de 3 a 5 fotogramas por segundo se escanean de forma fiable con los móviles.
  • Sigue llamando a nextPart() del codificador en lugar de repetir la primera pasada. Después de la primera pasada, el codificador bc-ur produce partes «fountain» mezcladas, que permiten al escáner terminar aunque se hayan perdido fotogramas.

BitTool Signer codifica sus propias respuestas con fragmentos de 180 bytes, así que las firmas cortas son un único código QR estático y los PSBT firmados pueden ser animados. Tu escáner debe admitir ambos casos.

La respuesta​

  • Comprueba primero el tipo de UR: la respuesta a un eth-sign-request debe ser un eth-signature, y así sucesivamente. Bitcoin responde con el mismo tipo que enviaste.
  • Comprueba el ID de solicitud (Bitcoin: comprueba que la transacción sin firmar dentro del PSBT no ha cambiado).
  • Verifica criptográficamente la firma con la clave esperada y sobre los bytes exactos que pediste firmar; después, ensambla y difunde.

No existe respuesta de rechazo. Si el usuario cancela en BitTool Signer, no se muestra nada. Tu interfaz debe permitir al usuario volver atrás, mostrar de nuevo el QR o empezar desde el principio, y nunca debe esperar una respuesta indefinidamente.

Mantén un registro pendiente​

Guarda cada solicitud hasta que se responda o se abandone:

pendingId, walletId, chain/network, signer path + key/address,
requestId, request type, exact bytes to sign, digest,
unsigned transaction, created at, expiry (nonce / blockhash / TRON expiration),
status: awaitingSignature → signatureVerified → broadcast → confirmed | failed | cancelled

Si algo de la transacción cambia (comisión, nonce, blockhash, importe), crea una solicitud nueva con un ID nuevo. No reutilices nunca una firma para bytes distintos y rechaza cualquier respuesta que llegue para una solicitud cancelada o caducada.

Cómo encuentra BitTool Signer la clave​

No le dices a BitTool Signer qué cartera usar; lo deduce a partir de la ruta, la huella y la dirección o clave pública de tu solicitud, comparándolas con las claves públicas de todas las carteras del móvil. Debe coincidir exactamente una cartera. Consulta Qué comprueba el firmante para ver todas las reglas. En la práctica: envía la ruta completa, la huella y la dirección (o la clave pública) en cada solicitud, y BitTool Signer encontrará la clave directamente.