Skip to content

[WIP] Clear text representation of wallet policies (attempt using Rust C bindings)#509

Closed
bigspider wants to merge 9 commits into
developfrom
cleartext2
Closed

[WIP] Clear text representation of wallet policies (attempt using Rust C bindings)#509
bigspider wants to merge 9 commits into
developfrom
cleartext2

Conversation

@bigspider

Copy link
Copy Markdown
Collaborator

Rewrite of #494 to use C bindings directly from the Rust crate.

bigspider and others added 6 commits June 24, 2026 16:13
Vendor the upstream BIP-388 cleartext crates from LedgerHQ/vanadium
(branch cleartext-c-bindings) under rust/. bip388-c exposes a no_std C ABI
(bip388_to_cleartext / bip388_confusion_score) over a caller-provided arena
and generates include/bip388.h via cbindgen; bip388 is its reference
parser/renderer dependency, kept as a sibling so its "../bip388" path resolves.

This replaces the hand-maintained C port + gen.py codegen of the earlier
attempt: the spec codegen now stays inside the Rust build.rs. The two target/
dirs are git-ignored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cross-compile the vendored bip388-c crate to the device's Rust target
(cortex-m3 -> thumbv7m-none-eabi; cortex-m35p -> thumbv8m.main-none-eabi)
and link the resulting libbip388_c.a, exposing the cleartext API to the C app.

Gated behind ENABLE_CLEARTEXT (default 1), which sets HAVE_CLEARTEXT and adds
the crate's include dir, the -lbip388_c link, and a cargo build hooked via
APP_CUSTOM_LINK_DEPENDENCIES so the lib is (re)built before the app is linked.
Building with ENABLE_CLEARTEXT=0 drops the feature and needs no Rust toolchain.

The device build requires cargo and the matching rustup target in the build
image; only the host unit-test path is exercised in CI here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
src/common/cleartext.{c,h} expose cleartext_encode(): one call into
bip388_to_cleartext() that renders a descriptor template into NUL-terminated
lines and reports has_cleartext and the confusion score. The whole .c body is
gated on HAVE_CLEARTEXT so the app builds without the Rust lib when the feature
is disabled; the public header carries only mirrored constants (static-asserted
against the binding) so it can be included widely without pulling in bip388.h.

The scratch arena lives in static storage (8 KiB) rather than on the stack to
respect Nano X's stack budget. A rust_eh_personality stub is emitted only under
CLEARTEXT_HOST_BUILD (unit tests), never for the freestanding device build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ui_display_register_wallet_policy() gains cleartext_lines/n_cleartext_lines
parameters and the review screen renders them as a spending-path block before
the descriptor template ("Spending policy" for a single line, otherwise
"Primary spending path" + "Spending path #N" per tapleaf). A NULL
descriptor_template now hides the raw-template pair.

Also refactor the NBGL flows onto a make_pair_list(nbPairs, wrapping) helper
that sets every nbgl_layoutTagValueList_t field explicitly, so the per-flow
"wrapping" choice (true for register/sign-message) can no longer leak between
flows via the shared static pairList.

No behavior change yet: the only caller passes NULL/0, so no cleartext is shown
until the handler is wired up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
handler_register_wallet now calls cleartext_encode() on the descriptor
template and shows the resulting spending-path lines when every part has a
cleartext form and the confusion score is at or below
CLEARTEXT_MAX_CONFUSION_SCORE; otherwise it falls back to the raw template.

For plain multisig policies (multi/sortedmulti, optionally under sh/wsh,
detected via is_plain_multisig() on the parsed AST) the raw descriptor template
is hidden, since the cleartext fully captures the spending condition. All of
this is gated on HAVE_CLEARTEXT.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CMake builds the vendored crate's host (no_std) libbip388_c.a via cargo and
links it through a small `cleartext` wrapper lib (HAVE_CLEARTEXT +
CLEARTEXT_HOST_BUILD). When cargo is absent the cleartext targets are skipped
and test_register_wallet falls back to the no-feature path, so the suite still
builds without a Rust toolchain.

