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.
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 - Cryptographic Secure Hash Functions and Public Key Signature Algorithms Made Easy"
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']
@@ -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.sha256bin( input, engine=nil ) ## todo/check: add alias sha256b or such to - why? why not?
14
- input_type = if input.is_a?( String )
15
- "#{input.class.name}/#{input.encoding}"
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.sha256( input, engine=nil )
49
- sha256bin( input, engine ).unpack( 'H*' )[0]
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
- def self.sha256hex( input, engine=nil )
54
- ## convenience helper - lets you pass in hex string
38
+ ########################
39
+ # (secure) hash functions
55
40
 
56
- ## check if input starts with 0x or 0X if yes - (auto-)cut off!!!!!
57
- if input.start_with?( '0x') || input.start_with?( '0X' )
58
- input = input[2..-1]
59
- end
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
- raise ArgumentError, "expected hex string (0-9a-f) - got >#{input}< - can't pack string; sorry" if input.downcase =~ /[^0-9a-f]/
62
- sha256( [input].pack( 'H*' ), engine )
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.pluralize( count, noun )
68
- count == 1 ? "#{count} #{noun}" : "#{count} #{noun}s"
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
- ## add convenience "top-level" helpers
76
- def sha256( input, engine=nil ) Crypto.sha256( input, engine ); end
77
- def sha256hex( input, engine=nil ) Crypto.sha256hex( input, engine ); end
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
@@ -2,8 +2,8 @@
2
2
  module CryptoLite
3
3
 
4
4
  MAJOR = 0
5
- MINOR = 0
6
- PATCH = 1
5
+ MINOR = 2
6
+ PATCH = 3
7
7
  VERSION = [MAJOR,MINOR,PATCH].join('.')
8
8
 
9
9
  def self.version
@@ -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