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-41 Stealth address standard #87

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ Please check out existing EIPs, such as [EIP-1](eip-0001.md), to understand the
| [EIP-0025](eip-0025.md) | Payment Request URI |
| [EIP-0027](eip-0027.md) | Emission Retargeting Soft-Fork |
| [EIP-0031](eip-0031.md) | Babel Fees |
| [EIP-0039](eip-0039.md) | Monotonic box creation height rule |
| [EIP-0039](eip-0039.md) | Monotonic box creation height rule |
| [EIP-0041](eip-0041.md) | Stealth address standard |
148 changes: 148 additions & 0 deletions eip-0041.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# EIP-41 Stealth Address Standard

* Author: ross-weir
* Status: Proposed
* Created: 16-Dec-2022
* Last edited: 17-Dec-2022
* License: CC0
* Forking: not needed

## Motivation

This specification defines a standardized way of implementing and interacting with stealth addresses on the Ergo blockchain.

Stealth addresses enable recipients of a payment to remain anonymous when receiving funds thus providing financial privacy should an actor desire it.

## Scenario

An actor, `Receiver`, wishes to receive a stealth payment so they generate a public address and share it.

An actor, `Sender`, wishes to make a stealth payment to `Receiver` so they create a box protected by a "one-time-secret" generated from the `Receiver`s public address. Due to the method of generation this box will be spendable by `Receiver` and cannot be linked to the `Receiver`s public address they shared.

## Stealth address specification

The implemention suggested in this EIP was posted by `scalahub` in a thread on `ergoforum.org` [[1]](#1) and is outlined below.

**Script protecting stealth boxes:**

```scala
{
// ===== Contract Information ===== //
// Name: EIP-0041 Stealth address contract
// Version: 1.0.0
val gr = SELF.R4[GroupElement].get
val gy = SELF.R5[GroupElement].get
val ur = SELF.R6[GroupElement].get
val uy = SELF.R7[GroupElement].get

proveDHTuple(gr,gy,ur,uy)
}
```

**Script ErgoTree:**

```
1000cee4c6a70407e4c6a70507e4c6a70607e4c6a70707
```

**Generation of stealth box registers [[5]](#5):**

```typescript
const g = new EC("secp256k1").g; // group element generator
const u = receiverPublicAddress;
ross-weir marked this conversation as resolved.
Show resolved Hide resolved
const r = BigInt(rand(32));
const y = BigInt(rand(32));
const gr = g.mul(r); // gr = g^r = R4
const gy = g.mul(y); // gy = g^y = R5
const ur = u.mul(r); // ur = u^r = R6
const uy = u.mul(y); // uy = u^y = R7
```

**Box register declarations:**

- Register `R4`
- Type: `SConstant[SGroupElement]`
- Value: gr = g^r
- Register `R5`
- Type: `SConstant[SGroupElement]`
- Value: gy = g^y
- Register `R6`
- Type: `SConstant[SGroupElement]`
- Value: ur = u^r
- Register `R7`
- Type: `SConstant[SGroupElement]`
- Value: uy = u^y

> 📝 As discussed in the `ergoforum` discussion [[1]](#1) this register declaration is larger in size than that originally proposed by `kushti` but possesses the useful property that it could look like a legitimate use-case.

## `Receiver`s public address and wallets
ross-weir marked this conversation as resolved.
Show resolved Hide resolved

Leaning on deterministically generated wallet addresses [[6]](#6) enables stealth boxes to be retreived following a fresh wallet restore but raises a problem. When a user shares such a public address and wishes to receive stealth payments how do wallets determine they are requesting a stealth payment - the address will appear to be a regular P2PK address.

Requiring `Sender` to manually perform special actions when sending a stealth payment in their wallet introduces the possibility of (in addition to other issues):

1. `Sender` uses a regular payment exposing a transaction that `Receiver` wanted to remain private
2. Introduces friction when making payments, possiblily reducing conversions
3. Less user friendly, `Sender` as a non-technical user might not know about stealth payments

Ideally when `Sender` is creating a stealth transaction in their wallet the UX flow should remain the same as a regular transaction, the fact that the `Receiver` is requesting a stealth payment should be opaque to the `Sender` when making the payment. To achieve this we could encode a "payment type" in the public address so wallets can detect the `Receiver` is requesting a specific type of payment and construct the transaction accordingly, similar to network and address type prefixes but only for supporting wallets.

This could extend in the future to more non-interactive custom payment types, not just stealth payments.

**Encoding**
ross-weir marked this conversation as resolved.
Show resolved Hide resolved

Encoding of the public key to include custom payment requests could be implemented as follows:

| Field | Type | Description |
|-------------------|----------|--------------------------------------------------------------------------|
| Magic Byte Prefix | byte | Magic byte indiciating `Receiver` is requesting a custom transaction |
| Custom Tx Type | byte | Enum defining the type of custom transaction `Receiver` is requesting |
| Network Address | byte[] | Standard network encoded p2pk address |

**Constructing the public address for a `Receiver` requesting a stealth payment**

```ts
enum CustomTxType {
Stealth = 0x0,
// More in future?
}

const magicBytePrefix = 0xCE // arbitrarily chosen for now
const customTxType = CustomTxType.Stealth
const publicShareableAddress = [magicBytePrefix, customTxType] + normalP2PKNetworkAddress

// publicShareableAddress is shared to social media, etc to receive payments
```

**Possible wallet implementation**

As mentioned previously, when `Sender` is making a payment to `Receiver` ideally their user journey would remain the same with the wallet handling the custom transaction type.

`Sender` inputs `Receiver`s public address encoded as above and a wallet might handle it in the following manner:

```ts
enum CustomTxType {
Stealth = 0x0
}

const magicBytePrefix = 0xCE
const receiverPublicAddress = new UInt8([/** receivers pub key **/])

if (receiverPublicAddress[0] === magicBytePrefix) {
const customTxType = receiverPublicAddress[1] as CustomTxType
const networkEncodedPubKey = receiverPublicAddress.slice(2)

handleCustomTx(customTxType, networkEncodedPubKey)
} else {
handleNormalTx(receiverPublicAddress) // handle making payment as normal
}
```

## References

- <a id="1">[1]</a> [Stealth Address Contract (ergoforum)](https://www.ergoforum.org/t/stealth-address-contract/255)
- <a id="2">[2]</a> [ErgoScript by example](https://github.com/ergoplatform/ergoscript-by-example/blob/main/stealthAddress.md)
- <a id="3">[3]</a> [Stealth-doc (ERGOHACK III aragogi)](https://github.com/aragogi/Stealth-doc)
- <a id="4">[4]</a> [Ethereum (EIP-5564)](https://eips.ethereum.org/EIPS/eip-5564#:~:text=A%20Stealth%20address%20is%20generated,compute%20the%20matching%20private%20key.)
- <a id="5">[5]</a> [TypeScript stealth address example](https://github.com/ross-weir/ergo-stealth-address-example/blob/main/index.ts)
- <a id="6">[6]</a> [Ergo (EIP-0003)](eip-0003.md)