# Chapter 1

This is an introduction to implicit accounts and key management in Tezos.

As you may know in Tezos there are currently two types of accounts: implicit and originated. In this chapter we will inspect the former type.
Implicit account with some tez (Tezos native tokens) is probably the first thing you will need to start working with Tezos.
Implicit account is linked to a public key, is always spendable and delegatable, cannot contain code nor reject a transaction.
Implicit account address starts with tz prefix.

# Base58 encoding

Tezos uses a special encoding for various entities identified by raw bytes, e.g. block id, account id, operation id, etc. A common scheme is used for making such identifiers readable, distinguashable, concise, and non-ambiguous:

  1. Prepend a predefined byte prefix (in some cases also a suffix) to the raw identifier;
  2. Encode raw bytes using Base58 algorithm with checksum.

Here is an example for an EC public key:

from pytezos.encoding import base58_encodings
from base58 import b58encode_check

base58_encodings  # Here is the list of all known entities used in PyTezos
[(b'B', 51, b'\x014', 32, 'block hash'),
 (b'o', 51, b'\x05t', 32, 'operation hash'),
 (b'Lo', 52, b'\x85\xe9', 32, 'operation list hash'),
 (b'LLo', 53, b'\x1d\x9fm', 32, 'operation list list hash'),
 (b'P', 51, b'\x02\xaa', 32, 'protocol hash'),
 (b'Co', 52, b'O\xc7', 32, 'context hash'),
 (b'tz1', 36, b'\x06\xa1\x9f', 20, 'ed25519 public key hash'),
 (b'tz2', 36, b'\x06\xa1\xa1', 20, 'secp256k1 public key hash'),
 (b'tz3', 36, b'\x06\xa1\xa4', 20, 'p256 public key hash'),
 (b'KT1', 36, b'\x02Zy', 20, 'Originated address'),
 (b'id', 30, b'\x99g', 16, 'cryptobox public key hash'),
 (b'expr', 54, b'\r,@\x1b', 32, 'script expression'),
 (b'edsk', 54, b'\r\x0f:\x07', 32, 'ed25519 seed'),
 (b'edpk', 54, b'\r\x0f%\xd9', 32, 'ed25519 public key'),
 (b'spsk', 54, b'\x11\xa2\xe0\xc9', 32, 'secp256k1 secret key'),
 (b'p2sk', 54, b'\x10Q\xee\xbd', 32, 'p256 secret key'),
 (b'edesk', 88, b'\x07Z<\xb3)', 56, 'ed25519 encrypted seed'),
 (b'spesk', 88, b'\t\xed\xf1\xae\x96', 56, 'secp256k1 encrypted secret key'),
 (b'p2esk', 88, b'\t09s\xab', 56, 'p256_encrypted_secret_key'),
 (b'sppk', 55, b'\x03\xfe\xe2V', 33, 'secp256k1 public key'),
 (b'p2pk', 55, b'\x03\xb2\x8b\x7f', 33, 'p256 public key'),
 (b'SSp', 53, b'&\xf8\x88', 33, 'secp256k1 scalar'),
 (b'GSp', 53, b'\x05\\\x00', 33, 'secp256k1 element'),
 (b'edsk', 98, b'+\xf6N\x07', 64, 'ed25519 secret key'),
 (b'edsig', 99, b'\t\xf5\xcd\x86\x12', 64, 'ed25519 signature'),
 (b'spsig', 99, b'\rse\x13?', 64, 'secp256k1 signature'),
 (b'p2sig', 98, b'6\xf0,4', 64, 'p256 signature'),
 (b'sig', 96, b'\x04\x82+', 64, 'generic signature'),
 (b'Net', 15, b'WR\x00', 4, 'chain id')]
public_key_hex = '419491b1796b13d756d394ed925c10727bca06e97353c5ca09402a9b6b07abcc'
# Selecting raw prefix for ed25519 public key from the table above
b58encode_check(b'\r\x0f%\xd9' + bytes.fromhex(public_key_hex))
b'edpku976gpuAD2bXyx1XGraeKuCo1gUZ3LAJcHM12W1ecxZwoiu22R'

