Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,66 @@
|
||||
module base58
|
||||
|
||||
const impossible = 'this should never happen'
|
||||
|
||||
pub const btc_alphabet = new_alphabet('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz') or {
|
||||
panic(impossible)
|
||||
}
|
||||
|
||||
pub const flickr_alphabet = new_alphabet('123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ') or {
|
||||
panic(impossible)
|
||||
}
|
||||
|
||||
pub const ripple_alphabet = new_alphabet('rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz') or {
|
||||
panic(impossible)
|
||||
}
|
||||
|
||||
// alphabets is a map of common base58 alphabets:
|
||||
pub const alphabets = {
|
||||
'btc': btc_alphabet
|
||||
'flickr': flickr_alphabet
|
||||
'ripple': ripple_alphabet
|
||||
}
|
||||
|
||||
// Alphabet is the series of characters that an input
|
||||
// will be encoded to and a decode table.
|
||||
struct Alphabet {
|
||||
mut:
|
||||
decode []i8 = []i8{len: 128, init: -1}
|
||||
encode []u8 = []u8{len: 58}
|
||||
}
|
||||
|
||||
// str returns an Alphabet encode table byte array as a string
|
||||
pub fn (alphabet Alphabet) str() string {
|
||||
// i guess i had a brain fart here. Why would I actually use this code?!
|
||||
// mut str := []u8{}
|
||||
// for entry in alphabet.encode {
|
||||
// str << entry
|
||||
// }
|
||||
// return str.bytestr()
|
||||
return alphabet.encode.bytestr()
|
||||
}
|
||||
|
||||
// new_alphabet instantiates an Alphabet object based on
|
||||
// the provided characters
|
||||
pub fn new_alphabet(str string) !Alphabet {
|
||||
if str.len != 58 {
|
||||
return error(@MOD + '.' + @FN + ': string must be 58 characters in length')
|
||||
}
|
||||
|
||||
mut ret := Alphabet{}
|
||||
copy(mut ret.encode, str.bytes())
|
||||
|
||||
mut distinct := 0
|
||||
for i, b in ret.encode {
|
||||
if ret.decode[b] == -1 {
|
||||
distinct++
|
||||
}
|
||||
ret.decode[b] = i8(i)
|
||||
}
|
||||
|
||||
if distinct != 58 {
|
||||
return error(@MOD + '.' + @FN + ': string must not contain repeating characters')
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
// algorthim is adapted from https://github.com/mr-tron/base58 under the MIT license
|
||||
|
||||
module base58
|
||||
|
||||
import math
|
||||
|
||||
// encode_int encodes any integer type to base58 string with Bitcoin alphabet
|
||||
pub fn encode_int(input int) !string {
|
||||
return encode_int_walpha(input, btc_alphabet)
|
||||
}
|
||||
|
||||
// encode_int_walpha any integer type to base58 string with custom alphabet
|
||||
pub fn encode_int_walpha(input int, alphabet Alphabet) !string {
|
||||
if input <= 0 {
|
||||
return error(@MOD + '.' + @FN + ': input must be greater than zero')
|
||||
}
|
||||
|
||||
mut buffer := []u8{}
|
||||
|
||||
mut i := input
|
||||
for i > 0 {
|
||||
remainder := i % 58
|
||||
buffer << alphabet.encode[i8(remainder)]
|
||||
// This needs to be casted so byte inputs can
|
||||
// be used. i8 because remainder will never be
|
||||
// over 58.
|
||||
i = i / 58
|
||||
}
|
||||
|
||||
return buffer.reverse().bytestr()
|
||||
}
|
||||
|
||||
// encode encodes the input string to base58 with the Bitcoin alphabet
|
||||
pub fn encode(input string) string {
|
||||
return encode_walpha(input, btc_alphabet)
|
||||
}
|
||||
|
||||
// encode_bytes encodes the input array to base58, with the Bitcoin alphabet
|
||||
pub fn encode_bytes(input []u8) []u8 {
|
||||
return encode_walpha_bytes(input, btc_alphabet)
|
||||
}
|
||||
|
||||
// encode_walpha encodes the input string to base58 with a custom aplhabet
|
||||
pub fn encode_walpha(input string, alphabet Alphabet) string {
|
||||
if input.len == 0 {
|
||||
return ''
|
||||
}
|
||||
bin := input.bytes()
|
||||
return encode_walpha_bytes(bin, alphabet).bytestr()
|
||||
}
|
||||
|
||||
// encode_walpha encodes the input array to base58 with a custom aplhabet
|
||||
@[direct_array_access]
|
||||
pub fn encode_walpha_bytes(input []u8, alphabet Alphabet) []u8 {
|
||||
if input.len == 0 {
|
||||
return []
|
||||
}
|
||||
mut sz := input.len
|
||||
|
||||
mut zcount := 0
|
||||
for zcount < sz && input[zcount] == 0 {
|
||||
zcount++
|
||||
}
|
||||
|
||||
// It is crucial to make this as short as possible, especially for
|
||||
// the usual case of Bitcoin addresses
|
||||
sz = zcount + (sz - zcount) * 555 / 406 + 1
|
||||
// integer simplification of
|
||||
// ceil(log(256)/log(58))
|
||||
|
||||
mut out := []u8{len: sz}
|
||||
mut i := 0
|
||||
mut high := 0
|
||||
mut carry := u32(0)
|
||||
|
||||
high = sz - 1
|
||||
for b in input {
|
||||
i = sz - 1
|
||||
for carry = u32(b); i > high || carry != 0; i-- {
|
||||
carry = carry + 256 * u32(out[i])
|
||||
out[i] = u8(carry % 58)
|
||||
carry /= 58
|
||||
}
|
||||
high = i
|
||||
}
|
||||
|
||||
// determine additional "zero-gap" in the buffer, aside from zcount
|
||||
for i = zcount; i < sz && out[i] == 0; i++ {}
|
||||
|
||||
// now encode the values with actual alphabet in-place
|
||||
val := unsafe { out[i - zcount..] }
|
||||
sz = val.len
|
||||
for i = 0; i < sz; i++ {
|
||||
out[i] = alphabet.encode[val[i]]
|
||||
}
|
||||
|
||||
return out[..sz]
|
||||
}
|
||||
|
||||
// decode_int decodes base58 string to an integer with Bitcoin alphabet
|
||||
pub fn decode_int(input string) !int {
|
||||
return decode_int_walpha(input, btc_alphabet)
|
||||
}
|
||||
|
||||
// decode_int_walpha decodes base58 string to an integer with custom alphabet
|
||||
pub fn decode_int_walpha(input string, alphabet Alphabet) !int {
|
||||
mut total := 0 // to hold the results
|
||||
b58 := input.reverse()
|
||||
for i, ch in b58 {
|
||||
ch_i := alphabet.encode.bytestr().index_u8(ch)
|
||||
if ch_i == -1 {
|
||||
return error(@MOD + '.' + @FN +
|
||||
': input string contains values not found in the provided alphabet')
|
||||
}
|
||||
|
||||
val := ch_i * math.pow(58, i)
|
||||
|
||||
total += int(val)
|
||||
}
|
||||
|
||||
return total
|
||||
}
|
||||
|
||||
// decode decodes the base58 input string, using the Bitcoin alphabet
|
||||
pub fn decode(str string) !string {
|
||||
return decode_walpha(str, btc_alphabet)
|
||||
}
|
||||
|
||||
// decode_bytes decodes the base58 encoded input array, using the Bitcoin alphabet
|
||||
pub fn decode_bytes(input []u8) ![]u8 {
|
||||
return decode_walpha_bytes(input, btc_alphabet)
|
||||
}
|
||||
|
||||
// decode_walpha decodes the base58 encoded input string, using custom alphabet
|
||||
pub fn decode_walpha(input string, alphabet Alphabet) !string {
|
||||
if input.len == 0 {
|
||||
return ''
|
||||
}
|
||||
bin := input.bytes()
|
||||
res := decode_walpha_bytes(bin, alphabet)!
|
||||
return res.bytestr()
|
||||
}
|
||||
|
||||
// decode_walpha_bytes decodes the base58 encoded input array using a custom alphabet
|
||||
pub fn decode_walpha_bytes(input []u8, alphabet Alphabet) ![]u8 {
|
||||
if input.len == 0 {
|
||||
return []
|
||||
}
|
||||
|
||||
zero := alphabet.encode[0]
|
||||
b58sz := input.len
|
||||
|
||||
mut zcount := 0
|
||||
for i := 0; i < b58sz && input[i] == zero; i++ {
|
||||
zcount++
|
||||
}
|
||||
|
||||
mut t := u64(0)
|
||||
mut c := u64(0)
|
||||
|
||||
// the 32-bit algorithm stretches the result up to 2x
|
||||
mut binu := []u8{len: 2 * ((b58sz * 406 / 555) + 1)}
|
||||
mut outi := []u32{len: (b58sz + 3) / 4}
|
||||
|
||||
for _, r in input {
|
||||
if r > 127 {
|
||||
panic(@MOD + '.' + @FN +
|
||||
': high-bit set on invalid digit; outside of ascii range (${r}). This should never happen.')
|
||||
}
|
||||
if alphabet.decode[r] == -1 {
|
||||
return error(@MOD + '.' + @FN + ': invalid base58 digit (${r})')
|
||||
}
|
||||
|
||||
c = u64(alphabet.decode[r])
|
||||
|
||||
for j := outi.len - 1; j >= 0; j-- {
|
||||
t = u64(outi[j]) * 58 + c
|
||||
c = t >> 32
|
||||
outi[j] = u32(t & 0xffffffff)
|
||||
}
|
||||
}
|
||||
|
||||
// initial mask depend on b58sz, on further loops it always starts at 24 bits
|
||||
mut mask := (u32(b58sz % 4) * 8)
|
||||
if mask == 0 {
|
||||
mask = 32
|
||||
}
|
||||
mask -= 8
|
||||
|
||||
mut out_len := 0
|
||||
for j := 0; j < outi.len; j++ {
|
||||
for mask < 32 {
|
||||
binu[out_len] = u8(outi[j] >> mask)
|
||||
mask -= 8
|
||||
out_len++
|
||||
}
|
||||
mask = 24
|
||||
}
|
||||
|
||||
// find the most significant byte post-decode, if any
|
||||
for msb := zcount; msb < binu.len; msb++ { // loop relies on u32 overflow
|
||||
if binu[msb] > 0 {
|
||||
return binu[msb - zcount..out_len]
|
||||
}
|
||||
}
|
||||
|
||||
// it's all zeroes
|
||||
return binu[..out_len]
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
module base58
|
||||
|
||||
fn test_encode_int() {
|
||||
a := 0x24 // should be 'd' in base58
|
||||
assert encode_int(a)! == 'd'
|
||||
|
||||
test_encode_int_walpha()
|
||||
}
|
||||
|
||||
fn test_encode_int_walpha() {
|
||||
// random alphabet
|
||||
abc := new_alphabet('abcdefghij\$lmnopqrstuvwxyz0123456789_ABCDEFGHIJLMNOPQRSTUV') or {
|
||||
panic(@MOD + '.' + @FN + ': this should never happen')
|
||||
}
|
||||
a := 0x24 // should be '_' in base58 with our custom alphabet
|
||||
assert encode_int_walpha(a, abc)! == '_'
|
||||
}
|
||||
|
||||
fn test_decode_int() {
|
||||
a := 'd'
|
||||
assert decode_int(a)! == 0x24
|
||||
|
||||
test_decode_int_walpha()
|
||||
}
|
||||
|
||||
fn test_decode_int_walpha() {
|
||||
abc := new_alphabet('abcdefghij\$lmnopqrstuvwxyz0123456789_ABCDEFGHIJLMNOPQRSTUV') or {
|
||||
panic(@MOD + '.' + @FN + ': this should never happen')
|
||||
}
|
||||
a := '_'
|
||||
assert decode_int_walpha(a, abc)! == 0x24
|
||||
}
|
||||
|
||||
fn test_encode_string() {
|
||||
// should be 'TtaR6twpTGu8VpY' in base58 and '0P7yfPSL0pQh2L5' with our custom alphabet
|
||||
a := 'lorem ipsum'
|
||||
assert encode(a) == 'TtaR6twpTGu8VpY'
|
||||
|
||||
aa := '0JHRktAz1yEtdGl6RnO'
|
||||
assert encode(aa) == '9qkYDxBXPdc6nW825HkNx7oFMY'
|
||||
|
||||
abc := new_alphabet('abcdefghij\$lmnopqrstuvwxyz0123456789_ABCDEFGHIJLMNOPQRSTUV') or {
|
||||
panic(@MOD + '.' + @FN + ': this should never happen')
|
||||
}
|
||||
assert encode_walpha(a, abc) == '0P7yfPSL0pQh2L5'
|
||||
}
|
||||
|
||||
fn test_decode_string() {
|
||||
a := 'TtaR6twpTGu8VpY'
|
||||
assert decode(a)! == 'lorem ipsum'
|
||||
|
||||
abc := new_alphabet('abcdefghij\$lmnopqrstuvwxyz0123456789_ABCDEFGHIJLMNOPQRSTUV') or {
|
||||
panic(@MOD + '.' + @FN + ': this should never happen')
|
||||
}
|
||||
b := '0P7yfPSL0pQh2L5'
|
||||
assert decode_walpha(b, abc)! == 'lorem ipsum'
|
||||
}
|
||||
|
||||
fn test_fails() ! {
|
||||
a := -238
|
||||
b := 0
|
||||
if z := encode_int(a) {
|
||||
return error(@MOD + '.' + @FN + ': expected encode_int to fail, got ${z}')
|
||||
}
|
||||
if z := encode_int(b) {
|
||||
return error(@MOD + '.' + @FN + ': expected encode_int to fail, got ${z}')
|
||||
}
|
||||
|
||||
c := '!'
|
||||
if z := decode_int(c) {
|
||||
return error(@MOD + '.' + @FN + ': expected decode_int to fail, got ${z}')
|
||||
}
|
||||
if z := decode(c) {
|
||||
return error(@MOD + '.' + @FN + ': expected decode to fail, got ${z}')
|
||||
}
|
||||
|
||||
// repeating character
|
||||
if abc := new_alphabet('aaaaafghij\$lmnopqrstuvwxyz0123456789_ABCDEFGHIJLMNOPQRSTUV') {
|
||||
return error(@MOD + '.' + @FN + ': expected new_alphabet to fail, got ${abc}')
|
||||
}
|
||||
// more than 58 characters long
|
||||
if abc := new_alphabet('abcdefghij\$lmnopqrstuvwxyz0123456789_ABCDEFGHIJLMNOPQRSTUVWXYZ') {
|
||||
return error(@MOD + '.' + @FN + ': expected new_alphabet to fail, got ${abc}')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import encoding.base58
|
||||
import encoding.hex
|
||||
|
||||
fn test_encode() {
|
||||
for input, expected in {
|
||||
'': ''
|
||||
'6263': '2PMCRQ'
|
||||
'hello world': 'StV1DL6CwTryKyV'
|
||||
'\x00\x00hello world': '11StV1DL6CwTryKyV'
|
||||
} {
|
||||
output := base58.encode(input)
|
||||
println('> input: `${input}` | ${input.hex()} | => output: `${output}`')
|
||||
assert output == expected
|
||||
}
|
||||
}
|
||||
|
||||
fn test_encode_int() {
|
||||
for input, expected in {
|
||||
0x6263: '8VG'
|
||||
0x61: '2g'
|
||||
0x626262: 'a3gV'
|
||||
0x636363: 'aPEr'
|
||||
} {
|
||||
output := base58.encode_int(input)!
|
||||
println('> input: 0x${input:x} | => output: `${output}`')
|
||||
assert output == expected
|
||||
}
|
||||
}
|
||||
|
||||
fn test_decode() {
|
||||
for output, expected in {
|
||||
'USm3fpXnKG5EUBx2ndxBDMPVciP5hGey2Jh4NDv6gmeo1LkMeiKrLJUUBk6Z': 'The quick brown fox jumps over the lazy dog.'
|
||||
'11StV1DL6CwTryKyV': '\x00\x00hello world'
|
||||
'2NEpo7TZRRrLZSi2U': 'Hello World!'
|
||||
'14cxpo3MBCYYWCgF74SWTdcmxipnGUsPw3': hex.decode('0027b5891b01da2db74cde1689a97a2acbe23d5fb1c0205bf6')!.bytestr()
|
||||
'3vQB7B6MrGQZaxCuFg4oh': hex.decode('68656c6c6f20776f726c64bc62d4b8')!.bytestr()
|
||||
} {
|
||||
input := base58.decode(output)!
|
||||
println('> output: `${output}` | decoded input: `${input}` | bytes: ${input.hex()}')
|
||||
assert input.hex() == expected.hex()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user