一、base58编码原理

Base58是用于Bitcoin中使用的一种独特的编码方式,主要用于产生Bitcoin的钱包地址。base58和base64一样是一种二进制转可视字符串的算法,主要用来转换大整数值。区别是,转换出来的字符串,去除了几个看起来会产生歧义的字符,如 0 (零), O (大写字母O), I (大写的字母i) and l (小写的字母L) ,和几个影响双击选择的字符,如/, +。结果字符集正好58个字符(包括9个数字,24个大写字母,25个小写字母)。看下base58的编码表:

base58
base58

也就是字符1代表0,字符2代表1,字符3代表2…字符z代表57。然后回一下辗转相除法。

如要将1234转换为58进制;
第一步:1234除于58,商21,余数为16,查表得H
第二步:21除于58,商0,余数为21,查表得N
所以得到base58编码为:NH

如果待转换的数前面有0怎么办?直接附加编码1来代表,有多少个就附加多少个(编码表中1代表0)。

二、代码实现

golang实现

以下是一个studygolang上看到的一个实现的golang版本,具体代码如下:

 1package main
 2import (
 3        "math/big"
 4        "fmt"
 5        "bytes"
 6)
 7var b58Alphabet = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
 8func Base58Encode(input []byte) []byte{
 9        var result []byte
10        x:= big.NewInt(0).SetBytes(input)
11  //把字节数组input转化为了大整数big.Int
12        base := big.NewInt(int64(len(b58Alphabet)))
13        zero := big.NewInt(0)
14        //大整数的指针
15        mod := &big.Int{}
16        for x.Cmp(zero) != 0 {
17                x.DivMod(x,base,mod)  // 对x取余数
18                result =  append(result, b58Alphabet[mod.Int64()])
19        }
20        for _,b:=range input{
21                if b ==0x00{
22                        result =  append([]byte{b58Alphabet[0]},result...)
23                }else{
24                        break
25                }
26        }
27        return result
28}
29func Base58Decode(input []byte) []byte{
30        result :=  big.NewInt(0)
31        zeroBytes :=0
32        for _,b :=range input{
33                if b=='1'{
34                        zeroBytes++
35                }else{
36                        break
37                }
38        }
39        payload:= input[zeroBytes:]
40        for _,b := range payload{
41                charIndex := bytes.IndexByte(b58Alphabet,b)  //反推出余数
42                result.Mul(result,big.NewInt(58))   //之前的结果乘以58
43                result.Add(result,big.NewInt(int64(charIndex)))  //加上这个余数
44        }
45        decoded :=result.Bytes()
46        decoded =  append(bytes.Repeat([]byte{0x00},zeroBytes),decoded...)
47        return decoded
48}
49func ReverseBytes(data []byte){
50        for i,j :=0,len(data) - 1;i<j;i,j = i+1,j - 1{
51                data[i],data[j] = data[j],data[i]
52        }
53}
54func main(){
55        org := []byte("qwerty")
56        fmt.Println(string(org))
57        ReverseBytes(org)
58        fmt.Println(string(org))
59        fmt.Printf("%s\n",string( Base58Encode([]byte("hello jonson"))))
60        fmt.Printf("%s",string(Base58Decode([]byte("2yGEbwRFyav6CimZ7"))))
61}

这里使用了反转,不是很方便,可以看后面使用一个高仿bitcoin版本的,也可以使用github上别我已经实现的版本:https://github.com/shengdoushi/base58/ (支持Bitcoin、Ripple、IPFS,缺点也是没有base58check)

比特币中的关于该算法的源码地址为:https://github.com/bitcoin/bitcoin/blob/master/src/base58.cpp (c++语言),一个高仿bitcoin源码的golang版实现的base58算法如下(不包含base58check算法):

1[root@361way base58]# go run 01base58.go
2kvMuJRLnworeVVrPSiY
3www.361way.com

我已把代码放在github上:https://github.com/361way/golang/blob/master/base58/01base58.go

python版本的实现

不得不说python版本写的还是不错的,base58.cpp上带的,这里都已经实现。地址为:https://pypi.org/project/base58/ (https://github.com/keis/base58)。

三、其他

base58编码不是所有的都使用的上面所列的基础码,不同的类型的里使用的编码不同,如下:

1BitcoinAlphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
2IPFSAlphabet    = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
3FlickrAlphabet  = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"
4RippleAlphabet  = "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"