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.
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