比特币加入了改进版的 Base58 算法—-base58check,主要为了解决 Base58 导出的字符串没有校验机制,这样,在传播过程中,如果漏写了几个字符,会检测不出来。所以使用了改进版的算法 Base58Check。校验和是从编码的数据的哈希值中得到的,可以用来检测并避免转录和输入中产生的错误。使用Base58Check编码时,解码软件会计算数据的校验和并和编码中自带的校验和进行对比。二者不匹配则表明有错误产生,那么这个Base58Check的数据就是无效的。
一、Base58Check编码原理
见上图:
1.首先对数据添加一个版本前缀,这个前缀用来识别编码的数据类型。例如,比特币地址的前缀是0(十六进制是0x00)。
2.对数据连续进行两次SHA256哈希算法
checksum = 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实现的操作,如下:
package main import ( "bytes" "crypto/sha256" "encoding/hex" "errors" "math/big" "reflect" "fmt" "log" ) const alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" // Encode encodes the given version and data to a base58check encoded string func Encode(version, data string) (string, error) { prefix, err := hex.DecodeString(version) if err != nil { return "", err } dataBytes, err := hex.DecodeString(data) if err != nil { return "", err } dataBytes = append(prefix, dataBytes...) // Performing SHA256 twice sha256hash := sha256.New() sha256hash.Write(dataBytes) middleHash := sha256hash.Sum(nil) sha256hash = sha256.New() sha256hash.Write(middleHash) hash := sha256hash.Sum(nil) checksum := hash[:4] dataBytes = append(dataBytes, checksum...) // For all the "00" versions or any prepended zeros as base58 removes them zeroCount := 0 for i := 0; i < len(dataBytes); i++ { if dataBytes[i] == 0 { zeroCount++ } else { break } } // Performing base58 encoding encoded := b58encode(dataBytes) for i := 0; i < zeroCount; i++ { encoded = "1" + encoded } return encoded, nil } // Decode decodes the given base58check encoded string and returns the version prepended decoded string func Decode(encoded string) (string, error) { zeroCount := 0 for i := 0; i < len(encoded); i++ { if encoded[i] == 49 { zeroCount++ } else { break } } dataBytes, err := b58decode(encoded) if err != nil { return "", err } dataBytesLen := len(dataBytes) if dataBytesLen <= 4 { return "", errors.New("base58check data cannot be less than 4 bytes") } data, checksum := dataBytes[:dataBytesLen-4], dataBytes[dataBytesLen-4:] for i := 0; i < zeroCount; i++ { data = append([]byte{0}, data...) } // Performing SHA256 twice to validate checksum sha256hash := sha256.New() sha256hash.Write(data) middleHash := sha256hash.Sum(nil) sha256hash = sha256.New() sha256hash.Write(middleHash) hash := sha256hash.Sum(nil) if !reflect.DeepEqual(checksum, hash[:4]) { return "", errors.New("Data and checksum don't match") } return hex.EncodeToString(data), nil } func b58encode(data []byte) string { var encoded string decimalData := new(big.Int) decimalData.SetBytes(data) divisor, zero := big.NewInt(58), big.NewInt(0) for decimalData.Cmp(zero) > 0 { mod := new(big.Int) decimalData.DivMod(decimalData, divisor, mod) encoded = string(alphabet[mod.Int64()]) + encoded } return encoded } func b58decode(data string) ([]byte, error) { decimalData := new(big.Int) alphabetBytes := []byte(alphabet) multiplier := big.NewInt(58) for _, value := range data { pos := bytes.IndexByte(alphabetBytes, byte(value)) if pos == -1 { return nil, errors.New("Character not found in alphabet") } decimalData.Mul(decimalData, multiplier) decimalData.Add(decimalData, big.NewInt(int64(pos))) } return decimalData.Bytes(), nil } func main() { encoded, err := Encode("80", "44D00F6EB2E5491CD7AB7E7185D81B67A23C4980F62B2ED0914D32B7EB1C5581") if err != nil { log.Fatal(err) } fmt.Println(encoded) // 5JLbJxi9koHHvyFEAERHLYwG7VxYATnf8YdA9fiC6kXMghkYXpk decoded, err := Decode("5JLbJxi9koHHvyFEAERHLYwG7VxYATnf8YdA9fiC6kXMghkYXpk") if err != nil { log.Fatal(err) } fmt.Println(decoded) }
执行后返回的结果如下:
[root@localhost golang]# go run base58check_01.go 5JLbJxi9koHHvyFEAERHLYwG7VxYATnf8YdA9fiC6kXMghkYXpk 8044d00f6eb2e5491cd7ab7e7185d81b67a23c4980f62b2ed0914d32b7eb1c5581
上面5J这段是加密后的结果,可以看出要比原数据短,后面执行后的结果中,前面的开头的80代表的是加密前缀。具体在实用时,可以直接使用别人已经实现的模块,链接地址:https://github.com/anaskhan96/base58check