# Wallet-based Authentication Flows

Zus supports multiple wallet-based authentication methods to give users flexibility in how they access the platform. Instead of traditional username/password logins, authentication relies on cryptographic signatures that prove control of a wallet address.&#x20;

The backend (“Obox”) then validates this proof and issues a **Firebase Custom Token**, which the frontend uses to establish a secure session.

There are three main flows, each serving a different user scenario:

1. **Metamask / Coinbase Wallet Login**\
   Designed for users who prefer to sign in with widely used Web3 wallets. The flow issues a unique nonce, requires the user to sign it, and validates the signature before returning a session token.
2. **ZCN Mnemonic Signup/Login**\
   Built around wallets derived from Zus Cloud Network mnemonics. The mnemonic never leaves the device; instead, the frontend proves wallet ownership by signing backend-issued challenges. This flow supports both first-time signup (with reCAPTCHA) and returning user login.
3. **Partner Wallet Login**\
   Enables login through integrated partner wallets (e.g., MetaMask, Coinbase, Binance). If the wallet is recognized, the system verifies ownership and links it to an existing account. If not, the flow gracefully falls back to the ZCN mnemonic-based signup/login.

This section documents all three flows with their **step-by-step sequences, API contracts, security considerations, and error models** to help developers implement wallet-based authentication consistently across Zus applications.

***

## 1. Metamask/Coinbase Signup/Login Flow

This flow enables users to sign up or log in to the platform using their **Metamask** or **Coinbase Wallet**. It leverages wallet-based authentication and message signing to verify ownership of the wallet address before issuing a Firebase authentication token.

### Overview

* **Frontend (FE):** Initiates login requests and manages interaction with the wallet provider.
* **Obox:** Backend service that issues nonces, verifies signed messages, and generates Firebase custom tokens.
* **Wallet (Metamask or Coinbase):** Used by the user to sign messages cryptographically, proving ownership of the wallet address.

<figure><img src="/files/FVw1Lyc8F4XMcjZPnuSj" alt=""><figcaption><p>Metamask/Coinbase Signup/Login Flow</p></figcaption></figure>

The authentication process consists of **two main API calls**:

1. `GET /wallet/nonce`
2. `POST /wallet/verify`

### Step-by-Step Flow

#### 1. Request Nonce

* **Endpoint:** `GET /wallet/nonce`
* **Initiated by:** FE → Obox
* **Purpose:** Generate a unique challenge (nonce + message) for the user to sign with their wallet.

**Response Example:**

```json
{
  "message": "Login request for authentication",
  "nonce": "123456"
}
```

#### 2. Sign Message

* The FE passes the received **message** and **nonce** to the connected wallet (Metamask or Coinbase).
* The user signs the message with their wallet’s private key.
* The wallet returns the signed payload back to the FE.

#### 3. Verify Wallet Signature

* **Endpoint:** `POST /wallet/verify`
* **Initiated by:** FE → Obox
* **Purpose:** Send signed data to backend for verification.

**Request Example:**

```json
{
  "walletAddress": "0x1234abcd...",
  "signature": "0xshjads9as...",
  "provider": "coinbase",
  "nonce": "123456"
}
```

#### 4. Backend Verification

* Obox validates the request by checking:
  * The signature matches the wallet address.
  * The nonce is correct and not reused.
* If successful, Obox generates a **Firebase custom token** for the user session.

**Response Example:**

```json
{
  "message": "certified successfully",
  "data": "firebaseCustomTokenHere"
}
```

### Security Considerations

* **Nonce Uniqueness:** Prevents replay attacks. Each nonce can only be used once.
* **Message Signing:** Ensures that only the wallet owner can authenticate.
* **Firebase Custom Token:** Used to manage authenticated sessions securely on the frontend

***

## 2. ZCN Mnemonic Signup/Login Flow

This section documents the wallet-based authentication shown in the “**ZCN Mnemonic Signup/Login flow**” diagram. Users authenticate by proving control of a wallet derived from a mnemonic. The mnemonic **never leaves the device**. The backend (“**Obox**”) issues short-lived challenges and, on successful verification, returns a **firebase\_custom\_token** and **uid**. (Firebase itself isn’t shown in the diagram; see **Token usage** below.)

### Actors

* **FE (Frontend)** — Derives the wallet from the user’s mnemonic locally, requests challenges, signs them, and calls the auth APIs.
* **Obox (Backend)** — Creates challenges, verifies signatures, links public keys to users, and returns `firebase_custom_token`.

<figure><img src="/files/ZmjXdRkWB1ckbHKLGIym" alt=""><figcaption><p>ZCN Mnemonic Signup/Login Flow</p></figcaption></figure>

### When to use each flow

