メインコンテンツまでスキップ

Solana(sol-sign-request)

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

sol-sign-request​

キーフィールド型内容
1request-id37(bytes)ちょうど 16 バイトのランダム値
2sign-databytessign-type 1:シリアライズした メッセージ · sign-type 2:テキストメッセージのバイト列
3derivation-path304(crypto-keypath)3 つのプリセットパスのいずれかと、マスターフィンガープリント
4addressbytes (32)署名者の公開鍵。テキストではなく、Base58 でデコードしたアドレス
5origintextユーザーに表示される
6sign-typeuint1 トランザクション · 2 メッセージ

キーの順序が eth- や tron-sign-request と異なる点に注意してください。パスはキー 3、タイプはキー 6 です。

Keystone のヘルパーは 16 進を要求します

@keystonehq/bc-ur-registry-sol の SolSignRequest.constructSOLRequest() は、アドレスを Base58 ではなく、32 バイトの鍵の 16 進 文字列として受け取ります。Base58 のアドレスを渡すとキー 4 が誤った値になり、リクエストは照合されません。

sign-type 1:トランザクション​

sign-data はトランザクションの メッセージ 部分、つまり署名の対象となるバイト列です。@solana/web3.js v1 なら transaction.serializeMessage()、VersionedTransaction なら message.serialize() で得られます。(署名数とスロットから始まる)トランザクション全体でも、ハッシュでもありません。

ルール詳細
メッセージのバージョンLegacy と v0
アドレスルックアップテーブル非対応。 署名デバイスはオフラインでテーブルの内容を検証できません。v0 メッセージは静的なキーのみでコンパイルし、結果が大きすぎる場合は送信しないでください。
署名者キー 4 の鍵は、メッセージの 必須署名者 のいずれかである必要があります(手数料支払者に限らず、どのスロットでも可)。
サイズ命令は 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 は制御文字を含まない 1〜1,024 バイトの UTF-8 テキストです。トランザクションヘッダーとして読み取れるバイト列は拒否されるため、トランザクションを「メッセージ」と偽って署名させることはできません。BitTool Signer はメッセージのバイト列に直接 Ed25519 で署名します(プレフィックスもハッシュもなし)。これは Solana のウォレットアダプターの signMessage が想定する方式です。

sol-signature​

sol-signature = {
1: 37(request-id bytes),
2: signature ; 64 bytes, Ed25519
}

これはデタッチド署名です。トランザクションを完成させる手順は次のとおりです。

  1. リクエスト ID を確認し、期待する公開鍵で、メッセージのバイト列そのものに対する署名を検証します。
  2. メッセージの必須署名者の中から署名者のインデックスを見つけ、署名を そのスロット に入れます。スロット 0 とは限りません。
  3. 他のスロットにすでにある署名は保持し、必須スロットがすべて埋まったらシリアライズしてブロードキャストします。

ブロードキャスト前に blockhash の有効期限が切れた場合は、新しいメッセージと新しいリクエストを作成してください。署名を別の blockhash のメッセージに移すことはできません。

コード​

@solana/web3.js v1 と Keystone の @keystonehq/bc-ur-registry-sol を使う例です。

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
}

テストベクター​

すべて standard プリセット m/44'/501'/0'/0'(HAgk14JpMQLgt6rVgv7cBQFJWFto5Dqxi472uT3DKpqk)から生成しています。blockhash は合成したものです。

レガシーメッセージで 0.25 SOL(250,000,000 lamports)を送金します。アカウントは、手数料支払者兼署名者、受取人、System Program です。

リクエスト(ウォレット → BitTool Signer)