That's how string identifiers derived from the raw data in a nutshell.
Of course, you don't have to do it manually, you can use PyTezos helpers for that:

from pytezos.encoding import base58_encode, base58_decode

base58_encode(bytes.fromhex(public_key_hex), prefix=b'edpk')
b'edpku976gpuAD2bXyx1XGraeKuCo1gUZ3LAJcHM12W1ecxZwoiu22R'
base58_decode(b'edpku976gpuAD2bXyx1XGraeKuCo1gUZ3LAJcHM12W1ecxZwoiu22R').hex()
'419491b1796b13d756d394ed925c10727bca06e97353c5ca09402a9b6b07abcc'

# Secret key, public key, public key hash

As was mentioned, implicit account is linked to a public key, but it would be more accurate to say that it's linked to a key pair (where public key can be derived from the secret key). Only the owner of the secret key can spend a particular implicit account, however anyone can check that spending is valid using the public key (which is by definition is accessible by everyone). The impicit account address, which is used for specifying the transaction destination, is derived from the public key using cryptographic hash function.

It's not possible to restore secret key from the public one (would take centuries)
It's not possible to restore public key from the address (public key hash)

Here are few examples demonstrating the derivations:

from pytezos.crypto import Key

sk = Key.from_encoded_key('edsk3nM41ygNfSxVU4w1uAW3G9EnTQEB5rjojeZedLTGmiGRcierVv')
sk.public_key()
'edpku976gpuAD2bXyx1XGraeKuCo1gUZ3LAJcHM12W1ecxZwoiu22R'
sk.public_key_hash()
'tz1eKkWU5hGtfLUiqNpucHrXymm83z3DG9Sq'

However if you try to do inverse transform it will fail:

pk = Key.from_encoded_key('edpku976gpuAD2bXyx1XGraeKuCo1gUZ3LAJcHM12W1ecxZwoiu22R')
pk.secret_key()
ValueError: Secret key is undefined

PyTezos allows you to initialize a keypair using raw values or access them afterwards.

# In this case (ed25519 curve) a seed is returned instead of a secret exponent
sk.secret_exponent.hex()
'92542d866a5263115aa416fd3e1dce4ced35f5545417d1f73763f7093552a72b419491b1796b13d756d394ed925c10727bca06e97353c5ca09402a9b6b07abcc'
sk.public_point.hex()  # Compact elliptic point format
'419491b1796b13d756d394ed925c10727bca06e97353c5ca09402a9b6b07abcc'

# Multiple elliptic curves

As you may already noticed, implicit account addresses can start with tz1, tz2, or tz3 — it depends on the chosen elliptic curve parameters.

tz1 — Ed25519, twisted Edwards curve, has very good properties in terms of implementation security and speed of digital signatures, currently one of the most perspective curves;
tz2 — Secp256k1, defined in Standards for Efficient Cryptography, used in Bitcoin, Ethereum, and other cryptocurrencies, has a wide adoption and library support;
tz3 — NIST P-256 (aka Secp256r1), one of the most used elliptic curves, natively supported on mobile devices as well as cloud HSMs.

The according Base58 prefixes:

Curve Secret key Public key Public key hash
Ed25519 edsk edpk tz1
Secp256k1 spsk sppk tz2
P-256 p2sk p2pk tz3

You may also noticed that there are two variants of edsk prefix: it depends on whether a Ed25519 seed (32 bytes) or a secret exponent (64 bytes, derived from the seed) is used. In Tezos it is seed that's mostly used.

edsk = Key.from_encoded_key(
    'edsk3nM41ygNfSxVU4w1uAW3G9EnTQEB5rjojeZedLTGmiGRcierVv')
