Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support subaddresses in light wallet API #647

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 159 additions & 22 deletions api/lightwallet_rest.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,14 @@ concept - a public key or hash.

**base58-address**

A standard Monero public address encoded as a string in JSON.
A Monero public address encoded as a string in JSON.

**address_meta** object

| Field | Type | Description |
|-------|----------|------------------------|
| maj_i | `uint32` | Subaddress major index |
| min_i | `uint32` | Subaddress minor index |

**output** object

Expand All @@ -71,6 +78,7 @@ Information needed to spend an output.
| spend_key_images | array of `binary` objects | Bytes of key images |
| timestamp | `timestamp` | Timestamp of containing block |
| height | `uint64` | Containing block height |
| recipient | `address_meta` object | Address data of the recipient |

> `tx_id` is determined by the monero daemon. It is the offset that a
> transaction appears in the blockchain from the genesis block.
Expand Down Expand Up @@ -121,13 +129,14 @@ Information needed to spend an output.

**spend** object

| Field | Type | Description |
|------------|-----------------|----------------------------|
| amount | `uint64-string` | XMR possibly being spent |
| key_image | `binary` | Bytes of the key image |
| tx_pub_key | `binary` | Bytes of the tx public key |
| out_index | `uint16` | Index of source output |
| mixin | `uint32` | Mixin of the spend |
| Field | Type | Description |
|------------|-----------------------|----------------------------|
| amount | `uint64-string` | XMR possibly being spent |
| key_image | `binary` | Bytes of the key image |
| tx_pub_key | `binary` | Bytes of the tx public key |
| out_index | `uint16` | Index of source output |
| mixin | `uint32` | Mixin of the spend |
| sender | `address_meta` object | Address data of the sender |

> `out_index` is a zero-based offset from the original received output. The
> variable within the monero codebase is the `vout` array, this is the index
Expand Down Expand Up @@ -208,22 +217,45 @@ Randomly selected outputs for use in a ring signature.
> `outputs` is omitted by the server if the `amount` does not have enough
> mixable outputs.

**index_range** array

An array of fixed size 2, denoting an inclusive range. The first element is the
inclusive lower bound of the range. The second element is the inclusive upper
bound of the range. The second element is greater than or equal to the first
element. Both elements are of type `uint32`. Example: `[3,5]`.

**subaddrs** dictionary

Keys are the major index of Monero subaddresses, values are the minor indexes
of subaddresses within that major index.

| | Type | Description |
|-------|-------------------------------|-----------------------------------------|
| Key | `uint32-string` | Subaddress major index |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this uint32-string? Why not just uint32? Was this supposed to be a uint64-string (seems unlikely)?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uint32 is fine here. There's no reason to make it a string since it cannot be a uint64.

| Value | array of `index_range` arrays | Minor subaddresses within the the major |

> Minor subaddresses are in strictly increasing order with no overlapping
> bounds. For example, they can be `[[0,3],[5,9]]`, but not `[[0,3],[2,6]]`.

### Methods
#### `get_address_info`
Returns the minimal set of information needed to calculate a wallet balance.
The server cannot calculate when a spend occurs without the spend key, so a
list of candidate spends is returned.
Returns the minimal set of information needed to calculate a wallet balance,
including the balance of subaddresses. The server cannot calculate when a spend
occurs without the spend key, so a list of candidate spends is returned.

**Request** object

| Field | Type | Description |
|-----------|------------------|---------------------------------------|
| address | `base58-address` | Address to retrieve |
| address | `base58-address` | Standard address of the wallet |
| view_key | `binary` | View key bytes for authorization |

> If `address` is not authorized, the server must return a HTTP 403
> "Forbidden" error.

> If `address` is not a standard address, the server must return a HTTP 400
> "Bad Request" error.

**Response** object

| Field | Type | Description |
Expand All @@ -244,18 +276,22 @@ list of candidate spends is returned.
#### `get_address_txs`
Returns information needed to show transaction history. The server cannot
calculate when a spend occurs without the spend key, so a list of candidate
spends is returned.
spends is returned. The response should show a wallet's entire history,
including transactions to and from subaddresses.

