QR による署名
ペアリングが済めば、どの署名も 1 往復で完了します。ウォレットが リクエスト の QR コードを表示し、BitTool Signer が レスポンス の QR コードを表示します。
| チェーン | リクエスト(ウォレット → 署名デバイス) | レスポンス(署名デバイス → ウォレット) | ページ |
|---|---|---|---|
| Bitcoin | ur:crypto-psbt(ur:psbt、BBQr、Base64/16 進テキストにも対応) | 同じ型に署名を追加したもの | Bitcoin |
| EVM | ur:eth-sign-request | ur:eth-signature | EVM |
| TRON | ur:tron-sign-request | ur:tron-signature | TRON |
| Solana | ur:sol-sign-request | ur:sol-signature | Solana |
これらは公開されている Keystone UR レジストリの型です。ウォレットレベルのペアリングフォーマットはアカウントのインポート専用です。BitTool 独自の署名用エンベロープは存在せず、新たに考案すべきでもありません。
リクエストの構成要素
EVM、TRON、Solana のリクエストは 整数キー を持つ CBOR マップで、そのまま UR 本体として運ばれます(ウォレットレベルのペアリングと異なり、CBOR は 1 層です)。これらは 3 つの構成要素を共有しています。
リクエスト ID:37(bytes)
ランダムな識別子で、バイト文字列を CBOR タグ 37 で包んだものです。BitTool Signer はこれを変更せずにレスポンスへコピーするので、レスポンスを保留中のリクエストと対応付けられます。
- リクエストごとに 16 バイトのランダム値 を生成してください(UUID のテキストではなく、そのバイト列)。
sol-sign-requestとtron-sign-requestはちょうど 16 バイトを必須とします。eth-sign-requestは、より長い不透明な ID を使うウォレットとの互換性のため、1〜64 バイトも受け付けます。- ID はバイト列として比較してください。ID が一致しても署名が有効だとは限らないため、署名の検証は別途必要です。
導出パス:304(crypto-keypath)
署名すべき鍵のフルパスと、マスターフィンガープリントです。
304({
1: [44, true, 60, true, 0, true, 0, false, 0, false], ; m/44'/60'/0'/0/0: index, hardened, index, hardened …
2: 1942346250 ; master fingerprint 0x73c5da0a as a big-endian uint32
})
- 要素は
index, hardened?の順に交互に並びます。強化インデックスに0x80000000を加算 しないでください。 - アカウントパスや
*ワイルドカードではなく、具体的なリーフのパス(…/0/5)を使ってください。 - フィンガープリント(キー 2)は必ず含めてください。含めないと照合の精度が下がり、まったく照合できないリクエストも出てきます。
cborg で組み立てるヘルパーです。
import { Tagged } from 'cborg';
/** crypto-keypath (tag 304): "m/44'/195'/0'/0/0" + "73c5da0a" →
* 304({1: [44, true, 195, true, 0, true, 0, false, 0, false], 2: 1942346250}) */
export function keypath(path, masterFingerprint) {
const components = path.replace(/^m\//, '').split('/')
.flatMap((part) => [Number.parseInt(part, 10), part.endsWith("'")]);
return new Tagged(304, new Map([[1, components], [2, Number.parseInt(masterFingerprint, 16)]]));
}
Origin:テキスト
リクエストの送信元としてユーザーに表示される短いラベルです(例:"BitTool Wallet")。最大 256 文字です。表示されるだけで認証はされません。BitTool Signer がこれを何かの証明として扱うことはありません。
追加フィールドは不可
受け付けるのは各型に定義されたキーだけです。eth-sign-request は 1〜7、sol- と tron-sign-request は 1〜6 です。未知のキーが 1 つでもあるとリクエスト全体が無効になるため、独自のメタデータを追加しないでください。
アニメーション QR コード
リクエストは 1 つの QR コードに収まらないことがよくあります。マルチパート UR としてエンコードし、フレームをループ表示してください。
- UR テキストは大文字にします。
UR:ETH-SIGN-REQUEST/…は QR の英数字モードに収まり、バイトモードより約 30% 少ないビット数で済みます。 - フラグメントあたり 200〜400 バイト、毎秒 3〜5 フレームであれば、スマートフォンで安定してスキャンできます。
- 最初の 1 巡を繰り返し再生するのではなく、エンコーダーの
nextPart()を呼び続けてください。1 巡目の後、bc-ur エンコーダーは混合された「ファウンテン」パートを生成し、フレームを取りこぼしてもスキャナーがデコードを完了できるようになります。
BitTool Signer は自身のレスポンスを 180 バイトのフラグメントでエンコードするため、短い署名は 1 枚の静的な QR コードになり、署名済み PSBT はアニメーションになることがあります。スキャナーはその両方に対応する必要があります。
レスポンス
- まず UR の型を確認します。
eth-sign-requestへのレスポンスはeth-signatureでなければならない、といった具合です。Bitcoin は送信したものと同じ型で応答します。 - リクエスト ID を確認します(Bitcoin の場合は、PSBT 内の未署名トランザクションが変更されていないことを確認します)。
- 期待する鍵と、署名を依頼したバイト列そのものに対して署名を暗号学的に検証し、その後トランザクションを組み立ててブロードキャストします。
拒否を示すレスポンスはありません。 ユーザーが BitTool Signer でキャンセルした場合、何も表示されません。UI では、ユーザーが前の画面に戻る、QR を再表示する、最初からやり直す、といった操作をできるようにし、応答を無期限に待ち続けないようにしてください。
保留中レコードを保持する
各リクエストは、応答が得られるか破棄されるまで保存しておきます。
pendingId, walletId, chain/network, signer path + key/address,
requestId, request type, exact bytes to sign, digest,
unsigned transaction, created at, expiry (nonce / blockhash / TRON expiration),
status: awaitingSignature → signatureVerified → broadcast → confirmed | failed | cancelled
トランザクションの内容(手数料、nonce、blockhash、金額)が少しでも変わったら、新しい ID で 新しい リクエストを作成してください。署名を別のバイト列に使い回してはいけません。また、キャンセル済みまたは期限切れのリクエストに対して届いたレスポンスは拒否してください。
BitTool Signer が鍵を見つける仕組み
どのウォレットを使うかを BitTool Signer に指示する必要はありません。BitTool Signer は、リクエストに含まれるパス、フィンガープリント、アドレスまたは公開鍵を、端末内のすべてのウォレットの公開鍵と照合して判断します。一致するウォレットはちょうど 1 つでなければなりません。詳しいルールは 署名デバイスが行うチェック を参照してください。実際には、すべてのリクエストにフルパス、フィンガープリント、アドレス(または公開鍵)を含めれば、BitTool Signer は鍵を直接見つけられます。