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

EIP-0012 - dApp-Wallet Web Bridge #23

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Changes from 1 commit
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
277 changes: 277 additions & 0 deletions eip-0012.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
# EIP-0012: dApp-Wallet Web Bridge

* Authors: rooooooooob, Robert Kornacki
* Created: 10-Aug-2020
* License: CC0
* Forking: not needed

## Description

This document describes a communication bridge between cryptocurrency wallets and Ergo dApps which interact via javascript code injected into a web context. The communication is one-way from the dApp to the wallet.

## Motivation

Distributed apps (dApps) often require access to a user's wallet in order to function, and this is typically from a web context. In other cryptocurrencies, such as Ethereum, this is done via a library such as web3 that injects code into a web browser that the dApp can interact with to create transactions or obtain address data using a user's wallet. This should be done in a manner that maximizes privacy and an easy user experience.
rooooooooob marked this conversation as resolved.
Show resolved Hide resolved

## API

The proposed API is limited to just the minimum wallet <-> dApp communication needed rather than providing lots of utility functions (tx building, data conversions, etc) or node-access (for UTXO scanning). This is different compared to web3 as that functionality could be modular in a separate library and Ergo smart contracts don't need as much direct node access for basic dApp functionality compared to Ethereum.

This API is accessibly from a javascript object `ergo` that is injected into the web context. This object contains just the `request_read_access()` method upon dApp start.
rooooooooob marked this conversation as resolved.
Show resolved Hide resolved

### request_read_access() -> Promise\<bool>

If the API was not already injected, requests access to the API, and if the user consents, injects the full API similar to EIP-1102 into the `ergo` object. Returns true if the API has been injected, or false if the user refused access.
Does nothing and returns true if the API was already injected.

The wallet can choose to maintain its own whitelist if desired but this is on the wallet side and not a part of this standard. It could possibly also expose a subset of its state as responses to the other calls but this is also out of scope. All following API methods are injected upon consent.

### check_read_access() -> bool

Returns true if the full API was injected and we have read access, and false otherwise. This is potentially useful to have a cleaner UX on the dApp side (e.g. display request read access button for optional functionality rather than always requesting it).

### get_utxos(amount: Value = undefined, token_id: String = 'ERG', paginate: Paginate = 'undefined') -> [Box] | undefined | PaginateError

Returns a list of all unspent boxes tracked by the wallet that it is capable of signing for.
Takes an optional {amount} parameter, if not `undefined`, limits the returned UTXOs to {amount} of the token {token_id} to create it. if {amount} is not `undefined` and there is not sufficient balance of the specified token to reach {amount} then `undefined` is returned.
If {paginate} is not `undefined` then results are returned in a paginated manner in chronological order of use on the blockchain. If {paginate} is out of range, `PaginateError` is returned.

### get_balance(token_id: String = 'ERG') -> Value

Returns available balance of {token_id} owned by the wallet. A useful and minimal convenience functionality, that while could be served by iterating the return of `get_utxos` and summing, is useful enough and could be most likely quickly returned by wallets without needing to do this calculation.

### get_used_addresses(paginate: Paginate = 'undefined') -> [Address]

Returns all used (exist in confirmed transactions) addresses of the user's wallet.
If {paginate} is not `undefined` then results are returned in a paginated manner in chronological order of use on the blockchain. If {paginate} is out of range, `PaginateError` is returned.

### get_unused_addresses() -> [Address]

Returns unused addresses available that have not been used yet. The wallet would need to pre-generate addresses here within its restrictions (e.g. discover gap on indices for HD wallets). These can be used for receive addresses.

### sign_tx(tx: Tx) -> Promise\<SignedTx> | TxSignError

A signed tx is returned if the wallet can provide proofs for all inputs (P2PK ones). If not it produces an error. This should also have some kind of user pop-up which if rejected would produce an error as well. This way signing/keys are kept entirely in the wallet.

### sign_tx_input(tx: Tx, index: number) -> Promise\<SignedInput> | TxSignError

Lower level tx signing functionality that signs a single input if the wallet is able to, and returns an error if not. This can be useful for constructing transactions consisting of inputs from multiple parties, and should be general enough for future transaction signing use-cases. Likewise with `sign_tx` this should ask for user consent and can be rejected producing an error.

### sign_data(addr: Address, message: String) -> Promise\<String> | DataSignError

