Working with Addresses
From public key to address
import { useBlockchain } from "@agntn/keys";
import Bitcoin from "@agntn/keys/blockchains/bitcoin";
const chain = useBlockchain(new Bitcoin());
const keys = chain.generateKeys();
const address = chain.getAddress(keys.keys.public);
console.log(address); // '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa'
Every chain does the same thing here: hash the public key, then encode the hash the way the chain likes. What differs is the hash, the encoding, and whether there's a checksum.
Address types
The second argument picks a format on chains that have more than one.
chain.getAddress(publicKey); // legacy, starts with 1
chain.getAddress(publicKey, "segwit"); // bc1q...
chain.getAddress(publicKey, "taproot"); // bc1p...
Bitcoin and Litecoin have five formats, Bitcoin Gold four, Cardano three. Sui and the XRP Ledger use it to name the curve. Everything else has one format and throws if you ask for another. Leave the argument out.
Validating
chain.validateAddress("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"); // true
chain.validateAddress("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNb"); // false, checksum
Validation checks the format and the checksum when the format has one. It doesn't touch the network. A valid address with no history is still valid.
validateAddress is optional on the BlockchainImplementation type so that structural implementations can skip it, but every class built on AbstractBlockchain has one. Every built in chain does.How each chain builds its address
| Chain | Recipe |
|---|---|
| Bitcoin | RIPEMD160(SHA256(pubkey)), then base58check for legacy and p2sh, bech32 for segwit, bech32m with a BIP341 tweak for taproot |
| Bitcoin Cash | Bitcoin's P2PKH hash behind a version byte, then CashAddr with the bitcoincash prefix and a 40-bit checksum |
| Bitcoin Gold | the same hash and encodings, version 0x26 for legacy (G), 0x17 for p2sh (A), bech32 with btg, no taproot |
| Bitcoin SV | Bitcoin's legacy recipe, version 0x00, base58check, so it starts with 1 |
| Litecoin | the same hash and encodings, version 0x30 for legacy (L), 0x32 for p2sh (M), bech32 with ltc |
| Dash | Bitcoin's legacy recipe, version 0x4c, base58check, so it starts with X |
| Decred | RIPEMD160(BLAKE256(pubkey)) behind the two byte prefix 07 3f, base58check with a double BLAKE-256 checksum, so it starts with Ds |
| Dogecoin | Bitcoin's legacy recipe, version 0x1e, base58check, so it starts with D |
| Zcash | Bitcoin's legacy recipe behind the two byte version 1c b8, base58check, so it starts with t1 |
| eCash | Bitcoin Cash's recipe under the ecash prefix, so the same key hash starts with ecash:q |
| Ethereum, Base | last 20 bytes of Keccak-256(uncompressed pubkey without 04), 0x prefix, EIP-55 mixed case checksum |
| TRON | same 20 bytes as Ethereum, version byte 0x41, base58check, so it starts with T |
| Solana | the ed25519 public key itself, base58 |
| Stellar | version byte 0x30, the ed25519 public key, a CRC16-XModem checksum, all in base32, so it starts with G |
| Aptos | SHA3-256(pubkey + 0x00), 0x prefix |
| Sui | Blake2b-256(flag + pubkey), flag 0x00 for ed25519 and 0x01 for secp256k1, 0x prefix |
| Cardano | Blake2b-224(pubkey) behind a header byte, bech32 with addr or stake |
| XRP Ledger | Bitcoin's RIPEMD160(SHA256(pubkey)) behind version 0x00, base58check in XRPL's alphabet, so it starts with r; an ed25519 key gets hashed with its ED byte |
| NEAR | the ed25519 public key itself, lowercase hex, no prefix; named accounts like alice.near come back invalid |
| Cosmos | Bitcoin's RIPEMD160(SHA256(compressed pubkey)), bech32 under the chain's prefix, cosmos1... on the Hub and osmo1... on Osmosis |
| Polkadot | the ed25519 public key itself behind the network prefix, two bytes of blake2b-512 as checksum, base58; 1... on Polkadot, a capital letter on Kusama |
| Monero | a network byte, the public spend key, the public view key and four bytes of Keccak-256, block base58, 95 characters or 106 with a payment ID; 4... on mainnet, 8... for a subaddress |
The chain pages have the code for each, plus the testnet variants where they exist.
A quick tour
import Ethereum from "@agntn/keys/blockchains/ethereum";
import Solana from "@agntn/keys/blockchains/solana";
import Sui from "@agntn/keys/blockchains/sui";
const ethereumChain = useBlockchain(new Ethereum());
ethereumChain.getAddress(ethereumChain.getKeyPublic(privateKey)); // '0x...' with checksum
const solanaChain = useBlockchain(new Solana());
solanaChain.getAddress(solanaChain.getKeyPublic(privateKey)); // base58, no hashing at all
const suiChain = useBlockchain(new Sui());
const secpKey = suiChain.getKeyPublic(privateKey, { scheme: "secp256k1" });
suiChain.getAddress(secpKey, "secp256k1"); // '0x...' from the secp256k1 flag
The Sui line is the one people get wrong: a secp256k1 public key hashed with the ed25519 flag produces a perfectly valid looking address that no key can spend from. Keep the scheme on both calls.