**Request** object

| Field | Type | Description |
|----------|------------------|---------------------------------------|
| address | `base58-address` | Address to retrieve |
| address | `base58-address` | Standard address of the wallet |
| view_key | `binary` | View key bytes for authorization |

> If `address` is not authorized, the server must return a HTTP 403
> "Forbidden" error.

> If `address` is not a standard address, the server must return a HTTP 400
> "Bad Request" error.

**Response** object

| Field | Type | Description |
Expand Down Expand Up @@ -300,14 +336,14 @@ locally select outputs using a triangular distribution
> shall omit the `outputs` field in `amount_outs`.

#### `get_unspent_outs`
Returns a list of received outputs. The client must determine when the output
was actually spent.
Returns a list of received outputs to the wallet, including to subaddresses.
The client must determine when the output was actually spent.

**Request** object

| Field | Type | Description |
|------------------|------------------|----------------------------------|
| address | `base58-address` | Address to create/probe |
| address | `base58-address` | Standard address of the wallet |
| view_key | `binary` | View key bytes |
| amount | `uint64-string` | XMR send amount |
| mixin | `uint32` | Minimum mixin for source output |
Expand All @@ -317,6 +353,9 @@ was actually spent.
> If the total received outputs for the address is less than `amount`, the
> server shall return a HTTP 400 "Bad Request" error code.

> If `address` is not a standard address, the server must return a HTTP 400
> "Bad Request" error.

**Response** object

| Field | Type | Description |
Expand All @@ -331,10 +370,10 @@ Request an account scan from the genesis block.

**Request** object

| Field | Type | Description |
|----------|------------------|-------------------------|
| address | `base58-address` | Address to create/probe |
| view_key | `binary` | View key bytes |
| Field | Type | Description |
|----------|------------------|--------------------------------|
| address | `base58-address` | Standard address of the wallet |
| view_key | `binary` | View key bytes |

**Response** object

Expand All @@ -350,14 +389,17 @@ Request an account scan from the genesis block.
> `payment_id`, `import_fee`, and `payment_address` may be omitted if the
> client does not need to send XMR to complete the request.

> If `address` is not a standard address, the server must return a HTTP 400
> "Bad Request" error.

#### `login`
Check for the existence of an account or create a new one.

**Request** object

| Field | Type | Description |
|-------------------|------------------|----------------------------------|
| address | `base58-address` | Address to create/probe |
| address | `base58-address` | Standard address of the wallet |
| view_key | `binary` | View key bytes |
| create_account | `boolean` | Try to create new account |
| generated_locally | `boolean` | Indicate that the address is new |
Expand All @@ -372,6 +414,9 @@ Check for the existence of an account or create a new one.
> must be returned. Subsequent requests shall be HTTP 403 "Forbidden" until
> account is approved.

> If `address` is not a standard address, the server must return a HTTP 400
> "Bad Request" error.

**Response** object

| Field | Type | Description |
Expand Down Expand Up @@ -400,3 +445,95 @@ Submit raw transaction to be relayed to monero network.

> `status` is typically the response by the monero daemon attempting to relay
> the transaction.

#### `provision_subaddrs`
Provision subaddresses at specified indexes. No two clients should ever receive
the same newly provisioned subaddresses when calling this endpoint; the server
should guarantee that newly provisioned subaddresses are fresh.

**Request** object

| Field | Type | Description |
|-----------|------------------|-------------------------------------------------|
| address | `base58-address` | Standard address of the wallet |
| view_key | `binary` | View key bytes |
| maj_i * | `uint32` | Subaddress major index (defaults to 0) |
| min_i * | `uint32` | Subaddress minor index (defaults to 0) |
| n_maj * | `uint32` | Number of major subaddresses to provision |
| n_min * | `uint32` | Number of minor subaddresses to provision |
| get_all * | `boolean` | Whether to include all subaddresses in response |