edsk.secret_key(ed25519_seed=False)  # returns full secret key
'edskRwA8M7ZDQfRwt2bEUi2TxpbdDYYfLLYNHB8qMYX3kDD8srM5P4NDuMpkDk3oHFrxP4q6Yyw26t55TMQ7sbaKdKETpZtUyR'

In all places where it is necessary, you can specify which curve to use, e.g.:

edpk = Key.from_public_point(
    bytes.fromhex('419491b1796b13d756d394ed925c10727bca06e97353c5ca09402a9b6b07abcc'), 
    curve=b'p2')
edpk.public_key_hash()
'tz3f1mS1a8pnyiZtXox1GtHALhueLhbb7cAq'

# Encrypted keys and commitments

There is a standardized scheme for password encrypted secret keys based on PBKDF2, such encrypted keys have special encoding prefixes edesk, spesk, and p2esk respectfully.

edesk = Key.from_encoded_key(
    'edesk1zxaPJkhNGSzgZDDSphvPzSNrnbmqes8xzUrw1wdFxdRT7ePiQz8D2Q18fMjn6fC9ZRS2rUbg8d8snxxznE', 
    passphrase='qqq')
edesk.secret_key(passphrase='qwerty')
'edesk1q4v8YyqrN1EPSnySoaDGcRByWZ4z4GnLB7xCnz99b8wLAo5eLLynupPG1cJNvT7K6yBgemb6x1L33rYAxh'

Tezos genesis (first) block contains so called "commitments" (aka recomended allocations) it's how initial token distribution was conducted. In order to claim your tokens you need:

  • 15-word mnemonic
  • email
  • password
  • activation code

A similar mechnanism is used for granting tokens in public test networks: on a special website you can download a json file with all that fields.
In order to derive a secret key from that data one need to generate seed from mnemonic using concatenated email+password as passphrase, and then take first 32 bytes.

sk = Key.from_mnemonic(
    mnemonic=["rather", "aware", "school", "often", "area", "quarter", "story", "note",
              "goddess", "dream", "winner", "result", "scheme", "stairs", "clown"],
    email='vsvkfovl.lzzsalmj@tezos.example.org',
    passphrase='8Rx7GLurGY')
sk.public_key_hash()
'tz1cnQZXoznhduu4MVWfJF6GSyP6mMHMbbWa'

# Generating keys and signatures

PyTezos can also generate a new key for you, internally it first creates a mnemonic (which is by default stored on disk, just in case) and then derives a secret key from it.

Note, that you cannot restore a mnemonic from the secret key.

newsk = Key.generate(curve=b'p2', export=False)
newsk.secret_key()
'p2sk3zqDakap9bXEqsTr4sM93ZehELQNGCMX1MXmBsJUi29tFQYijV'

Finally you can sign some arbitrary data with your secret key, or validate and existing signature with a public key. Note that there are four different encodings for the signatures depending on the curve: edsig, spsig, p2sig, and just sig (generic, meaning not holding any info about the curve used).

newsk.sign(b'hello')
'p2sigvTBnDjdDacJpSXjYZfsTtAijJb6DhNRcq3GdZq2gopsugVFV17C19h8EegAepyeEaixgB3Y4rG4qQZGhXTBE7P7C8R7MM'
newsk.sign(b'hello', generic=True)
'sigw8xw8e587gGMWGyHr5LgNhwLuvrxbx1QPGRFANbPbNbN9DUAd46msWhwTjeRPn4uxhSf6objMJkU1sLxvzynqgmFctwwK'
newpk = Key.from_encoded_key('spsk1zkqrmst1yg2c4xi3crWcZPqgdc9KtPtb9SAZWYHAdiQzdHy7j')
newpk.verify(
    signature='spsig1RriZtYADyRhyNoQMa6AiPuJJ7AUDcrxWZfgqexzgANqMv4nXs6qsXDoXcoChBgmCcn2t7Y3EkJaVRuAmNh2cDDxWTdmsz',
    message=b'test')

Verify method throws an exception in case signature is not valid, in our case everything is fine.

add add