From 6f3ec7356e1160a7512c68ca2d0c4ae3260f8b84 Mon Sep 17 00:00:00 2001 From: lugaxker Date: Thu, 12 Mar 2020 00:35:35 +0100 Subject: [PATCH] Create the Base58Check encoding section. --- protocol/blockchain/encoding/base58check.md | 176 ++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 protocol/blockchain/encoding/base58check.md diff --git a/protocol/blockchain/encoding/base58check.md b/protocol/blockchain/encoding/base58check.md new file mode 100644 index 0000000..37c1be8 --- /dev/null +++ b/protocol/blockchain/encoding/base58check.md @@ -0,0 +1,176 @@ +# Base58check encoding + +In Bitcoin Cash, an encoding used for keys and addresses is **Base58Check**. This is a Base58 encoding format that unambiguously encodes the type of data in the first few characters (the *version*) and includes an error detection code in the last few characters (the *checksum*). Its goal is to make it easier to copy and to share information, by using a QR code for instance. + +To encode a Bitcoin Cash address, it is however recommended to use the CashAddr encoding instead, because it prevents confusion with Bitcoin-BTC addresses. + +## Base58 + +Base58's goal is to avoid copy error and enable doublecliking selection. That is why it uses all the alphanumeric symbols excluding `0`, `O`, `I` and `l`, these last characters being hard to ditinguish from one another in some fonts. + +Base58 alphabet: + +``` +123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz +``` + +Base58 symbol chart: + +| Value | Character | Value | Character | Value | Character | Value | Character | +| ----- | --------- | ------| --------- | ------| --------- | ------| --------- | +| 0 | 1 | 15 | G | 30 | X | 45 | n | +| 1 | 2 | 16 | H | 31 | Y | 46 | o | +| 2 | 3 | 17 | J | 32 | Z | 47 | p | +| 3 | 4 | 18 | K | 33 | a | 48 | q | +| 4 | 5 | 19 | L | 34 | b | 49 | r | +| 5 | 6 | 20 | M | 35 | c | 50 | s | +| 6 | 7 | 21 | N | 36 | d | 51 | t | +| 7 | 8 | 22 | P | 37 | e | 52 | u | +| 8 | 9 | 23 | Q | 38 | f | 53 | v | +| 9 | A | 24 | R | 39 | g | 54 | w | +| 10 | B | 25 | S | 40 | h | 55 | x | +| 11 | C | 26 | T | 41 | i | 56 | y | +| 12 | D | 27 | U | 42 | j | 57 | z | +| 13 | E | 28 | V | 43 | k | +| 14 | F | 29 | W | 44 | m | + + +## Version bytes + +In the Base58Check encoding, the version byte indicates the type of the data encoded. The mainnet version bytes are: + +| Type | Hex value | Decimal value | Base58 prefix | +| ---------------------------- | ---------- | --------------| ------------- | +| P2PKH address | 0x00 | 0 | 1 | +| P2SH address | 0x05 | 5 | 3 | +| Private key (WIF) | 0x80 | 128 | 5 | +| Private key (WIF-compressed) | 0x80 | 128 | K or L | +| Extended private key | 0x0488ade4 | 76066276 | xpub | +| Extended public key | 0x0488b21e | 76067358 | xprv | + +The testnet version bytes are: + +| Type | Hex value | Decimal value | Base58 prefix | +| ------------------------------------ | ---------- | --------------| ------------- | +| Testnet P2PKH address | 0x6f | 111 | m or n | +| Testnet P2SH address | 0xc4 | 196 | 2 | +| Testnet private key (WIF) | 0xef | 239 | 9 | +| Testnet private key (WIF-compressed) | 0xef | 239 | c | +| Testnet extended private key | 0x043587cf | 70617039 | tpub | +| Testnet extended public key | 0x04358394 | 70615956 | tprv | + +## Encoding + +Base58Check is used to encode a `payload` and a `version` byte. It is done by following the steps described below. + +1. Take the `version` byte and the `payload` bytes, and concatenate them together (bytewise): + + ``` + version || payload + ``` + +2. Compute the `checksum` by taking the first four bytes of the double SHA256 hash function of this concatenation. + + ``` + checksum = SHA256( SHA256( version || payload ) )[:4] + ``` + +3. Concatenate all three of them together: + + ``` + version || payload || checksum + ``` + +4. Encode the result with Base58. Note that each leading zero bytes are encoded with the character `1` which is added to the string. + + ``` + Base58( version || payload || checksum ) + ``` + +## Encoding a private key (Wallet Import Format) + +Private keys in Bitcoin Cash are usually encoded with Base58Check. This is known as *Wallet Import Format* (WIF). + +Steps to encode a private key: + +1. Take a private key, i.e., a number between 0 and the order of the generator point (G) of secp256k1. Let's consider the following private key (32-byte array): + + ``` + 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd + ``` + +2. Take the `version` byte corresponding to it (`0x80` for mainnet, `0xef` for testnet), and concatenate them together: + + ``` + 801e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd + ``` + +3. Compute the checksum by performing the double SHA256 on it, and by taking the first four bytes of this hash: + + ``` + SHA256( SHA256( 801e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd ) ) = **c47e83ff**afda3ba4396e1bc6a648515e5fc9aa95910af6a4429537b87fb7b474 + ``` + +4. Concatenate the result from step 2 and the checksum together: + + ``` + 801e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aeddc47e83ff + ``` + +5. Encode it with Base58. The result is the Wallet Import Format, or WIF, of the private key. If it is a mainnet (uncompressed) private key, it will always start with a `5`. + + ``` + 5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn + ``` + +If you want to derive a compressed public key from this private key, which is usually done in every modern wallets, simply add the prefix `0x01` to the private key bytes: + +``` +1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd01 +``` + +and follow the steps (2-5) described above, to get the encoded private key: + +``` +KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ +``` + +It is known as the WIF-compressed format. Even if the private key is not compressed, the wallet will take this encoding into account and will derive a compressed public key from it. Note that WIF-compressed private keys always start with a `K` or a `L`. + +## Encoding a Bitcoin Cash address + +Addresses in Bitcoin Cash can sometimes be encoded with Base58Check. These encoded addresses are called *legacy address*. Even if this format is still supported by various wallets, it is strongly recommended to use CashAddr encoding instead. + +Steps to encode a legacy address: + +1. Take an address payload, i.e., the hash of a public key (P2PKH) or a redeem script (P2SH) which is a 20-byte array: + + ``` + 211b74ca4686f81efda5641767fc84ef16dafe0b + ``` + +2. Take the corresponding `version` byte (`0x00` for mainnet P2PKH, `0x05` for mainnet P2SH, `0x6f` for testnet P2PKH, `0xc4` for testnet P2SH), and concatenate them together. In our case, this is a mainnet P2PKH address: + + ``` + 00211b74ca4686f81efda5641767fc84ef16dafe0b + ``` + +3. Compute the checksum by performing the double SHA256 on it, and by taking the first four bytes of this hash: + + ``` + SHA256( SHA256( 00211b74ca4686f81efda5641767fc84ef16dafe0b ) ) = **388c8d1d**3f70ec351abf400fadf7756418e6b3835c01fe78206b39ec1ab8a37a + ``` + +4. Concatenate the result from step 2 and the checksum together: + + ``` + 00211b74ca4686f81efda5641767fc84ef16dafe0b388c8d1d + ``` + +5. Encode it with Base58. Note that each leading zero byte must be encoded with the prefix `1` which is appended to the beginning of the string. Thus, if it is a mainnet P2PKH legacy address (`0x00` version byte), it will always start with a `1`. + + ``` + 1424C2F4bC9JidNjjTUZCbUxv6Sa1Mt62x + ``` + +**Important notice.** Please do not use this encoding for P2SH address. It can (and should) be deactivated by wallets in order to prevent to send funds to P2SH-embedded SegWit addresses, which are not supported by the Bitcoin Cash protocol.