sol-sign-request2 フレーム
UR:SOL-SIGN-REQUEST/1-2/LPADAOCSZOCYLRESBDUEHDKBOLADTPDAGDHECKNDDWKIFTGLJLLEBDCEDPFMGWHTBDAOHDMTADAEADAXWTENDIIDFGOSHPNTVLEENNTYDNBZVOEYYNGYMYSABSHESNGWCAIEVSCTNDTDHDYLKBLKAYLTHNRSUECAUTTKEYSELBCXNDLFFWWYGMPKWNEHZSSNLOTIWDDWJNBDAMWZAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAESFGAWZEETBWS
1 / 2
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
}
QR テキストと生の CBOR を表示
QR テキスト · ur:sol-sign-request
UR:SOL-SIGN-REQUEST/1-2/LPADAOCSZOCYLRESBDUEHDKBOLADTPDAGDHECKNDDWKIFTGLJLLEBDCEDPFMGWHTBDAOHDMTADAEADAXWTENDIIDFGOSHPNTVLEENNTYDNBZVOEYYNGYMYSABSHESNGWCAIEVSCTNDTDHDYLKBLKAYLTHNRSUECAUTTKEYSELBCXNDLFFWWYGMPKWNEHZSSNLOTIWDDWJNBDAMWZAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAEAESFGAWZEETBWS
UR:SOL-SIGN-REQUEST/2-2/LPAOAOCSZOCYLRESBDUEHDKBBAMOLKTDVLLTFRQDFXZTMDTNEOCHNSOLBSGTRSFGSASRJTMEDTNTGOTYVARHADAOAOAEADBNAOAEAEAELAPRVABAAEAEAEAEAXTAADDYOEADLOCSDWYKCFADYKYKAEYKAEYKAOCYJKSKTNBKAAHDCXWTENDIIDFGOSHPNTVLEENNTYDNBZVOEYYNGYMYSABSHESNGWCAIEVSCTNDTDHDYLAHJTFWINJYGHJLJLJZCXHGHSJZJZIHJYAMADAEWYHKLNHH
UR CBOR(16 進)
a601d825505f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0b02589601000103f036276246a75b9de3349ed42b15e232f6518fc20f5fcd4f1d64e81f9bd258f77e8c088760bfde1dddcf32c17f209b8242ee52aaf131facd88d0ea2c6d0b06f20000000000000000000000000000000000000000000000000000000000000000cc490e928cd2e3873bb343fc95da33179ca60f4dbf46c2c36e91299d55d4e6b901020200010c0200000080b2e60e0000000003d90130a20188182cf51901f5f500f500f5021a73c5da0a045820f036276246a75b9de3349ed42b15e232f6518fc20f5fcd4f1d64e81f9bd258f7056e426974546f6f6c2057616c6c65740601

レスポンス(BitTool Signer → ウォレット)

デコードしたレスポンス · ur:sol-signature
{
"request-id": {
"uuid": "5f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0b"
},
"signature": "99b9a42fcb8eead46ae74c5e792098c247c21ab4d791af70a26d8392986d714fd4543af8f121c355ca32f747f321d52fc3856cf8b4ae678045a34648d924940c"
}
QR テキストと生の CBOR を表示
QR テキスト · ur:sol-signature
UR:SOL-SIGNATURE/OEADTPDAGDHECKNDDWKIFTGLJLLEBDCEDPFMGWHTBDAOHDFZNLRHOXDLSBMNWDTYIMVDGSHYKKCXMKSAFLSACYQZTSMEPEJOOEJNLSMOMKJNJSGWTYGHFTYAWNCLSRGOSGEYYLFLWFCLTLDLSRLPJZYAQZPLIOLAFEOTFGFDTADKMWBNDTZCBGGE
UR CBOR(16 進)
a201d825505f1e9b2c7d3a4e6f8a0b1c2d3e4f5a0b02584099b9a42fcb8eead46ae74c5e792098c247c21ab4d791af70a26d8392986d714fd4543af8f121c355ca32f747f321d52fc3856cf8b4ae678045a34648d924940c

ウォレット側で組み立てた結果

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

複数署名者のトランザクション​

トランザクションに複数の署名者が必要な場合(SPL マルチシグ、Squads の提案承認、送信者とは別の手数料支払者など)は、BitTool Signer の鍵ごとに 1 つのリクエストを、それぞれの鍵のパスとアドレスを付けて送ります。各レスポンスが 1 つのスロットを埋めます。Squads はプログラムレベルのマルチシグです。署名デバイスに見えるのは提案を承認する命令であって、提案の内容ではありません。確認画面にもそのことが表示されます。