Saltar al contenido principal

Solana (sol-sign-request)

ur:sol-sign-requestur:sol-signatureEd25519legacy + v0 messages

sol-sign-request​

ClaveCampoTipoContenido
1request-id37(bytes)Exactamente 16 bytes aleatorios
2sign-databytessign-type 1: el mensaje serializado · sign-type 2: los bytes del mensaje de texto
3derivation-path304(crypto-keypath)Una de las tres rutas predefinidas, con la huella maestra
4addressbytes (32)La clave pública del firmante: la dirección decodificada con Base58, no el texto
5origintextSe muestra al usuario
6sign-typeuint1 transacción · 2 mensaje

Ten en cuenta que el orden de las claves es distinto del de eth- y tron-sign-request: la ruta es la clave 3 y el tipo, la clave 6.

La función auxiliar de Keystone espera hex

SolSignRequest.constructSOLRequest() de @keystonehq/bc-ur-registry-sol recibe la dirección como una cadena hex de los 32 bytes de la clave, no en Base58. Si le pasas la dirección en Base58, la clave 4 será incorrecta y la solicitud no se asociará a ninguna cartera.

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 por el número de firmas y sus posiciones) ni un hash.

ReglaDetalle
Versiones de mensajeLegacy y v0
Tablas de búsqueda de direccionesNo compatibles. El firmante 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.
FirmanteLa clave pública indicada en la clave 4 debe ser uno de los firmantes obligatorios del mensaje (en cualquier posición, no solo el pagador de la comisión).
TamañoDe 1 a 64 instrucciones; la transacción firmada debe caber en el límite de paquete de 1232 bytes.
Nonce duraderoSe 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 comprobación de direcciones), Compute Budget, Memo, Stake (delegate, split, withdraw, deactivate) y programas de NFT habituales (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 1024 bytes, sin caracteres de control. Se rechazan los bytes que podrían interpretarse como la cabecera de una transacción, de modo que nunca se pueda hacer pasar una transacción por un «mensaje». BitTool Signer firma directamente los bytes del mensaje con Ed25519 (sin prefijo y sin hash), que es lo que espera signMessage en los wallet adapters 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:

  1. Comprueba el ID de solicitud y verifica la firma sobre los bytes exactos del mensaje con la clave pública esperada.
  2. Busca el índice del firmante entre los firmantes obligatorios del mensaje y pon la firma en esa posición. No siempre es la posición 0.
  3. Conserva las firmas que ya haya en las demás posiciones; serializa cuando estén cubiertas todas las posiciones obligatorias y difunde la transacción.

Si el blockhash caduca antes de la difusión, 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:

examples/solana.mjs
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 };
}
examples/solana.mjs
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 preajuste standard m/44'/501'/0'/0' (HAgk14JpMQLgt6rVgv7cBQFJWFto5Dqxi472uT3DKpqk). El blockhash es sintético.

Envío de 0.25 SOL (250 000 000 lamports) con un mensaje legacy: pagador de la comisión y firmante, destinatario y System Program.

Solicitud (tu cartera → BitTool Signer)