> The various combinations of `maj_i`, `min_i`, `n_maj`, and `n_min` behave
> differently, but generally the server provisions subaddresses where it has not
> already provisioned them, with the indexes specified as lower bounds.

> If, for example, only `maj_i` is included, then the server should provision a
> default number of minor subaddresses (e.g. 500) within that `maj_i`, wherever
> minor subaddresses have not already been provisioned, starting with `min_i`
> set to 0 as the lower bound.

> If, for example, `maj_i`, `min_i`, `n_maj` and `n_min` are included, then the
> server should provision `n_maj` majors wherever major subaddresses have not
> already been provisioned starting with `maj_i` as the lower bound, and for
> each major, provision `n_min` subaddresses starting with `min_i` as the lower
> bound.

> All combinations follow the above framework.

> The server can choose to "pad" counts and provision *more* than a client
> requests.

> If the server cannot provision a specified number of subaddresses because the
> server would provision more than the maximum number of subaddresses, HTTP 409
> should be returned. Ack that the status code is not perfect here. In this
> case, the client should make a new request either with a smaller requested
> number of subaddresses to provision, or at a different major or minor index.

> If none of `maj_i`, `min_i`, `n_maj`, and `n_min` are included in the request,
> the server must return a HTTP 400 "Bad Request" error.

> `get_all` defaults to true if not included in the request.

**Response** object

| Field | Type | Description |
|----------------|------------|-------------------------------------------------------------|
| new_subaddrs | `subaddrs` | All new subaddresses provisioned in the request |
| all_subaddrs * | `subaddrs` | All subaddresses provisioned for the wallet (including new) |

#### `upsert_subaddrs`
Upsert subaddresses at the specified major and minor indexes. This endpoint is
idempotent.

**Request** object

| Field | Type | Description |
|-----------|------------------|-------------------------------------------------|
| address | `base58-address` | Standard address of the wallet |
| view_key | `binary` | View key bytes |
| subaddrs | `subaddrs` | Subaddresses to upsert |
Copy link
Contributor

@vtnerd vtnerd Aug 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if it already exists? The client can't tell if it's a new insert or an existing one. I suppose this is the difference between the other API call? Or should a HTTP error be returned?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upsert as in update unless exists in which case insert. Server should return its known set of subaddrs so client can know, for various reasons, which I will describe soon in some updates.

Over the past 1-2 years, I have done a massive amount of work on the LWS and discovered that the subaddr support in the LWS API as currently specced and as it even currently behaves is badly insufficient, so I will share a list of things I've discovered with some spec proposals shortly. I'm finishing it up now after collecting everything.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

insert unless exists*

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@j-berman is this a single subaddrs or an array? I'm assuming an array because the other instances (new_subaddrs and all_subaddrs) have to be arrays by design.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vtnerd it needs to be what i call a complex range of idxs. that is, a dictionary with maj idx str keys pointing to arrays of arrays of min idxs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds nearly identical to this design. A major index, pointing to an array of index ranges.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, it was your question, not anything major I'm claiming to point out yet, but btw I did intend to type array of arrays. you need support for discontiguous min idx ranges.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This API does support discontiguous min ranges. [10, [0,2],[4,7]] means 0,1,2,4,5,6,7 minor indexes with major index 10.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but that is a discontiguous min range.

| get_all * | `boolean` | Whether to include all subaddresses in response |

**Response** object

| Field | Type | Description |
|----------------|------------|-------------------------------------------------------------|
| new_subaddrs | `subaddrs` | All new subaddresses inserted in the request |
| all_subaddrs * | `subaddrs` | All subaddresses provisioned for the wallet (including new) |

#### `get_subaddrs`

Returns all subaddresses provisioned for a wallet.

**Request** object

| Field | Type | Description |
|----------|------------------|--------------------------------|
| address | `base58-address` | Standard address of the wallet |
| view_key | `binary` | View key bytes |

**Response** object

| Field | Type | Description |
|--------------|------------|---------------------------------------------|
| all_subaddrs | `subaddrs` | All subaddresses provisioned for the wallet |