Skip to content

Registration complete: +41 plus a leading-0 national number fails with Invalid signature #905

Description

@joshuakrueger-dfx

Severity

High — a Swiss user who enters a normal national number (079… next to prefix +41) can complete the whole wizard and then lose the submit on Abschliessen. Nothing is stored. The snackbar blames «your entries», which points at the tax step they are looking at.

Seen in production: tax-residence step, country Switzerland, server reason Invalid signature. Tax is a red herring. POST /v1/realunit/register/complete verifies the EIP-712 signature before any tax check.

Problem

The app signs whatever PhoneNumberField concatenates:

// lib/widgets/form/phone_number_field.dart
final value = '$prefix$number';

Prefix defaults to +41. The national field accepts any digits, including a leading 0. A typical Swiss mobile therefore becomes +410791234567. That string is what RealUnitRegistrationService._completeRegistration puts in the EIP-712 envelope and in the DTO (toBitboxSafeAscii does not change it).

On the API the same field is rewritten before signature verification:

// DFXswiss/api — RealUnitRegistrationDto.phoneNumber
@Transform(DfxPhoneTransform)  // libphonenumber → E.164

Measured against libphonenumber-js:

Typed / signed After DfxPhoneTransform isValid
+41791234567 +41791234567 yes — hash matches
+41 79 123 45 67 +41791234567 yes — hash broken
+410791234567 +41791234567 yes — hash broken

Validation still passes. Recovery of the typed-data signature does not. The API returns 400 Invalid signature.

The number-field hint (1231234567) does not look like a Swiss mobile, so the trunk-0 input is the path a CH user actually takes.

Effect

  • Wizard state is local only. Retry with the same number fails the same way.
  • The user is on Steueransässigkeit and is told to re-check their entries. Switzerland as tax country is valid (swissTaxResidence: true, empty countryAndTINs).
  • Same class of break for any other signed field the API mutates after receive (Util.sanitize on name/street/city/ZIP). Phone is the one that fires on ordinary CH input.

Repro

  1. New registration, prefix +41, national number 0791234567 (or any valid Swiss mobile with the trunk 0).
  2. Fill the rest, tax country Switzerland, Abschliessen, sign.
  3. Snackbar: Invalid signature. Network: POST /v1/realunit/register/complete → 400.

Control: same flow with national number 791234567 succeeds.

Proposal

Sign the representation the API will keep, and do not let a CH trunk 0 reach the envelope.

  1. Compose E.164 before Eip712Signer.signRegistration and use that same string in the DTO. After that, DfxPhoneTransform is a no-op.
  2. In PhoneNumberField, when the prefix is a country calling code, drop a leading national trunk 0 (CH 079…79…). Keep the API as the authority on whether the number is valid — only normalize form.
  3. Hint / helper that matches a Swiss mobile (79 123 45 67), not 1231234567.
  4. For Invalid signature specifically, do not use registrationRejected («check your entries»). That copy was written for content-level 4xx and is wrong here.

Do not add an app-side retry that re-signs after a 400. That would be the capability anti-pattern in CONTRIBUTING.

The API half belongs in DFXswiss/api: verify the signature against the raw body, then transform. Until that lands, (1) is what stops CH registrations dying on a leading 0.

Out of scope

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions