Solana(sol-sign-request)
sol-sign-request
| 鍵 | 欄位 | 類型 | 內容 |
|---|---|---|---|
| 1 | request-id | 37(bytes) | 正好 16 個隨機位元組 |
| 2 | sign-data | bytes | sign-type 1:序列化後的 訊息 · sign-type 2:文字訊息位元組 |
| 3 | derivation-path | 304(crypto-keypath) | 三個預設組合路徑之一,並附上主指紋 |
| 4 | address | bytes(32) | 簽名者的公鑰:經 Base58 解碼後的地址,而非文字 |
| 5 | origin | text | 向使用者顯示 |
| 6 | sign-type | uint | 1 交易 · 2 訊息 |
請注意,鍵的順序與 eth- 和 tron-sign-request 不同:路徑是鍵 3,類型是鍵 6。
@keystonehq/bc-ur-registry-sol 中的 SolSignRequest.constructSOLRequest() 所接受的地址,是 32 個金鑰位元組的 hex 字串,而非 Base58。傳入 Base58 地址會產生錯誤的鍵 4,請求也將無法比對成功。
sign-type 1:交易
sign-data 是交易中的 訊息 部分,也就是簽名所涵蓋的位元組:在 @solana/web3.js v1 中為 transaction.serializeMessage(),對 VersionedTransaction 則為 message.serialize()。它不是完整的交易(完整交易以簽名數量與簽名位置開頭),也不是雜湊。
| 規則 | 細節 |
|---|---|
| 訊息版本 | Legacy 與 v0 |
| Address lookup table | 不支援。 簽名器無法在離線狀態下驗證查找表的內容。編譯 v0 訊息時只使用靜態金鑰;如果結果太大,就不要送出。 |
| 簽名者 | 鍵 4 中的金鑰必須是該訊息的 必要簽名者 之一(任何位置皆可,不限於 fee payer)。 |
| 大小 | 1–64 個指令;已簽名的交易必須符合 1,232 位元組的封包上限。 |
| Durable nonce | 允許,但會在審核畫面中特別標示(這種交易會無限期保持有效)。 |
可解碼的程式:System(transfer、create account、nonce、assign、allocate)、SPL Token 與 Token-2022(transfer、transferChecked、approve、mint、burn、close、set authority、sync native)、Associated Token Account(含地址檢查)、Compute Budget、Memo、Stake(delegate、split、withdraw、deactivate),以及常見的 NFT 程式(Metaplex Token Metadata、Bubblegum、Core)。Token-2022 擴充功能與 Squads 提案指令會部分解碼。其他程式則以原始指令顯示,並需要使用者明確同意。
sign-type 2:訊息
sign-data 是 UTF-8 文字,長度 1–1,024 位元組,不含控制字元。可能被解讀為交易標頭的位元組會被拒絕,因此交易永遠無法偽裝成「訊息」。BitTool Signer 會直接以 Ed25519 簽署訊息位元組(不加前綴、不做雜湊),這正是 Solana wallet adapter 中 signMessage 所預期的方式。
sol-signature
sol-signature = {
1: 37(request-id bytes),
2: signature ; 64 bytes, Ed25519
}
這是分離式簽名(detached signature)。完成交易的步驟:
- 檢查請求 ID,並以預期的公鑰針對確切的訊息位元組驗證簽名。
- 在訊息的必要簽名者中找出該簽名者的索引,並將簽名放入 該位置。它不一定是位置 0。
- 保留其他位置中已有的簽名;所有必要位置都填滿後,再序列化並廣播。
如果 blockhash 在廣播前過期,請建構新的訊息與新的請求。簽名無法移轉到使用另一個 blockhash 的訊息上。
程式碼
使用 @solana/web3.js v1 與 Keystone 的 @keystonehq/bc-ur-registry-sol:
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
}
測試向量
全部來自 standard 預設組合 m/44'/501'/0'/0'(HAgk14JpMQLgt6rVgv7cBQFJWFto5Dqxi472uT3DKpqk)。blockhash 為合成資料。
- SOL 轉帳
- SPL 代幣(USDC)
- 訊息
以 legacy 訊息傳送 0.25 SOL(250,000,000 lamports):fee payer 兼簽名者、收款人、System Program。
請求(你的錢包 → 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
}
顯示 QR 文字與原始 CBOR
UR:SOL-SIGN-REQUEST/1-2/LPADAOCSZOCYLRESBDUEHDKBOLADTPDAGDHECKNDDWKIFTGLJLLEBDCEDPFMGWHTBDAOHDMTADAEADAXWTENDIIDFGOSHPNTVLEENNTYDNBZVOEYYNGYMYSABSHESNGWCAIEVSCTNDTDHDYLKBLKAYLTHNRSUECAUTTKEYSELBCXNDLFFWWYGMPKWNEHZSSNLOTIWDDWJNBDAMWZAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAESFGAWZEETBWS
UR:SOL-SIGN-REQUEST/2-2/LPAOAOCSZOCYLRESBDUEHDKBBAMOLKTDVLLTFRQDFXZTMDTNEOCHNSOLBSGTRSFGSASRJTMEDTNTGOTYVARHADAOAOAEADBNAOAEAEAELAPRVABAAEAEAEAEAXTAADDYOEADLOCSDWYKCFADYKYKAEYKAEYKAOCYJKSKTNBKAAHDCXWTENDIIDFGOSHPNTVLEENNTYDNBZVOEYYNGYMYSABSHESNGWCAIEVSCTNDTDHDYLAHJTFWINJYGHJLJLJZCXHGHSJZJZIHJYAMADAEWYHKLNHH
a601d825505f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0b02589601000103f036276246a75b9de3349ed42b15e232f6518fc20f5fcd4f1d64e81f9bd258f77e8c088760bfde1dddcf32c17f209b8242ee52aaf131facd88d0ea2c6d0b06f20000000000000000000000000000000000000000000000000000000000000000cc490e928cd2e3873bb343fc95da33179ca60f4dbf46c2c36e91299d55d4e6b901020200010c0200000080b2e60e0000000003d90130a20188182cf51901f5f500f500f5021a73c5da0a045820f036276246a75b9de3349ed42b15e232f6518fc20f5fcd4f1d64e81f9bd258f7056e426974546f6f6c2057616c6c65740601
回應(BitTool Signer → 你的錢包)
{
"request-id": {
"uuid": "5f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0b"
},
"signature": "99b9a42fcb8eead46ae74c5e792098c247c21ab4d791af70a26d8392986d714fd4543af8f121c355ca32f747f321d52fc3856cf8b4ae678045a34648d924940c"
}
顯示 QR 文字與原始 CBOR
UR:SOL-SIGNATURE/OEADTPDAGDHECKNDDWKIFTGLJLLEBDCEDPFMGWHTBDAOHDFZNLRHOXDLSBMNWDTYIMVDGSHYKKCXMKSAFLSACYQZTSMEPEJOOEJNLSMOMKJNJSGWTYGHFTYAWNCLSRGOSGEYYLFLWFCLTLDLSRLPJZYAQZPLIOLAFEOTFGFDTADKMWBNDTZCBGGE
a201d825505f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0b02584099b9a42fcb8eead46ae74c5e792098c247c21ab4d791af70a26d8392986d714fd4543af8f121c355ca32f747f321d52fc3856cf8b4ae678045a34648d924940c
由你的錢包組裝
45G7cB2HaLBrWEuZxiWR2dFYu52Cje6d2vv5GnCaZ7YSc6ifhFVdFTGpJSk1PU7tSTPHjg3H4uufoe3XPmTRQiJP
AZm5pC/LjurUaudMXnkgmMJHwhq015GvcKJtg5KYbXFP1FQ6+PEhw1XKMvdH8yHVL8OFbPi0rmeARaNGSNkklAwBAAED8DYnYkanW53jNJ7UKxXiMvZRj8IPX81PHWToH5vSWPd+jAiHYL/eHd3PMsF/IJuCQu5SqvEx+s2I0OosbQsG8gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzEkOkozS44c7s0P8ldozF5ymD02/RsLDbpEpnVXU5rkBAgIAAQwCAAAAgLLmDgAAAAA=
以 TransferChecked(6 位小數)從簽名者的關聯代幣帳戶 5N3f1tj9v1vc5TUZ8S7mCAnVmjVKrfnzXWhxLaxyZAgt 傳送 12.5 USDC 到收款人的 FGETo8T8wMcN2wCjav8VK6eh3dLk63evNDPxzLSJra8B。
請求(你的錢包 → 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
}
顯示 QR 文字與原始 CBOR
UR:SOL-SIGN-REQUEST/1-2/LPADAOCFADFRCYGEKNLGGDHDNNOLADTPDAGDHECKNDDWKIFTGLJLLEBDCEDPFMGWHTBNAOHDTBADAEAOAHWTENDIIDFGOSHPNTVLEENNTYDNBZVOEYYNGYMYSABSHESNGWCAIEVSCTNDTDHDYLFZTDWZKEFGCTDTWLZERDCYROPSTEMWMTPTSWHPBKVADRJSFHADYTFPPTMURSGOWETEWDLKYKPSPSPDSNAHCXKPBGCHHHFXTOYKGEHLTANNUECXOYJEGODAEMETWFMSUOSWZSKNWFRNUYPMFTFSIHWFIMPYSOJYEHPARKVESATDYNVTVEKEOLAOAXFEDLHLHSAMUTSEPTJPLD
UR:SOL-SIGN-REQUEST/2-2/LPAOAOCFADFRCYGEKNLGGDHDNNYNVYTSIHOYMUTASBVYFGTOWMKKPSCEQZLPWEHEHPEMMEFTLKYKLPKBZMAEPTSFGABAMOLKTDVLLTFRQDFXZTMDTNEOCHNSOLBSGTRSFGSASRJTMEDTNTGOTYVARHADAAAAADAXAOAEBKBNCXRFRNAEAEAEAEAEAMAXTAADDYOEADLOCSDWYKCFADYKYKAEYKAEYKAOCYJKSKTNBKAAHDCXWTENDIIDFGOSHPNTVLEENNTYDNBZVOEYYNGYMYSABSHESNGWCAIEVSCTNDTDHDYLAHJTFWINJYGHJLJLJZCXHGHSJZJZIHJYAMADAEHTNETOQZ
a601d825505f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0c0258d601000205f036276246a75b9de3349ed42b15e232f6518fc20f5fcd4f1d64e81f9bd258f740d2f27c461f29e9feba1ab8acd39496a9c65b0ae62a713f01f941a993bf55edd3ea8cf5acaca8cd05207512175c43cef54a5dd99ede20a16b55253738f397dcc6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d6106ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9cc490e928cd2e3873bb343fc95da33179ca60f4dbf46c2c36e91299d55d4e6b9010404010302000a0c20bcbe00000000000603d90130a20188182cf51901f5f500f500f5021a73c5da0a045820f036276246a75b9de3349ed42b15e232f6518fc20f5fcd4f1d64e81f9bd258f7056e426974546f6f6c2057616c6c65740601
回應(BitTool Signer → 你的錢包)
{
"request-id": {
"uuid": "5f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0c"
},
"signature": "3ad36b0673be3dbb71ec3b8fd346e2a96692f9ffb7b0e55ccdffe2d43f7395a447487e05522b62fed93351603483e1e2278e2193185e39761ae62d901bd1880e"
}
顯示 QR 文字與原始 CBOR
UR:SOL-SIGNATURE/OEADTPDAGDHECKNDDWKIFTGLJLLEBDCEDPFMGWHTBNAOHDFZFTTEJEAMJKRNFSRKJSWPFRMYTEFGVOPTIYMOYTZMRLPFVWHHSNZMVOTYFHJKMDOXFLFDKBAHGMDNIDZETAEOGYHNEELSVYVODIMNCLMUCSHYESKOCYVADPMHCWTTLOBABBPLAXRD
a201d825505f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0c0258403ad36b0673be3dbb71ec3b8fd346e2a96692f9ffb7b0e55ccdffe2d43f7395a447487e05522b62fed93351603483e1e2278e2193185e39761ae62d901bd1880e
由你的錢包組裝
2BDTaXgjMPuTPcFZ7v87D5Vdcn9uFky9RixC5HbiStvCDGjGasBZuymph2oEEg2TmftbyB8EgTPdtHGgE1Rjmrvh
ATrTawZzvj27cew7j9NG4qlmkvn/t7DlXM3/4tQ/c5WkR0h+BVIrYv7ZM1FgNIPh4ieOIZMYXjl2GuYtkBvRiA4BAAIF8DYnYkanW53jNJ7UKxXiMvZRj8IPX81PHWToH5vSWPdA0vJ8Rh8p6f66Gris05SWqcZbCuYqcT8B+UGpk79V7dPqjPWsrKjNBSB1EhdcQ871Sl3Znt4goWtVJTc485fcxvp6877brTo9ZfNqq8l0MbG75MLS9uDkfKYCA0UvXWEG3fbh12Whk9nL4UbO63msHLSF7V9bN5E6jPWFfv8AqcxJDpKM0uOHO7ND/JXaMxecpg9Nv0bCw26RKZ1V1Oa5AQQEAQMCAAoMILy+AAAAAAAG
一則登入訊息;signatureBase58 是 dApp 預期從 signMessage 取得的格式。
請求(你的錢包 → 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
}
顯示 QR 文字與原始 CBOR
UR:SOL-SIGN-REQUEST/OLADTPDAGDHECKNDDWKIFTGLJLLEBDCEDPFMGWHTBTAOHDEHGUINIOJTCXINJTCXJYJLCXFEKSHSJNJOJZIHCXFPJOJOCXKTINJYISCXGUJLJZHSJTHSDMCXGLJLJTIAIHFTCXEMGYJNEYKSGDAXTAADDYOEADLOCSDWYKCFADYKYKAEYKAEYKAOCYJKSKTNBKAAHDCXWTENDIIDFGOSHPNTVLEENNTYDNBZVOEYYNGYMYSABSHESNGWCAIEVSCTNDTDHDYLAHJTFWINJYGHJLJLJZCXHGHSJZJZIHJYAMAORFCXVWPK
a601d825505f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0d0258315369676e20696e20746f204578616d706c6520417070207769746820536f6c616e612e204e6f6e63653a2037516d32785003d90130a20188182cf51901f5f500f500f5021a73c5da0a045820f036276246a75b9de3349ed42b15e232f6518fc20f5fcd4f1d64e81f9bd258f7056e426974546f6f6c2057616c6c65740602
回應(BitTool Signer → 你的錢包)
{
"request-id": {
"uuid": "5f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0d"
},
"signature": "0fad9739ab61e6c9de54b5aa59a00568fd4c12f4e7ac8d6624595c0df3f2f390b7a19b73f9ea08650115a672a81b2e8eb16810a5766519064cbd7f4fc9bb3b01"
}
顯示 QR 文字與原始 CBOR
UR:SOL-SIGNATURE/OEADTPDAGDHECKNDDWKIFTGLJLLEBDCEDPFMGWHTBTAOHDFZBSPMMSESPYHSVASOUEGHREPKHKNBAHISZCGSBGWKVDPSLGIYDKHKHHBTWFWZWFMHRLOYNDJKYTWDAYIHADBZOLJPPDCWDMMNPAISBEONKOIHCFAMGSRYLBGWSORKFRADOXRPNYAT
a201d825505f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0d0258400fad9739ab61e6c9de54b5aa59a00568fd4c12f4e7ac8d6624595c0df3f2f390b7a19b73f9ea08650115a672a81b2e8eb16810a5766519064cbd7f4fc9bb3b01
由你的錢包組裝
KBTsdycDs2SLRqv9vaEaq7fKYeSWTRnohspq2ngCgAZhR38CYqB2GqykyQkphhpkYYsCoeNEU5YWiu2hdM2ajwe
多簽名者交易
當一筆交易需要多位簽名者(SPL 多重簽名、Squads 提案核准、fee payer 不是傳送者)時,請針對每把 BitTool Signer 金鑰各送出一個請求,每個請求都帶有該金鑰的路徑與地址。每個回應會填入一個位置。Squads 是程式層級的多重簽名:簽名器看到的是核准提案的指令,而非提案的內容,並會在審核畫面中說明這一點。