* **First time on a device / new user:** **Sign-up / link key** via `POST /firebase/custom-token`.
* **Returning user:** **Login** via `GET /wallet/auth/challenge` → sign → `POST /wallet/auth/verify`.

### API contracts

#### 1) Sign-up / Link Public Key

**POST** `/firebase/custom-token`

Creates (or fetches) a user linked to the provided `public_key`. Requires a valid reCAPTCHA token.

**Request**

```json
{
  "public_key": "04a162a3d4e5f...",
  "recaptcha_token": "recaptcha_token"
}
```

**Response 200**

```json
{
  "token": "firebase_custom_token",
  "uid": "firebase_user_uid",
  "phone_number": "user_phone_number",
  "email": "user_email"
}
```

**Notes**

* **Idempotent:** if the `public_key` is already linked, returns a fresh custom token and existing `uid`.
* `recaptcha_token` is validated server-side.

#### 2) Start Login (issue challenge)

**GET** `/wallet/auth/challenge`

Issues a single-use challenge the FE must sign exactly as provided.

**Response 200**

```json
{
  "random_message": "440004d8f1",
  "expires_at": 1754163986,
  "challenge_token": "69a5fd2566b46e"
}
```

**Notes**

* `expires_at` is UNIX time (seconds).
* `challenge_token` is single-use and bound to the issued `random_message`.

#### 3) Complete Login (verify signature)

**POST** `/wallet/auth/verify`

Verifies that the signature corresponds to `public_key` and the issued `random_message` and `challenge_token`.

**Request**

```json
{
  "public_key": "04a162a3d4e5f...",
  "signed_message": "3045022100a1890a6b...",
  "random_message": "440004d8f1",
  "expires_at": 1754163986,
  "challenge_token": "69a5fd2566b46e"
}
```

**Response 200**

```json
{
  "token": "firebase_custom_token",
  "uid": "firebase_uid"
}
```

**Server checks**

* `challenge_token` exists, unused, and not expired.
* `random_message` and `expires_at` match the stored challenge.
* `signed_message` verifies against `public_key` **for the exact bytes** of `random_message`.
* `public_key` is linked to a user (else return `public_key_not_found`).

### Field reference

| Field                   | Type    | Description                                                                                    |
| ----------------------- | ------- | ---------------------------------------------------------------------------------------------- |
| `public_key`            | string  | Hex/Base58 (implementation-defined). Must match FE/BE convention (compressed vs uncompressed). |
| `recaptcha_token`       | string  | reCAPTCHA token collected on FE during sign-up.                                                |
| `token`                 | string  | Firebase Custom Token to establish the FE session.                                             |
| `uid`                   | string  | User id associated with `public_key`.                                                          |
| `phone_number`, `email` | string? | Optional profile fields if stored for the user.                                                |
| `random_message`        | string  | Opaque challenge payload to sign exactly as given.                                             |
| `expires_at`            | number  | UNIX seconds until the challenge expires.                                                      |
| `challenge_token`       | string  | One-time handle for the issued challenge.                                                      |
| `signed_message`        | string  | Signature over `random_message` using the wallet private key (encoding per implementation).    |

### End-to-end sequences

#### Sign-up (first time / linking)

1. **FE → Obox**: `POST /firebase/custom-token { public_key, recaptcha_token }`
2. **Obox → FE**: `{ token, uid, phone_number?, email? }`
3. *(Out of diagram scope)* FE uses the token to start the session (see **Token usage**).

#### Login (returning)

1. **FE → Obox**: `GET /wallet/auth/challenge`
2. **FE**: Signs `random_message` locally with the mnemonic-derived key
3. **FE → Obox**: `POST /wallet/auth/verify { public_key, signed_message, random_message, expires_at, challenge_token }`
4. **Obox → FE**: `{ token, uid }`

### Security & correctness

* **Mnemonic privacy:** Mnemonic and private key never leave the FE.
* **Replay protection:** `challenge_token` is single-use; mark as consumed after success.
* **Expiry enforcement:** Reject any challenge past `expires_at` (allow small clock skew).
* **Exact-bytes signing:** FE must sign the **exact** `random_message` (no trimming, prefixes, or encoding changes).
* **Rate limiting:** Apply per IP and per `public_key` for challenge and verify.
* **Audit logs:** Record sign-ups, challenge issuance, and verification attempts with non-PII identifiers.
* **Algorithm agreement:** FE and Obox must agree on curve, signature format (e.g., DER hex vs raw 64-byte base64), and public key format.

***

## 3. User Partner Wallet Login

This flow lets a user log in with a **partner wallet** (e.g., MetaMask, Coinbase Wallet, Binance Web3). If the partner wallet isn’t recognized in our system, we **fallback to the ZCN Mnemonic Signup/Login flow**.

### Purpose

* Support passwordless login with external wallets
* Verify the user by a signed challenge
* Map the partner wallet to an existing account and **return a `firebase_custom_token`**

