# Ed25519 Security Use Cases

This document describes how the Momswap Geo backend uses Ed25519 signatures for authentication and registration.

## Overview

- **Identity** = Ed25519 public key (base64url, 43 chars, no padding)
- **All keys** use RFC 8032 Ed25519; Go `crypto/ed25519` on server, `@noble/ed25519` in TypeScript client
- **Signatures** are base64url-encoded (64 bytes → 86 chars unpadded)
- **Private keys never leave the client**; server only verifies signatures

```mermaid
flowchart LR
  userClient[UserClient] -->|"holds private key locally"| privateKey[PrivateKey]
  userClient -->|"shares public key"| backendApi[BackendAPI]
  backendApi -->|"registers identity by public key"| usersTable[(UsersByPublicKey)]
```

## Use Cases

### 1. Login (Challenge–Response)

Proves the client controls the private key for a registered public key.

**Message to sign:** `login:` + nonce (exact UTF-8 string)

```mermaid
sequenceDiagram
  participant C as Client
  participant A as API
  participant U as UsersStore

  C->>A: POST /v1/auth/challenge {publicKey}
  A-->>C: {nonce, messageToSign}
  C->>C: sign(messageToSign, privateKey)
  C->>A: POST /v1/auth/login {publicKey, nonce, signature}
  A->>U: load user by publicKey
  A->>A: verify signature with publicKey
  A-->>C: {accessToken}
```

**Flow:**

1. `POST /v1/auth/challenge` with `{"publicKey":"<base64url>"}`
2. Server returns `{"nonce":"...", "messageToSign":"login:<nonce>"}`
3. Client signs `login:<nonce>` with private key
4. `POST /v1/auth/login` with `{"publicKey","nonce","signature"}`
5. Server verifies and returns `{"accessToken":"..."}`

**Example (curl):**

```bash
# Step 1: Get challenge
CHALLENGE=$(curl -s -X POST https://tenerife.baby/v1/auth/challenge \
  -H "Content-Type: application/json" \
  -d '{"publicKey":"txdkGKNdcZIEoQMJ0dqum3msjT6-2mO4yLVhtidRFJI"}')
NONCE=$(echo "$CHALLENGE" | jq -r '.nonce')

# Step 2: Sign "login:$NONCE" with your private key, then:
curl -s -X POST https://tenerife.baby/v1/auth/login \
  -H "Content-Type: application/json" \
  -d "{\"publicKey\":\"txdkGKNdcZIEoQMJ0dqum3msjT6-2mO4yLVhtidRFJI\",\"nonce\":\"$NONCE\",\"signature\":\"<your_signature_base64url>\"}"
```

**Example (TypeScript):**

```ts
const challenge = await client.createChallenge(publicKey);
const signature = await signMessage(privateKey, challenge.messageToSign);
const token = await client.loginWithSignature(publicKey, privateKey);
```

---

### 2. Register by Signing Service Key

Registers a new user without an invitation. User proves key ownership by signing the API's advertised public key.

**Message to sign:** The service public key string (exact value from `GET /v1/service-key`)

**Flow:**

1. `GET /v1/service-key` → `{"publicKey":"<base64url>"}`
2. Client signs that exact string with private key
3. `POST /v1/auth/register-by-signature` with `{"publicKey","signature"}`
4. Server verifies and registers the user

**Requires:** `SERVICE_PUBLIC_KEY` or `ADMIN_PUBLIC_KEY` must be set.

**Example (curl):**

```bash
# Step 1: Fetch service key
SERVICE_KEY=$(curl -s https://tenerife.baby/v1/service-key | jq -r '.publicKey')

# Step 2: Sign $SERVICE_KEY with your private key, then:
curl -s -X POST https://tenerife.baby/v1/auth/register-by-signature \
  -H "Content-Type: application/json" \
  -d "{\"publicKey\":\"<your_pubkey_base64url>\",\"signature\":\"<signature_base64url>\"}"
```

**Example (TypeScript):**

```ts
await client.registerBySigningServiceKey(publicKey, privateKey);
```

---

### 3. Create Invitation

An authenticated user creates a signed invitation for new users.

**Message to sign:** `invite:` + base64url(payload)

Payload JSON:

```json
{
  "jti": "unique-token-id",
  "inviterPublicKey": "<inviter_base64url>",
  "inviteePublicKey": "",
  "expiresAtUnix": 1735689600,
  "maxUses": 1
}
```

**Flow:**

1. Build payload, base64url-encode it → `payloadB64`
2. Sign `invite:` + payloadB64 with inviter's private key
3. `POST /v1/invitations` (with Bearer token) with `{"invitePayloadB64","inviteSignature"}`

**Example (TypeScript):**

```ts
const payload = { jti, inviterPublicKey, expiresAtUnix, maxUses };
const payloadB64 = bytesToBase64Url(textToBytes(JSON.stringify(payload)));
const inviteSig = await signMessage(inviterPrivateKey, `invite:${payloadB64}`);
await client.createInvitation(payload, inviterPrivateKey);
```

---

### 4. Register with Invitation

New user registers using a valid invitation and proves key ownership.

**Messages to sign:**

1. Invite: inviter signs `invite:` + payloadB64 (validated on create)
2. Proof: invitee signs `register:` + `<publicKey>` + `:` + `<jti>`

```mermaid
flowchart LR
  inviter[InviterUser] -->|"sign invite payload"| invitePayload[InvitePayloadAndSignature]
  newUser[NewUser] -->|"submit public key + invite + proof signature"| registerEndpoint[RegisterEndpoint]
  registerEndpoint --> verifyInvite[VerifyInviteSignatureAndRules]
  registerEndpoint --> verifyProof[VerifyNewUserProofSignature]
  verifyInvite --> usersStore[(UsersStore)]
  verifyProof --> usersStore
```

**Flow:**

1. Obtain invitation (payloadB64, inviteSignature, jti) from inviter
2. Sign `register:` + yourPublicKey + `:` + jti with your private key
3. `POST /v1/auth/register` with `{"publicKey","invitePayloadB64","inviteSignature","proofSignature"}`

**Example (TypeScript):**

```ts
const proofSig = await signMessage(privateKey, `register:${publicKey}:${jti}`);
await client.registerWithInvitation({
  publicKey, privateKey, invitePayloadB64, inviteSignature, jti
});
```

---

### 5. Secure Data Access (Collections & Features)

Only an authenticated token linked to a verified public key can create and read collections/features for that owner.

```mermaid
flowchart TD
  request[CreateOrReadRequest] --> bearerCheck[BearerTokenValidation]
  bearerCheck --> sessionStore[(Sessions)]
  sessionStore --> ownerKey[ResolvedOwnerPublicKey]
  ownerKey --> ownershipRule[OwnershipValidation]
  ownershipRule --> data[(CollectionsAndFeatures)]
  ownershipRule --> reject403[RejectForbidden]
```

---

## Message Format Summary

| Use Case              | Message to Sign                                      |
|-----------------------|------------------------------------------------------|
| Login                 | `login:` + nonce                                    |
| Register (service)     | Service public key (exact string from API)           |
| Create invitation      | `invite:` + base64url(payload)                       |
| Register (invitation)  | `register:` + publicKey + `:` + jti                 |

## Security Notes

- **Replay protection:** Challenges are single-use and expire (5 min TTL). Invitations have expiry and max-use limits.
- **Key binding:** Login challenge is bound to the public key that requested it. Invitation proof binds the new user's key to the JTI.
- **Canonical encoding:** Use UTF-8 for all messages; base64url without padding for keys and signatures.
- **Storage:** Client stores keys in `localStorage`; consider an encrypted wrapper for higher-security deployments.
- **Private keys stay client-side.** Session tokens are granted only after valid Ed25519 signature verification.

## API Endpoints Reference

| Method | Endpoint                        | Auth | Purpose                    |
|--------|---------------------------------|------|----------------------------|
| GET    | /v1/service-key                | No   | Fetch service pubkey       |
| POST   | /v1/auth/challenge             | No   | Get login nonce            |
| POST   | /v1/auth/login                 | No   | Exchange signature for JWT |
| POST   | /v1/auth/register-by-signature | No   | Register (sign service key)|
| POST   | /v1/auth/register              | No   | Register with invitation   |
| POST   | /v1/invitations                | Yes  | Create invitation          |
