base58編碼原理和實現(golang)
阿新 • • 發佈:2019-02-14
在看EOS原始碼時,接觸到base58編碼。和通常base64編碼一樣,base58編碼的作用也是將非可視字元視覺化(ASCII化)。但不同的是base58編碼去掉了幾個看起來會產生歧義的字元,如 0 (零), O (大寫字母O), I (大寫的字母i) and l (小寫的字母L) ,和幾個影響雙擊選擇的字元,如/, +。結果字符集正好58個字元(包括9個數字,24個大寫字母,25個小寫字母)。而且因為58 不是2的整次冪,所以沒有使用類似base64編碼中使用直接擷取3個字元轉4個字元(3*8=4*6 , 2的6次方剛好64)的方法進行轉換,而是採用我們數學上經常使用的進位制轉換方法——輾轉相除法(本質上,base64編碼是64進位制,base58是58進位制 )。看下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)。現在我們看下go語言中的實現:
// Copyright (c) 2015 The btcsuite developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. // AUTOGENERATED by genalphabet.go; do not edit. package base58 import ( "math/big" ) const ( // alphabet is the modified base58 alphabet used by Bitcoin. alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" alphabetIdx0 = '1' ) var b58 = [256]byte{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 255, 255, 255, 255, 255, 255, 255, 9, 10, 11, 12, 13, 14, 15, 16, 255, 17, 18, 19, 20, 21, 255, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 255, 255, 255, 255, 255, 255, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 255, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, } //go:generate go run genalphabet.go var bigRadix = big.NewInt(58) var bigZero = big.NewInt(0) // Decode decodes a modified base58 string to a byte slice. func Decode(b string) []byte { answer := big.NewInt(0) j := big.NewInt(1) scratch := new(big.Int) for i := len(b) - 1; i >= 0; i-- { //字元,ascii碼錶的簡版-->得到字元代表的值(0,1,2,..57) tmp := b58[b[i]] //出現不該出現的字元 if tmp == 255 { return []byte("") } scratch.SetInt64(int64(tmp)) //scratch = j*scratch scratch.Mul(j, scratch) answer.Add(answer, scratch) //每次進位都要乘上58 j.Mul(j, bigRadix) } //得到大端的位元組序 tmpval := answer.Bytes() var numZeros int for numZeros = 0; numZeros < len(b); numZeros++ { //得到高位0的位數 if b[numZeros] != alphabetIdx0 { break } } //得到原來數字的長度 flen := numZeros + len(tmpval) //構造一個新地存放結果的空間 val := make([]byte, flen, flen) copy(val[numZeros:], tmpval) return val } // Encode encodes a byte slice to a modified base58 string. func Encode(b []byte) string { x := new(big.Int) //將b解釋為大端儲存 x.SetBytes(b) //Base58編碼可以表示的位元位數為Log258 {\displaystyle \approx } \approx5.858bit。經過Base58編碼的資料為原始的資料長度的1.37倍 answer := make([]byte, 0, len(b)*136/100) for x.Cmp(bigZero) > 0 { mod := new(big.Int) //x除於58的餘數mod,並將商賦值給x x.DivMod(x, bigRadix, mod) answer = append(answer, alphabet[mod.Int64()]) } // leading zero bytes //因為如果高位為0,0除任何數為0,可以直接設定為‘1’ for _, i := range b { if i != 0 { break } answer = append(answer, alphabetIdx0) } // reverse //因為之前先附加低位的,後附加高位的,所以需要翻轉 alen := len(answer) for i := 0; i < alen/2; i++ { answer[i], answer[alen-1-i] = answer[alen-1-i], answer[i] } return string(answer) }