§ 04

§ 04 · Keys & addresses

Private key

A very large random number

A private key is a whole number, chosen at random from a range so large that nobody will ever pick the same one twice.

That’s all it is. There’s no file format, no account and no password inside it. The number lets you make signatures that spend coins, so whoever knows it owns the coins. Everything else in this group, from the public key to the prl1 address, is computed from it.

Try it

Generate a key, or type one in, and follow it down to an address.

Key forge

Private key → public key → Taproot tweak → prl1 address

For learning only. A key shown on a web page is not secret. Never send real PRL to an address made here.

Key 1 is the most famous key there is: its public key is the generator point G itself. Anyone can spend from its addresses.

  1. 1

    Private key d

    A secret number between 1 and n − 1 (n ≈ 1.16 × 10⁷⁷). 32 bytes.

    0000000000000000000000000000000000000000000000000000000000000001

  2. 2

    Public key P = d·G

    Multiply the curve’s starting point G by d. Compressed: a parity byte, then x.

    0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798

    02 · y is even Taproot keeps only x (32 bytes, “x-only”) and assumes the even y.

  3. 3

    Tweak t

    t = TaggedHash(“TapTweak”, P.x)

    3cf5216d476a5e637bf0da674e50ddf55c403270dd36494dfcca438132fa30e7

    BIP-86: no script tree, so the tweak commits to the key alone. It proves there is no hidden script.

  4. 4

    Output key Q = P + t·G

    The key that actually goes on the chain. Its private key is d + t (with d negated first if P’s y was odd).

    da4710964f7852695de2da025290e24af6d8c281de5a0b902b7135fd9fd74d21

  5. 5

    Address bech32m

    Prefix + “1” + witness version 1 (“p”) + Q in 5-bit letters + a 6-letter checksum.

    prl1pmfr3p9j00pfxjh0zmgp99y8zftmd3s5pmedqhyptwy6lm87hf5sse9xfq3

    scriptPubKey 51 20 da 47 10 96… (63 characters)

Runs in your browser · nothing is sent · keys are never saved src/lib/keys.ts · bech32m.ts

How big is the range?

A valid key is any whole number from 1 up to, but not including, a constant called n:

1≤d<n1 \le d < n

n is the number of points on the secp256k1 curve’s group, the same constant Bitcoin uses:

115792089237316195423570985008687907852837564279074904382605163141518161494337

That’s 78 digits, about 1.16×10771.16 \times 10^{77}. It is a tiny bit less than 22562^{256}, so every key fits in 32 bytes.

Size
32 bytes
256 bits
Smallest
1
0 is rejected
Largest
n − 1
…5e8cd0364140 in hex
Size
32 bytes
Type
Unsigned integer
Format
Big-endian
Range
1 to n − 1

Note the byte order. Pearl stores most numbers little-endian, smallest byte first. Keys are the exception: like Bitcoin, they are written biggest byte first, the way you’d write the number by hand.

Make a guess

If a billion computers each tried a billion keys a second, how long until they hit one that's already in use?

Think of your answer first, then check it.

Answer Far longer than the age of the universe.

A billion times a billion is 10¹⁸ guesses a second. Even with 10¹⁰ keys in use, each guess has about a 10⁻⁶⁷ chance of hitting one. The danger is never guessing. It’s a key that wasn’t really random, or one that leaked.

How does a Pearl wallet pick a key?

Not one by one. A Pearl wallet draws randomness once, when you create it, and derives every key from that.

  1. 128 random bits

    Oyster calls hdkeychain.GenerateSeed(RecommendedSeedLen), which Pearl sets to 16 bytes, read from the computer’s secure random number generator.
  2. 12 words

    Those bits become a 12-word mnemonic, your backup.
  3. A seed, then a tree

    The words become a 64-byte seed, and the seed becomes a tree of keys, one per address.

So the random part is the 128 bits behind the 12 words. Every private key in the wallet is a deterministic function of them, which is why the words are all you need to restore. The mnemonic page and derivation paths show the arithmetic.

What does a private key look like?

The same number can be written several ways. Here is one key, the example from the Bitcoin wiki’s WIF page. It is public, so never use it.

