How to create a Radix Wallet for development purposes

Creating a Radix Wallet Address for Development Purposes

Radix Wallets are created using Elliptic Curve Cryptography (ECC) to generate a Private/Public Key pair. Specifically, we use an Elliptic Curve Digital Signature Algorithm (ECDSA) with the curve parameters defined by SECP256K1.

1. Install Dependencies

Install the following libraries that will be used to create the key pair and then convert them into a Radix Wallet Address:

pip install ecdsa
pip install bech32

2. Create Private Key

The following snippet generates a Private Key using the ECDSA algorithm with the SECP256k1 curve parameters. By default, the generate function uses os.urandom as a cryptographically secure source of entropy (randomness).

import ecdsa
import bech32

# Create the Private Key
private_key = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1)
private_key_bytes = private_key.to_string()
print("Private Key: ", private_key_bytes.hex())

Save the Private Key value as you will need it to sign transactions later.

3. Generate Public Key from Private Key

Append the following snippet to the code above to generate the Public Key that corresponds to your Private Key:

# Generate Public Key from Private Key
verifying_key = private_key.get_verifying_key()
public_key_compressed_bytes = verifying_key.to_string("compressed")
print("Public Key (Compressed): ", public_key_compressed_bytes.hex())

3.1 Compressed Public Keys

The Public Key is made up of two co-ordinates (x, y) on the SECP256k1 curve. These x and y co-ordinates are generated from the Private Key and are 32 bytes each. For example:

x: f219ea5d6b54701c1c14de5b557eb42a8d13f3abbcd08affcc2a5e6b049b8d63
y: 4cb95957e83d40b0f73af4544cccf6b1f4b08d3c07b27fb8d8c2962a400766d1

The format of a “Full” Public Key consists of prefixing the Public Key with a byte value of “04” and then appending the x and y values to create a 65 byte (1 byte prefix + 32 byte x +32 byte y) value:

Full Public Key: 04f219ea5d6b54701c1c14de5b557eb42a8d13f3abbcd08affcc2a5e6b049b8d634cb95957e83d40b0f73af4544cccf6b1f4b08d3c07b27fb8d8c2962a400766d1

We can reduce the size of the public key without losing any information by “compressing” public key co-ordinates as follows:

  • If the value of the y co-ordinate is even, then use a byte value of “02” as a prefix
  • If the value of the y co-ordinate is odd, then use a byte value of “03” as a prefix
  • The compressed public key is: prefix + x co-ordinate

Below is an example of converting our public key into a compressed public key using the rules above:

# y: 4cb95957e83d40b0f73af4544cccf6b1f4b08d3c07b27fb8d8c2962a400766d1
# y is odd so our prefix will be 03
# Prepend 03 to x to create our 33 byte compressed public key:

Compressed Public Key: 03f219ea5d6b54701c1c14de5b557eb42a8d13f3abbcd08affcc2a5e6b049b8d63

The compressed public key can be converted back to a full public key using some simple maths (see references below for more details)

4. Generate Radix Wallet Address from Public Key

4.1 Convert Compressed Public Key to Radix Engine Address

Radix converts the Compressed Public Key into a Radix Engine Address. The Radix Engine Address is a “1 to 34 byte array describing a resource or component in the Radix Engine”. The first byte of the Radix Engine Address describes the type of address:

  • 0x01 - Native Token (XRD)
  • 0x03 - Hashed Key + Nonce (User created Tokens)
  • 0x04 - Public Key
# Convert Compressed Public Key into a Radix Engine Address
readdr_bytes = b"\x04" + public_key_compressed_bytes

4.2 Convert Radix Engine Address into Bech32 Encoded String

Next, we convert the Radix Engine Address into a Bech32 encoded string. Bech32 is standard for creating an encoded base 32, checksummed address format. A Bech32 address consists of the following parts:

  • A human readable part (HRP) which conveys the type of data. Radix mainnet uses rdx for the mainnet human readable part.
  • A separator. Always “1”
  • A data part which is always at least 6 alphanumeric characters long excluding “1”, “b”, “i” and “o”. The last 6 characters of the data form a checksum using a BCH Code and contain no information.

The BCH checksum algorithm guarantees detection of any error affecting at most 4 characters and has a less than 1 in 1 billion chance of failing to detect more errors. BCH can also identify where in an address the typos were made.

The python bech32 library we use is a reference implementation and is used to create Bitcoin segwit addresses. The Radix Wallet Address uses a modified version of the Bitcoin BIP-0173 Segwit Address Format so we need to use some lower level functions in the python bech32 library to get the right output format.

