crypto-lite 0.0.1 → 0.2.3
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 +4 -4
- data/Manifest.txt +7 -0
- data/README.md +491 -0
- data/Rakefile +5 -1
- data/lib/crypto-lite.rb +113 -48
- data/lib/crypto-lite/config.rb +32 -0
- data/lib/crypto-lite/helper.rb +25 -0
- data/lib/crypto-lite/metal.rb +128 -0
- data/lib/crypto-lite/sign_rsa.rb +29 -0
- data/lib/crypto-lite/version.rb +2 -2
- data/test/test_base58.rb +36 -0
- data/test/test_bitcoin_addr.rb +58 -0
- data/test/test_hash.rb +24 -55
- data/test/test_hash_sha.rb +87 -0
- metadata +55 -6
data/Rakefile
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
require 'hoe'
|
2
2
|
require './lib/crypto-lite/version.rb'
|
3
3
|
|
4
|
+
|
4
5
|
Hoe.spec 'crypto-lite' do
|
5
6
|
|
6
7
|
self.version = CryptoLite::VERSION
|
7
8
|
|
8
|
-
self.summary = "crypto-lite -
|
9
|
+
self.summary = "crypto-lite - cryptographic secure hash functions and public key signature algorithms made easy"
|
9
10
|
self.description = summary
|
10
11
|
|
11
12
|
self.urls = { home: 'https://github.com/rubycoco/blockchain' }
|
@@ -18,6 +19,9 @@ Hoe.spec 'crypto-lite' do
|
|
18
19
|
self.history_file = 'CHANGELOG.md'
|
19
20
|
|
20
21
|
self.extra_deps = [
|
22
|
+
['digest-sha3-patched'],
|
23
|
+
['base32-alphabets'],
|
24
|
+
['base58-alphabets'],
|
21
25
|
]
|
22
26
|
|
23
27
|
self.licenses = ['Public Domain']
|
data/lib/crypto-lite.rb
CHANGED
@@ -1,80 +1,145 @@
|
|
1
1
|
require 'pp'
|
2
2
|
require 'digest'
|
3
|
+
require 'base64'
|
3
4
|
require 'openssl'
|
4
5
|
|
6
|
+
## 3rd party gems
|
7
|
+
require 'digest/sha3' # e.g. keccak (original submission/proposal NOT official sha3)
|
8
|
+
## see https://rubygems.org/gems/digest-sha3-patched
|
9
|
+
## https://github.com/teamhedge/digest-sha3-ruby
|
10
|
+
|
11
|
+
## our own 3rd party (2nd party?)
|
12
|
+
require 'base32-alphabets'
|
13
|
+
require 'base58-alphabets'
|
14
|
+
|
15
|
+
|
5
16
|
|
6
17
|
## our own code
|
7
18
|
require 'crypto-lite/version' # note: let version always go first
|
19
|
+
require 'crypto-lite/config'
|
20
|
+
require 'crypto-lite/metal'
|
21
|
+
|
8
22
|
|
9
23
|
|
10
24
|
|
11
25
|
module Crypto
|
12
26
|
|
13
|
-
def self.
|
14
|
-
|
15
|
-
|
16
|
-
else
|
17
|
-
input.class.name
|
18
|
-
end
|
19
|
-
puts " input: #{input} (#{input_type})"
|
20
|
-
|
21
|
-
message = if input.is_a?( Integer ) ## assume byte if single (unsigned) integer
|
22
|
-
raise ArgumentError, "expected unsigned byte (0-255) - got #{input} (0x#{input.to_s(16)}) - can't pack negative number; sorry" if input < 0
|
23
|
-
## note: pack - H (String) => hex string (high nibble first)
|
24
|
-
## todo/check: is there a better way to convert integer number to (binary) string!!!
|
25
|
-
[input.to_s(16)].pack('H*')
|
26
|
-
else ## assume (binary) string
|
27
|
-
input
|
28
|
-
end
|
29
|
-
|
30
|
-
|
31
|
-
bytes = message.bytes
|
32
|
-
bin = bytes.map {|byte| byte.to_s(2).rjust(8, "0")}.join( ' ' )
|
33
|
-
hex = bytes.map {|byte| byte.to_s(16).rjust(2, "0")}.join( ' ' )
|
34
|
-
puts " #{pluralize( bytes.size, 'byte')}: #{bytes.inspect}"
|
35
|
-
puts " binary: #{bin}"
|
36
|
-
puts " hex: #{hex}"
|
37
|
-
|
38
|
-
if engine && ['openssl'].include?( engine.to_s.downcase )
|
39
|
-
puts " engine: #{engine}"
|
40
|
-
digest = OpenSSL::Digest::SHA256.new
|
41
|
-
digest.update( message )
|
42
|
-
digest.digest
|
43
|
-
else ## use "built-in" hash function from digest module
|
44
|
-
Digest::SHA256.digest( message )
|
45
|
-
end
|
27
|
+
def self.base58( *args, **kwargs )
|
28
|
+
input = args_to_input( args, kwargs )
|
29
|
+
Metal.base58bin( input )
|
46
30
|
end
|
47
31
|
|
48
|
-
def self.
|
49
|
-
|
32
|
+
def self.base58check( *args, **kwargs )
|
33
|
+
input = args_to_input( args, kwargs )
|
34
|
+
Metal.base58bin_check( input )
|
50
35
|
end
|
51
36
|
|
52
37
|
|
53
|
-
|
54
|
-
|
38
|
+
########################
|
39
|
+
# (secure) hash functions
|
55
40
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
41
|
+
def self.keccak256( *args, **kwargs )
|
42
|
+
input = args_to_input( args, kwargs )
|
43
|
+
Metal.keccak256bin( input ).unpack( 'H*' )[0]
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def self.rmd160( *args, **kwargs )
|
48
|
+
input = args_to_input( args, kwargs )
|
49
|
+
Metal.rmd160bin( input ).unpack( 'H*' )[0]
|
50
|
+
end
|
51
|
+
|
52
|
+
## add alias RIPEMD160 - why? why not?
|
53
|
+
class << self
|
54
|
+
alias_method :ripemd160, :rmd160
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def self.sha256( *args, **kwargs )
|
59
|
+
input = args_to_input( args, kwargs )
|
60
|
+
engine = kwargs[:engine]
|
61
|
+
Metal.sha256bin( input, engine ).unpack( 'H*' )[0]
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.sha3_256( *args, **kwargs )
|
65
|
+
input = args_to_input( args, kwargs )
|
66
|
+
Metal.sha3_256bin( input ).unpack( 'H*' )[0]
|
67
|
+
end
|
60
68
|
|
61
|
-
|
62
|
-
|
69
|
+
|
70
|
+
|
71
|
+
def self.hash160( *args, **kwargs )
|
72
|
+
input = args_to_input( args, kwargs )
|
73
|
+
Metal.hash160bin( input ).unpack( 'H*' )[0]
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.hash256( *args, **kwargs )
|
77
|
+
input = args_to_input( args, kwargs )
|
78
|
+
Metal.hash256bin( input ).unpack( 'H*' )[0]
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
########
|
84
|
+
# more helpers
|
85
|
+
## check if it is a hex (string)
|
86
|
+
## - allow optiona 0x or 0X and allow abcdef and ABCDEF
|
87
|
+
HEX_RE = /\A(?:0x)?[0-9a-f]+\z/i
|
88
|
+
|
89
|
+
def self.args_to_input( args, kwargs )
|
90
|
+
if kwargs[:hex]
|
91
|
+
hex = kwargs[:hex]
|
92
|
+
raise ArgumentError, "expected hex string (0-9a-f) - got >#{hex}< - can't pack string; sorry" unless hex =~ HEX_RE
|
93
|
+
|
94
|
+
hex = strip0x( hex ) ## check if input starts with 0x or 0X if yes - (auto-)cut off!!!!!
|
95
|
+
[hex].pack( 'H*' )
|
96
|
+
else ## assume single input arg for now
|
97
|
+
input = args[0]
|
98
|
+
input = hex_to_bin_automagic( input ) ## add automagic hex (string) to bin (string) check - why? why not?
|
99
|
+
input
|
100
|
+
end
|
63
101
|
end
|
64
102
|
|
103
|
+
def self.hex_to_bin_automagic( input )
|
104
|
+
## todo/check/fix: add configure setting to turn off automagic - why? why not?
|
105
|
+
if input.is_a?( String ) && input =~ HEX_RE
|
106
|
+
if input[0,2] == '0x' || input[0,2] == '0X'
|
107
|
+
## starting with 0x or 0X always assume hex string for now - why? why not?
|
108
|
+
input = input[2..-1]
|
109
|
+
[input].pack( 'H*' )
|
110
|
+
elsif input.size >= 10
|
111
|
+
## note: hex heuristic!!
|
112
|
+
## for now assumes string MUST have more than 10 digits to qualify!!!
|
113
|
+
[input].pack( 'H*' )
|
114
|
+
else
|
115
|
+
input ## pass through as is!!! (e.g. a, abc, etc.)
|
116
|
+
end
|
117
|
+
else
|
118
|
+
input ## pass through as is
|
119
|
+
end
|
120
|
+
end
|
65
121
|
|
66
122
|
|
67
|
-
def self.
|
68
|
-
|
123
|
+
def self.strip0x( str ) ## todo/check: add alias e.g. strip_hex_prefix or such - why? why not?
|
124
|
+
(str[0,2] == '0x' || str[0,2] == '0X') ? str[2..-1] : str
|
69
125
|
end
|
70
126
|
|
127
|
+
# def self.hex_to_bin( str )
|
128
|
+
# str = strip0x( str ) ## check if input starts with 0x or 0X if yes - (auto-)cut off!!!!!
|
129
|
+
# [str].pack( 'H*' )
|
130
|
+
# end
|
131
|
+
|
71
132
|
end # module Crypto
|
72
133
|
|
73
134
|
|
74
135
|
|
75
|
-
|
76
|
-
|
77
|
-
|
136
|
+
|
137
|
+
require 'crypto-lite/helper'
|
138
|
+
include CryptoHelper # add convenience "top-level" / global helpers
|
139
|
+
|
140
|
+
|
141
|
+
require 'crypto-lite/sign_rsa'
|
142
|
+
RSA = Crypto::RSA
|
78
143
|
|
79
144
|
|
80
145
|
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Crypto
|
2
|
+
|
3
|
+
class Configuration
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@debug = false
|
7
|
+
end
|
8
|
+
|
9
|
+
def debug?() @debug || false; end
|
10
|
+
def debug=(value) @debug = value; end
|
11
|
+
end # class Configuration
|
12
|
+
|
13
|
+
## lets you use
|
14
|
+
## Crypto.configure do |config|
|
15
|
+
## config.debug = true
|
16
|
+
## end
|
17
|
+
|
18
|
+
def self.configuration
|
19
|
+
@configuration ||= Configuration.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.configure
|
23
|
+
yield( configuration )
|
24
|
+
end
|
25
|
+
|
26
|
+
## add convenience helper for format
|
27
|
+
def self.debug?() configuration.debug?; end
|
28
|
+
def self.debug=(value) self.configuration.debug = value; end
|
29
|
+
end # module Crypto
|
30
|
+
|
31
|
+
|
32
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
module CryptoHelper
|
3
|
+
### note: use include CryptoHelper
|
4
|
+
## to get "top-level" / global helpers
|
5
|
+
|
6
|
+
## add convenience "top-level" helpers
|
7
|
+
def sha256( *args, **kwargs ) Crypto.sha256( *args, **kwargs ); end
|
8
|
+
def sha3_256( *args, **kwargs ) Crypto.sha3_256( *args, **kwargs ); end
|
9
|
+
|
10
|
+
def keccak256( *args, **kwargs ) Crypto.keccak256( *args, **kwargs ); end
|
11
|
+
|
12
|
+
def rmd160( *args, **kwargs ) Crypto.rmd160( *args, **kwargs ); end
|
13
|
+
## def ripemd160( input ) Crypto.rmd160( input ); end
|
14
|
+
alias_method :ripemd160, :rmd160
|
15
|
+
|
16
|
+
def hash160( *args, **kwargs ) Crypto.hash160( *args, **kwargs ); end
|
17
|
+
|
18
|
+
def hash256( *args, **kwargs ) Crypto.hash256( *args, **kwargs ); end
|
19
|
+
|
20
|
+
|
21
|
+
def base58( *args, **kwargs ) Crypto.base58( *args, **kwargs ); end
|
22
|
+
def base58check( *args, **kwargs ) Crypto.base58check( *args, **kwargs ); end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module Crypto
|
2
|
+
module Metal
|
3
|
+
|
4
|
+
def self.debug?() Crypto.debug?; end
|
5
|
+
|
6
|
+
########################
|
7
|
+
### to the "metal" crypto primitives
|
8
|
+
## work with binary strings (aka byte arrays) / data
|
9
|
+
|
10
|
+
##
|
11
|
+
## todo/check: use/keep bin-suffix in name - why? why not?
|
12
|
+
|
13
|
+
|
14
|
+
def self.base58bin( input )
|
15
|
+
## todo/check: input must be a (binary) string - why? why not?
|
16
|
+
Base58::Bitcoin.encode_bin( input )
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.base58bin_check( input )
|
20
|
+
## todo/check: input must be a (binary) string - why? why not?
|
21
|
+
hash256 = hash256bin( input )
|
22
|
+
base58bin( input + hash256[0,4] )
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
########################
|
27
|
+
# (secure) hash functions
|
28
|
+
|
29
|
+
def self.keccak256bin( input )
|
30
|
+
message = message( input ) ## "normalize" / convert to (binary) string
|
31
|
+
Digest::SHA3.digest( message, 256 )
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.rmd160bin( input )
|
35
|
+
message = message( input ) ## "normalize" / convert to (binary) string
|
36
|
+
Digest::RMD160.digest( message )
|
37
|
+
end
|
38
|
+
|
39
|
+
## add alias RIPEMD160 - why? why not?
|
40
|
+
class << self
|
41
|
+
alias_method :ripemd160bin, :rmd160bin
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def self.sha256bin( input, engine=nil ) ## todo/check: add alias sha256b or such to - why? why not?
|
46
|
+
message = message( input ) ## "normalize" / convert to (binary) string
|
47
|
+
|
48
|
+
if engine && ['openssl'].include?( engine.to_s.downcase )
|
49
|
+
puts " engine: #{engine}" if debug?
|
50
|
+
digest = OpenSSL::Digest::SHA256.new
|
51
|
+
## or use OpenSSL::Digest.new( 'SHA256' )
|
52
|
+
digest.update( message )
|
53
|
+
digest.digest
|
54
|
+
else ## use "built-in" hash function from digest module
|
55
|
+
Digest::SHA256.digest( message )
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.sha3_256bin( input )
|
60
|
+
message = message( input ) ## "normalize" / convert to (binary) string
|
61
|
+
|
62
|
+
digest = OpenSSL::Digest.new( 'SHA3-256' )
|
63
|
+
digest.update( message )
|
64
|
+
digest.digest
|
65
|
+
end
|
66
|
+
|
67
|
+
####
|
68
|
+
## helper
|
69
|
+
# def hash160( pubkey )
|
70
|
+
# binary = [pubkey].pack( "H*" ) # Convert to binary first before hashing
|
71
|
+
# sha256 = Digest::SHA256.digest( binary )
|
72
|
+
# ripemd160 = Digest::RMD160.digest( sha256 )
|
73
|
+
# ripemd160.unpack( "H*" )[0] # Convert back to hex
|
74
|
+
# end
|
75
|
+
|
76
|
+
def self.hash160bin( input )
|
77
|
+
message = message( input ) ## "normalize" / convert to (binary) string
|
78
|
+
|
79
|
+
rmd160bin(sha256bin( message ))
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
def self.hash256bin( input )
|
84
|
+
message = message( input ) ## "normalize" / convert to (binary) string
|
85
|
+
|
86
|
+
sha256bin(sha256bin( message ))
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
##############################
|
91
|
+
## helpers
|
92
|
+
def self.message( input ) ## convert input to (binary) string
|
93
|
+
if debug?
|
94
|
+
input_type = if input.is_a?( String )
|
95
|
+
"#{input.class.name}/#{input.encoding}"
|
96
|
+
else
|
97
|
+
input.class.name
|
98
|
+
end
|
99
|
+
puts " input: #{input} (#{input_type})"
|
100
|
+
end
|
101
|
+
|
102
|
+
message = if input.is_a?( Integer ) ## assume byte if single (unsigned) integer
|
103
|
+
raise ArgumentError, "expected unsigned byte (0-255) - got #{input} (0x#{input.to_s(16)}) - can't pack negative number; sorry" if input < 0
|
104
|
+
## note: pack - H (String) => hex string (high nibble first)
|
105
|
+
## todo/check: is there a better way to convert integer number to (binary) string!!!
|
106
|
+
[input.to_s(16)].pack('H*')
|
107
|
+
else ## assume (binary) string
|
108
|
+
input
|
109
|
+
end
|
110
|
+
|
111
|
+
if debug?
|
112
|
+
bytes = message.bytes
|
113
|
+
bin = bytes.map {|byte| byte.to_s(2).rjust(8, "0")}.join( ' ' )
|
114
|
+
hex = bytes.map {|byte| byte.to_s(16).rjust(2, "0")}.join( ' ' )
|
115
|
+
puts " #{pluralize( bytes.size, 'byte')}: #{bytes.inspect}"
|
116
|
+
puts " binary: #{bin}"
|
117
|
+
puts " hex: #{hex}"
|
118
|
+
end
|
119
|
+
|
120
|
+
message
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.pluralize( count, noun )
|
124
|
+
count == 1 ? "#{count} #{noun}" : "#{count} #{noun}s"
|
125
|
+
end
|
126
|
+
|
127
|
+
end # module Metal
|
128
|
+
end # module Crypto
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Crypto
|
2
|
+
|
3
|
+
|
4
|
+
module RSA
|
5
|
+
def self.generate_keys ## todo/check: add a generate alias - why? why not?
|
6
|
+
key_pair = OpenSSL::PKey::RSA.new( 2048 )
|
7
|
+
private_key = key_pair.export
|
8
|
+
public_key = key_pair.public_key.export
|
9
|
+
|
10
|
+
[private_key, public_key]
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
def self.sign( plaintext, private_key )
|
15
|
+
private_key = OpenSSL::PKey::RSA.new( private_key ) ## note: convert/wrap into to obj from exported text format
|
16
|
+
Base64.encode64( private_key.private_encrypt( plaintext ))
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.decrypt( ciphertext, public_key )
|
20
|
+
public_key = OpenSSL::PKey::RSA.new( public_key ) ## note: convert/wrap into to obj from exported text format
|
21
|
+
public_key.public_decrypt( Base64.decode64( ciphertext ))
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def self.valid_signature?( plaintext, ciphertext, public_key )
|
26
|
+
plaintext == decrypt( ciphertext, public_key )
|
27
|
+
end
|
28
|
+
end # module RSA
|
29
|
+
end # module Crypto
|
data/lib/crypto-lite/version.rb
CHANGED
data/test/test_base58.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
###
|
2
|
+
# to run use
|
3
|
+
# ruby -I ./lib -I ./test test/test_base58.rb
|
4
|
+
|
5
|
+
|
6
|
+
require 'helper'
|
7
|
+
|
8
|
+
|
9
|
+
class TestBase58 < MiniTest::Test
|
10
|
+
|
11
|
+
HEX_TESTS = [
|
12
|
+
["00000000000000000000", "1111111111"],
|
13
|
+
["00000000000000000000123456789abcdef0", "111111111143c9JGph3DZ"],
|
14
|
+
]
|
15
|
+
|
16
|
+
def test_hex
|
17
|
+
HEX_TESTS.each do |item|
|
18
|
+
assert_equal item[1], base58( hex: item[0] )
|
19
|
+
assert_equal item[1], base58( item[0] )
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def test_bitcoin_addr
|
25
|
+
addr_exp = '1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs'
|
26
|
+
|
27
|
+
pkh = 'f54a5851e9372b87810a8e60cdd2e7cfd80b6e31'
|
28
|
+
|
29
|
+
## all-in-one
|
30
|
+
assert_equal addr_exp, base58check( hex: '00' + pkh )
|
31
|
+
assert_equal addr_exp, base58check( '00' + pkh )
|
32
|
+
|
33
|
+
assert_equal addr_exp, Crypto::Metal.base58bin_check( "\x00" + [pkh].pack('H*') )
|
34
|
+
end
|
35
|
+
|
36
|
+
end # class TestBase58
|