Format The same key
Hex (32 bytes) 0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d
Decimal 5500171714335001507730457227127633683517613019341760098818554179534751705629
WIF, compressed KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617
WIF, uncompressed 5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ

What is WIF?

Size
51 or 52 characters
Type
Base58Check text
Format
version ‖ key ‖ [01] ‖ checksum

WIF (Wallet Import Format) is how you move a single key between programs. It adds a version byte in front, an optional 0x01 “compressed” flag behind, and a 4-byte checksum, then writes the lot in Base58Check.

The 38 bytes behind a compressed mainnet WIF
Field Size Description
version 1 0x80 on mainnet, 0xef on testnet and regtest. The same bytes as Bitcoin.
key 32 The private key, big-endian.
compressed flag 1 0x01: the matching public key is used in its 33-byte compressed form. Omitted for the old uncompressed form.
checksum 4 The first 4 bytes of SHA256d of everything before it.
Build the WIF by hand
  1. Put the pieces together: 80 ‖ 0c28fca3…9d72aa1d ‖ 01, 34 bytes.
  2. Hash twice: SHA256d of those 34 bytes is a62019d20340a1de1b5f254f07f2f6c96ad5165218459ab4f3c8f5a7c0e12183.
  3. Keep the first 4 bytes, a62019d2, as the checksum and append them: 38 bytes.
  4. Write the 38 bytes in Base58: KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617.

Without the 01 flag, the checksum is 507a5b8d and the result is 5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ, exactly the string on the Bitcoin wiki.

Mainnet compressed keys start with K or L, uncompressed ones with 5. Test networks give c (or 9). Pearl wallets always use compressed keys.

Where does Pearl use a private key?

  • Signing spends. Every Pearl spend is signed with a 64-byte Schnorr signature. For a normal Taproot address, the key that signs is not d itself but the tweaked key d + t (with d negated first if needed). The Taproot tweak page works this out for key 1: the signing key ends in …30e8 instead of …0001.
  • Exporting and importing. Oyster’s dumpprivkey writes a key as WIF, and importprivkey reads one. The exported key is the internal, untweaked one.
  • Signing messages. Two different schemes exist, described below.
Signing messages with a Pearl key

Oyster signmessage / verifymessage. Works for Taproot addresses only. The code calls it “BIP-322 Simple Signature”, but it is not standard BIP-322: it signs a little virtual transaction whose output is OP_RETURN <your message>, so messages longer than 80 bytes fail. Signing “Hello World” with key 1’s address gives a base64 witness starting AUCP…. It will not verify in Bitcoin BIP-322 tools.

pearld signmessagewithprivkey. Takes a WIF and makes an old-style compact ECDSA signature over SHA256d(varstr(“Pearl Signed Message:\n”) ‖ varstr(message)). For “Hello World” that digest is 8b208026794816c66d92f5d2dae08c55cacdf0cad6eef279b83495c1de675ac4. The node’s matching verifymessage is disabled at v1.5.2, so nothing on Pearl verifies these signatures.

Neither scheme touches the chain. Transactions use Schnorr signatures only.

How do you keep it safe?

  • Never type it into a website. No legitimate Pearl service needs your private key, a WIF or your 12 words.
  • Back up the words, not the keys. One 12-word backup covers every key the wallet will ever make.
  • Remember the famous keys are public. Private key 1, the wiki key above and the BIP-39 test phrase are on this page because everyone knows them. Coins sent to them are gone the moment a bot notices.
Code: make a valid key in JavaScript

A key is just 32 random bytes that land in the valid range. Rejection sampling keeps the distribution uniform:

JavaScript
const n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
function randomPrivateKey() {
  for (;;) {
    const bytes = crypto.getRandomValues(new Uint8Array(32));
    const d = BigInt('0x' + [...bytes].map((b) => b.toString(16).padStart(2, '0')).join(''));
    if (d >= 1n && d < n) return bytes;   // fails about once in 2^128 tries
  }
}

For real funds, let a wallet do this. Its keys come from the seed, so they can be restored.

The codex

Suggested

Up and down arrows move through the results, Enter opens one, Escape closes. Anywhere on the site, slash or Command K opens this search, and the square brackets [ and ] go to the previous and next page.