- test_cleartext: smoke-tests cleartext_encode() over single-sig, sortedmulti,
  a taproot tree, and a parse error (the audited rendering vectors live in the
  Rust crate's own tests, so they are not re-ported here).
- test_register_wallet: the mocked UI captures whether the raw descriptor was
  shown; cases gain an optional `descriptor_hidden` flag, asserted only under
  HAVE_CLEARTEXT. The five plain-multisig vectors are marked descriptor_hidden.

Also reword a cleartext.h doc comment to drop a literal "/**" that tripped
-Wcomment (fatal under the app's -Werror).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@bigspider bigspider requested a review from a team as a code owner June 24, 2026 09:06
@codecov-commenter

codecov-commenter commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 68.75000% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.36%. Comparing base (b3c49c4) to head (2972230).
⚠️ Report is 1 commits behind head on develop.

Files with missing lines Patch % Lines
unit-tests/test_register_wallet.c 61.53% 3 Missing and 2 partials ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #509      +/-   ##
===========================================
- Coverage    72.36%   72.36%   -0.01%     
===========================================
  Files           71       71              
  Lines        10347    10362      +15     
  Branches      1876     1879       +3     
===========================================
+ Hits          7488     7498      +10     
- Misses        2113     2116       +3     
- Partials       746      748       +2     
Flag Coverage Δ
unittests 72.36% <68.75%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

bigspider and others added 3 commits June 24, 2026 17:16
The ledger-app-builder CI image ships the Rust toolchain but not every
cross target, so `cargo build --target thumbv8m.main-none-eabi` failed with
"can't find crate for `core`". The lib recipe now runs `rustup target add
$(RUST_TARGET)` first (best-effort: skipped if rustup is absent, letting cargo
emit its own clear error).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The vendored crate's #[panic_handler] calls abort(). Linking the app pulled
newlib's abort()/raise() from libc.a, whose syscall stubs (_kill, _getpid,
_exit) BOLOS does not provide, failing the link with undefined symbols.

Define abort() in the app (device build only) as __builtin_trap(); being a
strong symbol in an app object, it satisfies the crate's reference so newlib's
abort/signal machinery is never pulled in. The host unit-test build keeps
libc's abort (guarded by CLEARTEXT_HOST_BUILD, alongside rust_eh_personality).

A panic is unreachable for in-range policies (the binding's a-priori arena-fit
check), so this is a safety backstop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Code coverage report

Code Coverage

Per-file coverage
Package Line Rate Branch Rate Health
src.boilerplate 100% 0%
src.common 85% 67%
src 87% 59%
src.debug-helpers 100% 0%
src.handler 85% 83%
src.handler.lib 65% 60%
src.handler.sign_psbt 88% 83%
src.musig 24% 15%
src.ui 86% 100%
Summary 75% (3035 / 4055) 63% (1619 / 2580)

@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

elf sizes
source = source branch cleartext2
target = target branch develop

Device .text source .text target .text delta .bss source .bss target .bss delta max stack size source max stack size target max stack size delta
flex 169417 106921 62496 18044 9658 8386 18816 27200 -8384
stax 169367 106359 63008 18044 9658 8386 18816 27200 -8384
nanos2 144020 81524 62496 16936 8552 8384 24020 32404 -8384
apex_p 165576 102568 63008 18044 9658 8386 22912 31296 -8384
nanox 143716 81252 62464 15164 6780 8384 8192 8192 0

Stack consumption summary

⚠️ This summary is for informative purpose only. It may not give the application actual worst case, for example if the test coverage is low.

Device Worst case (bytes) Remaining stack (bytes) Test
apex_p 9165 13747 test_register_wallet.py::test_register_wallet_not_sane_policy[apex_p-apex_p]
flex 9165 9651 test_register_wallet.py::test_register_wallet_not_sane_policy[flex-flex]
nanosp 9165 14855 test_register_wallet.py::test_register_wallet_not_sane_policy[nanosp-nanosp]
nanox 9165 4339 test_register_wallet.py::test_register_wallet_not_sane_policy[nanox-nanox]
stax 9165 9651 test_register_wallet.py::test_register_wallet_not_sane_policy[stax-stax]

Full details

@bigspider bigspider closed this Jun 25, 2026
@bigspider bigspider deleted the cleartext2 branch June 25, 2026 14:11
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.

2 participants