Skip to content

Added NFT Generation - #3

Merged
ehsan6sha merged 9 commits into
mainfrom
nft
Mar 12, 2026
Merged

Added NFT Generation#3
ehsan6sha merged 9 commits into
mainfrom
nft

Conversation

@ehsan6sha

Copy link
Copy Markdown
Member

rest is in nft-plan.md

rest is in nft-plan.md
Add runtime status updates and an open-wallet UX for NFT operations.

- NftService: added an optional onStatus(String) callback and invoke it at key steps (uploading asset, preparing metadata, approving FULA, waiting for approval/mint confirmations, prompting wallet confirmation) so callers can surface progress to the user.
- WalletService: import url_launcher and add connectedWalletName getter and tryOpenWallet() to launch the wallet app (native/universal link). Send-contract flow now logs debug info, applies a timeout to the request, attempts to open the wallet after sending the request, and surfaces clearer timeout errors.
- NftProvider: pass the onStatus callback into NftService calls and update provider state.statusMessage; set context-aware status messages depending on walletSource (internal vs external) for mint/claim/burn/transfer/cancel flows.
- NftDetailScreen: make the progress dialog reactive to the provider's statusMessage and show an "Open <Wallet>" button when the step indicates a wallet confirmation and a wallet is connected; move file resolution into the progress dialog so the UI shows preparation status during mint startup.

These changes improve user feedback during long-running blockchain flows and make it easier for users to jump to their wallet to confirm transactions.
@ehsan6sha ehsan6sha changed the title phase 1 Added NFT Generation Mar 12, 2026
@ehsan6sha
ehsan6sha merged commit 055ee95 into main Mar 12, 2026
@ehsan6sha
ehsan6sha deleted the nft branch March 12, 2026 22:50
ehsan6sha added a commit that referenced this pull request May 19, 2026
Four client-side fixes for the seed-auth flow. Pairs with the
matching server-side fixes in
https://github.com/functionland/pinning-service (see those commit
messages for the full audit narrative).

## #1 — Registration replay (CRITICAL)

`signInModeB` and `signInModeC` previously generated their own
challenge via `Random.secure()` and signed it locally before
POSTing register-mode-{b,c}. The server accepted the client-supplied
challenge without tracking it as single-use, so a captured request
body could be replayed to mint fresh (perpetually-valid, DT-1) JWTs.

Fix: both methods now fetch a server-issued challenge first
(`IssuerClient.challenge(uid, purpose: 'register-mode-{b,c}')`) and
sign that. The server consumes the same nonce in its store; replay
returns HTTP 401 CHALLENGE_INVALID.

`IssuerClient.challenge` gained a `purpose` parameter; the previous
default behavior (`'sign-in'`) is preserved when the parameter is
omitted. The now-unused `_randomBytes` helper was removed.

## #2 — NFKC inconsistency between KEK and effective_user_id (CRITICAL)

`_canonicalKekInputModeB` / `_canonicalKekInputModeC` in
`auth_service.dart` passed `utf8.encode(seed)` raw to Argon2id. The
matching `fula.computeEffectiveUserIdModeB/c` and
`fula.deriveSigningSeed` Rust FFI calls NFC-normalize the seed
internally. Consequence: a Mode B user typing a non-ASCII password
in NFC on Device A vs NFD on Device B got the SAME `effective_user_id`
(same vault on the server) but DIFFERENT master KEKs → permanent
cross-device decryption failure.

Fix: extracted the canonical-input logic to a new helper
`lib/core/utils/canonical_kek_input.dart` that NFC-normalizes the
seed using the `unorm_dart` package (pure Dart, no native deps —
the upstream `unicode_normalization` package doesn't exist on pub.dev
under that name). 8 unit tests cover NFC=NFD equivalence,
separator-injection resistance, determinism, and distinct-input
distinctness.

