Aaryamann Challani

Engineer and amateur cook writing about privacy, cryptography, and distributed systems

← Back to posts

Introduction

The Stealth address scheme was first proposed in 2022, with the paper being accepted in 2024. This idea piqued my interest since my work primarily involves RLN, a modification of Semaphore which brings rate-limiting capabilities through clever use of the external_nullifier used.

While working on RLN, we discovered that when a user registers to the membership set with their public key (more specifically, an identityCommitment), they risk deanonymizing their wallet address in the process.

An RLN membership set can be analyzed by the wallet addresses that have called the register() function on the smart contract. A need arose for obfuscating these registrations, by having counterparties register identityCommitment's on behalf of other users. This is possible today through the use of gas station networks, however, the privacy aspect of these services can be debated.

The stealth commitment protocol provides a unique mechanism through which Bob can generate an identityCommitment for Alice, and Alice can derive the private key (more specifically, identitySecret) for it. Bob can register the commitment on-chain, and signal through the stealth address protocol that someone (Alice) can generate the identitySecret for it.

The privacy aspect of the stealth commitment protocol can be further enhanced by using an ephemeral anonymous message passing system, like Waku, to ensure that there is plausible deniability that someone participated in the stealthcommitment protocol, and thereby reducing the costs associated with computing pairings on-chain.

Brief notes on the Stealth Commitment scheme

It is recommended to review the stealth address EIP before continuing to read through this article!

Adapting the Stealth Address protocol for the alt_bn128 curve

With the vast array of libraries available, computing the stealth commitment is fairly straightforward.

I chose to implement the stealth commitment protocol in Rust, using arkworks. While the implementation can be adapted for different curves, I implemented it just for the alt_bn128 curve as a PoC.

The PoC can be used in the following way -

use erc_5564_bn254::{random_keypair, generate_stealth_commitment, generate_stealth_private_key};

fn main() {
    let (spending_key, spending_public_key) = random_keypair();
    let (viewing_key, viewing_public_key) = random_keypair();

    // generate ephemeral keypair
    let (ephemeral_private_key, ephemeral_public_key) = random_keypair();

    let (stealth_commitment, view_tag) = generate_stealth_commitment(viewing_public_key, 
                                                                     spending_public_key, 
                                                                     ephemeral_private_key);

    let stealth_private_key_opt = generate_stealth_private_key(ephemeral_public_key, 
                                                               viewing_key, 
                                                               spending_key,
                                                               view_tag);

    if stealth_private_key_opt.is_none() {
        panic!("View tags did not match");
    }

    let derived_commitment = derive_public_key(stealth_private_key_opt.unwrap());
    assert_eq!(derived_commitment, stealth_commitment);
}

This way, a stealth_commitment generated can be registered to the RLN membership set on-chain.

How Waku can consume the library

The PoC in Rust has C FFI bindings that can be consumed by a variety of languages, including Nim, which is used in Waku's primary implementation, nwaku. There's an FFI test suite too!

Broadcasting Stealth Commitment metadata over Waku

The Stealth Address paper refers to a smart contract that is used for coordination for the viewing_public_key, spending_public_key and ephemeral_public_key. The smart contract emits events which are then received by all users, filtering by valid a view_tag from the event log.

One can replace this coordination scheme by using Waku - to broadcast the metadata at regular intervals, which can be received by the intended recipient and processed without any on-chain computation.

Below is the recommended wire format to be used with Waku -

message StealthCommitmentRequest {
	bytes spending_public_key = 1;
	bytes viewing_public_key = 2;
}

message StealthCommitmentPayload {
	bytes stealth_commitment = 1;
	bytes ephemeral_public_key = 2;
}

message StealthCommitmentResponse {
  bytes view_tag = 1;
  bytes stealth_commitment_payload = 3;
}

You may notice that the StealthCommitmentResponse uses bytes for the stealth_commitment_payload field. This is recommended since one can partially deserialise the StealthCommitmentResponse protobuf to check the view_tag , and if it belongs to them, they can proceed to deserialise the stealth_commitment_payload. It is faster to do so for commitments that do not belong to a user.

Stealth Commitments x RLN user flow

Future work

Most of the future work is to formalize this into a Waku Stealth Address protocol, which can be used for a varying set of elliptic curves, alt_bn128 just being one of the supported curves.

Thanks for reading!