Solana (sol-sign-request)
sol-sign-request
| Clave | Campo | Tipo | Contenido |
|---|---|---|---|
| 1 | request-id | 37(bytes) | Exactamente 16 bytes aleatorios |
| 2 | sign-data | bytes | sign-type 1: el mensaje serializado · sign-type 2: los bytes del mensaje de texto |
| 3 | derivation-path | 304(crypto-keypath) | Una de las tres rutas preset, con la huella digital maestra |
| 4 | address | bytes (32) | La clave pública del firmante: la dirección decodificada con Base58, no el texto |
| 5 | origin | texto | Se muestra al usuario |
| 6 | sign-type | uint | 1 transacción · 2 mensaje |
Ten en cuenta que el orden de las claves es distinto al de eth- y tron-sign-request: la ruta es la clave 3 y el tipo es la clave 6.
SolSignRequest.constructSOLRequest() en @keystonehq/bc-ur-registry-sol recibe la dirección como una cadena hex de los 32 bytes de la clave, no en Base58. Si pasas la dirección en Base58, se produce una clave 4 incorrecta y la solicitud no coincidirá.
sign-type 1: transacciones
sign-data es la parte del mensaje de la transacción, los bytes que cubren las firmas: transaction.serializeMessage() en @solana/web3.js v1, o message.serialize() para una VersionedTransaction. No es la transacción completa (que empieza con la cantidad de firmas y sus espacios) ni un hash.
| Regla | Detalle |
|---|---|
| Versiones de mensaje | Legacy y v0 |
| Tablas de búsqueda de direcciones | No compatibles. El firmador no puede verificar sin conexión el contenido de las tablas. Compila los mensajes v0 solo con claves estáticas; si el resultado es demasiado grande, no lo envíes. |
| Firmante | La clave de la clave 4 debe ser uno de los firmantes requeridos del mensaje (cualquier espacio, no solo el del pagador de la comisión). |
| Tamaño | 1–64 instrucciones; la transacción firmada debe caber en el límite de paquete de 1,232 bytes. |
| Durable nonce | Se permite, pero se señala en la revisión (una transacción así sigue siendo válida indefinidamente). |
Programas decodificados: System (transfer, create account, nonce, assign, allocate), SPL Token y Token-2022 (transfer, transferChecked, approve, mint, burn, close, set authority, sync native), Associated Token Account (con verificación de direcciones), Compute Budget, Memo, Stake (delegate, split, withdraw, deactivate) y programas comunes de NFT (Metaplex Token Metadata, Bubblegum, Core). Las extensiones de Token-2022 y las instrucciones de propuestas de Squads se decodifican parcialmente. Los demás programas se muestran como instrucciones sin procesar y requieren consentimiento explícito.
sign-type 2: mensajes
sign-data es texto UTF-8, de 1 a 1,024 bytes, sin caracteres de control. Se rechazan los bytes que podrían interpretarse como encabezado de transacción, de modo que nunca se puede hacer pasar una transacción por un "mensaje". BitTool Signer firma los bytes del mensaje directamente con Ed25519 (sin prefijo ni hash), que es lo que espera signMessage en los adaptadores de billetera de Solana.
sol-signature
sol-signature = {
1: 37(request-id bytes),
2: signature ; 64 bytes, Ed25519
}
Es una firma separada (detached). Para completar una transacción:
- Verifica el ID de solicitud y verifica la firma sobre los bytes exactos del mensaje con la clave pública esperada.
- Encuentra el índice del firmante entre los firmantes requeridos del mensaje y coloca la firma en ese espacio. No siempre es el espacio 0.
- Conserva las firmas que ya estén en los demás espacios; serializa cuando todos los espacios requeridos estén completos y transmite.
Si el blockhash vence antes de transmitir, construye un mensaje nuevo y una solicitud nueva. Una firma no se puede trasladar a un mensaje con otro blockhash.
Código
Con @solana/web3.js v1 y @keystonehq/bc-ur-registry-sol de Keystone:
import { randomUUID } from 'node:crypto';
import { PublicKey, Transaction, SystemProgram } from '@solana/web3.js';
import { SolSignRequest, SignType, SolSignature } from '@keystonehq/bc-ur-registry-sol';
import { URDecoder } from '@ngraveio/bc-ur';
import { ed25519 } from '@noble/curves/ed25519.js';
export function buildSolTransfer({ from, to, lamports, recentBlockhash }) {
const tx = new Transaction({ feePayer: new PublicKey(from), recentBlockhash });
tx.add(SystemProgram.transfer({ fromPubkey: new PublicKey(from), toPubkey: new PublicKey(to), lamports }));
return tx;
}
export function buildSolRequest(tx, { path, masterFingerprint, address }) {
const requestId = randomUUID();
const request = SolSignRequest.constructSOLRequest(
tx.serializeMessage(), // message bytes, not the whole transaction
path, // "m/44'/501'/0'/0'"
masterFingerprint, // "73c5da0a"
SignType.Transaction, // 1 (2 = text message)
requestId,
new PublicKey(address).toBuffer().toString('hex'), // this library wants hex, not Base58
'BitTool Wallet',
);
const encoder = request.toUREncoder(400);
const frames = [];
for (let i = 0; i < encoder.fragmentsLength; i++) frames.push(encoder.nextPart().toUpperCase());
return { requestId, frames };
}
export function attachSolSignature(frames, tx, { requestId, address }) {
const decoder = new URDecoder();
for (const frame of frames) decoder.receivePart(frame);
if (!decoder.isSuccess()) throw new Error('Incomplete or invalid QR');
const ur = decoder.resultUR();
if (ur.type !== 'sol-signature') throw new Error(`Expected sol-signature, got ${ur.type}`);
const response = SolSignature.fromCBOR(ur.cbor);
if (Buffer.from(response.getRequestId()).toString('hex') !== requestId.replace(/-/g, '')) throw new Error('Signature is for another request');
const signature = Buffer.from(response.getSignature()); // 64-byte Ed25519 signature
const signer = new PublicKey(address);
if (!ed25519.verify(signature, tx.serializeMessage(), signer.toBytes())) throw new Error('Bad signature');
tx.addSignature(signer, signature); // goes into this key's own slot
return tx.serialize(); // throws if another required signature is missing
}
Vectores de prueba
Todos desde el preset standard m/44'/501'/0'/0' (HAgk14JpMQLgt6rVgv7cBQFJWFto5Dqxi472uT3DKpqk). El blockhash es sintético.
- Transferencia de SOL
- Token SPL (USDC)
- Mensaje
Envía 0.25 SOL (250,000,000 lamports) con un mensaje legacy: pagador de la comisión y firmante, destinatario, System Program.
Solicitud (tu billetera → BitTool Signer)
{
"1 request-id": {
"uuid (tag 37)": "5f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0b"
},
"2 sign-data": "01000103f036276246a75b9de3349ed42b15e232f6518fc20f5fcd4f1d64e81f9bd258f77e8c088760bfde1dddcf32c17f209b8242ee52aaf131facd88d0ea2c6d0b06f20000000000000000000000000000000000000000000000000000000000000000cc490e928cd2e3873bb343fc95da33179ca60f4dbf46c2c36e91299d55d4e6b901020200010c0200000080b2e60e00000000",
"3 derivation-path": {
"crypto-keypath (tag 304)": {
"1 components": [
44,
true,
501,
true,
0,
true,
0,
true
],
"2 source-fingerprint": 1942346250
}
},
"4 address": "f036276246a75b9de3349ed42b15e232f6518fc20f5fcd4f1d64e81f9bd258f7",
"5 origin": "BitTool Wallet",
"6 sign-type": 1
}
Mostrar el texto del QR y el CBOR sin procesar
UR:SOL-SIGN-REQUEST/1-2/LPADAOCSZOCYLRESBDUEHDKBOLADTPDAGDHECKNDDWKIFTGLJLLEBDCEDPFMGWHTBDAOHDMTADAEADAXWTENDIIDFGOSHPNTVLEENNTYDNBZVOEYYNGYMYSABSHESNGWCAIEVSCTNDTDHDYLKBLKAYLTHNRSUECAUTTKEYSELBCXNDLFFWWYGMPKWNEHZSSNLOTIWDDWJNBDAMWZAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAESFGAWZEETBWS
UR:SOL-SIGN-REQUEST/2-2/LPAOAOCSZOCYLRESBDUEHDKBBAMOLKTDVLLTFRQDFXZTMDTNEOCHNSOLBSGTRSFGSASRJTMEDTNTGOTYVARHADAOAOAEADBNAOAEAEAELAPRVABAAEAEAEAEAXTAADDYOEADLOCSDWYKCFADYKYKAEYKAEYKAOCYJKSKTNBKAAHDCXWTENDIIDFGOSHPNTVLEENNTYDNBZVOEYYNGYMYSABSHESNGWCAIEVSCTNDTDHDYLAHJTFWINJYGHJLJLJZCXHGHSJZJZIHJYAMADAEWYHKLNHH
a601d825505f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0b02589601000103f036276246a75b9de3349ed42b15e232f6518fc20f5fcd4f1d64e81f9bd258f77e8c088760bfde1dddcf32c17f209b8242ee52aaf131facd88d0ea2c6d0b06f20000000000000000000000000000000000000000000000000000000000000000cc490e928cd2e3873bb343fc95da33179ca60f4dbf46c2c36e91299d55d4e6b901020200010c0200000080b2e60e0000000003d90130a20188182cf51901f5f500f500f5021a73c5da0a045820f036276246a75b9de3349ed42b15e232f6518fc20f5fcd4f1d64e81f9bd258f7056e426974546f6f6c2057616c6c65740601
Respuesta (BitTool Signer → tu billetera)
{
"request-id": {
"uuid": "5f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0b"
},
"signature": "99b9a42fcb8eead46ae74c5e792098c247c21ab4d791af70a26d8392986d714fd4543af8f121c355ca32f747f321d52fc3856cf8b4ae678045a34648d924940c"
}
Mostrar el texto del QR y el CBOR sin procesar
UR:SOL-SIGNATURE/OEADTPDAGDHECKNDDWKIFTGLJLLEBDCEDPFMGWHTBDAOHDFZNLRHOXDLSBMNWDTYIMVDGSHYKKCXMKSAFLSACYQZTSMEPEJOOEJNLSMOMKJNJSGWTYGHFTYAWNCLSRGOSGEYYLFLWFCLTLDLSRLPJZYAQZPLIOLAFEOTFGFDTADKMWBNDTZCBGGE
a201d825505f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0b02584099b9a42fcb8eead46ae74c5e792098c247c21ab4d791af70a26d8392986d714fd4543af8f121c355ca32f747f321d52fc3856cf8b4ae678045a34648d924940c
Ensamblado por tu billetera
45G7cB2HaLBrWEuZxiWR2dFYu52Cje6d2vv5GnCaZ7YSc6ifhFVdFTGpJSk1PU7tSTPHjg3H4uufoe3XPmTRQiJP
AZm5pC/LjurUaudMXnkgmMJHwhq015GvcKJtg5KYbXFP1FQ6+PEhw1XKMvdH8yHVL8OFbPi0rmeARaNGSNkklAwBAAED8DYnYkanW53jNJ7UKxXiMvZRj8IPX81PHWToH5vSWPd+jAiHYL/eHd3PMsF/IJuCQu5SqvEx+s2I0OosbQsG8gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzEkOkozS44c7s0P8ldozF5ymD02/RsLDbpEpnVXU5rkBAgIAAQwCAAAAgLLmDgAAAAA=
Envía 12.5 USDC con TransferChecked (6 decimales) desde la cuenta de token asociada del firmante 5N3f1tj9v1vc5TUZ8S7mCAnVmjVKrfnzXWhxLaxyZAgt a la del destinatario FGETo8T8wMcN2wCjav8VK6eh3dLk63evNDPxzLSJra8B.
Solicitud (tu billetera → BitTool Signer)
{
"1 request-id": {
"uuid (tag 37)": "5f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0c"
},
"2 sign-data": "01000205f036276246a75b9de3349ed42b15e232f6518fc20f5fcd4f1d64e81f9bd258f740d2f27c461f29e9feba1ab8acd39496a9c65b0ae62a713f01f941a993bf55edd3ea8cf5acaca8cd05207512175c43cef54a5dd99ede20a16b55253738f397dcc6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d6106ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9cc490e928cd2e3873bb343fc95da33179ca60f4dbf46c2c36e91299d55d4e6b9010404010302000a0c20bcbe000000000006",
"3 derivation-path": {
"crypto-keypath (tag 304)": {
"1 components": [
44,
true,
501,
true,
0,
true,
0,
true
],
"2 source-fingerprint": 1942346250
}
},
"4 address": "f036276246a75b9de3349ed42b15e232f6518fc20f5fcd4f1d64e81f9bd258f7",
"5 origin": "BitTool Wallet",
"6 sign-type": 1
}
Mostrar el texto del QR y el CBOR sin procesar
UR:SOL-SIGN-REQUEST/1-2/LPADAOCFADFRCYGEKNLGGDHDNNOLADTPDAGDHECKNDDWKIFTGLJLLEBDCEDPFMGWHTBNAOHDTBADAEAOAHWTENDIIDFGOSHPNTVLEENNTYDNBZVOEYYNGYMYSABSHESNGWCAIEVSCTNDTDHDYLFZTDWZKEFGCTDTWLZERDCYROPSTEMWMTPTSWHPBKVADRJSFHADYTFPPTMURSGOWETEWDLKYKPSPSPDSNAHCXKPBGCHHHFXTOYKGEHLTANNUECXOYJEGODAEMETWFMSUOSWZSKNWFRNUYPMFTFSIHWFIMPYSOJYEHPARKVESATDYNVTVEKEOLAOAXFEDLHLHSAMUTSEPTJPLD
UR:SOL-SIGN-REQUEST/2-2/LPAOAOCFADFRCYGEKNLGGDHDNNYNVYTSIHOYMUTASBVYFGTOWMKKPSCEQZLPWEHEHPEMMEFTLKYKLPKBZMAEPTSFGABAMOLKTDVLLTFRQDFXZTMDTNEOCHNSOLBSGTRSFGSASRJTMEDTNTGOTYVARHADAAAAADAXAOAEBKBNCXRFRNAEAEAEAEAEAMAXTAADDYOEADLOCSDWYKCFADYKYKAEYKAEYKAOCYJKSKTNBKAAHDCXWTENDIIDFGOSHPNTVLEENNTYDNBZVOEYYNGYMYSABSHESNGWCAIEVSCTNDTDHDYLAHJTFWINJYGHJLJLJZCXHGHSJZJZIHJYAMADAEHTNETOQZ
a601d825505f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0c0258d601000205f036276246a75b9de3349ed42b15e232f6518fc20f5fcd4f1d64e81f9bd258f740d2f27c461f29e9feba1ab8acd39496a9c65b0ae62a713f01f941a993bf55edd3ea8cf5acaca8cd05207512175c43cef54a5dd99ede20a16b55253738f397dcc6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d6106ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9cc490e928cd2e3873bb343fc95da33179ca60f4dbf46c2c36e91299d55d4e6b9010404010302000a0c20bcbe00000000000603d90130a20188182cf51901f5f500f500f5021a73c5da0a045820f036276246a75b9de3349ed42b15e232f6518fc20f5fcd4f1d64e81f9bd258f7056e426974546f6f6c2057616c6c65740601
Respuesta (BitTool Signer → tu billetera)
{
"request-id": {
"uuid": "5f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0c"
},
"signature": "3ad36b0673be3dbb71ec3b8fd346e2a96692f9ffb7b0e55ccdffe2d43f7395a447487e05522b62fed93351603483e1e2278e2193185e39761ae62d901bd1880e"
}
Mostrar el texto del QR y el CBOR sin procesar
UR:SOL-SIGNATURE/OEADTPDAGDHECKNDDWKIFTGLJLLEBDCEDPFMGWHTBNAOHDFZFTTEJEAMJKRNFSRKJSWPFRMYTEFGVOPTIYMOYTZMRLPFVWHHSNZMVOTYFHJKMDOXFLFDKBAHGMDNIDZETAEOGYHNEELSVYVODIMNCLMUCSHYESKOCYVADPMHCWTTLOBABBPLAXRD
a201d825505f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0c0258403ad36b0673be3dbb71ec3b8fd346e2a96692f9ffb7b0e55ccdffe2d43f7395a447487e05522b62fed93351603483e1e2278e2193185e39761ae62d901bd1880e
Ensamblado por tu billetera
2BDTaXgjMPuTPcFZ7v87D5Vdcn9uFky9RixC5HbiStvCDGjGasBZuymph2oEEg2TmftbyB8EgTPdtHGgE1Rjmrvh
ATrTawZzvj27cew7j9NG4qlmkvn/t7DlXM3/4tQ/c5WkR0h+BVIrYv7ZM1FgNIPh4ieOIZMYXjl2GuYtkBvRiA4BAAIF8DYnYkanW53jNJ7UKxXiMvZRj8IPX81PHWToH5vSWPdA0vJ8Rh8p6f66Gris05SWqcZbCuYqcT8B+UGpk79V7dPqjPWsrKjNBSB1EhdcQ871Sl3Znt4goWtVJTc485fcxvp6877brTo9ZfNqq8l0MbG75MLS9uDkfKYCA0UvXWEG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8AqcxJDpKM0uOHO7ND/JXaMxecpg9Nv0bCw26RKZ1V1Oa5AQQEAQMCAAoMILy+AAAAAAAG
Un mensaje de inicio de sesión; signatureBase58 es la forma que una dApp espera de signMessage.
Solicitud (tu billetera → BitTool Signer)
{
"1 request-id": {
"uuid (tag 37)": "5f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0d"
},
"2 sign-data": "5369676e20696e20746f204578616d706c6520417070207769746820536f6c616e612e204e6f6e63653a2037516d327850",
"3 derivation-path": {
"crypto-keypath (tag 304)": {
"1 components": [
44,
true,
501,
true,
0,
true,
0,
true
],
"2 source-fingerprint": 1942346250
}
},
"4 address": "f036276246a75b9de3349ed42b15e232f6518fc20f5fcd4f1d64e81f9bd258f7",
"5 origin": "BitTool Wallet",
"6 sign-type": 2
}
Mostrar el texto del QR y el CBOR sin procesar
UR:SOL-SIGN-REQUEST/OLADTPDAGDHECKNDDWKIFTGLJLLEBDCEDPFMGWHTBTAOHDEHGUINIOJTCXINJTCXJYJLCXFEKSHSJNJOJZIHCXFPJOJOCXKTINJYISCXGUJLJZHSJTHSDMCXGLJLJTIAIHFTCXEMGYJNEYKSGDAXTAADDYOEADLOCSDWYKCFADYKYKAEYKAEYKAOCYJKSKTNBKAAHDCXWTENDIIDFGOSHPNTVLEENNTYDNBZVOEYYNGYMYSABSHESNGWCAIEVSCTNDTDHDYLAHJTFWINJYGHJLJLJZCXHGHSJZJZIHJYAMAORFCXVWPK
a601d825505f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0d0258315369676e20696e20746f204578616d706c6520417070207769746820536f6c616e612e204e6f6e63653a2037516d32785003d90130a20188182cf51901f5f500f500f5021a73c5da0a045820f036276246a75b9de3349ed42b15e232f6518fc20f5fcd4f1d64e81f9bd258f7056e426974546f6f6c2057616c6c65740602
Respuesta (BitTool Signer → tu billetera)
{
"request-id": {
"uuid": "5f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0d"
},
"signature": "0fad9739ab61e6c9de54b5aa59a00568fd4c12f4e7ac8d6624595c0df3f2f390b7a19b73f9ea08650115a672a81b2e8eb16810a5766519064cbd7f4fc9bb3b01"
}
Mostrar el texto del QR y el CBOR sin procesar
UR:SOL-SIGNATURE/OEADTPDAGDHECKNDDWKIFTGLJLLEBDCEDPFMGWHTBTAOHDFZBSPMMSESPYHSVASOUEGHREPKHKNBAHISZCGSBGWKVDPSLGIYDKHKHHBTWFWZWFMHRLOYNDJKYTWDAYIHADBZOLJPPDCWDMMNPAISBEONKOIHCFAMGSRYLBGWSORKFRADOXRPNYAT
a201d825505f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0d0258400fad9739ab61e6c9de54b5aa59a00568fd4c12f4e7ac8d6624595c0df3f2f390b7a19b73f9ea08650115a672a81b2e8eb16810a5766519064cbd7f4fc9bb3b01
Ensamblado por tu billetera
KBTsdycDs2SLRqv9vaEaq7fKYeSWTRnohspq2ngCgAZhR38CYqB2GqykyQkphhpkYYsCoeNEU5YWiu2hdM2ajwe
Transacciones con varios firmantes
Cuando una transacción necesita varios firmantes (un multisig SPL, la aprobación de una propuesta de Squads, un pagador de la comisión que no es el remitente), envía una solicitud por cada clave de BitTool Signer, cada una con la ruta y la dirección de esa clave. Cada respuesta completa un espacio. Squads es un multisig a nivel de programa: el firmador ve la instrucción que aprueba una propuesta, no el contenido de la propuesta, y lo indica en la revisión.