crypto-lite 0.0.1 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|