sol-sign-request2 fotogramas
UR:SOL-SIGN-REQUEST/1-2/LPADAOCSZOCYLRESBDUEHDKBOLADTPDAGDHECKNDDWKIFTGLJLLEBDCEDPFMGWHTBDAOHDMTADAEADAXWTENDIIDFGOSHPNTVLEENNTYDNBZVOEYYNGYMYSABSHESNGWCAIEVSCTNDTDHDYLKBLKAYLTHNRSUECAUTTKEYSELBCXNDLFFWWYGMPKWNEHZSSNLOTIWDDWJNBDAMWZAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAESFGAWZEETBWS
1 / 2
Campos CBOR
{
"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
Texto del QR · ur:sol-sign-request
UR:SOL-SIGN-REQUEST/1-2/LPADAOCSZOCYLRESBDUEHDKBOLADTPDAGDHECKNDDWKIFTGLJLLEBDCEDPFMGWHTBDAOHDMTADAEADAXWTENDIIDFGOSHPNTVLEENNTYDNBZVOEYYNGYMYSABSHESNGWCAIEVSCTNDTDHDYLKBLKAYLTHNRSUECAUTTKEYSELBCXNDLFFWWYGMPKWNEHZSSNLOTIWDDWJNBDAMWZAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAESFGAWZEETBWS
UR:SOL-SIGN-REQUEST/2-2/LPAOAOCSZOCYLRESBDUEHDKBBAMOLKTDVLLTFRQDFXZTMDTNEOCHNSOLBSGTRSFGSASRJTMEDTNTGOTYVARHADAOAOAEADBNAOAEAEAELAPRVABAAEAEAEAEAXTAADDYOEADLOCSDWYKCFADYKYKAEYKAEYKAOCYJKSKTNBKAAHDCXWTENDIIDFGOSHPNTVLEENNTYDNBZVOEYYNGYMYSABSHESNGWCAIEVSCTNDTDHDYLAHJTFWINJYGHJLJLJZCXHGHSJZJZIHJYAMADAEWYHKLNHH
CBOR del UR (hex)
a601d825505f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0b02589601000103f036276246a75b9de3349ed42b15e232f6518fc20f5fcd4f1d64e81f9bd258f77e8c088760bfde1dddcf32c17f209b8242ee52aaf131facd88d0ea2c6d0b06f20000000000000000000000000000000000000000000000000000000000000000cc490e928cd2e3873bb343fc95da33179ca60f4dbf46c2c36e91299d55d4e6b901020200010c0200000080b2e60e0000000003d90130a20188182cf51901f5f500f500f5021a73c5da0a045820f036276246a75b9de3349ed42b15e232f6518fc20f5fcd4f1d64e81f9bd258f7056e426974546f6f6c2057616c6c65740601

Respuesta (BitTool Signer → tu cartera)

Respuesta decodificada · ur:sol-signature
{
"request-id": {
"uuid": "5f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0b"
},
"signature": "99b9a42fcb8eead46ae74c5e792098c247c21ab4d791af70a26d8392986d714fd4543af8f121c355ca32f747f321d52fc3856cf8b4ae678045a34648d924940c"
}
Mostrar el texto del QR y el CBOR sin procesar
Texto del QR · ur:sol-signature
UR:SOL-SIGNATURE/OEADTPDAGDHECKNDDWKIFTGLJLLEBDCEDPFMGWHTBDAOHDFZNLRHOXDLSBMNWDTYIMVDGSHYKKCXMKSAFLSACYQZTSMEPEJOOEJNLSMOMKJNJSGWTYGHFTYAWNCLSRGOSGEYYLFLWFCLTLDLSRLPJZYAQZPLIOLAFEOTFGFDTADKMWBNDTZCBGGE
CBOR del UR (hex)
a201d825505f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0b02584099b9a42fcb8eead46ae74c5e792098c247c21ab4d791af70a26d8392986d714fd4543af8f121c355ca32f747f321d52fc3856cf8b4ae678045a34648d924940c

Ensamblado por tu cartera

signatureBase58
45G7cB2HaLBrWEuZxiWR2dFYu52Cje6d2vv5GnCaZ7YSc6ifhFVdFTGpJSk1PU7tSTPHjg3H4uufoe3XPmTRQiJP
signedTransactionBase64
AZm5pC/LjurUaudMXnkgmMJHwhq015GvcKJtg5KYbXFP1FQ6+PEhw1XKMvdH8yHVL8OFbPi0rmeARaNGSNkklAwBAAED8DYnYkanW53jNJ7UKxXiMvZRj8IPX81PHWToH5vSWPd+jAiHYL/eHd3PMsF/IJuCQu5SqvEx+s2I0OosbQsG8gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzEkOkozS44c7s0P8ldozF5ymD02/RsLDbpEpnVXU5rkBAgIAAQwCAAAAgLLmDgAAAAA=

Transacciones con varios firmantes​

Cuando una transacción necesita varios firmantes (una multifirma 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 una posición. Squads es una multifirma a nivel de programa: el firmante ve la instrucción que aprueba una propuesta, no el contenido de la propuesta, y así lo indica en la revisión.