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 1bits in a row, and wrong represents 2037, which is 7 more 1bits glued to a 4bit checksum. (The checksum is the first nibble of the SHA256 hash of 128 1bits 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 airgapped computer.)
Write the private key to identity.pem:
$ keysmith privatekey
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 publickey 043cc849c77d5ead3aeaf2ea821dc85d6bb10483bbe97875d010ada2629e4a863e815793de69 ae4ffce46d52c4b14ed1a3ae40e85b53b5cb6c7ed6de89d80c4305
This is an ECDSA public key on the secp256k1 curve, that is, it represents a point on the curve y^{2} = x^{3} + 7 modulo p where:
> p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
The 04 indicates the point is in uncompressed form, that is, we have 32 bytes holding the xcoordinate followed by 32 bytes holding the ycoordinate:
> x = 0x3cc849c77d5ead3aeaf2ea821dc85d6bb10483bbe97875d010ada2629e4a863e > y = 0x815793de69ae4ffce46d52c4b14ed1a3ae40e85b53b5cb6c7ed6de89d80c4305
In Haskell or similar we can verify this lies on the curve:
> (y^2  x^3  7) `mod` p 0
Principal
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 publickey)  xxd r p  sha224sum 4ff2c79f70067d24bbba4a164737e0eddd62802ac603531fa0fc855b 
Next, we append 02 then compute its CRC32 checksum. We abuse gzip to compute the checksum because there seems to be no widely available commandline tool for this job.
(echo 4ff2c79f70067d24bbba4a164737e0eddd62802ac603531fa0fc855b; echo 02)  xxd r p  gzip  tail c 8  head c 4  xxd p  tac rs .. ; echo 66ff0a27
Concatenating this checksum, the SHA224 hash, and the 02 byte yields:
66ff0a274ff2c79f70067d24bbba4a164737e0eddd62802ac603531fa0fc855b02
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 AZ az  sed 's/...../&/g' m37quj2p6ldz64agpuslxoskczdtpyhn3vriakwganjr7ih4qvnqe
Thankfully keysmith and dfx can do all this for us:
$ keysmith principal m37quj2p6ldz64agpuslxoskczdtpyhn3vriakwganjr7ih4qvnqe $ dfx identity getprincipal m37quj2p6ldz64agpuslxoskczdtpyhn3vriakwganjr7ih4qvnqe
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:
3056301006072a8648ce3d020106052b8104000a034200
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": "1.3.132.0.10"}} ]}, {"bitstr": {"hex": "00043cc849c77d5ead3aeaf2ea821dc85d6bb10483bbe97875d010ada2629e4a863e815793de69ae4ffce46d52c4b14ed1a3ae40e85b53b5cb6c7ed6de89d80c4305"}} ]}
The Object Identifier 1.2.840.10045.2.1 means ecPublicKey and 1.3.132.0.10 means secp256k1.
Using this format makes it easier to add Internet Computer support for new cryptosystems in the future.
Accounts
To compute our default account ID:

Concatenate "\naccountid" with our principal (without a checksum).

Append 32 zero bytes.

Compute the SHA224 hash.

Prepend its CRC32 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 az AZ  base32 d i  xxd p c 80  tail c +9 4ff2c79f70067d24bbba4a164737e0eddd62802ac603531fa0fc855b02
We add the special prefix and suffix, and compute the SHA224 hash:
$ (printf "\naccountid"; (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 e77eab8d
Thus our default account is:
e77eab8d01a5102d67bbc0b3ba120151f005debecdeadfb3c91f4b4fe804db21
Which we confirm with keysmith and dfx:
$ keysmith account e77eab8d01a5102d67bbc0b3ba120151f005debecdeadfb3c91f4b4fe804db21 $ dfx ledger accountid e77eab8d01a5102d67bbc0b3ba120151f005debecdeadfb3c91f4b4fe804db21
Subaccounts
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.