Skip to content
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;
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

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**

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)