堕落不振功业废,勤耕不辍日月新

Base58Check编码

C/C++ hailen 6℃

比特币加入了改进版的 Base58 算法—-base58check,主要为了解决 Base58 导出的字符串没有校验机制,这样,在传播过程中,如果漏写了几个字符,会检测不出来。所以使用了改进版的算法 Base58Check。校验和是从编码的数据的哈希值中得到的,可以用来检测并避免转录和输入中产生的错误。使用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 

Base58Check编码,首发于运维之路

转载请注明:我是IT » Base58Check编码

喜欢 (0)or分享 (0)