# MineRace — Student Client Guide

This document is the contract between your client/bot and the server. If your code follows it, you can write your wallet, transactions, and miner in **any** language. Examples below are in JavaScript and Python; the spec applies to all.

Server base URL is whatever your instructor publishes (e.g. `http://minerace.example.com:3000`). Examples here use `http://localhost:3000`.

---

## 1. Wallet

A wallet is just an **secp256k1 ECDSA keypair**. There is no registration step — your public key *is* your address. The server only learns about your address when a transaction mentions it.

### Address format

- Curve: **secp256k1**
- Address = **uncompressed** public key, hex-encoded
- Format: `04 || X || Y` where X and Y are each 32 bytes (64 hex chars)
- Total: **130 hex chars** starting with `04`

> Common mistake: some libraries (like Python's `ecdsa`) give you only `X || Y` (128 hex chars). You must prepend `"04"` yourself.

### Generate a keypair

**JavaScript** (npm package `elliptic`):
```js
const { ec: EC } = require('elliptic');
const ec = new EC('secp256k1');

const key = ec.genKeyPair();
const privateKey = key.getPrivate('hex');     // 64 hex chars
const publicKey  = key.getPublic('hex');      // 130 hex chars, starts with "04"
console.log({ privateKey, publicKey });
```

**Python** (pip package `ecdsa`):
```python
from ecdsa import SigningKey, SECP256k1

sk = SigningKey.generate(curve=SECP256k1)
private_key = sk.to_string().hex()                        # 64 hex chars
public_key  = "04" + sk.verifying_key.to_string().hex()   # 130 hex chars (don't forget "04")
print(private_key, public_key)
```

**Save the private key safely.** If you lose it, you lose your coins. There is no recovery.

### Sanity-check an address

Before you trust an address (yours or someone else's), you can verify it's a syntactically and cryptographically valid secp256k1 pubkey:

```
GET /api/validate/:address
→ { "address": "04abc...", "valid": true }
→ { "address": "abc",      "valid": false, "reason": "wrong length: expected 130 hex chars, got 3" }
```

The check covers length, hex format, the `04` prefix, and that the (X, Y) coordinates lie on the curve. It does **not** check whether the address has ever been used on the chain.

---

## 2. Signing a transaction

Every non-coinbase transaction must carry a signature your address can be verified against.

### What gets signed

The hash to sign is:
```
SHA256( from + to + amount )
```

- **Direct string concatenation, no separators, no JSON.**
- `amount` is converted to its default string form (e.g. `5` → `"5"`, `5.5` → `"5.5"`).
- `from` is your public key (the 130-char hex).
- `to` is the recipient's public key.

> ⚠️ **Cross-language number gotcha (Python especially).** JS treats `5` and `5.0` as the same number — `(5.0).toString()` gives `"5"`. Python doesn't: `str(5.0) = "5.0"`. If you sign over `"5.0"` but send JSON `5.0`, the server hashes `"5"` and your signature fails.
>
> **Rule:** for whole-coin amounts, use **integers**, not floats. In Python: `amount = 5`, not `amount = 5.0`. In any language: if you have a float that happens to be a whole number, cast to int before signing and before sending.

### Signature format

- Algorithm: **ECDSA over secp256k1**
- Encoding: **DER**, then hex
- Typical length: 140–144 hex chars (variable; DER is length-prefixed)

### Sign in code

**JavaScript:**
```js
const crypto = require('crypto');
const { ec: EC } = require('elliptic');
const ec = new EC('secp256k1');

function signTx(privateKeyHex, from, to, amount) {
  const dataHash = crypto.createHash('sha256')
    .update(from + to + amount)
    .digest('hex');
  const key = ec.keyFromPrivate(privateKeyHex, 'hex');
  return key.sign(dataHash, 'hex').toDER('hex');
}
```

**Python:**
```python
import hashlib
from ecdsa import SigningKey, SECP256k1
from ecdsa.util import sigencode_der

def sign_tx(private_key_hex: str, from_addr: str, to_addr: str, amount) -> str:
    # Normalize whole-number floats to int so str() matches what JS produces.
    # str(5.0) -> "5.0" in Python, but JS's (5.0).toString() -> "5".
    if isinstance(amount, float) and amount.is_integer():
        amount = int(amount)
    sk = SigningKey.from_string(bytes.fromhex(private_key_hex), curve=SECP256k1)
    payload = (from_addr + to_addr + str(amount)).encode()
    digest  = hashlib.sha256(payload).digest()
    sig     = sk.sign_digest(digest, sigencode=sigencode_der)
    return sig.hex()
```

The server verifies with the same hash and your `from` field as the public key. If `from` is not a valid secp256k1 point or the signature doesn't match, the tx is rejected.

---

## 3. Transactions

### Send a transaction

`POST /api/transaction`

```json
{
  "from":      "04...",        // your public key
  "to":        "04...",        // recipient's public key
  "amount":    5,              // positive number
  "signature": "30440220..."   // DER-hex from §2
}
```

The server checks:
1. All four fields are present and `amount > 0` — else `400 Missing required fields: from, to, amount (>0), signature`
2. `to` is a well-formed secp256k1 pubkey — else `400 Invalid 'to' address: ...`
3. Signature is valid for `from` — else `400 Invalid signature`
4. `from`'s available balance ≥ `amount` (available = confirmed − unconfirmed outgoing in the mempool) — else `400 Insufficient balance`

On success: `200 { "message": "Transaction added to mempool" }`.

### Test before signing (or before submitting)

To validate inputs without committing anything, use the pre-flight endpoint. It accepts the same body as `POST /api/transaction`, except `signature` is optional:

```
POST /api/validate-transaction
Body: { from, to, amount, signature? }

→ { "valid": true }
→ { "valid": false, "errors": [
      { "field": "from",      "reason": "wrong length: expected 130 hex chars, got 64" },
      { "field": "amount",    "reason": "must be a positive number" },
      { "field": "balance",   "reason": "insufficient: available 3, need 5" },
      { "field": "signature", "reason": "does not verify against `from`" }
    ] }
```

All errors are returned at once so you can fix everything in a single round. This endpoint never mutates state — call it as often as you like.

**curl example:**
```bash
curl -X POST http://localhost:3000/api/transaction \
  -H "Content-Type: application/json" \
  -d '{"from":"04abc...","to":"04def...","amount":5,"signature":"3044..."}'
```

### Check a balance

`GET /api/balance/<address>` → `{ "address": "04...", "balance": 12 }`

Balance is computed from confirmed (mined) transactions only. Pending tx in the mempool do **not** count toward your balance until they're mined into a block.

### How to get coins

There is no faucet. Two ways:
1. **Mine a block** — earn the mining reward (see §4).
2. **Ask a friend** who has coins to send you some.

---

## 4. The mempool

The **mempool** is the pool of *pending* transactions: signed and accepted by the server, but not yet mined into a block. Once a miner includes a tx in a winning block, that tx is removed from the mempool and applied to balances.

### Inspect the mempool

`GET /api/mempool` → array of pending tx objects, e.g.:
```json
[
  { "from": "04abc...", "to": "04def...", "amount": 5, "signature": "30..." },
  { "from": "04xyz...", "to": "04abc...", "amount": 2, "signature": "30..." }
]
```

### What happens to a tx

```
POST /api/transaction
        │
        ▼
   ┌─────────┐         miner picks it
   │ mempool │ ───────────────────────► block #N
   └─────────┘                              │
        ▲                                   ▼
        │                            POST /api/submitBlock
        └────────── stays here              │
            until included in a block       ▼
                                       balances updated;
                                       tx removed from mempool
```

If a tx has insufficient balance at the time a block is being assembled (because a competing tx already drained the sender), the block containing it will be rejected. Miners are responsible for choosing a non-conflicting tx set.

---

## 5. Mining

Mining is how new blocks are added and how new coins enter circulation. To mine, you compete with other students (and 3 slow server bots) to be the **first** to find a valid nonce for the next block.

### Step 1 — Get the challenge

`GET /api/challenge`

```json
{
  "index":         42,
  "previousHash":  "0000a3f1...",
  "difficulty":    8,                  // required number of leading zero hex chars
  "miningReward":  50,                 // the live value — always read from the response, never hardcode
  "pendingTransactions": [
    { "from": "04abc...", "to": "04def...", "amount": 5, "signature": "30..." }
  ]
}
```

`difficulty: 8` means the block hash must start with **8 hex zeros** (`"00000000..."`). Both `difficulty` and `miningReward` may be adjusted at runtime (DAA + admin overrides), so always use the values from this response — don't bake them into your client.

### Step 2 — Build your block

A block is:

```json
{
  "index":         42,
  "timestamp":     1730000000000,
  "previousHash":  "0000a3f1...",
  "transactions":  [ ... ],
  "nonce":         123456789,
  "hash":          "00000000abc..."
}
```

Rules for `transactions`:
- Must contain **exactly one** mining-reward tx:
  ```json
  { "from": "MINING_REWARD", "to": "<your_address>", "amount": 50, "signature": null }
  ```
  (`amount` must equal `miningReward` from the challenge response. The example shows the current live value, but it can change — read it dynamically.)
  - `signature` must be the literal `null`, not omitted, not an empty string, not anything else. The server validates this strictly.
  - `to` must be a syntactically and cryptographically valid secp256k1 pubkey (130 hex, leading `04`, on-curve). Burning rewards to a non-pubkey is rejected.
- May include **any subset** of the pending transactions from the challenge. Including more pending tx makes your block bigger but doesn't pay extra (no fees in this network).
- Each non-coinbase tx is re-validated by the server: signature must verify, sender must have enough balance when the block is applied sequentially.

### Step 3 — Compute the block hash

The block hash is:
```
SHA256( index + timestamp + previousHash + JSON.stringify(transactions) + nonce )
```

⚠️ **Critical for non-JS students** — the server validates by re-serializing the `transactions` array with JavaScript's `JSON.stringify` (compact, no whitespace). To make your hash match the server's recomputation:

1. Use **compact JSON** when computing the hash — no spaces, no newlines, no indentation.
2. Inside each tx object, use this **exact key order**: `from`, `to`, `amount`, `signature`. Same for the reward tx.
3. Numbers are not quoted (`"amount":5`, not `"amount":"5"`).
4. `null` is the literal `null` (the reward tx's `signature` field).

A valid serialized tx looks like:
```
{"from":"MINING_REWARD","to":"04abc...","amount":50,"signature":null}
```

### Step 4 — Find a valid nonce (proof of work)

Loop: increment `nonce`, recompute the hash, check if it has the required leading zeros. Stop when you find one.

**JavaScript:**
```js
const crypto = require('crypto');

function mineNonce(block, difficulty) {
  const target = '0'.repeat(difficulty);
  let nonce = 0;
  while (true) {
    const data = block.index + block.timestamp + block.previousHash
               + JSON.stringify(block.transactions) + nonce;
    const hash = crypto.createHash('sha256').update(data).digest('hex');
    if (hash.startsWith(target)) return { nonce, hash };
    nonce++;
  }
}
```

**Python:**
```python
import hashlib, json

def mine_nonce(block, difficulty):
    target = "0" * difficulty
    nonce = 0
    # Use compact JSON, key order matching the server
    txs_json = json.dumps(block["transactions"], separators=(",", ":"))
    while True:
        data = f"{block['index']}{block['timestamp']}{block['previousHash']}{txs_json}{nonce}"
        h = hashlib.sha256(data.encode()).hexdigest()
        if h.startswith(target):
            return nonce, h
        nonce += 1
```

> Python tip: build your tx dicts with `OrderedDict` or insert keys in the order `from, to, amount, signature` so `json.dumps` preserves it.

### Step 5 — Submit your block

`POST /api/submitBlock`

```json
{
  "index":        42,
  "timestamp":    1730000000000,
  "previousHash": "0000a3f1...",
  "transactions": [ ... ],
  "nonce":        123456789,
  "hash":         "00000000abc..."
}
```

Server checks (in order):
1. `index` must equal `latestBlock.index + 1`
2. `previousHash` must equal `latestBlock.hash`
3. Recomputed hash from your block fields must equal `hash`
4. `hash` must start with `'0' * difficulty`
5. Exactly one `MINING_REWARD` tx, amount = `miningReward`
6. Each other tx: valid signature, sufficient balance when applied sequentially

On success: `200 { "message": "Block accepted", "block": {...} }`.

Common errors:
| Error | Cause |
|---|---|
| `Stale block: index mismatch` | Someone else got block #N before you |
| `Previous hash mismatch` | The chain advanced; refetch `/api/challenge` |
| `Hash mismatch` | Your hash was computed differently than the server recomputes (usually a JSON ordering / whitespace issue — see §5 step 3) |
| `Insufficient proof of work` | Hash doesn't start with enough zeros |
| `Must have exactly one mining reward` | Zero or multiple coinbase tx |
| `Mining reward must be N` | Wrong reward amount |
| `` Mining reward `to` must be a valid secp256k1 pubkey `` | Reward recipient isn't a real pubkey (130 hex, `04` prefix, on-curve) |
| `Invalid transaction signature` | A tx in your block has a bad signature (or a coinbase tx whose `signature` isn't literal `null`) |
| `` Transaction `to` must be a valid secp256k1 pubkey `` | A non-coinbase tx in your block sends to a non-pubkey string |
| `Insufficient balance in transaction` | A tx in your block would overdraft the sender |

### Step 6 — Loop

After submitting (success or fail), refetch `/api/challenge` and try again. The chain moves on with or without you.

---

## 6. Other useful endpoints

| Endpoint | Purpose |
|---|---|
| `GET /api/chain` | Full chain (all blocks) |
| `GET /api/chain/:index` | A single block, looked up by its `index` field |
| `GET /api/balance/:address` | Balance for an address |
| `GET /api/validate/:address` | Sanity-check that an address is a well-formed secp256k1 pubkey before you sign/send. Returns `{ address, valid: true }` or `{ address, valid: false, reason: "..." }`. |
| `POST /api/validate-transaction` | Pre-flight a tx draft. Body: `{ from, to, amount, signature? }` — signature is optional. Returns `{ valid: true }` or `{ valid: false, errors: [...] }` with per-field reasons. Does not mutate state. |
| `GET /api/mempool` | Pending transactions |
| `GET /api/transactions?limit=N` | Recent confirmed transactions across the whole chain, newest first. `limit` defaults to 50, capped at 500. |
| `GET /api/transactions/:address` | Every confirmed tx touching the address (sent or received), in chronological order. Each row carries `direction: "in" \| "out"` and a `coinbase` flag. |
| `GET /api/stats` | Per-address mining stats (raw map) |
| `GET /api/leaderboard` | Sorted leaderboard, bots tagged with `isBot: true` |
| `GET /api/difficulty` | Current difficulty + reward |
| `POST /api/chat` | Ask the AI agent about chain state |

### `/api/validate/:address` reason strings

The server returns one of these `reason` values when an address is rejected (useful if your client wants to surface a specific hint to the user):

| Reason | What's wrong |
|---|---|
| `not a string` | The value isn't a string (e.g. you passed a number or null) |
| `wrong length: expected 130 hex chars, got N` | Address length is anything other than 130 |
| `must start with "04" (uncompressed pubkey prefix)` | Missing the `04` prefix |
| `not a valid hex string` | Contains characters outside `0-9a-fA-F` |
| `point not on secp256k1 curve` (or library-specific) | Syntactically OK but (X, Y) isn't on the curve |

### Extra fields on block objects

Blocks returned by `GET /api/chain` carry a `receivedAt` field in addition to the spec schema (§5 step 2). It's the server's timestamp at the moment the block was accepted (`Date.now()`), and is used internally for the difficulty adjustment algorithm. You can ignore it if your client doesn't need it.

### CORS

The server sends `Access-Control-Allow-Origin: *` on all routes, so browser-based clients on any origin can talk to it directly — no proxy needed.

---

## 7. Suggested order to build

1. **Wallet generator** — print and save your keypair somewhere safe.
2. **Balance checker** — `GET /api/balance/<your_address>` (will be 0 until you mine or someone sends).
3. **Miner** — implement the §5 loop until you win a block. You now have 10 coins.
4. **Tx sender** — sign and `POST /api/transaction` to a friend's address. Watch the mempool with `GET /api/mempool`. Wait for someone to mine your tx into a block. Re-check both balances.
5. **Iterate** — make your miner faster, smarter (include other students' tx in your blocks), or more efficient.

Good luck.
