Skip to content

fix(realunit): accept BitBox-safe ASCII transliterations in registration#3709

Merged
TaprootFreak merged 3 commits into
developfrom
fix/realunit-bitbox-ascii-validation
May 15, 2026
Merged

fix(realunit): accept BitBox-safe ASCII transliterations in registration#3709
TaprootFreak merged 3 commits into
developfrom
fix/realunit-bitbox-ascii-validation

Conversation

@TaprootFreak
Copy link
Copy Markdown
Collaborator

@TaprootFreak TaprootFreak commented May 15, 2026

Summary

Unblocks realunit-app#332 (BitBox sign hardening). Without this PR every BitBox-signed registration that touches a non-ASCII character (umlaut, accent, …) is rejected by the backend with firstName + lastName does not match signed name.

The client transliterates EIP-712 string fields to BitBox-safe ASCII (äae, ßss, …) because the BitBox02 firmware rejects any non-ASCII byte in a string-typed value, while keeping kycData UTF-8 for ID verification. Two server checks need to learn about this duality:

  1. validateRegistrationDto compares dto.kycData.firstName + lastName (UTF-8) to dto.name (ASCII). Same for street/zip/city/organizationName. → introduces matchesSignedField(kycValue, signedValue) which accepts either the literal or the ASCII form.
  2. verifyRealUnitRegistrationSignature recovers the wallet address from the EIP-712 hash over the stored accountData. For users that registered before realunit-app v0.0.3 the stored fields are UTF-8 — a fresh ASCII signature no longer recovers. → retry with ASCII-transliterated message when the primary verify fails.

A new toBitboxAscii helper mirrors the Dart toBitboxSafeAscii exactly. It is intentionally kept separate from the existing npm transliteration package: that one maps ü to u (single char), while the BitBox-safe / German-romanization convention maps it to ue — the only convention the client actually emits.

Test plan

  • npm run format clean
  • npm run lint clean
  • npm run type-check clean
  • npx jest src/shared/utils/__tests__/bitbox-ascii.spec.ts (6/6 pass)
  • npx jest src/subdomains/supporting/realunit/__tests__/realunit.service.spec.ts (10/10 pass, no regressions)
  • Manual: realunit-app#332 against DEV API → BitBox registration with umlaut name → complete + re-login on a second wallet works.

Compatibility

  • Old app versions that signed UTF-8 directly: matchesSignedField still accepts literal equality, primary verify still succeeds → no change.
  • New app versions that sign ASCII: matchesSignedField now passes the ASCII check, primary verify on freshly stored ASCII accountData succeeds → works.
  • Pre-PR users with UTF-8 in the stored KYC step who now re-login from a v0.0.3+ app: primary verify fails, ASCII-fallback verify succeeds → re-login works.

@github-actions
Copy link
Copy Markdown

❌ Security: 1 critical vulnerabilities

TaprootFreak added a commit that referenced this pull request May 15, 2026
…3711)

GHSA-rpr9-rxv7-x643 — default XSS via xmp raw-text passthrough,
fixed in 2.17.4. Patch bump only; package range stays ^2.17.x.

Unblocks the CI 'Security audit' step (npm audit
--audit-level=critical), which started failing this morning after the
advisory dropped and now blocks every PR build (#3707, #3708, #3709).
realunit-app v0.0.3+ transliterates EIP-712 string fields to
ASCII-with-German-digraphs (Krüger → Krueger) so the BitBox firmware
accepts them, while keeping the kycData copy in UTF-8 for ID
verification. Without this change the backend rejects every BitBox
registration that touches a non-ASCII character:

- validateRegistrationDto compares dto.kycData.firstName/lastName (UTF-8)
  against dto.name (ASCII) and throws "firstName + lastName does not
  match signed name". Same for street/zip/city/organizationName.
- verifyRealUnitRegistrationSignature recomputes the EIP-712 hash from
  the stored accountData; for users who registered before this fix the
  stored fields are UTF-8 and a fresh ASCII signature no longer
  recovers the wallet address.

Adds a `toBitboxAscii` helper that mirrors the Dart implementation
(`ä` → `ae`, `ß` → `ss`, …) — kept separate from the existing
`transliteration` npm package, which uses single-char substitution and
would not match the client's hash.

validateRegistrationDto now accepts either the literal or the ASCII
form via `matchesSignedField`. verifyRealUnitRegistrationSignature
retries with ASCII-transliterated message values when the primary
recovery fails, so re-login (registerWallet) keeps working for
pre-fix registrations.
@TaprootFreak TaprootFreak force-pushed the fix/realunit-bitbox-ascii-validation branch from 9f31c29 to 73ab3e1 Compare May 15, 2026 12:39
Maps now group by base letter (one row per letter, multiple
diacritic variants per row) for readability — 212 → 89 lines.
Behavior unchanged, all 6 spec tests still pass.
Match the existing house style for character normalization
(Util.removeSpecialChars, util.ts). 89 → 79 lines, removes the
need for // prettier-ignore (which was the only place in the
repo using that pragma).

Parity verified: all 168 entries in the Dart source map produce
identical output in the chained-replace version.
@TaprootFreak TaprootFreak merged commit 1998101 into develop May 15, 2026
7 checks passed
@TaprootFreak TaprootFreak deleted the fix/realunit-bitbox-ascii-validation branch May 15, 2026 18:03
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