### Actors

* **FE (Frontend):** Connects to the chosen partner wallet, requests challenges, signs them, calls APIs.
* **Obox (Backend):** Checks existence, issues/verifies challenges, validates provider mapping, returns `firebase_custom_token`.

<figure><img src="/files/h1yiUEE4Q4h8rmZiFt3O" alt=""><figcaption><p>User Partner Wallet Login</p></figcaption></figure>

### Prerequisites

* FE can request the active wallet address from the chosen provider.
* Both sides agree on:
  * **Chain/curve:** (e.g., Ethereum `secp256k1`)
  * **Signing method:** EIP-191 `personal_sign` (or provider-specific equivalent)
  * **Key/address format:** checksum-normalized hex (all addresses should be normalized server-side)

### Data model (server)

**Table: `partner_wallets`**

* `id` (pk)
* `provider` (`metamask|coinbase|binance|...`)
* `wallet_address` (checksum-normalized, unique per provider)
* `user_id` (maps to account / `uid`)
* `wallet_id` (internal wallet identifier, if applicable)
* `created_at`, `last_login_at`, `status`

### Endpoints

#### 1) Check existence

**GET** `/wallet/partner/exists?address={addr}&provider={provider}`

**Response 200**

```json
{ "exists": true, "wallet_id": "w_123", "uid": "firebase_uid" }
```

or

```json
{ "exists": false }
```

**Notes**

* Normalize `address` before lookup.
* If `exists: false`, FE should **fallback to ZCN Mnemonic Signup/Login** (see that section).

#### 2) Issue challenge (only if exists)

**GET** `/wallet/partner/challenge?address={addr}&provider={provider}`

**Response 200**

```json
{
  "random_message": "8a4d0da9c0",
  "expires_at": 1754163986,
  "challenge_token": "c9d1f0f4a1"
}
```

**Notes**

* Single-use `challenge_token`, bound to `address`, `provider`, and `random_message`.
* `expires_at` in UNIX seconds (allow small clock skew).

#### 3) Verify signature (complete login)

**POST** `/wallet/partner/verify`

**Request**

```json
{
  "provider": "coinbase",
  "wallet_address": "0x1234...ABCD",
  "signed_message": "0x3045022100...",
  "random_message": "8a4d0da9c0",
  "expires_at": 1754163986,
  "challenge_token": "c9d1f0f4a1"
}
```

**Response 200**

```json
{
  "message": "certified successfully",
  "token": "firebase_custom_token",
  "uid": "firebase_uid",
  "wallet_id": "w_123"
}
```

**Server checks**

* `challenge_token` exists, unused, not expired.
* `random_message` & `expires_at` match the stored challenge.
* `wallet_address` and `provider` match the challenge binding.
* `signed_message` verifies for the **exact** `random_message` using `wallet_address`’s public key.
* Partner wallet record exists and maps to `wallet_id`/`uid`.

### End-to-end sequences

#### A) Happy path (wallet exists)

1. **FE → Obox:** `GET /wallet/partner/exists?address&provider`
2. **Obox:** returns `{ exists: true, wallet_id, uid }`
3. **FE → Obox:** `GET /wallet/partner/challenge?address&provider`
4. **FE:** wallet signs `random_message` via `personal_sign`
5. **FE → Obox:** `POST /wallet/partner/verify { provider, wallet_address, signed_message, random_message, expires_at, challenge_token }`
6. **Obox:** returns `{ token, uid, wallet_id }`
7. **FE:** exchanges `token` with Firebase SDK to establish the session

#### B) Fallback (wallet not found)

1. **FE → Obox:** `GET /wallet/partner/exists?address&provider`
2. **Obox:** returns `{ exists: false }`
3. **FE:** **fallback to “ZCN Mnemonic Signup/Login Flow”** to create/link an account
4. (Optional) Offer a post-signup step in profile settings to map the partner wallet

### FE notes

* Use the provider’s recommended signing method (e.g., `ethereum.request({ method: "personal_sign", params: [hexMessage, address] })` for EVM wallets).
* **Sign exactly the `random_message`** as returned; don’t add banners/prefixes unless the backend expects them.
* On `{ exists: false }`, route to **ZCN Mnemonic Signup/Login** UI.

### Optional extensions

* **Link partner wallet** in user settings (after mnemonic signup) by running the challenge/verify sequence and persisting `(provider, address) → {uid, wallet_id}`.
* **Multi-wallet accounts:** allow multiple partner wallets mapped to the same `uid`; return the active `wallet_id` used for the token.
* **Network guardrails:** include `chainId` in the challenge and enforce it at verification time.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.zus.network/zus-docs/system-overview/user-authentication-and-wallet-management-system/wallet-based-authentication-flows.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
