Principals and Accounts

Ever wondered what those mysterious principals and accounts really mean? Let’s find out!

Install keysmith and generate a seed:

$ keysmith generate

This generates a random seed, appends a checksum, then writes them to seed.txt. The output format is interesting: rather than hexadecimal or similar, we use words from the BIP39 Word List, which makes it easier for a human to commit the seed to memory.

For example, if the system gets 1 every time for 128 consecutive coin flips, then the generated seed.txt contains:

zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong

(See? Seeds are easy to remember!)

The word zoo is last on a list of size 2048, thus represents 2047, which is 11 1-bits in a row, and wrong represents 2037, which is 7 more 1-bits glued to a 4-bit checksum. (The checksum is the first nibble of the SHA256 hash of 128 1-bits in a row, which turns out to be 0x5.)

Private Key

Private and public keys are derived from this seed. (Ideally, important seeds and corresponding private keys should be generated on an air-gapped computer.)

Write the private key to identity.pem:

$ keysmith private-key

We can use this private key with dfx by copying it to the appropriate subdirectory:

$ mkdir ~/.config/dfx/identity/zoo
$ cp identity.pem ~/.config/dfx/identity/zoo
$ dfx identity use zoo

Public Key

The public key can be derived from the private key, or we can run keysmith to figure it out from the seed:

$ keysmith public-key

This is an ECDSA public key on the secp256k1 curve, that is, it represents a point on the curve y2 = x3 + 7 modulo p where:


The 04 indicates the point is in uncompressed form, that is, we have 32 bytes holding the x-coordinate followed by 32 bytes holding the y-coordinate:

> x = 0x3cc849c77d5ead3aeaf2ea821dc85d6bb10483bbe97875d010ada2629e4a863e
> y = 0x815793de69ae4ffce46d52c4b14ed1a3ae40e85b53b5cb6c7ed6de89d80c4305

In Haskell or similar we can verify this lies on the curve:

> (y^2 - x^3 - 7) `mod` p


It takes a few steps to derive a principal from a public key. First, we prefix the public key with a certain magic string then compute a SHA224 hash:

$ (echo 3056301006072a8648ce3d020106052b8104000a034200; keysmith public-key) | xxd -r -p | sha224sum
4ff2c79f70067d24bbba4a164737e0eddd62802ac603531fa0fc855b  -

Next, we append 02 then compute its CRC-32 checksum. We abuse gzip to compute the checksum because there seems to be no widely available command-line tool for this job.

(echo 4ff2c79f70067d24bbba4a164737e0eddd62802ac603531fa0fc855b; echo 02) | xxd -r -p | gzip | tail -c 8 | head -c 4 | xxd -p | tac -rs .. ; echo


Concatenating this checksum, the SHA224 hash, and the 02 byte yields:


Lastly, we encode this in RFC 4648 Base32 except we:

  • Use lowercase letters instead of uppercase.

  • Insert dashes every five characters.

  • Drop the padding at the end.

$ echo 66ff0a274ff2c79f70067d24bbba4a164737e0eddd62802ac603531fa0fc855b02 | xxd -r -p | base32 | tr -d = | tr A-Z a-z | sed 's/...../&-/g'

Thankfully keysmith and dfx can do all this for us:

$ keysmith principal
$ dfx identity get-principal

There are several kinds of principals. Here, the 02 tag indicates the principal is derived from a public key.

We still have to explain the above magic string:


It results from encoding our public key in the DER format, which can be seen by pasting the following into an online encoder:

{"seq": [
  {"seq": [
    {"oid": {"oid": "1.2.840.10045.2.1"}},
    {"oid": {"oid": ""}}
  {"bitstr": {"hex": "00043cc849c77d5ead3aeaf2ea821dc85d6bb10483bbe97875d010ada2629e4a863e815793de69ae4ffce46d52c4b14ed1a3ae40e85b53b5cb6c7ed6de89d80c4305"}}

The Object Identifier 1.2.840.10045.2.1 means ecPublicKey and means secp256k1.

Using this format makes it easier to add Internet Computer support for new cryptosystems in the future.


To compute our default account ID:

  1. Concatenate "\naccount-id" with our principal (without a checksum).

  2. Append 32 zero bytes.

  3. Compute the SHA224 hash.

  4. Prepend its CRC-32 checksum.

Since we use xxd, we want our principal in hex. We skip the first 8 bytes of hex to drop the checksum.

$ echo `keysmith principal`=== | tr a-z A-Z | base32 -d -i | xxd -p -c 80 | tail -c +9

We add the special prefix and suffix, and compute the SHA224 hash:

$ (printf "\naccount-id"; (echo 4ff2c79f70067d24bbba4a164737e0eddd62802ac603531fa0fc855b02 ; printf %064d 0) | xxd -r -p) | sha224sum
01a5102d67bbc0b3ba120151f005debecdeadfb3c91f4b4fe804db21  -

We abuse gzip again for the last step:

$ echo 01a5102d67bbc0b3ba120151f005debecdeadfb3c91f4b4fe804db21 | xxd -r -p | gzip | tail -c 8 | head -c 4 | xxd -p | tac -rs .. ; echo


Thus our default account is:


Which we confirm with keysmith and dfx:

$ keysmith account
$ dfx ledger account-id


Above, in step 2, instead of 32 zero bytes, we can in fact use any arbitrary 32 bytes, known as the subaccount, and the result is an account that we control. The zero subaccount is simply the default.

Subaccounts are related to salts in the context of password hashes. If we supply a subaccount along with the account derived from our principal and subaccount, then the ledger lets us operate on the account.

In effect, we have as many accounts as we want. We can use a fresh one for each new application, making it harder for independent transactions to stomp over each other.