比特币加入了改进版的 Base58 算法—-base58check,主要为了解决 Base58 导出的字符串没有校验机制,这样,在传播过程中,如果漏写了几个字符,会检测不出来。所以使用了改进版的算法 Base58Check。校验和是从编码的数据的哈希值中得到的,可以用来检测并避免转录和输入中产生的错误。使用Base58Check编码时,解码软件会计算数据的校验和并和编码中自带的校验和进行对比。二者不匹配则表明有错误产生,那么这个Base58Check的数据就是无效的。

一、Base58Check编码原理

base58check
base58check

见上图:

1.首先对数据添加一个版本前缀,这个前缀用来识别编码的数据类型。例如,比特币地址的前缀是0(十六进制是0x00)。
2.对数据连续进行两次SHA256哈希算法

1checksum = SHA256(SHA256(prefix+data))

3.在产生的长度为32个字节(两次哈希云算)的哈希值中,取其前4个字节作为检验和添加到数据第一步产生的数据之后。
4.将数据进行Base58编码处理

在比特币中,大多数需要向用户展示的数据都使用Base58Check编码,可以实现数据压缩,易读而且有错误检验。Base58Check编码中的版本前缀是用来创造易于辨别的格式,具体的前缀分类可以参考:https://en.bitcoin.it/wiki/List_of_address_prefixes

二、代码实现

这里就不给python实现了(https://pypi.org/project/base58/)这里有已经实现好的,这里列一个golang实现的操作,如下:

 1package main
 2import (
 3        "bytes"
 4        "crypto/sha256"
 5        "encoding/hex"
 6        "errors"
 7        "math/big"
 8        "reflect"
 9        "fmt"
10        "log"
11)
12const alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
13// Encode encodes the given version and data to a base58check encoded string
14func Encode(version, data string) (string, error) {
15        prefix, err := hex.DecodeString(version)
16        if err != nil {
17                return "", err
18        }
19        dataBytes, err := hex.DecodeString(data)
20        if err != nil {
21                return "", err
22        }
23        dataBytes = append(prefix, dataBytes...)
24        // Performing SHA256 twice
25        sha256hash := sha256.New()
26        sha256hash.Write(dataBytes)
27        middleHash := sha256hash.Sum(nil)
28        sha256hash = sha256.New()
29        sha256hash.Write(middleHash)
30        hash := sha256hash.Sum(nil)
31        checksum := hash[:4]
32        dataBytes = append(dataBytes, checksum...)
33        // For all the "00" versions or any prepended zeros as base58 removes them
34        zeroCount := 0
35        for i := 0; i  0 {
36                mod := new(big.Int)
37                decimalData.DivMod(decimalData, divisor, mod)
38                encoded = string(alphabet[mod.Int64()]) + encoded
39        }
40        return encoded
41}
42func b58decode(data string) ([]byte, error) {
43        decimalData := new(big.Int)
44        alphabetBytes := []byte(alphabet)
45        multiplier := big.NewInt(58)
46        for _, value := range data {
47                pos := bytes.IndexByte(alphabetBytes, byte(value))
48                if pos == -1 {
49                        return nil, errors.New("Character not found in alphabet")
50                }
51                decimalData.Mul(decimalData, multiplier)
52                decimalData.Add(decimalData, big.NewInt(int64(pos)))
53        }
54        return decimalData.Bytes(), nil
55}
56func main() {
57        encoded, err := Encode("80", "44D00F6EB2E5491CD7AB7E7185D81B67A23C4980F62B2ED0914D32B7EB1C5581")
58        if err != nil {
59                log.Fatal(err)
60        }
61        fmt.Println(encoded) // 5JLbJxi9koHHvyFEAERHLYwG7VxYATnf8YdA9fiC6kXMghkYXpk
62        decoded, err := Decode("5JLbJxi9koHHvyFEAERHLYwG7VxYATnf8YdA9fiC6kXMghkYXpk")
63        if err != nil {
64                log.Fatal(err)
65        }
66        fmt.Println(decoded)
67}

执行后返回的结果如下:

1[root@localhost golang]# go run base58check_01.go
25JLbJxi9koHHvyFEAERHLYwG7VxYATnf8YdA9fiC6kXMghkYXpk
38044d00f6eb2e5491cd7ab7e7185d81b67a23c4980f62b2ed0914d32b7eb1c5581

上面5J这段是加密后的结果,可以看出要比原数据短,后面执行后的结果中,前面的开头的80代表的是加密前缀。具体在实用时,可以直接使用别人已经实现的模块,链接地址:https://github.com/anaskhan96/base58check