Saltar al contenido principal

QR a nivel de billetera (bittool-signer-wallet v1)

ur:bytescanonical CBORanimatedversion 1

El código QR a nivel de billetera exporta todas las cuentas de una billetera de BitTool Signer en un solo escaneo: cuatro cuentas de Bitcoin (una por cada tipo de dirección de firma única), las cuentas de EVM y TRON, y las tres direcciones preset de Solana. En la app está en Connect › Wallet Level.

Codificación​

pairing object (text keys, see below)
→ canonical CBOR (RFC 8949 §4.2.1) "inner", 1561 bytes for the test wallet
→ CBOR byte string containing those bytes "outer"
→ UR type "bytes" (BCR-2020-006)
→ multi-part UR, 400-byte fragments, upper-case 4 frames for the test wallet

Hay cuatro detalles importantes:

  1. Dos capas CBOR. El cuerpo del UR es una cadena de bytes CBOR; el mapa de emparejamiento está dentro de ella. Decodifica la capa externa para obtener los bytes y luego decodifica esos bytes. No es JSON, ni un elemento con tag 24, ni un tipo personalizado ur:bittool-….
  2. Claves de texto. Las claves del mapa son los nombres de campo JSON como cadenas de texto CBOR, así que el CBOR y la vista JSON de abajo son los mismos datos.
  3. Codificación canónica. Longitudes definidas, la forma más corta de cada entero y claves de mapa ordenadas por los bytes de su codificación (para claves de texto: primero las más cortas, luego byte a byte). Solo aparecen texto, enteros sin signo, arreglos y mapas; los campos ausentes se omiten.
  4. Siempre animado. El tamaño de fragmento es de 400 bytes, limitado a la mitad de la carga útil, así que siempre hay al menos dos fotogramas. BitTool Signer rechaza cargas útiles de más de 4,096 bytes. Tu escáner debe reunir todos los fotogramas (en cualquier orden) antes de decodificar.

Ejemplo de orden canónico de claves: el objeto {version: 1, mode: "full", b: "x", aa: 2} se codifica como

a4 61 62 61 78 62 61 61 02 64 6d6f6465 64 66756c6c 67 76657273696f6e 01
"b" "x" "aa" 2 "mode" "full" "version" 1

El objeto de emparejamiento​

Sobre (envelope)​

CampoTipoValor
protocoltexto"bittool-signer-wallet"
versionuint1. Rechaza cualquier otra versión; nunca leas una versión superior como si fuera v1.
modetexto"full". BitTool Signer solo produce full.
networktexto"mainnet"
masterFingerprinttextoHuella digital de la clave maestra BIP32, 8 dígitos hex en minúsculas
accountsarreglo7 entradas, descritas abajo. Búscalas por chain y script/profile, nunca por posición.

Cuentas​

chainCamposNotas
btc (×4)accountPath, script, xpub, derive: {receive: "0/*", change: "1/*"}Una por script: p2wpkh (m/84'/0'/0'), p2tr (m/86'/0'/0'), p2sh-p2wpkh (m/49'/0'/0'), p2pkh (m/44'/0'/0')
evmaccountPath m/44'/60'/0', xpub, derive: "0/*"Una cuenta para todas las redes EVM
tronaccountPath m/44'/195'/0', xpub, derive: "0/*"
solprofile: "bip44-solana", accounts: [{index, profile, path, address}]Tres entradas: standard m/44'/501'/0'/0', trust m/44'/501'/0', legacy m/44'/501'

Cada xpub usa los bytes de versión estándar de mainnet 0x0488B21E, incluso para SegWit y Taproot: es el campo script, y no un prefijo ypub/zpub, el que indica qué direcciones derivar. Las claves de cofirmante multisig (BIP48) no se incluyen; una billetera multisig necesita a todos los cofirmantes y el umbral, así que para eso usa la exportación de cofirmante a nivel de moneda.

Vector de prueba​

A partir de la frase de prueba pública abandon … about, sin passphrase. Apunta tu escáner a la animación o usa los fotogramas de abajo.