Signs the generic data {data} encoded as a hex string using the private key associated with {addr} if possible. If the wallet does not own this address, an error is returned. This should ask for user consent and produce an error if rejected. The wallet should also implement a message signing protocol such as the proposed [EmIP-005](https://github.com/Emurgo/EmIPs/blob/5b00fce84f31eb763892186eb9c88739ec809333/specs/emip-005.md) in order to make this endpoint safer for use, and make it harder for the user to accidentally sign data such as transactions, blocks, etc. Returns the signed data as a hex-encoded string.

TBD: Hash algorithm/format to use.

### submit_tx(tx: SignedTx) -> TxId | TxSendError

Uses the wallet to send the given tx and returns the TxId for the dApp to track when/if it is included as its own responsibility. This is technically not mandatory but any wallet that is using such a bridge should have this functionality. Wallets can additionally choose to rate-limit this call, and can return an error if they did not or could not send the transaction.

### add_external_box(box_id: BoxId)
rooooooooob marked this conversation as resolved.
Show resolved Hide resolved

Informs the wallet of a box it should track, specifically for P2S/P2SH boxes that the wallet might not be aware of. The wallet does not need to follow this suggestion and can handle how it does this on its own.

## Data Formats

Most of these types are meant to be the same as the JSON types in the Ergo full node, which matches up as of now with the `sigma-rust` representations.

### Address

String as the standard base58 encoding of an address.

### Box

Same as `sigma-rust`:
```
{
boxId: BoxId,
value: Value,
ergoTree: ErgoTree,
assets: [TokenAmount],
additionalRegisters: { String => Constant },
creationHeight: int,
transactionId: TxId,
index: int,
}
```
Additional registers have string ids "R4", "R5" to "R9".

### BoxCandidate

Same as `Box` without `transactionId` and `index` as those will be computed when the tx is completed.

### BoxId

Hex string of the box id.

### Constant

Uses `sigma-rust` rep - Hex-encoded bytes for `SigmaSerialize` of a constant.

### ContextExtension

Uses the full node JSON rep (id to hex-encoded binary buffer)
```
{
values: { String => String }
}
```
or the empty `{}` object for P2PK inputs.

### ErgoTree

Uses `sigma-rust` rep - Hex string representing serialized bytes of the tree using `SigmaSerialize`

### Input

Read-only input (e.g. oracle usage or one yet to be signed) that is not spendable. Uses `sigma-rust` JSON rep:
kushti marked this conversation as resolved.
Show resolved Hide resolved
```
{
boxId: BoxId,
}
```
This is `DataInput` in `sigma-rust` and was renamed since it's used for both data inputs and for unsigned inputs.
For an `Input` in `sigma-rust`, see `SignedInput` here.

### SignedInput

Uses `sigma-rust` rep:
```
{
boxId: BoxId,
spendingProof: ProverResult,
}
```
This is `Input` in `sigma-rust`.

## Paginate

```
{
page: int,
limit: int,
}
```
Used to specify optional pagination for some API calls. Limits results to {limit} each page, and uses a 0-indexing {page} to refer to which of those pages of {limit} items each.

## PaginateError

```
{
maxSize: int,
}
```
{maxSize} is the maximum size for pagination and if we try to request pages outside of this boundary this error is thrown.

### ProverResult
Uses `sigma-rust` rep - proof is a byte array serialized using `SigmaSerialize` in hex format:
```
{
proofBytes: String,
extension: ContextExtension,
}
```

### SignedTx

Uses `sigma-rust` rep for a transaction:
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Uses `sigma-rust` rep for a transaction:
Uses `sigma-rust` `Transaction` rep for a transaction:


```
{
id: TxId,
inputs: [SignedInput],
dataInputs: [Input],
outputs: [Box],
size: int,
}
```
with the difference with `Tx` is that the inputs are signed (`Input` instead of `DataInput`)


### TokenAmount

Uses `sigma-rust` rep:
```
{
tokenId: TokenId,
amount: Value,
}
```

### TokenId

Uses `sigma-rust` rep - Hex string of the id.

### Tx

An unsigned transaction. Uses a modified version of the transaction representation in `sigma-rust`.
```
{
id: TxId,
inputs: [Input],
dataInputs: [Input],
outputCandidates: [BoxCandidate],
}
```
This differs from `Tx` in that that `inputs` would be `[Input]`, however we haven't signed this tx yet and also the outputs are merely candidates.

### TxId

Same as `sigma-rust` - string hex encoding

### TxSendError

```
TxSendErrorCode {
Refused: 1,
Failure: 2,
}
TxSendError {
code: TxSendErrorCode,
info: String
}
```

* Refused - Wallet refuses to send the tx (could be rate limiting)
* Failure - Wallet could not send the tx

### TxSignError

```
TxSignErrorCode {
ProofGeneration: 1,
UserDeclined: 2,
}
TxSignError {
code: TxSignErrorCode,
info: String
}
```

* ProofGeneration - User has accepted the transaction sign, but the wallet was unable to sign the transaction (e.g. not having some of the private keys)
* UserDeclined - User declined to sign the transaction


### DataSignError

```
DataSignErrorCode {
ProofGeneration: 1,
AddressNotPK: 2,
UserDeclined: 3,
InvalidFormat: 4,
}
DataSignError {
code: DataSignErrorCode,
info: String
}
```

* ProofGeneration - Wallet could not sign the data (e.g. does not have the secret key associated with the address)
* AddressNotPK - Address was not a P2PK address and thus had no SK associated with it.
* UserDeclined - User declined to sign the data
* InvalidFormat - If a wallet enforces data format requirements, this error signifies that the data did not conform to valid formats.

### Value

BigNum-like object. Represents a value which may or may not be ERG. Value is in the smallest possible unit (e.g. nanoErg for ERG). It can be either a `number` or a `string` using standard unsigned integer text representation.
Copy link
Member

Choose a reason for hiding this comment

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

Value is a positive but encoded as i64 number actually.

Copy link
Member

Choose a reason for hiding this comment

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

@kushti Actually it was a "raw" u64 initially, but now I made a newtype BoxValue(u64)

Copy link
Author

Choose a reason for hiding this comment

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

If it's positive somewhere (even if Long/i64 encoded), then it should be fine as-is here, right? I didn't look into how Scala handles Long deserialization from JSON for numbers too big to fit, but in sigma-rust the wrapped u64 should take in a string-encoded uint like "1053546432154" afaik. Please correct me if I'm wrong and this should be changed.

Copy link
Member

Choose a reason for hiding this comment

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

Scala representation of Long is signed, so max value is 2^63-1.

Copy link
Author

Choose a reason for hiding this comment

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

For JSON deserialization in the full node is this description fine though? Wouldn't the max possible value for nanoergs be much smaller than this? I guess for other token types it could be relevant - in which case should we keep it as-is but specify that it cant' be more than a 64-bit signed number or what?