base58block 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +116 -0
- data/Rakefile +2 -0
- data/base58block.gemspec +23 -0
- data/lib/base58block.rb +98 -0
- data/lib/base58block/version.rb +3 -0
- metadata +80 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5d77a784c0b92e665ae1d0f03fb92307d78335d1
|
4
|
+
data.tar.gz: 628a12d811841276f0321339bae069db2642e07e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cd87241e1e8d9561718520205a00c290d12c7fa80f3131b0f325d227b3476ae874f9d5443ac7acf93a4b629a3c30e8b02d25f6a0e7fa24f03632dbbe8cd29fd8
|
7
|
+
data.tar.gz: 1aaaaaef83a9f52d797c5356734a8c2f2632d74e41890423a236e3180a2ea220d7d507fa416053648f8a65180a1d89353bac26ca908de61f3c3e4c855ea14cb3
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 David Waite
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
# Base58block
|
2
|
+
|
3
|
+
Implementation of the Base58 Block algorithm
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'base58block'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install base58block
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
The library itself currently just consists of an encode and decode method.
|
24
|
+
|
25
|
+
### Encoding
|
26
|
+
The encoding is relatively simple. Using a 58 character dictionary
|
27
|
+
(matching the bitcoin dictionary), we divide the input into 8 byte (64 bit)
|
28
|
+
blocks, then convert these to 64 bit integers (in network octet order). This
|
29
|
+
block is converted to base58, with each base58 'digit' represented by a
|
30
|
+
different character in the dictionary. The value is always represented by a
|
31
|
+
full 11 base58 'digit' number. So for example:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
Base58Block.encode "\0\0\0\0\0\0\0\0" # => "11111111"
|
35
|
+
```
|
36
|
+
|
37
|
+
This is because the zero base58 digit is represented by the character "1" in
|
38
|
+
the dictionary.
|
39
|
+
|
40
|
+
The maximum value 64 bit number is represented in hex as 0xffffffffffffffff
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
Base58Block.encode(["FFFFFFFFFFFFFFFF"].pack("H*")) # => "jpXCZedGfVQ"
|
44
|
+
```
|
45
|
+
|
46
|
+
You may need to represent the last bytes in a sequence total less than eight
|
47
|
+
bytes. This is done rather simply - pad the highest order bytes with zero values
|
48
|
+
before converting to an integer, convert to base58 per normal, then change the
|
49
|
+
highest order base58 'digit' to a flag value.
|
50
|
+
|
51
|
+
This flag value is `43 + {number of bytes in original block}`. This technique
|
52
|
+
was chosen because:
|
53
|
+
1. The highest leading base58 'digit' for a 64 bit unsigned number will be 42,
|
54
|
+
so no Base58 value will ever result in the leading digit set to one of the
|
55
|
+
flag values
|
56
|
+
2. The highest leading base58 'digit' for a 56 bit/7 byte unsigned number will
|
57
|
+
always be zero, so we will not lose information by assigning an additional
|
58
|
+
purpose to the leading digit.
|
59
|
+
|
60
|
+
To show an example:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
# Convert the highest seven byte value, 0x00ffffffffffffff
|
64
|
+
result = Base58Block.encode(["00FFFFFFFFFFFFFF"].pack("H*")) # => "1Ahg1opVcGW"
|
65
|
+
|
66
|
+
# Change first 'digit' to indicate we had only seven bytes input
|
67
|
+
result[0] = DICTIONARY[43+7] # => "s"
|
68
|
+
|
69
|
+
Base58Block.encode(["FFFFFFFFFFFFFF".pack("H*")]) == result # => true
|
70
|
+
```
|
71
|
+
|
72
|
+
Note that a partial block still results in 11 characters of output.
|
73
|
+
|
74
|
+
###Decoding
|
75
|
+
Decoding works along the same lines as encoding, so it will likely be useful to
|
76
|
+
familiarize yourself with encoding before reading the following description.
|
77
|
+
|
78
|
+
Given an 11 character block, treat that block as a base58 number. The
|
79
|
+
individual 'digits' of this number are represented by the character values
|
80
|
+
within the dictionary. Reverse the dictionary mapping to come up with the
|
81
|
+
individual digit values of the base58 number. This will allow you to construct
|
82
|
+
an integer value, which should always be representable with an unsigned 64 bit
|
83
|
+
value. Then, convert this 64 bit value to network order bytes.
|
84
|
+
|
85
|
+
There is a special case when decoding a partial block. If the first 'digit' in
|
86
|
+
the base 58 value is 43 or higher, this value is a flag indicating a partial
|
87
|
+
block. Subtracting 43 from this value will give the number of bytes to be output
|
88
|
+
(1 - 7).
|
89
|
+
|
90
|
+
To convert this value to an integer, you should first set the leading 'digit' to
|
91
|
+
a zero value. You can then generate an eight byte output as normal. Strip off
|
92
|
+
the high order bytes (which will all be zero) to get a value of the desired size.
|
93
|
+
|
94
|
+
### Normalization
|
95
|
+
For the purpose of cryptographic validity, it is useful to have a single source
|
96
|
+
value represented with a single base58 block encoded value. To this end, the
|
97
|
+
following additional rules are provided for normalization:
|
98
|
+
|
99
|
+
1. Only the last block can be a partial block
|
100
|
+
2. Partial blocks must indicate between 1 and 7 bytes
|
101
|
+
3. No additional characters (including whitespace) may appear within the encoded
|
102
|
+
value.
|
103
|
+
|
104
|
+
|
105
|
+
## Known issues
|
106
|
+
|
107
|
+
- The current decoding algorithm will fail if whitespace or invalid characters
|
108
|
+
are in the input value
|
109
|
+
|
110
|
+
## Contributing
|
111
|
+
|
112
|
+
1. Fork it ( https://github.com/dwaite/base58block/fork )
|
113
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
114
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
115
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
116
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/base58block.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'base58block/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "base58block"
|
8
|
+
spec.version = Base58block::VERSION
|
9
|
+
spec.authors = ["David Waite"]
|
10
|
+
spec.email = ["david@alkaline-solutions.com"]
|
11
|
+
spec.summary = %q{Implementation of my own Base58-Block algorithm}
|
12
|
+
spec.description = %q{Encoding algorithm for human input of binary values}
|
13
|
+
spec.homepage = "https://github.com/dwaite/base58block"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
end
|
data/lib/base58block.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
require "base58block/version"
|
2
|
+
|
3
|
+
module Base58Block
|
4
|
+
DICTIONARY = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
5
|
+
REVERSE = [
|
6
|
+
0, 1, 2, 3, 4, 5, 6, 7, 8, nil, nil, nil, nil,
|
7
|
+
nil, nil, nil, 9, 10, 11, 12, 13, 14, 15, 16, nil, 17,
|
8
|
+
18, 19, 20, 21, nil, 22, 23, 24, 25, 26, 27, 28, 29,
|
9
|
+
30, 31, 32, nil, nil, nil, nil, nil, nil, 33, 34, 35, 36,
|
10
|
+
37, 38, 39, 40, 41, 42, 43, nil, 44, 45, 46, 47, 48,
|
11
|
+
49, 50, 51, 52, 53, 54, 55, 56, 57 ]
|
12
|
+
REVERSE_START = 49
|
13
|
+
|
14
|
+
private
|
15
|
+
def self.reverse_dictionary c
|
16
|
+
idx = c.bytes.first
|
17
|
+
if idx < REVERSE_START
|
18
|
+
nil
|
19
|
+
else
|
20
|
+
REVERSE[ idx - REVERSE_START]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
public
|
25
|
+
def self.encode data
|
26
|
+
data = data.dup
|
27
|
+
result = ""
|
28
|
+
|
29
|
+
# operate on blocks of 8 bytes. The last block may less than 8 bytes
|
30
|
+
data.each_byte.each_slice(8) do |block|
|
31
|
+
encoded_block = ""
|
32
|
+
count = block.length
|
33
|
+
|
34
|
+
#convert input block into an integer using network order
|
35
|
+
total = block.inject {|sum, n| sum * 256 + n }
|
36
|
+
|
37
|
+
# repeatedly mod/divide
|
38
|
+
while total > 0 do
|
39
|
+
total, index = total.divmod 58
|
40
|
+
encoded_block << DICTIONARY[index]
|
41
|
+
end
|
42
|
+
|
43
|
+
# if output is less than 11 characters, pad with "1"
|
44
|
+
padding = 11 - encoded_block.bytesize
|
45
|
+
encoded_block << "1" * padding
|
46
|
+
|
47
|
+
# indicate less than eight bytes by changing the high order character
|
48
|
+
# (which is always '1' if you have less than eight bytes input) to
|
49
|
+
# a value which cannot occur
|
50
|
+
if count < 8
|
51
|
+
encoded_block[10] = DICTIONARY[43+count]
|
52
|
+
end
|
53
|
+
|
54
|
+
# We want the data to be in network order, so we reverse it now
|
55
|
+
encoded_block = encoded_block.reverse
|
56
|
+
result << encoded_block
|
57
|
+
end
|
58
|
+
result
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.decode text
|
62
|
+
text = text.dup
|
63
|
+
result = "".force_encoding "binary"
|
64
|
+
|
65
|
+
# operate on blocks of 8 bytes. The last block may less than 8 bytes
|
66
|
+
text.each_char.each_slice(11) do |block|
|
67
|
+
|
68
|
+
# create an array of the individual base 58 'digits'
|
69
|
+
decoded_block = block.map{|c| reverse_dictionary c }
|
70
|
+
if decoded_block.include? nil
|
71
|
+
return nil
|
72
|
+
end
|
73
|
+
|
74
|
+
# decode the byte count, stripping off the flag if needed
|
75
|
+
byte_count = 8
|
76
|
+
if decoded_block[0] >= 43
|
77
|
+
byte_count = c[0] - 43
|
78
|
+
decoded_block = decoded_block[1..-1]
|
79
|
+
end
|
80
|
+
|
81
|
+
# convert to integer value
|
82
|
+
integer = decoded_block.inject{|sum, i| sum * 58 + i }
|
83
|
+
|
84
|
+
# convert integer to 8 byte array (in reverse order)
|
85
|
+
data = []
|
86
|
+
8.times do
|
87
|
+
integer, mod = integer.divmod 256
|
88
|
+
data << mod
|
89
|
+
end
|
90
|
+
# strip off the leading zero bytes if we are a different byte count
|
91
|
+
data = data[0...byte_count].reverse
|
92
|
+
|
93
|
+
# pack bytes to a (binary) string and append to result
|
94
|
+
result << data.pack("C*")
|
95
|
+
end
|
96
|
+
result
|
97
|
+
end
|
98
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: base58block
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David Waite
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-08-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
description: Encoding algorithm for human input of binary values
|
42
|
+
email:
|
43
|
+
- david@alkaline-solutions.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".gitignore"
|
49
|
+
- Gemfile
|
50
|
+
- LICENSE.txt
|
51
|
+
- README.md
|
52
|
+
- Rakefile
|
53
|
+
- base58block.gemspec
|
54
|
+
- lib/base58block.rb
|
55
|
+
- lib/base58block/version.rb
|
56
|
+
homepage: https://github.com/dwaite/base58block
|
57
|
+
licenses:
|
58
|
+
- MIT
|
59
|
+
metadata: {}
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 2.2.2
|
77
|
+
signing_key:
|
78
|
+
specification_version: 4
|
79
|
+
summary: Implementation of my own Base58-Block algorithm
|
80
|
+
test_files: []
|