QR de emparejamiento a nivel de billetera (billetera de prueba)4 fotogramas · carga útil de 1,561 bytes · se repite en bucle como en la app
UR:BYTES/1-4/LPADAACFAMCECYJYHYKIWKHKADLTHKAMCFOLIEJNJLIEIHIEIYKPJZJZIOJTIHJYKTJLJPJEIOJNHSINJTJTIHJYIOKOIHJPJKINJLJTADISHSIAIAJLKPJTJYJKLTONIEKSJOKPIDKSJLKSJOKPIDENFXHSJYHGIEINHTINJLIEJNGOIHGHFYJOETGSGHECJLJPETJTJNIDGRGLIAKPKKKOKNEMHGKKJEJKHFFGJEGRFWEEGMFDKTFXFYEOHDKKKPKOGDFEIDKOJSFPGYHKEOJPFPGDJKISHGIAGTGSJLGDEYIYGTFGGTGRFDGDGEEEHTIHHTHDHKHFGOISGSKOEHHFGTJPIMGDFXEMGDHGENHFIHIAISHSINJTIAIDJYIAIYIEIHJPINKOIHOEIYIAISHSJTIOIHIAEHDLDRIOJPIHIAIHINKOIHIADYDLDRIYJKIAJPINJOJYIYJOEYKTJOJEISJEHSIAIAJLKPJTJYGDHSJYISJEJNDLETEEDIDLDYDIDLDYDIONIEKSJOKPIDKSJLKSJOKPIDENFWIOFWIOJKIHJKJOHGKOFEGMFGEOGSFDGYKPENFXJTJSIEKOIYFEKOJYGTIAGYIMHKJPIAGMKNKSECEOGYGEIMGUKSHSJPIMEYHSIYHKHGIAGSJYIHJLFLHFJEKKEMFYEOGOGRFYGDESGYKKJPGSJOJPGYEOHFFXFEFXJLHKEEESKKIYIEFYFEFDFLFXJYGTGTIMESEYJOGMIHGOJKGYIHIAISHSINJTIAIDJYIAIYIEIHJPINKOIHOEIYIAISHSJTIOIHUYSWLYTA
1 / 4
Objeto de emparejamiento (vista JSON del CBOR interno)
{
"protocol": "bittool-signer-wallet",
"version": 1,
"mode": "full",
"network": "mainnet",
"masterFingerprint": "73c5da0a",
"accounts": [
{
"chain": "btc",
"accountPath": "m/84'/0'/0'",
"script": "p2wpkh",
"xpub": "xpub6CatWdiZiodmUeTDp8LT5or8nmbKNcuyvz7WyksVFkKB4RHwCD3XyuvPEbvqAQY3rAPshWcMLoP2fMFMKHPJ4ZeZXYVUhLv1VMrjPC7PW6V",
"derive": {
"receive": "0/*",
"change": "1/*"
}
},
{
"chain": "btc",
"accountPath": "m/86'/0'/0'",
"script": "p2tr",
"xpub": "xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ",
"derive": {
"receive": "0/*",
"change": "1/*"
}
},
{
"chain": "btc",
"accountPath": "m/49'/0'/0'",
"script": "p2sh-p2wpkh",
"xpub": "xpub6C6nQwHaWbSrzs5tZ1q7m5R9cPK9eYpNMFesiXsYrgc1P8bvLLAet9JfHjYXKjToD8cBRswJXXbbFpXgwsswVPAZzKMa1jUp2kVkGVUaJa7",
"derive": {
"receive": "0/*",
"change": "1/*"
}
},
{
"chain": "btc",
"accountPath": "m/44'/0'/0'",
"script": "p2pkh",
"xpub": "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj",
"derive": {
"receive": "0/*",
"change": "1/*"
}
},
{
"chain": "evm",
"accountPath": "m/44'/60'/0'",
"xpub": "xpub6DCoCpSuQZB2jawqnGMEPS63ePKWkwWPH4TU45Q7LPXWuNd8TMtVxRrgjtEshuqpK3mdhaWHPFsBngh5GFZaM6si3yZdUsT8ddYM3PwnATt",
"derive": "0/*"
},
{
"chain": "tron",
"accountPath": "m/44'/195'/0'",
"xpub": "xpub6D1AabNHCupeiLM65ZR9UStMhJ1vCpyV4XbZdyhMZBiJXALQtmn9p42VTQckoHVn8WNqS7dqnJokZHAHcHGoaQgmv8D45oNUKx6DZMNZBCd",
"derive": "0/*"
},
{
"chain": "sol",
"profile": "bip44-solana",
"accounts": [
{
"index": 0,
"profile": "standard",
"path": "m/44'/501'/0'/0'",
"address": "HAgk14JpMQLgt6rVgv7cBQFJWFto5Dqxi472uT3DKpqk"
},
{
"index": 1,
"profile": "trust",
"path": "m/44'/501'/0'",
"address": "GjJyeC1r2RgkuoCWMyPYkCWSGSGLcz266EaAkLA27AhL"
},
{
"index": 2,
"profile": "legacy",
"path": "m/44'/501'",
"address": "D2PPQSYFe83nDzk96FqGumVU8JA7J8vj2Rhjc2oXzEi5"
}
]
}
]
}
Fotogramas QR (ur:bytes, 4 partes)
fotogramas
UR:BYTES/1-4/LPADAACFAMCECYJYHYKIWKHKADLTHKAMCFOLIEJNJLIEIHIEIYKPJZJZIOJTIHJYKTJLJPJEIOJNHSINJTJTIHJYIOKOIHJPJKINJLJTADISHSIAIAJLKPJTJYJKLTONIEKSJOKPIDKSJLKSJOKPIDENFXHSJYHGIEINHTINJLIEJNGOIHGHFYJOETGSGHECJLJPETJTJNIDGRGLIAKPKKKOKNEMHGKKJEJKHFFGJEGRFWEEGMFDKTFXFYEOHDKKKPKOGDFEIDKOJSFPGYHKEOJPFPGDJKISHGIAGTGSJLGDEYIYGTFGGTGRFDGDGEEEHTIHHTHDHKHFGOISGSKOEHHFGTJPIMGDFXEMGDHGENHFIHIAISHSINJTIAIDJYIAIYIEIHJPINKOIHOEIYIAISHSJTIOIHIAEHDLDRIOJPIHIAIHINKOIHIADYDLDRIYJKIAJPINJOJYIYJOEYKTJOJEISJEHSIAIAJLKPJTJYGDHSJYISJEJNDLETEEDIDLDYDIDLDYDIONIEKSJOKPIDKSJLKSJOKPIDENFWIOFWIOJKIHJKJOHGKOFEGMFGEOGSFDGYKPENFXJTJSIEKOIYFEKOJYGTIAGYIMHKJPIAGMKNKSECEOGYGEIMGUKSHSJPIMEYHSIYHKHGIAGSJYIHJLFLHFJEKKEMFYEOGOGRFYGDESGYKKJPGSJOJPGYEOHFFXFEFXJLHKEEESKKIYIEFYFEFDFLFXJYGTGTIMESEYJOGMIHGOJKGYIHIAISHSINJTIAIDJYIAIYIEIHJPINKOIHOEIYIAISHSJTIOIHUYSWLYTA
UR:BYTES/2-4/LPAOAACFAMCECYJYHYKIWKHKADLTIAEHDLDRIOJPIHIAIHINKOIHIADYDLDRIYJKIAJPINJOJYIEJOEYJYJPJEHSIAIAJLKPJTJYGDHSJYISJEJNDLETENDIDLDYDIDLDYDIONIEKSJOKPIDKSJLKSJOKPIDENFXENJTGYKTFDHSHGIDGUJPKNJKECJYHTEHJSEMJNECGMESIAGDGRESIHHKJOGLGTFGIHJKINHDJKHKJPIOIAEHGDETIDKOGSGSFPIHJYESGEIYFDIMHKHDGRIMGHJLFYETIAFWGMJKKTGEHDHDIDIDFGJOHDIOKTJKJKKTHFGDFPHTKNGRGTHSEHIMGOJOEYJEHFJEFLHFGOHSGEHSEMIHIAISHSINJTIAIDJYIAIYIEIHJPINKOIHOEIYIAISHSJTIOIHIAEHDLDRIOJPIHIAIHINKOIHIADYDLDRIYJKIAJPINJOJYJEJOEYJKISDPJOEYKTJOJEISJEHSIAIAJLKPJTJYGDHSJYISJEJNDLEEESDIDLDYDIDLDYDIONIEKSJOKPIDKSJLKSJOKPIDENFWJLJKIYFXJTINIYKNKSIAFGKTJPGUKNGYINJSKPEYFYFWHFGHJKISJEFXHDHSIAKOGLJKHGFLHKGEHFHFISISHSKTFPEMIEEEGMECHGGUHGFLFGGLIDINETFPKTENHTGMIAEHIDJPKSGTKKHGGTKNFLEOFYGUGUGUGUJLIHJEJEKPIEISGOIEESKKGSIDENJSKSEOESGHESJTGTIEIMIHIAISHSINJTIAIDJYIAIYIEIHJPINKOIHBZSSDEKE
UR:BYTES/3-4/LPAXAACFAMCECYJYHYKIWKHKADLTOEIYIAISHSJTIOIHIAEHDLDRIOJPIHIAIHINKOIHIADYDLDRIYJKIAJPINJOJYIHJOEYJOJEISJEHSIAIAJLKPJTJYGDHSJYISJEJNDLEEEEDIDLDYDIDLDYDIOXIEKSJOKPIDKSJLKSJOKPIDENFYFXJLFXJOGUKPGYHTFWEYIMHSKTJSJTFLGTFEGDGUENEOIHGDGRHGJEKTHGGDFDEEGHGOEEECGYEMGSGDHDHGKPGLIEETGHGTJYHFKSGMJPIOIMJYFEJKISKPJSJOGREOJNIEISHSHGFDGDFGJKFWJTIOISECFLFGHTHSGTENJKINEOKKHTIEGOJKGHETIEIEHKGTEOGDKTJTFPGHJYIHIAISHSINJTIAIHKOJNIYIEIHJPINKOIHIADYDLDRJEHSIAIAJLKPJTJYGDHSJYISJZJNDLEEEEDIDLENDYDIDLDYDIOXIEKSJOKPIDKSJLKSJOKPIDENFYEHFPHSIDGLFDFXKPJOIHINGSGTENECHTGMESGOGUJYGTISGEEHKOFXJOKKHFEEHDIDHTIEKKISGTHTFWINGEHDFPGSGYJYJNJTESJOEEEYHFGHGYIAJEJLFDHFJTETHGGLJSGUEMIEJSJTGEJLJEHTFDFPFDIAFDFLJLHSGYIOJNKOETFYEEECJLGLGOGRKSENFYHTGTGLHTFWFXIEIHIAISHSINJTIEJYJPJLJTIYIEIHJPINKOIHIADYDLDRJEHSIAIAJLKPJTJYGDHSJYISJNJNDLEEEEDIDLEHESECDIDLPRFLWYJT
UR:BYTES/4-4/LPAAAACFAMCECYJYHYKIWKHKADLTDYDIOTIHIAISHSINJTIAJKJLJZIOJOJPJLIYINJZIHJZIDINJOEEEEDPJKJLJZHSJTHSISHSIAIAJLKPJTJYJKLSOXIEJOHSJYISJOJNDLEEEEDIDLECDYEHDIDLDYDIDLDYDIIHINJTIEIHKSAEIOHSIEIEJPIHJKJKKSDWFDFPIOJEEHEEGEJOGTGYGSIOJYENJPHFIOKOEMIAFWGYFGGEHGFGJYJLECFYJSKSINEEEMEYKPGHEOFYGRJOJSJEIOJOJPJLIYINJZIHISJKJYHSJTIEHSJPIEOXIEJOHSJYISJNJNDLEEEEDIDLECDYEHDIDLDYDIIHINJTIEIHKSADIOHSIEIEJPIHJKJKKSDWFLIMGEKKIHFXEHJPEYGMIOJEKPJLFXHGGTKKGDHKJEFXHGGUFLGUFLGSIAKNEYENENFEHSFPJEGSFPEYEMFPISGSIOJOJPJLIYINJZIHIHJYJPKPJKJYOXIEJOHSJYISIMJNDLEEEEDIDLECDYEHDIIHINJTIEIHKSAOIOHSIEIEJPIHJKJKKSDWFYEYGDGDGYGUHKFGIHETEOJTFYKNJEESENFGJSFLKPJNHFGOETGEFPEMGEETKOIMEYGMISIMIAEYJLHDKNFEINECIOJOJPJLIYINJZIHIYJZIHIOHSIAKKISJOJPJLJYJLIAJLJZKPIDINJYJYJLJLJZDPJKINIOJTIHJPDPKTHSJZJZIHJYJSJNHSJKJYIHJPFGINJTIOIHJPJOJPINJTJYISEMEOIAECIEHSDYHSVOTIMSMD
CBOR canónico interno (hex) y su SHA-256
CBOR interno, 1561 bytes
a6646d6f64656466756c6c676e6574776f726b676d61696e6e65746776657273696f6e01686163636f756e747387a56478707562786f78707562364361745764695a696f646d5565544470384c54356f72386e6d624b4e637579767a3757796b7356466b4b423452487743443358797576504562767141515933724150736857634d4c6f5032664d464d4b48504a345a655a58595655684c7631564d726a5043375057365665636861696e6362746366646572697665a2666368616e676563312f2a677265636569766563302f2a6673637269707466703277706b686b6163636f756e74506174686b6d2f3834272f30272f3027a56478707562786f787075623642674267736573705776455246334c48517536436e716476664576744d63516a597263527a783533514a6a537861726a3261665957634c74656f47566b79374433554b4450395179724c70725133564345436f5934397966644445484743744d4d6a393270526555735165636861696e6362746366646572697665a2666368616e676563312f2a677265636569766563302f2a6673637269707464703274726b6163636f756e74506174686b6d2f3836272f30272f3027a56478707562786f787075623643366e51774861576253727a7335745a3171376d35523963504b396559704e4d4665736958735972676331503862764c4c416574394a66486a59584b6a546f443863425273774a5858626246705867777373775650415a7a4b4d61316a5570326b566b475655614a613765636861696e6362746366646572697665a2666368616e676563312f2a677265636569766563302f2a667363726970746b703273682d703277706b686b6163636f756e74506174686b6d2f3439272f30272f3027a56478707562786f7870756236426f7366436e69667a7863467772537a51697175324442565473686b43586163764e735747594a56566868617741376434523557535747464e6269384177365a5263316272784d79574d7a473344535353536f656b6b756468556439794c62367178333954396e4d646a65636861696e6362746366646572697665a2666368616e676563312f2a677265636569766563302f2a66736372697074657032706b686b6163636f756e74506174686b6d2f3434272f30272f3027a46478707562786f787075623644436f43705375515a42326a6177716e474d455053363365504b576b77575048345455343551374c505857754e6438544d7456785272676a744573687571704b336d6468615748504673426e67683547465a614d36736933795a64557354386464594d3350776e41547465636861696e6365766d6664657269766563302f2a6b6163636f756e74506174686c6d2f3434272f3630272f3027a46478707562786f787075623644314161624e4843757065694c4d36355a52395553744d684a3176437079563458625a6479684d5a42694a58414c51746d6e39703432565451636b6f48566e38574e71533764716e4a6f6b5a4841486348476f6151676d76384434356f4e554b7836445a4d4e5a42436465636861696e6474726f6e6664657269766563302f2a6b6163636f756e74506174686d6d2f3434272f313935272f3027a365636861696e63736f6c6770726f66696c656c62697034342d736f6c616e61686163636f756e747383a46470617468706d2f3434272f353031272f30272f302765696e646578006761646472657373782c4841676b31344a704d514c6774367256677637634251464a5746746f3544717869343732755433444b70716b6770726f66696c65687374616e64617264a464706174686d6d2f3434272f353031272f302765696e646578016761646472657373782c476a4a79654331723252676b756f43574d7950596b4357534753474c637a3236364561416b4c41323741684c6770726f66696c65657472757374a464706174686a6d2f3434272f3530312765696e646578026761646472657373782c44325050515359466538336e447a6b3936467147756d5655384a41374a38766a3252686a63326f587a4569356770726f66696c65666c65676163796870726f746f636f6c75626974746f6f6c2d7369676e65722d77616c6c6574716d617374657246696e6765727072696e74683733633564613061
SHA-256(interno)
c8db82cd92e0cf45cb6329b2429494b04a3b6bad7653c9bed1d2a5ef10f4a5fc

