Repository navigation
Import a cold wallet's NEAR public key and accept cold wallets in all near commands - #175
Conversation
A cold wallet file holds only an SS58 address, which cannot be turned back
into the ML-DSA-65 key NEAR needs. `quantus near import-cold-key` reads the
key export the cold wallet app shows (v1 {kind: near-public-key, address,
near_public_key}), refuses it unless the key hashes to the wallet's stored
address, and saves it on the wallet record. Every `quantus near` command
then accepts the cold wallet: hot wallets sign locally, cold wallets run the
QR roundtrip of sign-cold.
The simulator gains --export-near-key to emit the same payload a device
would, for end-to-end testing.
n13
left a comment
There was a problem hiding this comment.
Reviewer model: GPT-6 Sol
Verdict: Request changes. The import can report success after saving a public key to a different cold wallet address.
- [P2] Bind the save to the address that was validated (
src/cli/near.rs:377-411,src/wallet/mod.rs:362-369).handle_import_cold_keyreads the wallet address before waiting for the QR scan. If another process deletes and reimports that wallet name with a different address during the scan,set_cold_near_public_keyreloads the replacement record and saves the previously validated key to it. Its compare-and-save check only protects changes after that reload, so the command reports success with a mismatched key; latershow-keyandsendreject the wallet. Pass the validated address or wallet snapshot into the setter and reject the update if the current record differs, or validate the key against the current address as part of the guarded save.
Validation: reviewed the full diff and the cold wallet QR export at Quantus-Network/quantus-apps#677; git diff --check and cargo +nightly fmt --all -- --check passed locally. All seven PR checks passed, including macOS and Ubuntu build/test jobs. I did not rerun the full Rust test suite locally.
The address is read before the QR scan waits; if the wallet name is deleted and re-imported with another address meanwhile, the guarded save would have attached the validated key to the replacement record.
|
Addressed in a9510f2. [P2] Bind the save to the validated address —
|
n13
left a comment
There was a problem hiding this comment.
Reviewer model: GPT-6 Sol
Verdict: Approve. The new address check resolves my earlier finding: the import refuses to save a validated key if the wallet name now points to a different address. I found no blocking issues at head a9510f2.
I reviewed the full diff against base 72a89a8, including the cold-signing path and the cold wallet app's export format. The decoder checks the key scheme, derived Quantus address, export address, and exact wire fields; cold transaction signing verifies the returned signature and wallet address.
Validation: git diff --check and cargo +nightly fmt --all -- --check passed. Focused Rust tests passed (7 export, 2 wallet persistence, 4 cold NEAR signing). An isolated CLI roundtrip passed for simulated QR export, import, show-key, repeat import, and hot-wallet rejection. Ubuntu build/tests, Clippy/docs, security audit, format, dependency checks, and examples are green; the macOS CI build/test job was still running when I posted this review.
Summary
Step 5 of the cold-wallet NEAR plan: the CLI side of the NEAR public key export QR added in Quantus-Network/quantus-apps#677.
A cold wallet file holds only an SS58 address, which is a hash of the key and cannot be turned back into the 1952-byte ML-DSA-65 public key NEAR needs for
AddKeyor to name a transaction's signer. Until nownear show-key,create-account,keys,sendanddaowere hot-wallet only.quantus near import-cold-key --wallet <cold> [--key ml-dsa-65:<b58>]— scans the export QR the cold wallet app shows (account → Show public key → NEAR), or takes the key as text. Refused unless the key hashes to the wallet's stored address and (when scanned) the export'saddressmatches, so another device's QR cannot be attached to the wallet. Saved on the wallet record viasave_wallet_if_current.EncryptedWallet.near_public_key: Option<String>—#[serde(default, skip_serializing_if = "Option::is_none")]; absent for hot wallets and for cold wallets written before this change.qr::key_export::NearPublicKeyExport— strict decoder for{v: 1, kind: "near-public-key", address, near_public_key}(deny_unknown_fields), mirrorsNearPublicKeyExportin quantus_sdk.public_key()re-derives the AccountId32 from the key and compares it toaddress.quantus nearcommand now accepts a cold wallet.load_ml_dsa_65_walletbecameload_near_signerreturning aNearSigner::{Hot, Cold};sendand the DAO calls sign through it, so a cold wallet runs the same QR roundtrip assign-cold. The key is re-checked against the address every time it is read from the (unencrypted) wallet file. Cold wallets with no imported key get an error pointing atimport-cold-key.developer cold-sign-sim --export-near-key— emits the same payload a device would, for end-to-end testing without hardware.quantus wallet create-cold(the command isimport-cold).Testing
./clippy.shclean (nightly fmt, taplo, clippy-D warnings).cargo test --all-features: 419 passed. New: 7 tests inqr::key_export(round trip, SDK key set, unknown keys / version / kind, signing request rejected, key–address mismatch, non-ML-DSA key, bad address),test_cold_wallet_near_public_key_round_tripinwallet.$HOME: create ML-DSA-65 hot wallet →import-coldits address →cold-sign-sim --export-near-key→near import-cold-key --cold-response-in→near show-key --wallet <cold>prints the same key and handle as the hot wallet; re-import reports "already holds"; import into a cold wallet with a different address, scanning another device's export, and importing into a hot wallet are all refused with specific errors;near keys --wallet <cold>works without a password prompt.Notes
near create-account --wallet <cold>needs no QR: the parent signs; only the public key is read.send/dao(sign_transaction_cold) is the one already exercised bysign-coldin Add cold-wallet signing for NEAR transactions #174; not re-run against testnet here as it requires a funded account with the key registered.