Skip to main content

Generate Addresses

Deterministic Address Paths (BIP32)

You can use the Ed25519 master seed to generate addresses in a deterministic manner. These addresses will be Ed25519 key pairs generated through the BIP32 method and structured as per the BIP44 logical hierarchy.

The iota.js library provides a method generateBip44Address that creates these BIP32 paths using a state object that is updated on each call, as shown in the following snippet:

const NUM_ADDR = 6;
const addressGeneratorAccountState = {
accountIndex: 0,
addressIndex: 0,
isInternal: false,
};
const paths: string[] = [];
for (let i = 0; i < NUM_ADDR; i++) {
const path = generateBip44Address(
addressGeneratorAccountState,
COIN_TYPE_SHIMMER,
);
paths.push(path);

console.log(`${path}`);
}

In this case we are generating addresses for the Shimmer Coin Type.

The script above will generate the following BIP32 paths:

m/44'/4218'/0'/0'/0'
m/44'/4218'/0'/1'/0'
m/44'/4218'/0'/0'/1'
m/44'/4218'/0'/1'/1'
m/44'/4218'/0'/0'/2'
m/44'/4218'/0'/1'/2'

Where:

  • 44 is a constant that denotes purpose, (0x8000002C) following the BIP43 recommendation.
  • 4218 is the coin type defined for Shimmer
  • The three following numbers are:
    • The account index. Users can use these accounts to organize the funds in the same fashion as bank accounts; for donation purposes (where all addresses are considered public), for saving purposes, for common expenses, etc.
    • The change index. Allows separate addresses used for external operations (i.e., receive funds) or internal operations (i.e., generate change).
    • The address index that increments sequentially

The example above generated six address paths for the account 0. One address is for external operations and another for internal operations for each index from 0 to 2.

Ed25519 Key Pairs for the Addresses

Before you can generate an Ed25519 key pair, you will need to generate a subsequent Ed25519 seed from a BIP32 path before.

const keyPairs: IKeyPair[] = [];

for (const path of paths) {
// Master seed was generated previously
const addressSeed = masterSeed.generateSeedFromPath(new Bip32Path(path));
const addressKeyPair = addressSeed.keyPair();
keyPairs.push(addressKeyPair);

console.log(Converter.bytesToHex(addressKeyPair.privateKey, true));
console.log(Converter.bytesToHex(addressKeyPair.publicKey, true));
}

As the keys are generated as byte arrays (UInt8Array) it is necessary to encode them using displayable characters. In this case, hexadecimal characters using the Converter.bytesToHex() function. The trailing true parameter indicates that the 0x prefix will be included in the representation. You can revert the result to bytes (UInt8Array) using the Converter.hexToBytes()

The generated key pairs should look like the following:

  • 0x6f0fa2f7a9d5fbd221c20f54d944378acb871dcdeafc3761e73d7f0aa05c75356f8eeee559daa287ec40a3a7113e88df2fc27bc77819e6d3d146a7dc7a4e939c
  • 0x6f8eeee559daa287ec40a3a7113e88df2fc27bc77819e6d3d146a7dc7a4e939c

The Ed25519 private key contains 128 hex chars that correspond to 64 bytes. The public key can be represented using 64 hex chars, i.e. 32 bytes.

You now have your asymmetric cryptography set, but you still need to generate public addresses that will be used in the Shimmer network.

Putting It All Together

By this point in the tutorial, your generate-addresses.tsfile should look something like this:

import { Bip32Path, Bip39 } from '@iota/crypto.js';
import { Ed25519Seed, generateBip44Address, IKeyPair } from '@iota/iota.js';
import { Converter } from '@iota/util.js';

// Default entropy length is 256

const randomMnemonic = Bip39.randomMnemonic();

console.log('Seed phrase:', randomMnemonic);

const masterSeed = Ed25519Seed.fromMnemonic(randomMnemonic);

const NUM_ADDR = 6;
const addressGeneratorAccountState = {
accountIndex: 0,
addressIndex: 0,
isInternal: false,
};
const paths: string[] = [];
for (let i = 0; i < NUM_ADDR; i++) {
const path = generateBip44Address(
addressGeneratorAccountState,
COIN_TYPE_SHIMMER,
);
paths.push(path);

console.log(`${path}`);
}

const keyPairs: IKeyPair[] = [];

for (const path of paths) {
// Master seed was generated previously
const addressSeed = masterSeed.generateSeedFromPath(new Bip32Path(path));
const addressKeyPair = addressSeed.keyPair();
keyPairs.push(addressKeyPair);

console.log(Converter.bytesToHex(addressKeyPair.privateKey, true));
console.log(Converter.bytesToHex(addressKeyPair.publicKey, true));
}