La primera dirección de cada cuenta, que BitTool Signer muestra debajo del código QR:

CuentaRutaDirección
Bitcoin P2WPKHm/84'/0'/0'/0/0bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu
Bitcoin P2TRm/86'/0'/0'/0/0bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr
Bitcoin P2SH-P2WPKHm/49'/0'/0'/0/037VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf
Bitcoin P2PKHm/44'/0'/0'/0/01LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA
EVMm/44'/60'/0'/0/00x9858EfFD232B4033E47d90003D41EC34EcaEda94
TRONm/44'/195'/0'/0/0TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH
Solana standardm/44'/501'/0'/0'HAgk14JpMQLgt6rVgv7cBQFJWFto5Dqxi472uT3DKpqk
Solana trustm/44'/501'/0'GjJyeC1r2RgkuoCWMyPYkCWSGSGLcz266EaAkLA27AhL
Solana legacym/44'/501'D2PPQSYFe83nDzk96FqGumVU8JA7J8vj2Rhjc2oXzEi5

Cómo decodificarlo​

Este decodificador usa @ngraveio/bc-ur para la capa UR y cborg en modo estricto para CBOR:

examples/wallet-pairing.mjs
import { URDecoder } from '@ngraveio/bc-ur';
import { decode } from 'cborg';