Append the following snippet to the code above to convert the Radix Engine Address into the Bech32 encoded Radix Wallet Address format:

# Convert Radix Engine Address to Bech32 Radix Wallet Address
readdr_bytes5 = bech32.convertbits(readdr_bytes, 8, 5)
wallet_address = bech32.bech32_encode("rdx", readdr_bytes5)
print("Wallet Address: ", wallet_address)

You now have all 3 values required to perform transactions on the Radix Ledger using the RPC API:

  • Private Key
  • Compressed Public Key
  • Radix Wallet Address

5. Complete Code Snippet

import ecdsa
import bech32

# Create the Private Key
private_key = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1)
private_key_bytes = private_key.to_string()
print("Private Key: ", private_key_bytes.hex())

# Generate Public Key from Private Key
verifying_key = private_key.get_verifying_key()
public_key_compressed_bytes = verifying_key.to_string("compressed")
print("Public Key (Compressed): ", public_key_compressed_bytes.hex())

# Convert Compressed Public Key into a Radix Engine Address
readdr_bytes = b"\x04" + public_key_compressed_bytes

# Convert Radix Engine Address to Bech32 Radix Wallet Address
readdr_bytes5 = bech32.convertbits(readdr_bytes, 8, 5)
wallet_address = bech32.bech32_encode("rdx", readdr_bytes5)
print("Wallet Address: ", wallet_address)

References

This work by RadixPool.com is licensed under a Creative Commons Attribution 4.0 International License.

5 Likes

@Stuart do you know how to create a validator address? I followed how its done here and just changed rdx to rv but am not getting the correct address. How i currently create addresses (rdx/tdx) matches what comes back from ReDoc Interactive Demo

i saw this and it makes me think i should just be able to change rdx to rv and get the correct address

figured it out!

iex(1)> Radixir.Key.generate
%{
  mainnet_address: "rdx1qsp746mjzhuprnkkpl8k4mj4psengdpg7f42ea0nn8asd6askvcvw9shvqzm9",
  private_key: "981c473d0ec07c0eb771a5896e51465b052cedf31a331098de105f230e20457d",
  public_key: "03eaeb7215f811ced60fcf6aee550c33343428f26aacf5f399fb06ebb0b330c716",
  testnet_address: "tdx1qsp746mjzhuprnkkpl8k4mj4psengdpg7f42ea0nn8asd6askvcvw9skq4stx",
  validator_mainnet_address: "rv1q04wkus4lqgua4s0ea4wu4gvxv6rg28jd2k0tuuelvrwhv9nxrr3vgyfcuf",
  validator_testnet_address: "tv1q04wkus4lqgua4s0ea4wu4gvxv6rg28jd2k0tuuelvrwhv9nxrr3vwaw7n7"
}
iex(2)> Radixir.Gateway.API.derive_validator_identifier(%{network_identifier: %{network: "mainnet"}, public_key: %{hex: "03eaeb7215f811ced60fcf6aee550c33343428f26aacf5f399fb06ebb0b330c716"}}, url: "https://mainnet.radixdlt.com", headers: ["X-Radixdlt-Target-Gw-Api": "1.0.2"])
{:ok,
 %{
   "account_identifier" => %{
     "address" => "rv1q04wkus4lqgua4s0ea4wu4gvxv6rg28jd2k0tuuelvrwhv9nxrr3vgyfcuf"
   }
 }}

iex(3)> Radixir.Key.public_key_to_addresses("03eaeb7215f811ced60fcf6aee550c33343428f26aacf5f399fb06ebb0b330c716")
%{
  mainnet_address: "rdx1qsp746mjzhuprnkkpl8k4mj4psengdpg7f42ea0nn8asd6askvcvw9shvqzm9",
  testnet_address: "tdx1qsp746mjzhuprnkkpl8k4mj4psengdpg7f42ea0nn8asd6askvcvw9skq4stx",
  validator_mainnet_address: "rv1q04wkus4lqgua4s0ea4wu4gvxv6rg28jd2k0tuuelvrwhv9nxrr3vgyfcuf",
  validator_testnet_address: "tv1q04wkus4lqgua4s0ea4wu4gvxv6rg28jd2k0tuuelvrwhv9nxrr3vwaw7n7"
}
4 Likes

I’m unable to produce the same address using this method.
Isn’t Radix using Bech32m ?

Is there a version of this that would work on the Babylon network?