Note: my commit messages on `7fa2f32` and the audit transcript use
"NFKC"; the Rust code calls `.nfc()` (canonical-composed NFC, not
the compatibility-decomposing NFKC). Both are fine; the code is
self-consistent; the Dart side now uses the same NFC.

## #3 — Mode B signing key not OAuth-bound (CRITICAL)

`fula.deriveSigningSeed(seed)` derives the 32-byte Ed25519 seed
from `BLAKE3_derive_key("fula:signing-key:v2", NFC(seed))` — the
password ONLY. Two Mode B users under different OAuth identities
but the same password derived IDENTICAL keypairs. Combined with the
public users-index CBOR (which exposes effective_user_ids), this
would let one of them sign in to the other's vault: read the target's
effective_user_id from the public CBOR, sign the sign-in transcript
with their own (identical) keypair, server verifies, mints JWT.

Fix: new helper `lib/core/utils/seed_signing_input.dart` constructs
a tagged-and-length-separated string for Mode B
(`'b\x00$provider\x00$oauthSub\x00$password'`) that's passed to
`deriveSigningSeed` instead of the raw password. The signing seed is
now bound to the full `(provider, oauth_sub, password)` tuple. Mode C
stays seed-only (no OAuth to bind to); the leading `'b\x00'` tag
guarantees Mode B inputs can't collide with Mode C inputs. 6 unit
tests cover the distinctness and collision-resistance properties.

## #4 — `has_mode_a` flag wiring + logic

The server's `has_mode_a` field on the register-mode-b response was
both miscomputed (counted seed_users rows for the same oauth_sub,
which detects "other seed vaults" not "Mode A account") AND ignored
by the client (`SeedAuthResult.hasModeA` was returned by `IssuerClient`
but `signInModeB`/`signInGoogleModeB`/`signInAppleModeB` dropped it
when returning `AuthUser?`).

Fix server-side (separate pinning-service commit): replaced the
helper with `checkModeAExistsForEmail` doing a direct PK lookup on
`webui_users` keyed by `SHA-256(lowercase(email))`. Fix client-side:
`signInModeB` / `signInGoogleModeB` / `signInAppleModeB` now return
`({AuthUser user, bool hasModeA})?`. The Mode B sign-in screen
checks `result.hasModeA` and shows an "Existing vault detected"
warning dialog ("Your existing files are NOT in this vault — to
access them, sign out and use Standard security") before navigating
home.

## Files

New:
- `lib/core/utils/canonical_kek_input.dart` + unit test (8 cases)
- `lib/core/utils/seed_signing_input.dart` + unit test (6 cases)

Modified:
- `lib/core/services/auth_service.dart` — challenge round-trip,
  canonical KEK helper usage, Mode B signing input, record-typed
  return values for the convenience wrappers.
- `lib/core/services/issuer_client.dart` — `purpose` param on
  `challenge()`.
- `lib/features/onboarding/screens/mode_b_signin_screen.dart` —
  Existing-vault warning dialog wired to `result.hasModeA`.
- `pubspec.yaml` — `unorm_dart: ^0.3.0` for Dart-side NFC.

`flutter analyze` clean on all touched files (2 pre-existing INFO
warnings on auth_service.dart, neither from this change).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ehsan6sha added a commit that referenced this pull request Jun 23, 2026
Mirror the category tabs: file rows in the Cloud Files manager now show
compact tag chips under the name, reusing WebTagService.tagsForObjects.

- _fileTags map refreshed after each listing (indexed over the WHOLE flat
  bucket, so subfolder rows resolve without a reload) and after a tag edit
- chips rendered in the row subtitle (up to 2 + "+N"), only when present
- _fileTags cleared on bucket switch to avoid a stale cross-bucket flash
- _TagChipRow duplicated from WebBucketScreen (copy #2; extract on #3)

No SDK / shared-service changes; tagsForObjects + _bareKey untouched, so
chips compute identically to the already-shipped Tags action.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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