export function decodeWalletPairing(frames) {
const decoder = new URDecoder();
for (const frame of frames) decoder.receivePart(frame); // any order; repeats are ignored
if (!decoder.isComplete()) throw new Error(`Keep scanning: ${Math.round(decoder.estimatedPercentComplete() * 100)}%`);
if (!decoder.isSuccess()) throw new Error(decoder.resultError());

const ur = decoder.resultUR();
if (ur.type !== 'bytes') throw new Error('Not a BitTool Signer wallet QR');
const strict = { strict: true, rejectDuplicateMapKeys: true };
const inner = decode(ur.cbor, strict); // layer 1: a CBOR byte string
if (!(inner instanceof Uint8Array)) throw new Error('ur:bytes must hold a byte string');
const pairing = decode(inner, strict); // layer 2: the text-keyed map

if (pairing.protocol !== 'bittool-signer-wallet') throw new Error('Unknown protocol');
if (pairing.version !== 1) throw new Error('Unsupported version');
if (pairing.mode !== 'full' && pairing.mode !== 'simple') throw new Error('Unsupported mode');
if (pairing.network !== 'mainnet') throw new Error('Unsupported network');
if (!/^[0-9a-f]{8}$/.test(pairing.masterFingerprint)) throw new Error('Bad fingerprint');
return pairing;
}

Luego deriva las direcciones. Bitcoin usa los cuatro tipos de script; EVM y TRON calculan el hash Keccak-256 de la clave pública sin comprimir y conservan los últimos 20 bytes (TRON agrega el prefijo 0x41 y Base58Check); las direcciones de Solana ya vienen en la carga útil:

examples/wallet-pairing.mjs
import { HDKey } from '@scure/bip32';
import { keccak_256 } from '@noble/hashes/sha3.js';
import { sha256 } from '@noble/hashes/sha2.js';
import { secp256k1 } from '@noble/curves/secp256k1.js';
import { base58check } from '@scure/base';
import { p2wpkh, p2tr, p2sh, p2pkh } from '@scure/btc-signer';
import { getAddress } from 'ethers';

const child = (xpub, branch, index) => HDKey.fromExtendedKey(xpub).deriveChild(branch).deriveChild(index);

export function firstAddresses(pairing) {
const out = {};
for (const account of pairing.accounts) {
if (account.chain === 'btc') {
const pub = child(account.xpub, 0, 0).publicKey; // receive branch 0, index 0
out[`btc:${account.script}`] = {
p2wpkh: () => p2wpkh(pub).address,
p2tr: () => p2tr(pub.subarray(1)).address, // BIP86: untweaked x-only key
'p2sh-p2wpkh': () => p2sh(p2wpkh(pub)).address,
p2pkh: () => p2pkh(pub).address,
}[account.script]();
} else if (account.chain === 'evm' || account.chain === 'tron') {
const point = secp256k1.Point.fromBytes(child(account.xpub, 0, 0).publicKey);
const hash = keccak_256(point.toBytes(false).subarray(1)).subarray(-20);
out[account.chain] = account.chain === 'evm'
? getAddress('0x' + Buffer.from(hash).toString('hex'))
: base58check(sha256).encode(Uint8Array.from([0x41, ...hash]));
} else if (account.chain === 'sol') {
for (const sol of account.accounts) out[`sol:${sol.profile}`] = sol.address;
}
}
return out;
}

