# Register Device Key

Attach a device public key to a wallet, authorised by a signature from the wallet's own private key.

## Overview

Stores `devicePublicKey` on the wallet record. A wallet holds one device key: a later successful call replaces the previous value. The OpenAPI description states the key is used for push-notification-based signing flows. In this API version the stored key is not part of the wallet object returned by the wallet endpoints, and no other part of the API uses it. It is not used to encrypt key material — key export uses the ephemeral key passed to [Export Encrypted Key](https://docs.aureahub.com/docs/wallets-export-key.md).

The request must carry a signature over the wallet's most recent unused [Migration Challenge](https://docs.aureahub.com/docs/wallets-migration-challenge.md), made with the **wallet's** private key (EIP-191 over the 32 challenge bytes for EVM wallets; hex-encoded Ed25519 for Solana). A successful call consumes that challenge.

> ⚠️ `GET /v1/wallets/migration-challenge` returns `409` for wallets that are already `client_side` — including every wallet created with `POST /v1/wallets/client`. In the current implementation this endpoint can therefore only be completed for a wallet that is not yet `client_side`, in practice during a key migration after the key has been exported. Because it consumes the challenge, request a new one before calling [Confirm Client Custody](https://docs.aureahub.com/docs/wallets-confirm-custody.md).

## Endpoint

### `POST /v1/wallets/register-device`

Authentication: bearer token required.

Associates a device public key with a wallet after verifying a migration-challenge signature made with the wallet's key.

**Request body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `walletId` | string (uuid) | yes | Wallet to attach the device key to. |
| `signedChallenge` | string | yes | Signature over the 32 bytes of the wallet's migration challenge, made with the wallet's private key. |
| `devicePublicKey` | string | yes | Base64-encoded device public key. Stored as provided; its format is not validated. |

**Responses**

`200` OK

```json
{ "registered": true }
```

`400` Bad Request

```json
{
  "statusCode": 400,
  "error": "BadRequestError",
  "message": "No active challenge found. Call GET /migration-challenge first."
}
```

## Errors

Error responses

|  |  |  |
| --- | --- | --- |
| 400 | — | `No active challenge found. Call GET /migration-challenge first.` |
| 400 | — | `Challenge has expired. Request a new one.` |
| 404 | — | `Wallet not found` — unknown id, or the wallet belongs to another user. |
| 422 | WRONG_SIGNER | The signature was not made by the wallet's key. |
| 422 | SIGNATURE_DECODE_FAILED | The EVM signature could not be decoded. |

## Implementation

```javascript
import { getBytes } from 'ethers';

// walletKey: ethers Wallet for the wallet's own private key (not the device key)
async function registerDeviceKey(accessToken, walletId, walletKey, devicePublicKeyBase64) {
  const API = 'https://api.aureahub.com';
  const bearer = { Authorization: `Bearer ${accessToken}` };

  const { challenge } = await fetch(
    `${API}/v1/wallets/migration-challenge?walletId=${walletId}`,
    { headers: bearer }
  ).then(r => r.json());

  const res = await fetch(`${API}/v1/wallets/register-device`, {
    method: 'POST',
    headers: { ...bearer, 'Content-Type': 'application/json' },
    body: JSON.stringify({
      walletId,
      signedChallenge: await walletKey.signMessage(getBytes('0x' + challenge)),
      devicePublicKey: devicePublicKeyBase64
    })
  });
  if (!res.ok) throw new Error((await res.json()).message);
  return res.json(); // { registered: true }
}
```

---

Web version: https://docs.aureahub.com/#wallets-register-device
