With proof of work skipped, chaining headers from a checkpoint up to a
transaction's block proves nothing: a server can fabricate a header and
fake headers after a real checkpoint. The header must now hash-chain up to
the nearest checkpoint at or above its height, whose hash is hardcoded.
This also anchors heights below the first checkpoint. The cached anchor
state is renamed AnchoredDownTo and caches without it are re-verified
once. Blocks above the newest checkpoint remain a documented limit.
See ADR 0040.
After connecting, the Send tab offers Fast/Normal/Economy rates from the
server's fee estimates (never below the relay fee, implausible values
ignored). A manual rate below the relay fee is refused before building and
a fee above 1% of the amount is flagged in the preview. See ADR 0039.
The password no longer has to be a command-line argument: it can come from
--password-env, --password-stdin or a no-echo prompt (--password still works
but warns). Plaintext storage is now an explicit opt-in (--no-encryption)
instead of the result of a forgotten flag. See ADR 0038.
Adding a contact now checks the address against the wallet's network and
rejects duplicates; whitespace in a pasted address is removed. Before, any
string was stored and the error only surfaced at send time.
After the handshake the wallet checks server.features genesis_hash and,
when the profile has checkpoints, the header at the highest checkpoint
not above the server's tip (mainnet shares Bitcoin's genesis, so the
checkpoint is what tells the chains apart). A server on another chain is
refused with WrongChainException and the next candidate is tried. Used by
the GUI, peer discovery, the CLI and the server probe. See ADR 0036.
On Italian/German systems "1.5" was read as 15 sat/vB. The Send form,
the Donate form and the CLI now share CoinAmount.TryParseFeeRate
(invariant culture, '.' or ',' as decimal separator, range (0, 1000]).
Donate and the CLI report an invalid rate instead of silently using 1.
Explains why initial sync scales with transaction count rather than
wallet age (one Merkle-proof round trip per confirmed transaction, no
batching in the Electrum-style protocol) and why it's slower on mobile,
plus why later syncs are fast (cache persists proofs/headers/anchoring
state). Advises against using this wallet as a mining payout address:
the many small transactions typical of payouts make sync noticeably
slower (observed ~2 minutes past 5,000 transactions).
ChainProfiles.Mainnet.Checkpoints was an empty array with a "populate
before release" TODO, and even BlockHeaderInfo.MatchesCheckpoint()/
IsValidChild() — the methods meant to enforce it — were never called
anywhere in WalletSynchronizer. So on this LWMA chain, where PoW can't
be recomputed client-side (SkipPowValidation), a malicious or
eclipsing server could hand back any internally-consistent 80-byte
header for a Merkle proof: nothing tied it to the real Palladium
chain.
Populated Mainnet.Checkpoints with 24 real [height, hash, bits]
checkpoints (every 20,000 blocks + one near tip), pulled via RPC from
a fully-synced palladiumd. Testnet/Regtest are left empty (no node
available to verify against, and WalletSynchronizer already treats a
missing checkpoint as a no-op, so this is safe, just unanchored).
Added WalletSynchronizer.AnchorToCheckpointAsync: for every header
used in a Merkle proof, downloads the intervening headers back to the
nearest checkpoint at or below that height and verifies an unbroken
prev-hash chain terminating in the checkpoint's exact hash, finally
wiring up the previously-dead MatchesCheckpoint/IsValidChild. Verified
ranges are memoized in-memory per sync session to avoid re-walking.
Updated SECURITY.md and USERGUIDE.md to describe what is actually
enforced now (previously the guide deliberately avoided promising
checkpoint anchoring, since the array was empty).
The CLI printed all wallet/sync/send output, error messages and usage
text in Italian by design; per the project's language policy (code,
comments and docs are English-only, Italian is reserved for user
conversation) this was a design mismatch, not a documented feature.
Translated every user-facing string and the file's Italian comments,
and updated USERGUIDE.md §17 to match.
Send/Donate/Sync/Wizard view models wrote status/error strings
directly in Italian ("Importo non valido.", "Errore: …", the TLS
pin-mismatch message, …), so a user running the app in English/German/
etc. still saw them in Italian despite the 6-language Loc system
covering the rest of the UI.
Added the missing Loc keys and routed every occurrence through
Loc.Tr(...). CertificatePinMismatchException (Core) no longer bakes an
Italian message into .Message; it now exposes Host/Port so the UI can
translate it, with an English fallback for any exception type the UI
doesn't specifically handle. Also drops the now-stale USERGUIDE.md
caveat about a few messages staying in Italian regardless of language.
Full reference for every user-facing behavior (wizard flows, script
types, fees, gap limit, TOFU cert pinning, CLI commands) with the
exact numbers the software enforces, so users don't have to guess or
read the source to understand a rule.