Ambas funciones se ejecutan contra el vector de prueba anterior mediante examples/test.mjs en el repositorio de la documentación.

Reglas para el receptor​

Un receptor robusto debe:

  • Exigir protocol == "bittool-signer-wallet", un version == 1 entero, mode == "full", network == "mainnet" y un masterFingerprint válido de 8 dígitos hex. Explicar claramente qué verificación falló.
  • Buscar las cuentas por clave, no por posición, y rechazar una carga útil a la que le falte una cuenta conocida o que tenga una duplicada.
  • Ignorar los campos desconocidos: las cargas útiles v1 posteriores pueden agregar algunos. Verifica los bytes tal como los recibiste; no elimines los campos desconocidos para volver a codificar antes de una verificación de codificación canónica, o una carga útil más nueva y válida no la pasará.
  • Validar cada xpub: Base58Check, versión 0x0488B21E, profundidad 3, un índice hijo endurecido (hardened), una clave secp256k1 comprimida válida. Rechazar xprv, tpub y ypub/zpub.
  • Comprobar las rutas con la tabla anterior, en lugar de reescribir en silencio una ruta desconocida a un valor predeterminado.
  • Validar las claves de Solana: el Base58 se decodifica en exactamente 32 bytes, un punto Ed25519 válido, sin duplicados.
  • Tratarla como entrada no confiable: limitar la cantidad de fotogramas y los tamaños, y rechazar claves CBOR duplicadas y bytes sobrantes. El propio decodificador de referencia de BitTool Signer también rechaza CBOR no canónico.

Se puede comprobar la estructura de un xpub, pero no su origen. Nada en la carga útil demuestra que las claves provengan de la huella digital o la ruta que declara, y por eso el usuario debe comparar las direcciones.

Notas de seguridad​

  • La carga útil contiene solo claves públicas extendidas y direcciones. Se construye a partir del índice público autenticado del firmador; la bóveda nunca se abre.
  • Cualquiera que la escanee puede ver todas las direcciones de estas cuentas y todo su historial. Trátala como cualquier xpub: no la registres en logs, no la subas a herramientas de analítica ni la incluyas en reportes de fallos.
  • El código QR no está firmado y tampoco autentica a la billetera receptora. La verificación es la comparación de direcciones por parte del usuario.