DEV Community

marpme (Marvin)
marpme (Marvin)

Posted on

Stealth Addressing in depth [1/3]

First, we will start with the normal procedure which will occur on a daily recurring basis. This is a simple user-case situation where two individuals want to transact privately, as is their right. To do this, we need to a stealth address which makes it possible for the receiver to receive his funds from the paying person. Let’s call the payer Bob. For today’s text, we will cover the part on how to create a stealth address, and what it is made of, and what is it being used for.

How to generate a stealth addresses?

SHA256 workflow

First off you will need math, a lot of math. We basically need some crypto algorithms for this entire processes including SHA256, SECP256k1 (elliptic curve crypto with this equation y² = x³ + 7) and Base58 encoding.

The data structure of our stealth addresses:

 // [version] [options] [scan_key] [N] ... [Nsigs] [prefix_length]
Enter fullscreen mode Exit fullscreen mode

The overall structure of a normal stealth address is the following. It’s based on the version of the current stealth address, which simply specifies for which currency it could be used. If the version were to change, the blockchain would reject your address. The option is a simple unsigned integer, which can specify any options. You can a scan public key and multiple spend public keys (where N = number of keys; NSigs = all the spend keys) to send the funds to. The prefix length is by default zero.

Verge’s current version of the stealth address is 0x28.

Step-by-Step creation of a verge based stealth address

  1. Generate two private random numbers with a lenght of 32 bytes (aka. secure random numbers)
  2. One will be declared as spend secret and the other as scan secret
  3. each, scan and spend secret, will be used to generate a public key by utilizing SECP256k1 (length should be 33 bytes). Make sure to not overflow the SECP256k1 algorithm.
  4. Now putting everything together into a single buffer in the abovementioned order.
  5. Generate a hashsum of the current address buffer by double hashing this buffer with SHA256 and append the first 4 bytes of the checksum to the address buffer.
  6. finally base58 encode your address If you have done everything right you address should start with smY.

Stealth addressing with JavaScript

const { randomBytes } = require('crypto');
const secp256k1 = require('secp256k1');
const SxAddress = require('./StealthAddress');

// generates a private key from a secure/random source
generatePrivateKey = () => {
  // generate privKey
  let privKey;
  do {
    privKey = randomBytes(32);
    // check if the seed is within the secp256k1 range
  } while (!secp256k1.privateKeyVerify(privKey));

  return privKey;
};

// generate a public key based on the current seeds
generatePublicKey = privKey => {
  // get the public key in a compressed format
  return secp256k1.publicKeyCreate(privKey);
};

const scanPriv = generatePrivateKey();
const spendPriv = generatePrivateKey();
// pass all the data into the stealth address class
const address = new SxAddress(
  scanPriv,
  generatePublicKey(scanPriv),
  spendPriv,
  generatePublicKey(spendPriv)
);

console.log(address.toJson());
Enter fullscreen mode Exit fullscreen mode

After this procedure, we have assembled the basic requirements needed to create a stealth address and we have gathered all the inputs needed. Namely having two pairs of keys (public and private) representing the scan and spend key. Those are needed to verify the transaction and signing them in new blocks. We will go in depth about that one later. First, we will cover the basics of stealth addressing.

With that being said, let’s finally create an encoded address by having a look at the stealth address class:

const stealth_version_byte = 0x28;
const crypto = require('crypto');
const bs58 = require('bs58');

module.exports = class StealthAddress {
  constructor(scanPriv, scanPub, spendPriv, spendPub) {
    this.scanPub = scanPub;
    this.spendPub = spendPub;
    this.scanPriv = scanPriv;
    this.spendPriv = spendPriv;
    this.options = 0;
  }

  encode() {
    const address = new Buffer.from([
      stealth_version_byte,
      this.options,
      ...this.scanPub,
      1, // size of public keys
      ...this.spendPub,
      0, // size of signatures
      0, // ??
    ]);

    const result = Buffer.concat([address, this.generateChecksum(address)]);
    return bs58.encode(result);
  }

  generateChecksum(data) {
    return crypto
      .createHash('sha256')
      .update(
        crypto
          .createHash('sha256')
          .update(data)
          .digest()
      )
      .digest()
      .slice(0, 4);
  }

  validateChecksum(modules) {
    const buffered = new Buffer.from(modules);
    const checksumModule = buffered.slice(
      buffered.byteLength - 4,
      buffered.byteLength
    );

    const informationModules = buffered.slice(0, buffered.byteLength - 4);
    const informationChecksum = this.generateChecksum(informationModules);
    return {
      valid: Buffer.compare(informationChecksum, checksumModule) === 0,
      checksum: informationChecksum.toString('hex'),
    };
  }

  isStealth(bs58address) {
    const modules = bs58.decode(bs58address);
    let checks = this.validateChecksum(modules);
    if (!checks.valid) {
      return {
        valid: false,
      };
    }

    if (modules.length < 1 + 1 + 33 + 1 + 33 + 1 + 1 + 4) {
      return {
        valid: false,
      };
    }

    checks = { ...checks, length: modules.length };

    if (modules[0] !== stealth_version_byte) {
      return {
        valid: false,
      };
    }

    checks = {
      ...checks,
      stealthVersion: `0x${modules[0].toString('16')}`,
    };

    return checks;
  }

  toJsonPrivate() {
    return JSON.stringify(
      {
        scanPub: this.scanPub.toString('hex'),
        spendPub: this.spendPub.toString('hex'),
        scanPriv: this.scanPriv.toString('hex'),
        spendPriv: this.spendPriv.toString('hex'),
        options: this.options,
        address: this.encode(),
        isStealth: this.isStealth(this.encode()),
      },
      null,
      2
    );
  }

  toJson() {
    return JSON.stringify(
      {
        scanPub: this.scanPub.toString('hex'),
        spendPub: this.spendPub.toString('hex'),
        scanPriv: 'hidden',
        spendPriv: 'hidden',
        options: this.options,
        address: this.encode(),
        isStealth: this.isStealth(this.encode()),
      },
      null,
      2
    );
  }
};
Enter fullscreen mode Exit fullscreen mode

Okay, that was a big chunk for a read-through, focus on the path along the encode() method, which combines all the important buffers to one simple address buffer.

In the end, we will be covered with a checksum which is based on the address buffer. This will then merge with that address buffer, and give the possibility to verify if the address has been manually modified or corruption due to network failures, etc.

… to be continued : )

All the details explained over are based on the original idea of this stealth addressing workflow:
// Copyright © 2014 The ShadowCoin developers
// Distributed under the MIT/X11 software license, see the accompanying

Discussion (1)

Collapse
aitorp6 profile image
Aitor P.

Hi

Nice article, very clear and educational. One question, are you planning to continue with this series?