透過 QR Code 簽名
配對完成後,每一次簽名都只需一次往返:你的錢包顯示 請求 QR Code,BitTool Signer 顯示 回應 QR Code。
| 鏈 | 請求(你 → 簽名器) | 回應(簽名器 → 你) | 頁面 |
|---|---|---|---|
| Bitcoin | ur:crypto-psbt(亦支援 ur:psbt、BBQr、Base64/hex 文字) | 相同類型,並加入簽名 | 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 registry 中的類型。錢包層級配對格式僅用於匯入帳戶;並沒有 BitTool 專屬的簽名封裝格式,也不應自行發明。
請求的基本組成
EVM、TRON 與 Solana 的請求是以 整數鍵 組成的 CBOR map,直接作為 UR 主體傳送(只有一層 CBOR,不同於錢包層級配對)。它們共用三個基本組成元件。
請求 ID:37(bytes)
一個隨機識別碼,即以 CBOR 標籤 37 包住的位元組字串。BitTool Signer 會將它原封不動地複製到回應中,讓你能將回應與待處理的請求對應起來。
- 每個請求都產生 16 個隨機位元組(使用 UUID 的位元組,而非其文字形式)。
sol-sign-request與tron-sign-request要求正好 16 個位元組。為了相容使用較長不透明 ID 的錢包,eth-sign-request也接受 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。出現未知的鍵會使整個請求無效,因此請勿加入自己的中繼資料。
動態 QR Code
請求的大小經常超出單一 QR Code 的容量。請將它編碼為多部分 UR,並循環播放各幀:
- 將 UR 文字轉為大寫:
UR:ETH-SIGN-REQUEST/…可使用 QR 的英數字模式,所需位元數比位元組模式少約 30%。 - 每個片段 200–400 位元組、每秒 3–5 幀,在手機上能穩定掃描。
- 持續呼叫編碼器的
nextPart(),而不是重播第一輪。第一輪之後,bc-ur 編碼器會產生混合的「fountain」部分,即使漏掉幾幀,掃描器也能完成接收。
BitTool Signer 以 180 位元組的片段編碼自己的回應,因此較短的簽名會是單一靜態 QR Code,而已簽名的 PSBT 則可能是動態的。你的掃描器必須兩者都能處理。
回應
- 先檢查 UR 類型:
eth-sign-request的回應必須是eth-signature,以此類推。Bitcoin 會以你送出的相同類型回應。 - 檢查請求 ID(Bitcoin:檢查 PSBT 內的未簽名交易未被更動)。
- 以預期的金鑰,針對你要求簽署的確切位元組,以密碼學方式驗證簽名,然後組裝並廣播。
沒有拒絕回應。 如果使用者在 BitTool Signer 中取消,就不會顯示任何內容。你的 UI 應讓使用者可以返回、再次顯示 QR Code 或重新開始,且絕不應無限期等待回覆。
保存待處理紀錄
儲存每個請求,直到它得到回應或被放棄為止:
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 就能直接找到金鑰。