Skip to content

Private multisig signing in every wallet app (fullnode v0.8.0, chain 21) - #3

Merged
dendisuhubdy merged 53 commits into
mainfrom
feat/msauth-signing
Oct 12, 2026
Merged

dendisuhubdy merged 53 commits into
mainfrom
feat/msauth-signing

Conversation

@dendisuhubdy

Copy link
Copy Markdown
Member

Summary

Every wallet app can now sign as a member of a private multisig account. That covers web, extension, desktop, iOS and Android, all through fullnode v0.8.0's randprotocol-msig crate.

A signer adds a signer profile: the policy plus their key, both kept in the app's existing secure store. For each proposal they then:

  1. Open the proposal, from a file or by pasting it.
  2. Enter the intended payments: one row per payee, each with an address and a RAND amount.
  3. Check the proposal and review its lines verbatim.
  4. Confirm by typing the last four characters of the account fingerprint.
  5. Re-authenticate for this signature: the password, Face ID or passcode, or fingerprint or screen lock.
  6. Share the resulting i:hex signature share.

Preparing, proving and submitting stay in the CLI.

The core is pinned to fullnode v0.8.0 (c0878c3), which makes this branch chain-21-only:

  • DEFAULT_CHAIN_ID is 21.
  • On iOS and Android, a saved chain id of 20 is migrated to 21 once.
  • The real-node CI job now runs against the v0.8.0 release.
  • Fixes for chain 20 go to release/chain20.

Refusals, the same on every platform and in the core

  • Payees:
    • A paying output that isn't change must match an entered payee.
    • Each pinned amount must equal the total that payee receives.
    • A payee left without an amount is allowed only if that payee is paid solely in a non-RAND asset. That stops a dust-token output from removing the RAND amount check.
    • msauth_sign uses sign_expecting and refuses an empty payee list, so the core alone never signs for an unchecked payee.
  • Malformed summaries: any malformed field, including lines, refuses the proposal.
  • Node, chain and expiry:
    • No node, or a node that gives no head, refuses.
    • A different chain id refuses, as does a different genesis.
    • Signing stops 16 blocks before expires_at, and expiry is checked again after authentication.
  • What gets signed: exactly the proposal text and payment list that were checked. Editing either disarms Sign.
  • Secrets:
    • Proposals are never stored.
    • The seed is decrypted for one signature and then zeroed. The comments state what cannot be zeroed.
    • "Forget wallet" removes every signer item, clearing them first and failing closed if that fails.

Tests

Suite Result
Core 149 + 8 + 4
JS (ui, web, extension) 1186 pass, 34 skipped
iOS 183, 0 failures
Android 187, 0 failures
Desktop 23, 0 failures
  • Fixture vectors: shares from fullnode's fixtures are reproduced byte for byte in the real wasm and in the iOS core. Fixture hashes are pinned in ui/test/fixtures/msauth.sha256.
  • Devnet run: done up to the prove. The CLI prepared a proposal, and the web engine and iOS each signed it. The tier-18 msauth prove was killed on a 48 GB Mac: Test-FRI peak footprint was 76.4 GB. The prove, submit and payout leg still needs a host with at least 96 GB, using scripts/e2e-msauth.sh.

Before release

  • Default node: the RPC default (rpc.randprotocol.org) serves chain 20 until the cutover, and chain-21 apps refuse it.
  • On-device checks: the Keychain .userPresence prompt; BiometricPrompt with the Keystore on API 26–29 and 30+; removing a signer on Android 9 and 10.
  • Native-speaker review: 8 iOS and 5 Android strings were translated by hand.
  • Existing Android unlock: the Android 9–10 unlock (Unlock.isUnlocked) may skip device auth. It's pre-existing and outside this PR.

The spec is docs/superpowers/specs/2026-10-11-msauth-signing-design.md and the plan is docs/superpowers/plans/2026-10-12-msauth-signing.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8

dendisuhubdy and others added 30 commits October 11, 2026 15:07
…how, sign, share through randprotocol-msig; signer profiles in each app's key store)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…e wasm32 gate on dilithium-core

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…nt with the delivery-key caveat; two more fixtures

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…tputs labelled by to_account

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…de, re-authenticate every signature

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…ning refuses an unchecked payee

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…s, engine, screens, locales, iOS, Android, devnet e2e); payees entered per spec 639cae7, pending crate names tagged [confirm: 0e]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…9 (MODQ chip, msauth guest, hc_auth_multisig), chain 21 onward

- core/vendor/fullnode 37c4fdd → e34117b; CHAIN_BUILD "e34117b" (wallet-core, the web core
  integration test, the iOS smoke test, README). This makes the branch chain-21-only; chain 20
  fixes go to release/chain20.
