Skip to main content

What the signer checks before signing

A QR code from an online device is untrusted input. BitTool Signer treats every request as potentially hostile and runs the same pipeline for every chain:

Decode and reviewNo password needed
Authorize and signPassword per task
  1. 1Assemble the QR frames and decode the request strictly (CBOR, PSBT, transaction bytes).
  2. 2Parse the exact bytes that will be signed and build the review: amounts, recipients, fees, contract calls, warnings.
  3. 3Match the request to a local wallet using public keys only, and fix a signing plan bound to the request hash.
  4. user reviews, confirms risks, enters the password ▶
  5. 4Re-derive the keys from the seed and check them against the plan before using them.
  6. 5Sign, verify the new signature, and encode the response in the request’s own format.

Strict decoding​

Before any chain logic runs, the transport is validated:

  • CBOR must use definite lengths, no duplicate map keys, no trailing bytes, at most 24 levels and 20,000 items. Unknown request fields are rejected.
  • Animated QR codes must come from one session. Mixing frames from two requests is an error, not a merge.
  • Size limits: 2,048 frames, 4,300 characters per frame, 1 MiB of scanned text, 128 KiB decoded payload.

The original CBOR (or PSBT) bytes stay attached to the decoded request. Right before signing, the engine decodes them again and refuses to sign if they no longer match what was reviewed.

The review shows the real bytes​

The engine parses the transaction or message itself; it never trusts summaries supplied by the online wallet. For example:

  • Bitcoin: every input and output with its amount and address, the fee, and which output is change. An output counts as change only if the signer re-derives its script from its own public keys. PSBT labels alone never make an output "change".
  • EVM: network, nonce, fees, recipient, native value, and decoded calls (ERC-20/721/1155 transfers and approvals, Permit, Permit2, Safe transactions and more). Spender and operator addresses are compared with a bundled list of known public contracts; an unknown spender raises the risk level.
  • TRON: contract type, amounts, TRC-20/721 calls, fee limit, permission ID.
  • Solana: every instruction it knows (System, SPL Token, Token-2022, ATA, Compute Budget, Memo, Stake and NFT programs), fee payer and signers.

Each request gets two independent labels:

DecodingRisk
Fully decoded · Partially decoded · OpaqueNormal · Warning · High

A request that is valid but not fully decoded (for example an unknown contract call) can still be signed, but only after the user explicitly acknowledges the risk for that request. There is no "blind signing" setting that stays on. Malformed bytes, unsupported transaction types and conflicting network data are rejected outright and cannot be overridden.

Known issues in 1.0.0

These are in the engine both apps share, unless noted. See Security reviews.

  • Risk acknowledgement (BTS-OP5-01 on iPhone, BTS-12 on Android, Low). High-risk requests that are fully decoded, such as an unlimited approval or setApprovalForAll, also need the explicit acknowledgement, but that rule is enforced by the app's interface (the Sign button stays disabled); the engine enforces it only for partially decoded and opaque requests. It cannot be bypassed from a QR code.
  • Solana summaries (BTS-07, Medium). An authority change, delegation or opaque instruction sent together with a tiny transfer is summarized as that transfer. On iPhone 1.0.0 a burn can be hidden the same way. The warnings and the acknowledgement remain.
  • Bitcoin change (BTS-08, Medium). An output counts as change at any index of the wallet's own single-sig profiles, including indexes a watch-only wallet will never scan.
  • EVM and TRON summaries (BTS-09, Low–Medium). The stored summary omits native value sent with an unknown contract call; the review card shows it.

Matching the right wallet​

The user never picks which wallet signs. The engine compares the request with the public data of every local wallet and accepts exactly one match:

ChainEvidence used
Bitcoinbip32Derivation fingerprint and path, checked against the input's actual script. Without derivation data only /0/0 of each single-sig account is tried.
EVMDerivation path (+ master fingerprint) and/or address. Without a path only index 0 is compared.
TRONDerivation path (any index), or an address compared with indexes 0–99.
SolanaOne of the three preset keys, which must also occupy a required signer slot in the message.

No match, a missing signer, an ambiguous match (two wallets fit) and incomplete wallet data are distinct errors. The signer never tries the seed of every wallet.

The result is an immutable plan that binds the canonical request hash, the original bytes, the exact signing bytes and the target keys and inputs. After the password is entered, the engine re-derives the keys, rebuilds the public data, re-runs matching and compares the full plan before it touches a private key.

What this means for your integration

Always include derivation data: bip32Derivation in PSBTs, and the path and fingerprint in eth-, tron- and sol-sign-request. Requests with complete signer data match immediately; requests without it fall back to index 0 and may fail for any other account.

Binding the signature to the request​

  • The engine hashes the whole canonical request, signs, verifies its own signature, and returns the result together with the original request hash. If the user leaves the app or a new request replaces the old one while signing, a late result is discarded.
  • EVM, TRON and Solana responses echo the request ID, and the signature covers the exact reviewed bytes. Bitcoin responses return the same PSBT with signatures added; every other field must be unchanged.
  • One PSBT can need several local wallets (a multisig with two keys on the same phone). Each wallet is a separate task with its own password, and partial signatures are kept between tasks.

Both security reviews of version 1.0.0 checked this chain. The Opus 5 review confirmed by execution that a tampered plan is refused and that the only signing entry point in Release builds goes through all of these checks.

What is out of scope offline​

The review says so explicitly when it cannot know something: token contract behavior, current balances and allowances, whether a TRON multisig member is still authorized, whether a Solana blockhash is still valid, or what a Blur Merkle root really commits to. Your wallet, which is online, is in a better position to show that context before it creates the QR code.