crypto-lite 0.1.0 → 0.3.0
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 +443 -119
- data/Rakefile +4 -0
- data/lib/crypto-lite.rb +98 -65
- 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 +1 -1
- 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 +65 -2
data/Rakefile
CHANGED
data/lib/crypto-lite.rb
CHANGED
@@ -3,112 +3,145 @@ require 'digest'
|
|
3
3
|
require 'base64'
|
4
4
|
require 'openssl'
|
5
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
|
+
require 'elliptic'
|
15
|
+
|
16
|
+
|
6
17
|
|
7
18
|
## our own code
|
8
19
|
require 'crypto-lite/version' # note: let version always go first
|
20
|
+
require 'crypto-lite/config'
|
21
|
+
require 'crypto-lite/metal'
|
22
|
+
|
9
23
|
|
10
24
|
|
11
25
|
|
12
26
|
module Crypto
|
13
27
|
|
14
|
-
def self.
|
15
|
-
|
16
|
-
|
17
|
-
else
|
18
|
-
input.class.name
|
19
|
-
end
|
20
|
-
puts " input: #{input} (#{input_type})"
|
21
|
-
|
22
|
-
message = if input.is_a?( Integer ) ## assume byte if single (unsigned) integer
|
23
|
-
raise ArgumentError, "expected unsigned byte (0-255) - got #{input} (0x#{input.to_s(16)}) - can't pack negative number; sorry" if input < 0
|
24
|
-
## note: pack - H (String) => hex string (high nibble first)
|
25
|
-
## todo/check: is there a better way to convert integer number to (binary) string!!!
|
26
|
-
[input.to_s(16)].pack('H*')
|
27
|
-
else ## assume (binary) string
|
28
|
-
input
|
29
|
-
end
|
30
|
-
|
31
|
-
|
32
|
-
bytes = message.bytes
|
33
|
-
bin = bytes.map {|byte| byte.to_s(2).rjust(8, "0")}.join( ' ' )
|
34
|
-
hex = bytes.map {|byte| byte.to_s(16).rjust(2, "0")}.join( ' ' )
|
35
|
-
puts " #{pluralize( bytes.size, 'byte')}: #{bytes.inspect}"
|
36
|
-
puts " binary: #{bin}"
|
37
|
-
puts " hex: #{hex}"
|
38
|
-
|
39
|
-
if engine && ['openssl'].include?( engine.to_s.downcase )
|
40
|
-
puts " engine: #{engine}"
|
41
|
-
digest = OpenSSL::Digest::SHA256.new
|
42
|
-
digest.update( message )
|
43
|
-
digest.digest
|
44
|
-
else ## use "built-in" hash function from digest module
|
45
|
-
Digest::SHA256.digest( message )
|
46
|
-
end
|
28
|
+
def self.base58( *args, **kwargs )
|
29
|
+
input = args_to_input( args, kwargs )
|
30
|
+
Metal.base58bin( input )
|
47
31
|
end
|
48
32
|
|
49
|
-
def self.
|
50
|
-
|
33
|
+
def self.base58check( *args, **kwargs )
|
34
|
+
input = args_to_input( args, kwargs )
|
35
|
+
Metal.base58bin_check( input )
|
51
36
|
end
|
52
37
|
|
53
38
|
|
54
|
-
|
55
|
-
|
39
|
+
########################
|
40
|
+
# (secure) hash functions
|
41
|
+
|
42
|
+
def self.keccak256( *args, **kwargs )
|
43
|
+
input = args_to_input( args, kwargs )
|
44
|
+
Metal.keccak256bin( input ).unpack( 'H*' )[0]
|
45
|
+
end
|
56
46
|
|
57
|
-
## check if input starts with 0x or 0X if yes - (auto-)cut off!!!!!
|
58
|
-
if input.start_with?( '0x') || input.start_with?( '0X' )
|
59
|
-
input = input[2..-1]
|
60
|
-
end
|
61
47
|
|
62
|
-
|
63
|
-
|
48
|
+
def self.rmd160( *args, **kwargs )
|
49
|
+
input = args_to_input( args, kwargs )
|
50
|
+
Metal.rmd160bin( input ).unpack( 'H*' )[0]
|
64
51
|
end
|
65
52
|
|
53
|
+
## add alias RIPEMD160 - why? why not?
|
54
|
+
class << self
|
55
|
+
alias_method :ripemd160, :rmd160
|
56
|
+
end
|
66
57
|
|
67
58
|
|
68
|
-
def self.
|
69
|
-
|
59
|
+
def self.sha256( *args, **kwargs )
|
60
|
+
input = args_to_input( args, kwargs )
|
61
|
+
engine = kwargs[:engine]
|
62
|
+
Metal.sha256bin( input, engine ).unpack( 'H*' )[0]
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.sha3_256( *args, **kwargs )
|
66
|
+
input = args_to_input( args, kwargs )
|
67
|
+
Metal.sha3_256bin( input ).unpack( 'H*' )[0]
|
70
68
|
end
|
71
69
|
|
72
70
|
|
73
71
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
public_key = key_pair.public_key.export
|
72
|
+
def self.hash160( *args, **kwargs )
|
73
|
+
input = args_to_input( args, kwargs )
|
74
|
+
Metal.hash160bin( input ).unpack( 'H*' )[0]
|
75
|
+
end
|
79
76
|
|
80
|
-
|
77
|
+
def self.hash256( *args, **kwargs )
|
78
|
+
input = args_to_input( args, kwargs )
|
79
|
+
Metal.hash256bin( input ).unpack( 'H*' )[0]
|
81
80
|
end
|
82
81
|
|
83
82
|
|
84
|
-
|
85
|
-
|
86
|
-
|
83
|
+
|
84
|
+
########
|
85
|
+
# more helpers
|
86
|
+
## check if it is a hex (string)
|
87
|
+
## - allow optiona 0x or 0X and allow abcdef and ABCDEF
|
88
|
+
HEX_RE = /\A(?:0x)?[0-9a-f]+\z/i
|
89
|
+
|
90
|
+
def self.args_to_input( args, kwargs )
|
91
|
+
if kwargs[:hex]
|
92
|
+
hex = kwargs[:hex]
|
93
|
+
raise ArgumentError, "expected hex string (0-9a-f) - got >#{hex}< - can't pack string; sorry" unless hex =~ HEX_RE
|
94
|
+
|
95
|
+
hex = strip0x( hex ) ## check if input starts with 0x or 0X if yes - (auto-)cut off!!!!!
|
96
|
+
[hex].pack( 'H*' )
|
97
|
+
else ## assume single input arg for now
|
98
|
+
input = args[0]
|
99
|
+
input = hex_to_bin_automagic( input ) ## add automagic hex (string) to bin (string) check - why? why not?
|
100
|
+
input
|
101
|
+
end
|
87
102
|
end
|
88
103
|
|
89
|
-
def self.
|
90
|
-
|
91
|
-
|
104
|
+
def self.hex_to_bin_automagic( input )
|
105
|
+
## todo/check/fix: add configure setting to turn off automagic - why? why not?
|
106
|
+
if input.is_a?( String ) && input =~ HEX_RE
|
107
|
+
if input[0,2] == '0x' || input[0,2] == '0X'
|
108
|
+
## starting with 0x or 0X always assume hex string for now - why? why not?
|
109
|
+
input = input[2..-1]
|
110
|
+
[input].pack( 'H*' )
|
111
|
+
elsif input.size >= 10
|
112
|
+
## note: hex heuristic!!
|
113
|
+
## for now assumes string MUST have more than 10 digits to qualify!!!
|
114
|
+
[input].pack( 'H*' )
|
115
|
+
else
|
116
|
+
input ## pass through as is!!! (e.g. a, abc, etc.)
|
117
|
+
end
|
118
|
+
else
|
119
|
+
input ## pass through as is
|
120
|
+
end
|
92
121
|
end
|
93
122
|
|
94
123
|
|
95
|
-
def self.
|
96
|
-
|
124
|
+
def self.strip0x( str ) ## todo/check: add alias e.g. strip_hex_prefix or such - why? why not?
|
125
|
+
(str[0,2] == '0x' || str[0,2] == '0X') ? str[2..-1] : str
|
97
126
|
end
|
98
|
-
end # module RSA
|
99
|
-
end # module Crypto
|
100
127
|
|
128
|
+
# def self.hex_to_bin( str )
|
129
|
+
# str = strip0x( str ) ## check if input starts with 0x or 0X if yes - (auto-)cut off!!!!!
|
130
|
+
# [str].pack( 'H*' )
|
131
|
+
# end
|
101
132
|
|
133
|
+
end # module Crypto
|
102
134
|
|
103
135
|
|
104
|
-
## add convenience "top-level" helpers
|
105
|
-
def sha256( input, engine=nil ) Crypto.sha256( input, engine ); end
|
106
|
-
def sha256hex( input, engine=nil ) Crypto.sha256hex( input, engine ); end
|
107
136
|
|
108
|
-
RSA = Crypto::RSA
|
109
137
|
|
138
|
+
require 'crypto-lite/helper'
|
139
|
+
include CryptoHelper # add convenience "top-level" / global helpers
|
110
140
|
|
111
141
|
|
142
|
+
require 'crypto-lite/sign_rsa'
|
143
|
+
RSA = Crypto::RSA
|
144
|
+
|
112
145
|
|
113
146
|
|
114
147
|
|
@@ -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
|