- The [patch]ed Plonky3 forks (p3-fri, p3-merkle-tree, p3-batch-stark) are unchanged at e34117b;
  core/ and desktop/ keep patching them as before. The vendored zkvm gates dilithium-core and
  dilithium_host off wasm32 (its own Cargo.toml/lib.rs), so the browser build is unaffected.
- wallet-core's DeployExec test executor forwards the six new ConfidentialExecutor methods
  (auth_header, warm_auth_multisig, auth_multisig_key_ready, install_msauth_vk,
  enable_auth_multisig, build_msauth_key) to the stub, as it does the rest of the auth path.
- No fixture changes: the hc_bundle/hc_auth pins are unchanged, and the in-process proved
  transfer verifies.

cargo test --release: 132 + 8 + 4 passed (2 ignored, the real delegated-proof tests).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…h code yet)

The private multisig signer checks (summary, signing message, share) come from fullnode's
crates/randprotocol-msig, vendored with its tests/fixtures/*.json at e34117b. Its dependency cone
is the one core already builds for wasm32 (randprotocol-core, randprotocol-zkvm, serde, hex,
thiserror): the wasm build (core/scripts/build-wasm.sh) compiles it. The arms that call it are a
later task (spec 2026-10-11-msauth-signing-design §6 step 2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
core/scripts/vendor-fullnode.sh <rev> [<fullnode clone or URL>] checks core/vendor/fullnode out at
<rev> (fetching from a local clone, read only, when the commit is not pushed; populating an empty
submodule), checks that the zkvm still gates dilithium-core/dilithium_host off wasm32, that the
Plonky3 forks core/Cargo.toml patches exist and that randprotocol-msig carries its fixtures
(printing their SHA-256s), rewrites CHAIN_BUILD and the three tests that pin it, and refreshes
core/ and desktop/src-tauri lockfiles. It prints what is left: the prose, the test runs, the commit.
Round-tripped e34117b → 2fb491a → e34117b with a byte-identical tree. core/README.md points at it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…n core-errors

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…seeds under the password only; passwordAttempt shared with the unlock

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
… overwritten; test the wipe and the wrong-password remove

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…gainst a verified head, no node no signature, the password per signature

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…tched paying output whatever recipients_checked says, re-check the head after the password backoff

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…count, amount}, and a null summary, as malformed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…nto Downloads (desktop save_text_to_downloads over save_text_in)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…iew/sign a proposal (last four + password), save or share the share, check others' shares; desktop saves to Downloads

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…der the crate's set matching

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…d-payments list (addr:amount), engine refuses empty/amountless entries, desktop save never fails on a taken name

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…D doc per the plan; the msig link test; DeployExec forwards to the wrapped executor; the delegated finish test names a genesis

- wallet-core: DEFAULT_CHAIN_ID 21 (its doc: chain 21, fullnode v0.8.0, constraint set 9, cut from
  chain 20's state); CHAIN_BUILD's doc names e34117be's constraint set 9 with hc_bundle 60af094a…
  and hc_auth 1e4e347f… unchanged and a node that refuses chains 18–20; the three default_chain_id
  asserts move to 21; the_vendored_msig_crate_is_linked (POLICY_VERSION 1, SIG_BYTES 2420,
  PK_BYTES 1312).
- DeployExec forwards the six msauth executor methods to the wrapped ZkExecutor (install_msauth_vk
  through the trait: ZkExecutor's inherent method of that name returns a typed error).
- finish_proof_matches_prove_transfer_field_for_field (ignored, a real proof): moving the pending
  to the next chain id lands on a genesis-bound chain (BIND-1), so the moved pending carries a
  genesis and the verify still refuses the proof; the same move without one is asserted refused
  "BIND-1" before any verify. It had failed on that refusal since BIND-1.
- Android Prefs.DEFAULT_CHAIN_ID 21, iOS Settings fallback 21, CoreSmokeTests (21, e34117b),
  LiveSendTests/LiveSwapTests chainId 21, the UI engine's FALLBACK.chainId 21, the web core
  integration test (21) and both real-node e2e CHAIN_IDs (21).

core cargo test --release: 133 + 8 + 4 passed, 2 ignored; -- --ignored: 2 passed. UI: 994 passed.
Web core integration: 15 passed. Desktop cargo test: 22 passed. Prover e2e on a chain-21 genesis
(chain id 21, fees, multisig, hc_auth_multisig): 4 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…nt set 9, randprotocol-msig linked), chain 21 by default

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…rged) — randprotocol-msig's payee check (check_proposal_expecting / sign_expecting, rand1…[:AMOUNT] expected recipients, each output's matches, recipients_checked) and the proposal-substituted-recipient vector; CHAIN_BUILD 9b01492

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…protocol-msig (check_proposal_expecting: rand1…[:AMOUNT] expected recipients, each output's matches, recipients_checked, expected [{entry, fingerprint}]); the eleven 9b01492 vectors under ui/test/fixtures/msauth with one hash list, which vendor-fullnode.sh now rewrites

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…h sign_expecting: the core refuses a share whose payee does not check out, and a paying proposal with no payee named); the wasm signs the 9b01492 vectors exactly; the JS sign passes the check's expected_recipients to msauth_sign

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…n disarmed after a payment edit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
dendisuhubdy and others added 22 commits October 12, 2026 10:36
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…ape (passed on 9b014927: 97 s)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…rPresence, read per signature)

- RandCore: msauthCheck / msauthSign (with expected_recipients) / msauthVerifyShare /
  msauthSignerImport / msauthPolicyInfo.
- MsauthRules.verdict: the same decisions as ui/engine/signers.js, including the review rulings:
  .malformedSummary, per-output matches, empty or blank payment lists refused, amounts pinned
  except per-row non-RAND owners (summary.expected), no node / wrong chain / wrong genesis /
  head + 16 >= expires_at.
- MsauthSigning: check, judge, prompted seed read, head re-read and re-judge, sign with the same
  list; the seed buffer zeroed on every exit.
- SignerStore: seed item WhenPasscodeSetThisDeviceOnly + .userPresence, fresh LAContext per read;
  forgetWallet removes every profile.
- CoreErrors: the msauth sentences (Task 5's list).

Tests: MsauthTests 25 pass; full suite 145 run, 2 skipped (live), 0 failures, on the
iPhone 17 Pro simulator (A0ED72E7, iOS Simulator). The Keychain ACL is tested through
FakeSignerSecrets; the device check is manual.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…d in an owned buffer, code-point label limit, a head past 2^53-1 is no node

- SignerSecrets.deleteAll(accountPrefix:): the Keychain lists the service's items by attributes
  (no prompt) and deletes each msauth.* account; removeAll calls it after the listed profiles, so
  an orphan (undecodable index, crash before the index write) goes with the wallet.
- KeychainSignerSecrets.load copies the reply's bytes once into an owned Data, so resetBytes
  zeroes them in place instead of copy-on-writing away from the framework's CFData; comments say
  only the framework's copy is out of reach.
- Label limit counts code points (unicodeScalars), as the JS does.
- MsauthSigning.judge: a node head above 2^53-1 is no node, as the JS does.

MsauthTests 28/28 on the iPhone 17 Pro simulator (A0ED72E7, iOS 26.2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…en the sweep fails

- SignerSecrets.deleteAll(accountPrefix:keeping:) throws: a listing other than success or
  errSecItemNotFound, or a delete other than success or not-found, is an error.
- SignerStore.removeAll() throws: listed profiles, then the sweep (keeping the index), then the
  index; a failed sweep keeps the index and `profiles` for a retry.
- WalletService.forgetWallet throws and removes the signer profiles first, before anything else
  is forgotten; SettingsView shows the error and stays.

MsauthTests 29/29 and ContactsTests 8/8 on the iPhone 17 Pro simulator (A0ED72E7, iOS 26.2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…s, review the core's lines, last four + device auth, ShareLink; 20 languages

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…vive a refused check, latest-check-wins, confirmed removal

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…ncellation stops before Face ID, the check counter survives leaving, editor locked while signing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…Vault, a per-use Keystore key per seed

SignFlow makes the web's and iOS's decisions (iOS where stricter): a
malformed summary refuses, every paying output needs a match, amount pins
with the per-row non-RAND exception, no node / head past 2^53-1 is no node,
chain, genesis, the CLI's 16-block margin. sign() judges before the prompt,
re-reads the head and re-judges after it, honours cancellation before the
prompt and before signing, passes msauth_sign the very list the check was
given, and zeroes the seed in finally.

Signer profiles live in KeyVault's encrypted file under msauth.*; each seed
is sealed by its own AES-GCM Keystore key that needs authentication per use
(biometric or credential on API 30+, a strong biometric on 26-29). Removing
the wallet sweeps every msauth.* Keystore key and entry first, listed or
not, keeps the index and the wallet if the sweep fails, and never touches
the spend key or the prover's record. Task 5's core sentences in
CoreErrors. 148 JVM tests pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…ictly (a missing or malformed height is no node, not height 0)

SignFlow.node(RpcClient) no longer goes through RpcClient.headHeight()
(optLong default 0) or chainId() (truncates): SignFlow.headHeightOf and
chainIdOf read the replies with the rule's strict uint and throw, which the
flow judges as NO_NODE. Lint: @ChecksSdkIntAtLeast on credentialPerUse, the
SDK check inlined in createKey, literal BOMs replaced by  escapes. A
seed sealed under another key fails its tag. 150 JVM tests pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…per-use auth), check, review, last four + BiometricPrompt, ACTION_SEND; 20 languages

The sign screen goes only through SignFlow.sign on its own thread (a
CryptoPrompt authenticator: BiometricPrompt with a CryptoObject, posted to
the main thread). Intended payments are address + amount rows; editing a
row after a check disarms Sign until a check of exactly those rows lands;
Sign signs the text and payees the shown review was checked with; only the
latest check lands. The decisions live in SignRows, PaymentsGate,
CheckSession, SignReview and PickedFile, JVM-tested (SignScreenTest, 26).
All 93 msauth strings translated into the 19 locales.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…a secure device (RemoveAuth: weak biometric or credential, else confirm-credential; skipped only without a secure lock), prompt "up" only once it takes focus, rows kept while typing, rows locked while signing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…OS sign through the core, CLI proves and submits, the payout lands

scripts/e2e-msauth.sh runs a one-validator chain-21-shape devnet (Test FRI profile, split
authorisation + the multisig auth guest), makes a 2-of-3 policy with `rand multisig-private`,
funds it, prepares a 10 RAND payment, has signer 0 sign in the web engine (signers.add/check/sign
over the real wasm core) and signer 2 in iOS (MsauthSigning + RandCore on the simulator over
FakeSignerSecrets: a simulator has no passcode), then proves, submits and checks the payee's scan.
Both platform tests refuse the proposal with no payee and with the payee's address but no amount.

Run 2026-10-12 against fullnode 9b014927: both signers passed; the tier-18 msauth prove (Test
profile) was killed by macOS at 273.5 s, max RSS 22.5 GB, peak physical footprint 76.4 GB on a
48 GB Mac. The prove's watchdog now gauges the physical footprint (`footprint`), not RSS, and stops
the prove past MSAUTH_E2E_MAX_MEM_GB (default 40). The full run needs a >= 96 GB host.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…erified fold), the pin is on GitHub

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
… through decryptSecret and decryptBytes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…token bypass), lines must be an array of strings, a proposal edit clears the review and disarms Sign

The amountless exception now needs the entry's fingerprint to own a paying non-RAND output and
no paying RAND output; the screen withholds the exception from such a row and says RAND and
token payments to one payee need separate proposals (20 languages).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…ity with Android and iOS); a review whose lines are not all strings shows the refusal alone

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…dust-token bypass), lines must be an array of strings, the confirmation ignores inner whitespace, a saved chain id 20 becomes 21 once

SignFlow.judge allows an amountless entry only when its fingerprint owns a paying non-RAND output
and no paying RAND output; SignRows withholds the exception from such a row and the row shows
msauth_mixed_note (20 languages). Prefs migrates chain_id 20 -> 21 once behind a marker; nothing
else (the RPC URL included) is touched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
…-token bypass), lines must be an array of strings, the confirmation ignores inner whitespace, a saved chain id 20 becomes 21 once

MsauthRules.verdict allows an amountless entry only when its fingerprint owns a paying non-RAND
output and no paying RAND output; SignRows withholds the exception from such a row and shows the
both-RAND-and-token sentence (20 languages, l10n + ios-merge). Settings migrates chainId 20 -> 21
once behind a marker; nothing else (the RPC URL included) is touched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
… proves for constraint set 9 (chain 21 onward), which a v0.7.1 node cannot verify

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6dmK1EC4QerMmqWR1h7d8
@codecov

codecov Bot commented Oct 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.70833% with 28 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
core/crates/wallet-core/src/lib.rs 46.00% 27 Missing ⚠️
core/crates/wallet-core/src/msauth.rs 99.70% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

… chain this core is built for

The suite's CHAIN_ID stayed 20 after the core moved to DEFAULT_CHAIN_ID 21, so `version` answered
21 where the test expected 20, and the spend case refused the chain-20 devnet node as another
chain. The genesis the suite cuts is now chain 21, like the core; the integration test's title
names chain 21 too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KA8jgT2gancsDkMscTUBp2
@dendisuhubdy
dendisuhubdy merged commit d3683dc into main Oct 12, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant