ring_sig 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 81ca882165cbb38793b1973cc43b8861eda76b88
4
- data.tar.gz: 9be01854e734261e115c2c8d8edbfb2cc3cff8a9
3
+ metadata.gz: 99fef8c39d8ec2e0d7fc86ed6f25a09625940dfd
4
+ data.tar.gz: 9e989018593339b1148a858c2b508c6cc17fc016
5
5
  SHA512:
6
- metadata.gz: e73648fab59657fe7a30dbef07b1b8457b0e511b136bf7a3750a08791843a0aa2ad4e45a280da5b3d05b7455b7c0a69440dc20cbdd0f97b9c05b413362e5896b
7
- data.tar.gz: 8a23930dbc658f1a91c4dde5ed32443366f205c116961a874f3365f788eb1297086626393126e7d77305c08e9e8f0dcb79bfeb56b1a55b3890e678524d39748c
6
+ metadata.gz: 5f0f5ec506578ffa6e9a6a7593f4418303a9f65be9b9cac64a3176cbf253de76f956efe56c935ecd065bba18f19508972c2ff06f0df9d710f94274fc779e58a0
7
+ data.tar.gz: e8527bea3482085ea27a8a77334f497b4922cc14f969f98a27981d5d239c6e129386fe40229b90ea35f4b23a75e5a3fe7823e9c44ff822af6a632256fd9785c0
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ script: "rspec"
2
+ rvm:
3
+ - 2.0.0
4
+ - 2.1.0
5
+ - ruby-head
data/.yardopts ADDED
@@ -0,0 +1,3 @@
1
+ --readme README.md
2
+ --markup markdown
3
+ lib/ring_sig/**/*.rb
data/README.md CHANGED
@@ -0,0 +1,134 @@
1
+ # RingSig gem for Ruby
2
+
3
+ [![Build Status](https://travis-ci.org/jamoes/ring_sig.svg?branch=master)](https://travis-ci.org/jamoes/ring_sig)
4
+
5
+ This gem implements a signature scheme known as one-time ring signatures.
6
+ The algorithm for one-time ring signatures was originally described in section
7
+ 4.4 of the [CryptoNote Whitepaper](https://cryptonote.org/whitepaper.pdf).
8
+
9
+ ## Ring signatures
10
+
11
+ Ring signatures are a special type of digital signature that allows the signer
12
+ to achieve *unconditional unlinkability* between their signature and their
13
+ public key. Signers sign a message using their private key and an arbitrary set
14
+ of foreign public keys. Verifiers are given the full set of public keys that a
15
+ message was signed with. Verifiers can prove that *one* of the private keys
16
+ signed the message, but they cannot determine *which* private key was actually
17
+ used for signing.
18
+
19
+ The signatures produced by this gem are said to be *one-time* ring signatures,
20
+ because the signature includes a Key Image, which is the same for all messages
21
+ signed with the same private key. Therefore, if a signer signs multiple messages
22
+ with the same private key, the signatures can be linked.
23
+
24
+ This gem does not use any randomness. All the algorithms are deterministic, and
25
+ do not require any sort of external source of randomness. When signing, a seed
26
+ is computed from a hash of the message and the private key. That seed is used
27
+ along with the hash algorithm and a nonce anywhere the signing algorithm calls
28
+ for randomness. As a result, the same inputs will always generate the same
29
+ signature.
30
+
31
+ ## Current limitations
32
+
33
+ - This gem is not optimized for speed. All elliptical curve arithmetic is
34
+ computed in pure ruby. As a result, the sign and verify operations are slow.
35
+ Future versions will hopefully be better optimized for speed.
36
+ - This gem was not written by a cryptography expert. It is provided "as is"
37
+ and it is the user's responsibility to make sure it will be suitable for the
38
+ desired purpose.
39
+
40
+ ## Installation
41
+
42
+ This library is distributed as a gem named [ring_sig](https://rubygems.org/gems/ring_sig)
43
+ at RubyGems.org. To install it, run:
44
+
45
+ gem install ring_sig
46
+
47
+ ## Usage
48
+
49
+ First, require the gem:
50
+ ```ruby
51
+ require 'ring_sig'
52
+ ```
53
+
54
+ Next, create a private key for signing. For our example, we'll just use the
55
+ private key `1`. In the wild, you'll want to utilize a securely generated key.
56
+ ```ruby
57
+ key = RingSig::PrivateKey.new(1)
58
+ ```
59
+
60
+ Next, access a set a foreign public keys for signing. To demonstrate that any
61
+ arbitrary keys can be used, we'll use the public keys from the coinbase
62
+ transactions of the first three blocks on the bitcoin blockchain.
63
+
64
+ ```ruby
65
+ foreign_keys = %w{
66
+ 04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f
67
+ 0496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858ee
68
+ 047211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073dee6c89064984f03385237d92167c13e236446b417ab79a0fcae412ae3316b77
69
+ }.map {|s| RingSig::PublicKey.from_hex(s) }
70
+ ```
71
+
72
+ Next, we sign the message. This will assign a `RingSig::Signature` to the `sig`
73
+ variable, and a deterministically shuffled Array of `RingSig::PublicKey`s to the
74
+ `public_keys` variable.
75
+ ```ruby
76
+ sig, public_keys = key.sign("Hello World!", foreign_keys)
77
+ ```
78
+
79
+ You can see the signature contents by using the `to_hex` method:
80
+ ```ruby
81
+ puts sig.to_hex
82
+ ```
83
+
84
+ Finally, verify the signature:
85
+ ```ruby
86
+ sig.verify("Hello World!", public_keys)
87
+ ```
88
+
89
+ By default, this gem uses SHA256 for its hash algorithm, and Secp256k1 for its
90
+ ECDSA group. You can specify alternates if you'd like:
91
+ ```ruby
92
+ key = RingSig::PrivateKey.new(1, group: ECDSA::Group::Secp256r1, hash_algorithm: OpenSSL::Digest::RIPEMD160)
93
+ ```
94
+
95
+ ## Standards
96
+
97
+ There currently aren't any standards around Ring Signatures that I know of. This
98
+ gem attempts to make sensible choices such that the exact same algorithm can
99
+ easily be implemented in other languages.
100
+
101
+ I'd love to see standards emerge around this powerful cryptographic primitive.
102
+ If you have any feedback about how to make the implementation of the algorithms
103
+ in this gem more inter-operable with other systems, I'd love to hear it!
104
+
105
+ ## Contributing
106
+
107
+ To submit a bug, please go to this gem's [github page](https://github.com/jamoes/ring_sig)
108
+ and create a new issue.
109
+
110
+ If you'd like to contribute code, these are the general steps:
111
+
112
+ 1. Fork and clone the repository
113
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
114
+ 3. Commit your changes (`git commit -a 'Added some feature'`)
115
+ 4. Push to the branch (`git push origin my-new-feature`)
116
+ 5. Create a Pull Request
117
+
118
+ Please make sure to write tests for your new code, and follow the existing code
119
+ standards wherever possible.
120
+
121
+ ## Credit
122
+
123
+ Special thanks to the authors of the very excellent [ECDSA](https://github.com/DavidEGrayson/ruby_ecdsa)
124
+ gem. Not only is it a dependency of this gem, I also used it to gain a
125
+ much better understanding of elliptical curve crypto, and used it as inspiration
126
+ for this gem.
127
+
128
+ ## Supported platforms
129
+
130
+ Ruby 2.0.0 and above.
131
+
132
+ ## Documentation
133
+
134
+ For complete documentation, see the [RingSig page on RubyDoc.info](http://rubydoc.info/gems/ring_sig).
data/lib/ring_sig.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  require 'openssl'
2
2
  require 'ecdsa'
3
3
  require 'ring_sig/hasher'
4
- require 'ring_sig/key'
4
+ require 'ring_sig/private_key'
5
+ require 'ring_sig/public_key'
5
6
  require 'ring_sig/signature'
6
7
  require 'ring_sig/version'
7
8
  require 'ring_sig/ecdsa/point'
@@ -0,0 +1,165 @@
1
+ module RingSig
2
+ # Instances of this class represent a private ECDSA key.
3
+ class PrivateKey
4
+
5
+ # The integer value of this private key. A number between 0 and the
6
+ # group's order (non-inclusive).
7
+ #
8
+ # @return [Integer]
9
+ attr_reader :value
10
+
11
+ # @return [PublicKey]
12
+ attr_reader :public_key
13
+
14
+ # @return [ECDSA::Group]
15
+ attr_reader :group
16
+
17
+ # @return [#digest]
18
+ attr_reader :hash_algorithm
19
+
20
+ # Creates a new instance of {PrivateKey}.
21
+ #
22
+ # @param value [Integer]
23
+ # @param group [ECDSA::Group]
24
+ # @param hash_algorithm [#digest]
25
+ def initialize(value, group: ECDSA::Group::Secp256k1, hash_algorithm: OpenSSL::Digest::SHA256)
26
+ raise ArgumentError, "Value is not an integer" unless value.is_a?(Integer)
27
+ raise ArgumentError, "Value is too small" if value < 1
28
+ raise ArgumentError, "Value is too large" if value >= group.order
29
+
30
+ @value = value
31
+ @public_key = PublicKey.new(group.generator.multiply_by_scalar(value), group: group)
32
+
33
+ @group = group
34
+ @hash_algorithm = hash_algorithm
35
+ @hasher = Hasher.new(group, hash_algorithm)
36
+ end
37
+
38
+ # Creates a new instance of {PrivateKey} from a hex string.
39
+ #
40
+ # @param octet_string [String]
41
+ # @param group [ECDSA::Group]
42
+ # @param hash_algorithm [#digest]
43
+ # @return [PrivateKey]
44
+ def self.from_hex(hex_string, group: ECDSA::Group::Secp256k1, hash_algorithm: OpenSSL::Digest::SHA256)
45
+ self.from_octet([hex_string].pack('H*'), group: group, hash_algorithm: hash_algorithm)
46
+ end
47
+
48
+ # Creates a new instance of {PrivateKey} from an octet string.
49
+ #
50
+ # @param octet_string [String]
51
+ # @param group [ECDSA::Group]
52
+ # @param hash_algorithm [#digest]
53
+ # @return [PrivateKey]
54
+ def self.from_octet(octet_string, group: ECDSA::Group::Secp256k1, hash_algorithm: OpenSSL::Digest::SHA256)
55
+ value = ECDSA::Format::FieldElementOctetString.decode(octet_string, group.field)
56
+ PrivateKey.new(value, group: group, hash_algorithm: hash_algorithm)
57
+ end
58
+
59
+ # Encodes this private key into an octet string. The encoded data contains
60
+ # only the value. It does not contain the group or hash_algorithm.
61
+ #
62
+ # @return [String]
63
+ def to_hex
64
+ to_octet.unpack('H*').first
65
+ end
66
+
67
+ # Encodes this public key into a hex string. The encoded data contains
68
+ # only the value. It does not contain the group or hash_algorithm.
69
+ #
70
+ # @return [String]
71
+ def to_octet
72
+ ECDSA::Format::FieldElementOctetString.encode(value, group.field)
73
+ end
74
+
75
+ # Signs a message with this key's private key and a set of foreign public
76
+ # keys. The resulting signature can be verified against the ordered set of
77
+ # all public keys used for creating this signature. The signature will also
78
+ # contain a key_image which will be the same for all messages signed with
79
+ # this key.
80
+ #
81
+ # @param message [String] The message to sign.
82
+ # @param foreign_keys [Array<PublicKey>] The foreign keys for the signature.
83
+ # @return [Array(Signature, Array<PublicKey>)] A pair containing the signature
84
+ # and the set of public keys (in the correct order) for verifying.
85
+ def sign(message, foreign_keys)
86
+ raise ArgumentError "Foreign keys must all have the same group" unless foreign_keys.all?{ |e| e.group == group }
87
+
88
+ message_digest = @hasher.hash_string(message)
89
+ seed = @hasher.hash_array([value, message_digest])
90
+
91
+ all_keys = @hasher.shuffle([self] + foreign_keys, seed)
92
+
93
+ q_array, w_array = generate_q_w(all_keys, seed)
94
+ ll_array, rr_array = generate_ll_rr(all_keys, q_array, w_array)
95
+ challenge = @hasher.hash_array([message_digest] + ll_array + rr_array)
96
+ c_array, r_array = generate_c_r(all_keys, q_array, w_array, challenge)
97
+
98
+ public_keys = all_keys.map(&:public_key)
99
+ signature = Signature.new(key_image, c_array, r_array, group: group, hash_algorithm: @hasher.algorithm)
100
+
101
+ [signature, public_keys]
102
+ end
103
+
104
+ # @return [ECDSA::Point] the key image.
105
+ def key_image
106
+ @key_image ||= @hasher.hash_point(point) * value
107
+ end
108
+
109
+ # @return [ECDSA::Point] the public key's point.
110
+ def point
111
+ public_key.point
112
+ end
113
+
114
+ # @return [Boolean] true if the private keys are equal.
115
+ def ==(other)
116
+ return false unless other.is_a?(PrivateKey)
117
+ value == other.value && group == other.group && hash_algorithm == other.hash_algorithm
118
+ end
119
+
120
+ private
121
+
122
+ def generate_q_w(all_keys, seed)
123
+ q_array, w_array = [], []
124
+
125
+ all_keys.each_with_index do |k, i|
126
+ q_array[i] = @hasher.hash_array(['q', seed, i])
127
+ w_array[i] = 0
128
+ w_array[i] = @hasher.hash_array(['w', seed, i]) if k.is_a?(PublicKey)
129
+ end
130
+
131
+ [q_array, w_array]
132
+ end
133
+
134
+ def generate_ll_rr(all_keys, q_array, w_array)
135
+ ll_array, rr_array = [], []
136
+
137
+ all_keys.each_with_index do |k, i|
138
+ ll_array[i] = group.generator * q_array[i]
139
+ rr_array[i] = @hasher.hash_point(k.point) * q_array[i]
140
+ if k.is_a?(PublicKey)
141
+ ll_array[i] += k.point * w_array[i]
142
+ rr_array[i] += key_image * w_array[i]
143
+ end
144
+ end
145
+
146
+ [ll_array, rr_array]
147
+ end
148
+
149
+ def generate_c_r(all_keys, q_array, w_array, challenge)
150
+ c_array, r_array = [], []
151
+
152
+ all_keys.each_with_index do |k, i|
153
+ if k.is_a?(PublicKey)
154
+ c_array[i] = w_array[i]
155
+ r_array[i] = q_array[i]
156
+ else
157
+ c_array[i] = (challenge - w_array.inject{|a, b| a + b}) % group.order
158
+ r_array[i] = (q_array[i] - c_array[i] * k.value) % group.order
159
+ end
160
+ end
161
+
162
+ [c_array, r_array]
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,73 @@
1
+ module RingSig
2
+ # Instances of this class represent a public ECDSA key.
3
+ class PublicKey
4
+
5
+ # The elliptical curve point of this public key.
6
+ #
7
+ # @return [ECDSA::Point]
8
+ attr_reader :point
9
+
10
+ # @return [ECDSA::Group]
11
+ attr_reader :group
12
+
13
+ # Creates a new instance of {PublicKey}.
14
+ #
15
+ # @param point [ECDSA::Point]
16
+ # @param group [ECDSA::Group]
17
+ def initialize(point, group: ECDSA::Group::Secp256k1)
18
+ raise ArgumentError, "Point is not an ECDSA::Point" unless point.is_a?(ECDSA::Point)
19
+ raise ArgumentError, "Point is not on the group's curve" unless group.include?(point)
20
+
21
+ @point = point
22
+ @group = group
23
+ end
24
+
25
+ # Creates a new instance of {PublicKey} from a hex string.
26
+ #
27
+ # @param hex_string [String]
28
+ # @param group [ECDSA::Group]
29
+ # @return [PublicKey]
30
+ def self.from_hex(hex_string, group: ECDSA::Group::Secp256k1)
31
+ self.from_octet([hex_string].pack('H*'), group: group)
32
+ end
33
+
34
+ # Creates a new instance of {PublicKey} from an octet string.
35
+ #
36
+ # @param octet_string [String]
37
+ # @param group [ECDSA::Group]
38
+ # @return [PublicKey]
39
+ def self.from_octet(octet_string, group: ECDSA::Group::Secp256k1)
40
+ point = ECDSA::Format::PointOctetString.decode(octet_string, group)
41
+ PublicKey.new(point, group: group)
42
+ end
43
+
44
+ # Encodes this public key into an octet string. The encoded data contains
45
+ # only the point. It does not contain the group.
46
+ #
47
+ # @param compression [Boolean]
48
+ # @return [String]
49
+ def to_hex(compression: true)
50
+ to_octet(compression: compression).unpack('H*').first
51
+ end
52
+
53
+ # Encodes this public key into a hex string. The encoded data contains
54
+ # only the point. It does not contain the group.
55
+ #
56
+ # @param compression [Boolean]
57
+ # @return [String]
58
+ def to_octet(compression: true)
59
+ ECDSA::Format::PointOctetString.encode(point, compression: compression)
60
+ end
61
+
62
+ # @return [PublicKey] self.
63
+ def public_key
64
+ self
65
+ end
66
+
67
+ # @return [Boolean] true if the public keys are equal.
68
+ def ==(other)
69
+ return false unless other.is_a?(PublicKey)
70
+ point == other.point && group == other.group
71
+ end
72
+ end
73
+ end
@@ -31,7 +31,7 @@ module RingSig
31
31
 
32
32
  @group = group
33
33
  @hash_algorithm = hash_algorithm
34
- @hasher = RingSig::Hasher.new(group, hash_algorithm)
34
+ @hasher = Hasher.new(group, hash_algorithm)
35
35
  end
36
36
 
37
37
  # Creates a new instance of {Signature} from a der string.
@@ -47,7 +47,7 @@ module RingSig
47
47
  c_array = asn1.value[1].value.map{|i| i.value.to_i}
48
48
  r_array = asn1.value[2].value.map{|i| i.value.to_i}
49
49
 
50
- RingSig::Signature.new(key_image, c_array, r_array, group: group, hash_algorithm: hash_algorithm)
50
+ Signature.new(key_image, c_array, r_array, group: group, hash_algorithm: hash_algorithm)
51
51
  end
52
52
 
53
53
  # Creates a new instance of {Signature} from a hex string.
@@ -57,13 +57,14 @@ module RingSig
57
57
  # @param hash_algorithm [#digest]
58
58
  # @return [Signature]
59
59
  def self.from_hex(hex_string, group: ECDSA::Group::Secp256k1, hash_algorithm: OpenSSL::Digest::SHA256)
60
- RingSig::Signature.from_der([hex_string].pack('H*'), group: group, hash_algorithm: hash_algorithm)
60
+ Signature.from_der([hex_string].pack('H*'), group: group, hash_algorithm: hash_algorithm)
61
61
  end
62
62
 
63
63
  # Encodes this signature into a der string. The encoded data contains
64
64
  # the key_image, c_array, and r_array. It does not contain the group
65
65
  # or hash_algorithm.
66
66
  #
67
+ # @param compression [Boolean]
67
68
  # @return [String]
68
69
  def to_der(compression: true)
69
70
  OpenSSL::ASN1::Sequence.new([
@@ -77,6 +78,7 @@ module RingSig
77
78
  # the key_image, c_array, and r_array. It does not contain the group
78
79
  # or hash_algorithm.
79
80
  #
81
+ # @param compression [Boolean]
80
82
  # @return [String]
81
83
  def to_hex(compression: true)
82
84
  to_der(compression: compression).unpack('H*').first
@@ -85,15 +87,15 @@ module RingSig
85
87
  # Verifies this signature against an ordered set of public keys.
86
88
  #
87
89
  # @param message [String]
88
- # @param public_keys [Array<Key>]
90
+ # @param public_keys [Array<PublicKey>]
89
91
  # @return [Boolean] true if the signature verifies, false otherwise.
90
92
  def verify(message, public_keys)
91
93
  ll_array = []
92
94
  rr_array = []
93
95
 
94
96
  public_keys.each_with_index do |k, i|
95
- ll_array[i] = (group.generator * r_array[i]) + (k.public_key * c_array[i])
96
- rr_array[i] = (@hasher.hash_point(k.public_key) * (r_array[i]) + key_image * c_array[i])
97
+ ll_array[i] = (group.generator * r_array[i]) + (k.point * c_array[i])
98
+ rr_array[i] = (@hasher.hash_point(k.point) * (r_array[i]) + key_image * c_array[i])
97
99
  end
98
100
 
99
101
  c_sum = c_array.inject{|a, b| a + b} % group.order
@@ -1,3 +1,3 @@
1
1
  module RingSig
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.0'
3
3
  end
data/ring_sig.gemspec CHANGED
@@ -15,12 +15,12 @@ Gem::Specification.new do |s|
15
15
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
16
16
 
17
17
  s.add_development_dependency 'bundler', '~> 1.3'
18
- s.add_development_dependency 'rake', '~> 0'
18
+ s.add_development_dependency 'rake', '~> 10'
19
19
  s.add_development_dependency 'rspec', '~> 3.0'
20
20
  s.add_development_dependency 'simplecov', '~> 0'
21
21
  s.add_development_dependency 'yard', '~> 0'
22
- s.add_development_dependency 'markdown', '~> 0'
23
- s.add_development_dependency 'redcarpet', '~> 0'
22
+ s.add_development_dependency 'markdown', '~> 1'
23
+ s.add_development_dependency 'redcarpet', '~> 3'
24
24
 
25
25
  s.add_runtime_dependency 'ecdsa', '~> 1.1'
26
26
  end
@@ -0,0 +1,107 @@
1
+ require 'spec_helper'
2
+
3
+ describe RingSig::PrivateKey do
4
+ key = RingSig::PrivateKey.new(1)
5
+ key_hex = '0000000000000000000000000000000000000000000000000000000000000001'
6
+ group = ECDSA::Group::Secp256k1
7
+ message = 'a'
8
+ # The public keys from the coinbase transactions in the first three bitcoin blocks:
9
+ foreign_keys = %w{
10
+ 04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f
11
+ 0496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858ee
12
+ 047211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073dee6c89064984f03385237d92167c13e236446b417ab79a0fcae412ae3316b77
13
+ }.map {|s| RingSig::PublicKey.from_hex(s) }
14
+
15
+ it 'raises ArgumentError if value is too small' do
16
+ expect { RingSig::PrivateKey.new(0) }.to raise_error(ArgumentError)
17
+ end
18
+
19
+ it 'raises ArgumentError if value is too large' do
20
+ expect { RingSig::PrivateKey.new(group.order) }.to raise_error(ArgumentError)
21
+ end
22
+
23
+ describe '#key_image' do
24
+ it 'computes correctly' do
25
+ expect(key.key_image.x).to eq(19808304348355547845585283516832906889081321816618757912787193259813413622341)
26
+ expect(key.key_image.y).to eq(6456680440731674563715553325029463353567815591885844101408227481418612066782)
27
+ end
28
+ end
29
+
30
+ describe '#public_key' do
31
+ it 'computes correctly' do
32
+ expect(key.public_key.to_hex).to eq("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")
33
+ end
34
+ end
35
+
36
+ describe '#point' do
37
+ it 'equals the public key point' do
38
+ expect(key.point).to eq(key.public_key.point)
39
+ end
40
+ end
41
+
42
+ describe '#to_hex' do
43
+ it 'converts to hex' do
44
+ expect(key.to_hex).to eq key_hex
45
+ end
46
+ end
47
+
48
+ describe '#from_hex' do
49
+ it 'converts from hex' do
50
+ expect(RingSig::PrivateKey.from_hex(key_hex)).to eq key
51
+ end
52
+ end
53
+
54
+ describe '#to_octet' do
55
+ it 'converts to octet' do
56
+ expect(key.to_octet).to eq [key_hex].pack('H*')
57
+ end
58
+ end
59
+
60
+ describe '#from_octet' do
61
+ it 'converts from octet' do
62
+ expect(RingSig::PrivateKey.from_octet([key_hex].pack('H*'))).to eq key
63
+ end
64
+ end
65
+
66
+ describe '==' do
67
+ it 'returns true when keys are the same' do
68
+ expect(key).to eq key
69
+ expect(RingSig::PrivateKey.new(key.value) == key).to eq true
70
+ end
71
+
72
+ it 'returns false when keys are different' do
73
+ expect(RingSig::PrivateKey.new(2) == key).to eq false
74
+ end
75
+ end
76
+
77
+ describe '#sign' do
78
+ sig, public_keys = key.sign(message, foreign_keys)
79
+
80
+ it 'signs and verifies' do
81
+ expect(sig.to_hex).to eq '3082013d0421022bcb1a5b3c70421bfac818f6bd13289a5c9a3cfb42d3b81f023a0276974c924530818a02200b084320e064c99a4c25122fbbae407f9a5b2b4f063d2276500e051641a04f79022100b6c3d4ec0f42acf78ffd5697da7145cf1f274410ccbdb02bae3da79c25dd324c02210086861195f0eecb9948bf421ca5ce4c0f4e5838e7fe0735d2afd40bc5c10c849102200c84c1450e3a0b092f4449204b531a02d1f9f7eafef6d34bbe599d944a85eba930818a022100cb8f91859d1cd0308ecd3a278d0334da2e97a7dc54d36bbbb266cd2187c78bc4022100a81cff48a1e5ca431c30e3e658d22447cf808cd414cccdb84d43bb72a91c3add02201c9ef437f57532ab0e916536709aacde09f2243297e1d6e3992385cc603d41540220690ddd89d38482bb42e59ee6f2279c8f35a8211300e13939aed00e91bb3d9661'
82
+
83
+ expected_public_keys = [2, 1, 0, 3].map{|i| ([key] + foreign_keys)[i].public_key}
84
+ expect(public_keys).to eq expected_public_keys
85
+
86
+ expect(sig.verify(message, public_keys)).to be true
87
+ expect(sig.verify(message + '0', public_keys)).to be false
88
+ expect(sig.verify(message, public_keys.reverse)).to be false
89
+ end
90
+
91
+ it 'has the same key_image for different foreign keys' do
92
+ other_sig, other_public_keys = key.sign(message, foreign_keys[0..1])
93
+
94
+ expect(sig.to_hex).not_to eq(other_sig.to_hex)
95
+ expect(sig.key_image).to eq(other_sig.key_image)
96
+ end
97
+
98
+ it 'signs and verifies with no foreign keys' do
99
+ sig, public_keys = key.sign(message, [])
100
+
101
+ expect(sig.to_hex).to eq '306c0421022bcb1a5b3c70421bfac818f6bd13289a5c9a3cfb42d3b81f023a0276974c9245302202202a2c0676992db106d54b0d2834c53b95b55d524c7449b55202f3ccb465ce73873023022100a1638b0f03ef1f29b9822cff583df944793a558fe089b669af73006d21f9183d'
102
+ expect(public_keys).to eq [key.public_key]
103
+
104
+ expect(sig.verify(message, public_keys)).to be true
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ describe RingSig::PublicKey do
4
+ compressed_hex = '03678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb6'
5
+ uncompressed_hex = '04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f'
6
+ group = ECDSA::Group::Secp256k1
7
+ key = RingSig::PublicKey.new(group.new_point([
8
+ 46833799212576611471711417854818141128240043280360231002189938627535641370294,
9
+ 33454781559405909841731692443380420218121109572881027288991311028992835919199
10
+ ]))
11
+
12
+ describe '#to_hex' do
13
+ it 'converts to compressed hex' do
14
+ expect(key.to_hex(compression: true)).to eq compressed_hex
15
+ end
16
+
17
+ it 'converts to uncompressed hex' do
18
+ expect(key.to_hex(compression: false)).to eq uncompressed_hex
19
+ end
20
+ end
21
+
22
+ describe '#from_hex' do
23
+ it 'converts from compressed hex' do
24
+ expect(RingSig::PublicKey.from_hex(compressed_hex)).to eq key
25
+ end
26
+
27
+ it 'converts from uncompressed hex' do
28
+ expect(RingSig::PublicKey.from_hex(uncompressed_hex)).to eq key
29
+ end
30
+ end
31
+
32
+ describe '#to_octet' do
33
+ it 'converts to compressed octet' do
34
+ expect(key.to_octet(compression: true)).to eq [compressed_hex].pack('H*')
35
+ end
36
+
37
+ it 'converts to uncompressed octet' do
38
+ expect(key.to_octet(compression: false)).to eq [uncompressed_hex].pack('H*')
39
+ end
40
+ end
41
+
42
+ describe '#from_octet' do
43
+ it 'converts from compressed octet' do
44
+ expect(RingSig::PublicKey.from_octet([compressed_hex].pack('H*'))).to eq key
45
+ end
46
+
47
+ it 'converts from uncompressed octet' do
48
+ expect(RingSig::PublicKey.from_octet([uncompressed_hex].pack('H*'))).to eq key
49
+ end
50
+ end
51
+
52
+ describe '==' do
53
+ it 'returns true when keys are the same' do
54
+ expect(key).to eq key
55
+ expect(RingSig::PublicKey.new(key.point) == key).to eq true
56
+ end
57
+
58
+ it 'returns false when keys are different' do
59
+ expect(RingSig::PublicKey.new(group.generator) == key).to eq false
60
+ end
61
+ end
62
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ring_sig
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen McCarthy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-09 00:00:00.000000000 Z
11
+ date: 2014-09-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '10'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '10'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -86,28 +86,28 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '0'
89
+ version: '1'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '0'
96
+ version: '1'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: redcarpet
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '0'
103
+ version: '3'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: '0'
110
+ version: '3'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: ecdsa
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -130,6 +130,8 @@ extensions: []
130
130
  extra_rdoc_files: []
131
131
  files:
132
132
  - ".gitignore"
133
+ - ".travis.yml"
134
+ - ".yardopts"
133
135
  - Gemfile
134
136
  - LICENSE
135
137
  - README.md
@@ -137,12 +139,14 @@ files:
137
139
  - lib/ring_sig.rb
138
140
  - lib/ring_sig/ecdsa/point.rb
139
141
  - lib/ring_sig/hasher.rb
140
- - lib/ring_sig/key.rb
142
+ - lib/ring_sig/private_key.rb
143
+ - lib/ring_sig/public_key.rb
141
144
  - lib/ring_sig/signature.rb
142
145
  - lib/ring_sig/version.rb
143
146
  - ring_sig.gemspec
144
147
  - spec/hasher_spec.rb
145
- - spec/key_spec.rb
148
+ - spec/private_key_spec.rb
149
+ - spec/public_key_spec.rb
146
150
  - spec/signature_spec.rb
147
151
  - spec/spec_helper.rb
148
152
  homepage: https://github.com/jamoes/ring_sig
@@ -172,7 +176,8 @@ summary: This gem implements ring signatures, built on top of ECDSA, as specifie
172
176
  by CryptoNote
173
177
  test_files:
174
178
  - spec/hasher_spec.rb
175
- - spec/key_spec.rb
179
+ - spec/private_key_spec.rb
180
+ - spec/public_key_spec.rb
176
181
  - spec/signature_spec.rb
177
182
  - spec/spec_helper.rb
178
183
  has_rdoc:
data/lib/ring_sig/key.rb DELETED
@@ -1,142 +0,0 @@
1
- module RingSig
2
- # Instances of this class represent an ECDSA key.
3
- class Key
4
-
5
- # @return [Integer]
6
- attr_reader :private_key
7
-
8
- # @return [ECDSA::Point]
9
- attr_reader :public_key
10
-
11
- # @return [ECDSA::Group]
12
- attr_reader :group
13
-
14
- # @return [#digest]
15
- attr_reader :hash_algorithm
16
-
17
- # Creates a new instance of {Key}. Must provide either a private_key or
18
- # a public_key, but not both.
19
- #
20
- # @param private_key [Integer]
21
- # @param public_key [ECDSA::Point]
22
- # @param group [ECDSA::Group]
23
- # @param hash_algorithm [#digest]
24
- def initialize(
25
- private_key: nil,
26
- public_key: nil,
27
- group: ECDSA::Group::Secp256k1,
28
- hash_algorithm: OpenSSL::Digest::SHA256)
29
-
30
- if private_key && public_key
31
- raise ArgumentError, "Must not provide both private_key and public_key"
32
- elsif private_key
33
- raise ArgumentError, "Private key is not an integer" unless private_key.is_a?(Integer)
34
- raise ArgumentError, "Private key is too small" if private_key < 1
35
- raise ArgumentError, "Private key is too large" if private_key >= group.order
36
- @private_key = private_key
37
- @public_key = group.generator.multiply_by_scalar(private_key)
38
- elsif public_key
39
- raise ArgumentError, "Public key is not an ECDSA::Point" unless public_key.is_a?(ECDSA::Point)
40
- raise ArgumentError, "Public key is not on the group's curve" unless group.include?(public_key)
41
- @public_key = public_key
42
- else
43
- raise ArgumentError, "Must provide either private_key or public_key"
44
- end
45
-
46
- @group = group
47
- @hash_algorithm = hash_algorithm
48
- @hasher = RingSig::Hasher.new(group, hash_algorithm)
49
- end
50
-
51
- # Signs a message with this key's private_key and a set of public foreign
52
- # keys. The resulting signature can be verified against the ordered set of
53
- # all public keys used for creating this signature. The signature will also
54
- # contain a key_image which will be the same for all messages signed with
55
- # this key.
56
- #
57
- # @param message [String] The message to sign.
58
- # @param foreign_keys [Array<Key>] The foreign keys for the signature.
59
- # @return [Array(Signature, Array<Key>)] A pair containing the signature
60
- # and the set of public keys (in the correct order) for verifying.
61
- def sign(message, foreign_keys)
62
- raise ArgumentError "Cannot sign without a private key" unless private_key
63
- raise ArgumentError "Foreign keys must all have to the same group" unless foreign_keys.all?{|e| e.group == group}
64
- raise ArgumentError "Foreign keys must all have to the same hash_algorithm" unless foreign_keys.all?{|e| e.hash_algorithm == hash_algorithm}
65
-
66
- message_digest = @hasher.hash_string(message)
67
- seed = @hasher.hash_array([private_key, message_digest])
68
-
69
- foreign_keys = foreign_keys.map(&:drop_private_key)
70
- all_keys = @hasher.shuffle([self] + foreign_keys, seed)
71
-
72
- q_array, w_array = generate_q_w(all_keys, seed)
73
- ll_array, rr_array = generate_ll_rr(all_keys, q_array, w_array)
74
- challenge = @hasher.hash_array([message_digest] + ll_array + rr_array)
75
- c_array, r_array = generate_c_r(all_keys, q_array, w_array, challenge)
76
-
77
- public_keys = all_keys.map(&:drop_private_key)
78
-
79
- [RingSig::Signature.new(key_image, c_array, r_array, group: group, hash_algorithm: @hasher.algorithm), public_keys]
80
- end
81
-
82
- # @return [ECDSA::Point] the key image.
83
- def key_image
84
- raise ArgumentError "Cannot compute key image without the private key" unless private_key
85
- @key_image ||= @hasher.hash_point(@public_key) * @private_key
86
- end
87
-
88
- # Returns self if this key has no private key. Otherwise, returns a new key
89
- # with only the public_key component.
90
- #
91
- # @return [Key]
92
- def drop_private_key
93
- return self unless private_key
94
- Key.new(public_key: public_key, group: group, hash_algorithm: hash_algorithm)
95
- end
96
-
97
- private
98
-
99
- def generate_q_w(all_keys, seed)
100
- q_array, w_array = [], []
101
-
102
- all_keys.each_with_index do |k, i|
103
- q_array[i] = @hasher.hash_array(['q', seed, i])
104
- w_array[i] = @hasher.hash_array(['w', seed, i])
105
- w_array[i] = 0 if k.private_key
106
- end
107
-
108
- [q_array, w_array]
109
- end
110
-
111
- def generate_ll_rr(all_keys, q_array, w_array)
112
- ll_array, rr_array = [], []
113
-
114
- all_keys.each_with_index do |k, i|
115
- ll_array[i] = group.generator * q_array[i]
116
- rr_array[i] = @hasher.hash_point(k.public_key) * q_array[i]
117
- if k.private_key.nil?
118
- ll_array[i] += k.public_key * w_array[i]
119
- rr_array[i] += key_image * w_array[i]
120
- end
121
- end
122
-
123
- [ll_array, rr_array]
124
- end
125
-
126
- def generate_c_r(all_keys, q_array, w_array, challenge)
127
- c_array, r_array = [], []
128
-
129
- all_keys.each_with_index do |k, i|
130
- if k.private_key.nil?
131
- c_array[i] = w_array[i]
132
- r_array[i] = q_array[i]
133
- else
134
- c_array[i] = (challenge - w_array.inject{|a, b| a + b}) % group.order
135
- r_array[i] = (q_array[i] - c_array[i] * k.private_key) % group.order
136
- end
137
- end
138
-
139
- [c_array, r_array]
140
- end
141
- end
142
- end
data/spec/key_spec.rb DELETED
@@ -1,86 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe RingSig::Key do
4
- key = RingSig::Key.new(private_key: 1)
5
- group = ECDSA::Group::Secp256k1
6
- message = 'a'
7
-
8
- it 'computes a key image' do
9
- expect(key.key_image.x).to eq(19808304348355547845585283516832906889081321816618757912787193259813413622341)
10
- expect(key.key_image.y).to eq(6456680440731674563715553325029463353567815591885844101408227481418612066782)
11
- end
12
-
13
- it 'raises ArgumentError when providing both public and private keys' do
14
- expect { RingSig::Key.new(private_key: 1, public_key: group.generator) }.to raise_error(ArgumentError)
15
- end
16
-
17
- it 'raises ArgumentError if neither public or private key are provided' do
18
- expect { RingSig::Key.new }.to raise_error(ArgumentError)
19
- end
20
-
21
- it 'raises ArgumentError if private key is too small' do
22
- expect { RingSig::Key.new(private_key: 0) }.to raise_error(ArgumentError)
23
- end
24
-
25
- describe '#key_image' do
26
- it 'computes correctly' do
27
- expect(key.key_image.x).to eq(19808304348355547845585283516832906889081321816618757912787193259813413622341)
28
- expect(key.key_image.y).to eq(6456680440731674563715553325029463353567815591885844101408227481418612066782)
29
- end
30
- end
31
-
32
- describe '#drop_private_key' do
33
- it 'creates a new Key without the private_key component' do
34
- key2 = key.drop_private_key
35
- expect(key).not_to eq(key2)
36
- expect(key2.private_key).to be(nil)
37
- expect(key2.public_key).not_to be(nil)
38
- end
39
- end
40
-
41
- context 'Three foreign keys' do
42
- # The public keys from the coinbase transactions in the first three bitcoin blocks.
43
- foreign_keys = %w{
44
- 04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f
45
- 0496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858ee
46
- 047211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073dee6c89064984f03385237d92167c13e236446b417ab79a0fcae412ae3316b77
47
- }.map do |s|
48
- RingSig::Key.new(public_key: ECDSA::Format::PointOctetString.decode([s].pack('H*'), group))
49
- end
50
-
51
- describe '#sign' do
52
- sig, public_keys = key.sign(message, foreign_keys)
53
-
54
- it 'signs and verifies' do
55
- expect(sig.to_hex).to eq '3082013d0421022bcb1a5b3c70421bfac818f6bd13289a5c9a3cfb42d3b81f023a0276974c924530818a02200b084320e064c99a4c25122fbbae407f9a5b2b4f063d2276500e051641a04f79022100b6c3d4ec0f42acf78ffd5697da7145cf1f274410ccbdb02bae3da79c25dd324c02210086861195f0eecb9948bf421ca5ce4c0f4e5838e7fe0735d2afd40bc5c10c849102200c84c1450e3a0b092f4449204b531a02d1f9f7eafef6d34bbe599d944a85eba930818a022100cb8f91859d1cd0308ecd3a278d0334da2e97a7dc54d36bbbb266cd2187c78bc4022100a81cff48a1e5ca431c30e3e658d22447cf808cd414cccdb84d43bb72a91c3add02201c9ef437f57532ab0e916536709aacde09f2243297e1d6e3992385cc603d41540220690ddd89d38482bb42e59ee6f2279c8f35a8211300e13939aed00e91bb3d9661'
56
-
57
- expected_public_keys = [2, 1, 0, 3].map{|i| ([key] + foreign_keys)[i].public_key}
58
- expect(public_keys.map(&:public_key)).to eq expected_public_keys
59
-
60
- expect(sig.verify(message, public_keys)).to be true
61
- expect(sig.verify(message + '0', public_keys)).to be false
62
- expect(sig.verify(message, public_keys.reverse)).to be false
63
- end
64
-
65
- it 'has the same key_image for different foreign keys' do
66
- other_sig, other_public_keys = key.sign(message, foreign_keys[0..1])
67
-
68
- expect(sig.to_hex).not_to eq(other_sig.to_hex)
69
- expect(sig.key_image).to eq(other_sig.key_image)
70
- end
71
- end
72
- end
73
-
74
- context 'Zero foreign keys' do
75
- foreign_keys = []
76
-
77
- it 'signs and verifies' do
78
- sig, public_keys = key.sign(message, foreign_keys)
79
-
80
- expect(sig.to_hex).to eq '306c0421022bcb1a5b3c70421bfac818f6bd13289a5c9a3cfb42d3b81f023a0276974c9245302202202a2c0676992db106d54b0d2834c53b95b55d524c7449b55202f3ccb465ce73873023022100a1638b0f03ef1f29b9822cff583df944793a558fe089b669af73006d21f9183d'
81
- expect(public_keys.map(&:public_key)).to eq [key.public_key]
82
-
83
- expect(sig.verify(message, public_keys)).to be true
84
- end
85
- end
86
- end