ring_sig 0.0.1 → 0.1.0

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