Saltar al contenido principal

Firma mediante QR

Después del emparejamiento, cada firma es un solo viaje de ida y vuelta: tu billetera muestra un código QR de solicitud y BitTool Signer muestra un código QR de respuesta.

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

Estos son los tipos del registro UR público de Keystone. El formato de emparejamiento a nivel de billetera sirve solo para importar cuentas; no existe un sobre de firma específico de BitTool, y no se debe inventar ninguno.

Componentes 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 del emparejamiento a nivel de billetera). Comparten tres componentes.

ID de solicitud: 37(bytes)​

Un identificador aleatorio: el tag CBOR 37 envolviendo 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 billeteras que usan IDs opacos más largos.
  • Compara los IDs como bytes. Que un ID coincida no hace válida una firma; de todos modos debes verificarla.

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

La ruta completa de la clave que debe firmar, junto con la huella digital 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 endurecidos.
  • Usa la ruta concreta de la hoja (…/0/5), nunca una ruta de cuenta ni un comodín *.
  • Incluye siempre la huella digital (clave 2). Sin ella, la coincidencia es más débil y algunas solicitudes no se pueden asociar en absoluto.

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 corta que se muestra al usuario como fuente de la solicitud, por ejemplo "BitTool Wallet". Máximo 256 caracteres. Se muestra, pero no se autentica: BitTool Signer nunca la considera prueba de nada.

Sin campos adicionales​

Solo se aceptan las claves definidas para cada tipo: 1–7 para eth-sign-request, 1–6 para sol- y tron-sign-request. Una clave desconocida invalida toda la solicitud, así que no agregues 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 un UR de varias partes y reproduce los fotogramas en bucle:

  • Pasa el texto 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.
  • De 200 a 400 bytes por fragmento y de 3 a 5 fotogramas por segundo se escanean de forma confiable con teléfonos.
  • 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" mixtas, que permiten al escáner terminar incluso si se perdieron fotogramas.

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

La respuesta​

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

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

Lleva 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 cambia en la transacción (comisión, nonce, blockhash, monto), crea una solicitud nueva con un ID nuevo. Nunca reutilices una firma para bytes distintos, y rechaza una respuesta que llegue para una solicitud cancelada o vencida.

Cómo encuentra BitTool Signer la clave​

No le indicas a BitTool Signer qué billetera usar; la deduce a partir de la ruta, la huella digital y la dirección o clave pública de tu solicitud, comparándolas con las claves públicas de cada billetera del teléfono. Debe coincidir exactamente una billetera. Consulta Qué verifica el firmador para conocer las reglas completas. En la práctica: envía la ruta completa, la huella digital y la dirección (o clave pública) en cada solicitud, y BitTool Signer encontrará la clave directamente.