aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/vlib/encoding/base58
diff options
context:
space:
mode:
Diffstat (limited to 'v_windows/v/vlib/encoding/base58')
-rw-r--r--v_windows/v/vlib/encoding/base58/alphabet.v65
-rw-r--r--v_windows/v/vlib/encoding/base58/base58.v181
-rw-r--r--v_windows/v/vlib/encoding/base58/base58_test.v89
3 files changed, 335 insertions, 0 deletions
diff --git a/v_windows/v/vlib/encoding/base58/alphabet.v b/v_windows/v/vlib/encoding/base58/alphabet.v
new file mode 100644
index 0000000..44d4fc3
--- /dev/null
+++ b/v_windows/v/vlib/encoding/base58/alphabet.v
@@ -0,0 +1,65 @@
+module base58
+
+// alphabets is a map of common base58 alphabets
+pub const alphabets = init_alphabets()
+
+// init_alphabet instantiates the preconfigured `Alphabet`s and returns them as `map[string]Alphabet`.
+// This is a temporary function. Setting const alphabets to the value returned in this function
+// causes a C error right now.
+fn init_alphabets() map[string]Alphabet {
+ return {
+ 'btc': new_alphabet('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz') or {
+ panic(@MOD + '.' + @FN + ': this should never happen')
+ }
+ 'flickr': new_alphabet('123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ') or {
+ panic(@MOD + '.' + @FN + ': this should never happen')
+ }
+ 'ripple': new_alphabet('rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz') or {
+ panic(@MOD + '.' + @FN + ': this should never happen')
+ }
+ }
+}
+
+// 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 []byte = []byte{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 := []byte{}
+ // 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(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
+}
diff --git a/v_windows/v/vlib/encoding/base58/base58.v b/v_windows/v/vlib/encoding/base58/base58.v
new file mode 100644
index 0000000..fb2ff72
--- /dev/null
+++ b/v_windows/v/vlib/encoding/base58/base58.v
@@ -0,0 +1,181 @@
+// 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, alphabets['btc'])
+}
+
+// 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 := []byte{}
+
+ 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 byte array to base58 with Bitcoin alphabet
+pub fn encode(input string) string {
+ return encode_walpha(input, alphabets['btc'])
+}
+
+// encode_walpha encodes byte array to base58 with custom aplhabet
+pub fn encode_walpha(input string, alphabet Alphabet) string {
+ if input.len == 0 {
+ return ''
+ }
+
+ bin := input.bytes()
+ mut sz := bin.len
+
+ mut zcount := 0
+ for zcount < sz && bin[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 := []byte{len: sz}
+ mut i := 0
+ mut high := 0
+ mut carry := u32(0)
+
+ high = sz - 1
+ for b in bin {
+ i = sz - 1
+ for carry = u32(b); i > high || carry != 0; i-- {
+ carry = carry + 256 * u32(out[i])
+ out[i] = byte(carry % 58)
+ carry /= 58
+ }
+ high = 1
+ }
+
+ // 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 := out[i - zcount..]
+ sz = val.len
+ for i = 0; i < sz; i++ {
+ out[i] = alphabet.encode[val[i]]
+ }
+
+ return out[..sz].bytestr()
+}
+
+// decode_int decodes base58 string to an integer with Bitcoin alphabet
+pub fn decode_int(input string) ?int {
+ return decode_int_walpha(input, alphabets['btc'])
+}
+
+// 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_byte(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 base58 string using the Bitcoin alphabet
+pub fn decode(str string) ?string {
+ return decode_walpha(str, alphabets['btc'])
+}
+
+// decode_walpha decodes base58 string using custom alphabet
+pub fn decode_walpha(str string, alphabet Alphabet) ?string {
+ if str.len == 0 {
+ return ''
+ }
+
+ zero := alphabet.encode[0]
+ b58sz := str.len
+
+ mut zcount := 0
+ for i := 0; i < b58sz && str[i] == zero; i++ {
+ zcount++
+ }
+
+ mut t := u64(0)
+ mut c := u64(0)
+
+ // the 32-bit algorithm stretches the result up to 2x
+ mut binu := []byte{len: 2 * ((b58sz * 406 / 555) + 1)}
+ mut outi := []u32{len: (b58sz + 3) / 4}
+
+ for _, r in str {
+ 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] = byte(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].bytestr()
+ }
+ }
+
+ // it's all zeroes
+ return binu[..out_len].bytestr()
+}
diff --git a/v_windows/v/vlib/encoding/base58/base58_test.v b/v_windows/v/vlib/encoding/base58/base58_test.v
new file mode 100644
index 0000000..5cbd37b
--- /dev/null
+++ b/v_windows/v/vlib/encoding/base58/base58_test.v
@@ -0,0 +1,89 @@
+module base58
+
+fn main() {
+ test_encode_int() or {}
+ test_decode_int() or {}
+ test_encode_string()
+ test_fails() or {}
+}
+
+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'
+
+ 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')
+ }
+}