Skip to content

Private and public keys

SVPrivateKey holds a secp256k1 Private Key, and its publicKey property gives us the matching SVPublicKey. Transactions, message signing and encryption all take these two classes.

The default constructor draws a fresh key from a secure random source. The network we pass decides how the key is written out as WIF.

final privateKey = SVPrivateKey(networkType: NetworkType.TEST);
final publicKey = privateKey.publicKey;
final wif = privateKey.toWIF(); // store this, or the hex below, to keep the key

WIF (Wallet Import Format) is the usual way to store or move a single key. The prefix tells the library the network, and whether the Public Key is compressed, so fromWIF takes no other arguments.

final privateKey = SVPrivateKey.fromWIF('cVVvUsNHhbrgd7aW3gnuGo2qJM45LhHhTCVXrDSJDDcNGE6qmyCs');
privateKey.networkType; // NetworkType.TEST, read from the WIF prefix
privateKey.isCompressed; // true, so its public key and address use the compressed form
privateKey.toWIF(); // the same WIF string again

An invalid string throws. We get a BadChecksumException for a mistyped key, and an InvalidKeyException for a string of the wrong length or form.

try {
SVPrivateKey.fromWIF('cVVvUsNHhbrgd7aW3gnuGo2qJM45LhHhTCVXrDSJDDcNGE6qmyCt'); // last character changed
} on BadChecksumException {
// reject the input
}

toHex() gives us the raw 32-byte key as 64 hex characters. A raw key has no network, so we supply one when we load it.

final hex = SVPrivateKey.fromWIF('cVVvUsNHhbrgd7aW3gnuGo2qJM45LhHhTCVXrDSJDDcNGE6qmyCs').toHex();
// A raw key carries no network, so we name it when importing.
final privateKey = SVPrivateKey.fromHex(hex, NetworkType.TEST);
final wif = privateKey.toWIF(); // cVVvUsNHhbrgd7aW3gnuGo2qJM45LhHhTCVXrDSJDDcNGE6qmyCs

fromBigInt takes the key as a number. Keys made this way are mainnet keys.

final privateKey = SVPrivateKey.fromBigInt(BigInt.one);
privateKey.publicKey.toHex();
// 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
privateKey.toWIF(); // KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn (mainnet)

To load a key from raw bytes, we hex-encode them and use fromHex:

final bytes = List<int>.filled(32, 1); // 32 bytes of key material
final hex = bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
final key = SVPrivateKey.fromHex(hex, NetworkType.TEST);

New keys, and WIF strings starting with K, L or c, use compressed Public Keys (33 bytes). Older wallets exported uncompressed keys, whose WIF starts with 5 (mainnet) or 9 (testnet). The library keeps the form it was given, because the Address depends on it.

// A WIF starting with 5 (mainnet) or 9 (testnet) is an uncompressed key.
final legacyKey = SVPrivateKey.fromWIF('5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf');
legacyKey.isCompressed; // false
legacyKey.publicKey.toHex(); // 65 bytes, starting 04
legacyKey.toAddress().toString(); // 1EHNa6Q4Jz2uvNExL497mE43ikXhwF6kZm

We parse a Public Key from hex when we want to verify Signatures, build Locking Scripts, or derive an Address for someone else. getEncoded gives either encoding, regardless of how the key was parsed.

final publicKey = SVPublicKey.fromHex('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798');
publicKey.toHex(); // as parsed: the 33-byte compressed form
publicKey.getEncoded(false); // the 65-byte uncompressed form, 04 + x + y
publicKey.getEncoded(true); // the compressed form
SVPublicKey.isValid('02deadbeef'); // false: not a point on the curve

NetworkType has four values: MAIN, TEST, REGTEST and SCALINGTEST. The three test networks share their WIF and Address prefixes.

Next: Addresses.