ring_sig 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 81ca882165cbb38793b1973cc43b8861eda76b88
4
+ data.tar.gz: 9be01854e734261e115c2c8d8edbfb2cc3cff8a9
5
+ SHA512:
6
+ metadata.gz: e73648fab59657fe7a30dbef07b1b8457b0e511b136bf7a3750a08791843a0aa2ad4e45a280da5b3d05b7455b7c0a69440dc20cbdd0f97b9c05b413362e5896b
7
+ data.tar.gz: 8a23930dbc658f1a91c4dde5ed32443366f205c116961a874f3365f788eb1297086626393126e7d77305c08e9e8f0dcb79bfeb56b1a55b3890e678524d39748c
@@ -0,0 +1,7 @@
1
+ .ruby-version
2
+ .ruby-gemset
3
+ Gemfile.lock
4
+ *.gem
5
+ coverage/
6
+ doc/
7
+ .yardoc/
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Stephen McCarthy
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
File without changes
@@ -0,0 +1,20 @@
1
+ desc "Run the specs and generate a code coverage report."
2
+
3
+ task 'default' => 'spec'
4
+
5
+ desc 'Run specs'
6
+ task 'spec' do
7
+ sh 'rspec'
8
+ end
9
+
10
+ desc 'Run specs and generate coverage report'
11
+ task 'coverage' do
12
+ ENV['COVERAGE'] = 'Y'
13
+ Rake::Task['spec'].invoke
14
+ end
15
+
16
+ desc 'Print out lines of code and related statistics.'
17
+ task 'stats' do
18
+ puts 'Lines of code and comments (including blank lines):'
19
+ sh "find lib -type f | xargs wc -l"
20
+ end
@@ -0,0 +1,11 @@
1
+ require 'openssl'
2
+ require 'ecdsa'
3
+ require 'ring_sig/hasher'
4
+ require 'ring_sig/key'
5
+ require 'ring_sig/signature'
6
+ require 'ring_sig/version'
7
+ require 'ring_sig/ecdsa/point'
8
+
9
+ # The top-level module for the RingSig gem.
10
+ module RingSig
11
+ end
@@ -0,0 +1,6 @@
1
+ module ECDSA
2
+ class Point
3
+ alias_method :*, :multiply_by_scalar
4
+ alias_method :+, :add_to_point
5
+ end
6
+ end
@@ -0,0 +1,94 @@
1
+ module RingSig
2
+ # A customized hasher specifically for Ring Signatures.
3
+ class Hasher
4
+ # @return [ECDSA::Group]
5
+ attr_reader :group
6
+
7
+ # @return [#digest]
8
+ attr_reader :algorithm
9
+
10
+
11
+ # Creates a new instance of {Hasher}.
12
+ #
13
+ # @param group [ECDSA::Group]
14
+ # @param algorithm [#digest]
15
+ def initialize(group, algorithm)
16
+ @group = group
17
+ @algorithm = algorithm
18
+ end
19
+
20
+ # Continuously hashes until a value less than the group's order is found.
21
+ #
22
+ # @param s (String) The value to be hashed.
23
+ # @return (Integer) A number between 0 and the group's order.
24
+ def hash_string(s)
25
+ n = nil
26
+ loop do
27
+ s = @algorithm.digest(s)
28
+ n = s.unpack('H*').first.to_i(16)
29
+ break if n < @group.order
30
+ end
31
+ n
32
+ end
33
+
34
+ # Hashes an array. Converts the Array to an OpenSSL::ASN1::Sequence der
35
+ # string, and then hashes that string.
36
+ #
37
+ # @param array [Array<String,Integer,ECDSA::Point>] The array to be hashed.
38
+ # @return [Integer] A number between 0 and the group's order.
39
+ def hash_array(array)
40
+ array = array.map do |e|
41
+ case e
42
+ when String
43
+ OpenSSL::ASN1::UTF8String.new(e)
44
+ when Integer
45
+ OpenSSL::ASN1::Integer.new(e)
46
+ when ECDSA::Point
47
+ OpenSSL::ASN1::OctetString.new(ECDSA::Format::PointOctetString.encode(e, compression: true))
48
+ else
49
+ raise ArgumentError, "Unsupported type: #{p.inspect}"
50
+ end
51
+ end
52
+
53
+ hash_string(OpenSSL::ASN1::Sequence.new(array).to_der)
54
+ end
55
+
56
+ # Hashes a point to another point.
57
+ #
58
+ # @param point [ECDSA::Point] The point to be hashed.
59
+ # @return [ECDSA::Point] A new point, deterministically computed from the input point.
60
+ def hash_point(point)
61
+ @group.generator * hash_array(point.coords)
62
+ end
63
+
64
+ # Shuffles an array in a deterministic manner.
65
+ #
66
+ # @param array (Array) The array to be shuffled.
67
+ # @param seed (Integer) A random seed which determines the outcome of the suffle.
68
+ # @return (Array) The shuffled array.
69
+ def shuffle(array, seed)
70
+ seed_array = [seed, 0]
71
+ (array.size - 1).downto(1) do |i|
72
+ r = next_rand(i + 1, seed_array)
73
+ array[i], array[r] = array[r], array[i]
74
+ end
75
+ array
76
+ end
77
+
78
+ private
79
+
80
+ # Deterministically returns a random number between 0 and n.
81
+ #
82
+ # @param n (Integer) The maximum value.
83
+ # @param seed_array (Array<Integer>) A pair `[seed, suffix]`.
84
+ # The suffix will be modified.
85
+ # @return (Integer) A number between 0 and n.
86
+ def next_rand(n, seed_array)
87
+ loop do
88
+ r = hash_array(seed_array)
89
+ seed_array[1] += 1
90
+ return r % n if r < @group.order - @group.order % n
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,142 @@
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
@@ -0,0 +1,113 @@
1
+ module RingSig
2
+ # Instances of this class represent RingSig signatures.
3
+ class Signature
4
+ # @return [ECDSA::Point]
5
+ attr_reader :key_image
6
+
7
+ # @return [Array]
8
+ attr_reader :c_array
9
+
10
+ # @return [Array]
11
+ attr_reader :r_array
12
+
13
+ # @return [ECDSA::Group]
14
+ attr_reader :group
15
+
16
+ # @return [#digest]
17
+ attr_reader :hash_algorithm
18
+
19
+ # Creates a new instance of {Signature}.
20
+ #
21
+ # @param key_image [ECDSA::Point]
22
+ # @param c_array [Array<Integer>]
23
+ # @param r_array [Array<Integer>]
24
+ # @param group [ECDSA::Group]
25
+ # @param hash_algorithm [#digest]
26
+ def initialize(key_image, c_array, r_array, group: ECDSA::Group::Secp256k1, hash_algorithm: OpenSSL::Digest::SHA256)
27
+ @key_image, @c_array, @r_array = key_image, c_array, r_array
28
+ key_image.is_a?(ECDSA::Point) or raise ArgumentError, 'key_image is not an ECDSA::Point.'
29
+ c_array.is_a?(Array) or raise ArgumentError, 'c_array is not an array.'
30
+ r_array.is_a?(Array) or raise ArgumentError, 'r_array is not an array.'
31
+
32
+ @group = group
33
+ @hash_algorithm = hash_algorithm
34
+ @hasher = RingSig::Hasher.new(group, hash_algorithm)
35
+ end
36
+
37
+ # Creates a new instance of {Signature} from a der string.
38
+ #
39
+ # @param der_string [String]
40
+ # @param group [ECDSA::Group]
41
+ # @param hash_algorithm [#digest]
42
+ # @return [Signature]
43
+ def self.from_der(der_string, group: ECDSA::Group::Secp256k1, hash_algorithm: OpenSSL::Digest::SHA256)
44
+ asn1 = OpenSSL::ASN1.decode(der_string)
45
+
46
+ key_image = ECDSA::Format::PointOctetString.decode(asn1.value[0].value, group)
47
+ c_array = asn1.value[1].value.map{|i| i.value.to_i}
48
+ r_array = asn1.value[2].value.map{|i| i.value.to_i}
49
+
50
+ RingSig::Signature.new(key_image, c_array, r_array, group: group, hash_algorithm: hash_algorithm)
51
+ end
52
+
53
+ # Creates a new instance of {Signature} from a hex string.
54
+ #
55
+ # @param hex_string [String]
56
+ # @param group [ECDSA::Group]
57
+ # @param hash_algorithm [#digest]
58
+ # @return [Signature]
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)
61
+ end
62
+
63
+ # Encodes this signature into a der string. The encoded data contains
64
+ # the key_image, c_array, and r_array. It does not contain the group
65
+ # or hash_algorithm.
66
+ #
67
+ # @return [String]
68
+ def to_der(compression: true)
69
+ OpenSSL::ASN1::Sequence.new([
70
+ OpenSSL::ASN1::OctetString.new(ECDSA::Format::PointOctetString.encode(key_image, compression: compression)),
71
+ OpenSSL::ASN1::Sequence.new(c_array.map{|i| OpenSSL::ASN1::Integer.new(i)}),
72
+ OpenSSL::ASN1::Sequence.new(r_array.map{|i| OpenSSL::ASN1::Integer.new(i)}),
73
+ ]).to_der
74
+ end
75
+
76
+ # Encodes this signature into a hex string. The encoded data contains
77
+ # the key_image, c_array, and r_array. It does not contain the group
78
+ # or hash_algorithm.
79
+ #
80
+ # @return [String]
81
+ def to_hex(compression: true)
82
+ to_der(compression: compression).unpack('H*').first
83
+ end
84
+
85
+ # Verifies this signature against an ordered set of public keys.
86
+ #
87
+ # @param message [String]
88
+ # @param public_keys [Array<Key>]
89
+ # @return [Boolean] true if the signature verifies, false otherwise.
90
+ def verify(message, public_keys)
91
+ ll_array = []
92
+ rr_array = []
93
+
94
+ 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
+ end
98
+
99
+ c_sum = c_array.inject{|a, b| a + b} % group.order
100
+
101
+ message_digest = @hasher.hash_string(message)
102
+ challenge = @hasher.hash_array([message_digest] + ll_array + rr_array)
103
+
104
+ c_sum == challenge
105
+ end
106
+
107
+ # Returns an array containing key image coordinates, c_array, and r_array.
108
+ # @return (Array)
109
+ def components
110
+ key_image.coords + c_array + r_array
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,3 @@
1
+ module RingSig
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,26 @@
1
+ require File.expand_path('../lib/ring_sig/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'ring_sig'
5
+ s.version = RingSig::VERSION
6
+ s.authors = ['Stephen McCarthy']
7
+ s.email = 'sjmccarthy@gmail.com'
8
+ s.summary = 'This gem implements ring signatures, built on top of ECDSA, as specified by CryptoNote'
9
+ s.description = 'Ring Signatures allow someone to non-interactively sign a message which can be verified against a set of chosen public keys.'
10
+ s.homepage = 'https://github.com/jamoes/ring_sig'
11
+ s.license = 'MIT'
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
15
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
16
+
17
+ s.add_development_dependency 'bundler', '~> 1.3'
18
+ s.add_development_dependency 'rake', '~> 0'
19
+ s.add_development_dependency 'rspec', '~> 3.0'
20
+ s.add_development_dependency 'simplecov', '~> 0'
21
+ s.add_development_dependency 'yard', '~> 0'
22
+ s.add_development_dependency 'markdown', '~> 0'
23
+ s.add_development_dependency 'redcarpet', '~> 0'
24
+
25
+ s.add_runtime_dependency 'ecdsa', '~> 1.1'
26
+ end
@@ -0,0 +1,93 @@
1
+ require 'spec_helper'
2
+
3
+ describe RingSig::Hasher do
4
+ context 'Standard: SHA256 hash algorithm, secp256k1 group' do
5
+ group = ECDSA::Group::Secp256k1
6
+ hash_algorithm = OpenSSL::Digest::SHA256
7
+ hasher = RingSig::Hasher.new(group, hash_algorithm)
8
+
9
+ describe '#hash_string' do
10
+ it 'hashes "a"' do
11
+ expect(hasher.hash_string('a')).to eq 91634880152443617534842621287039938041581081254914058002978601050179556493499
12
+ end
13
+ end
14
+
15
+ describe '#hash_array' do
16
+ it 'hashes array of integers' do
17
+ expect(hasher.hash_array([1, 2, 3])).to eq 108327230196833505301150634709321652091196191739965401474258808571764922687322
18
+ end
19
+
20
+ it 'hashes array of strings' do
21
+ expect(hasher.hash_array(%w(a b c))).to eq 9077136522292755305325573261332124424180056729600426071187952904380324423800
22
+ end
23
+
24
+ it 'hashes array of points' do
25
+ expect(hasher.hash_array([group.generator, group.generator])).to eq 112151076631064605889327921696882492390839695314815668972759101076317607858646
26
+ end
27
+
28
+ it 'hashes mixes array' do
29
+ expect(hasher.hash_array([1, 'a', group.generator])).to eq 43575008266016611275304127474943853239256831409985077531779052441823152705495
30
+ end
31
+
32
+ it 'raises ArgumentError on invalid types' do
33
+ expect { hasher.hash_array([1.1]) }.to raise_error(ArgumentError)
34
+ expect { hasher.hash_array([[]]) }.to raise_error(ArgumentError)
35
+ expect { hasher.hash_array([{}]) }.to raise_error(ArgumentError)
36
+ end
37
+ end
38
+
39
+ describe '#hash_point' do
40
+ it 'hashes generator point' do
41
+ expected_point = group.new_point([0x2bcb1a5b3c70421bfac818f6bd13289a5c9a3cfb42d3b81f023a0276974c9245, 0xe465a0409b09a11894755e9b9d6e86938d1b5035587458ad29c00154ddfc9de])
42
+ expect(hasher.hash_point(group.generator)).to eq expected_point
43
+ end
44
+ end
45
+
46
+ describe '#shuffle' do
47
+ it 'shuffles deterministically' do
48
+ expect(hasher.shuffle([1, 2, 3, 4, 5, 6], 1)).to eq [6, 3, 4, 1, 5, 2]
49
+ end
50
+ end
51
+ end
52
+
53
+ context 'Simple hasher, simple group' do
54
+ let(:group) do
55
+ # A simple group with order equal to 200
56
+ ECDSA::Group.new(name: 'simple', p: 1, a: 1, b: 1, g: [0, 1], n: 200, h: 1)
57
+ end
58
+
59
+ let(:hash_algorithm) do
60
+ # A hash algorithm which returns a number less than 256
61
+ stub_const 'SimpleHashAlgorithm', Class.new
62
+ SimpleHashAlgorithm.class_eval do
63
+ def self.digest(s)
64
+ [(OpenSSL::Digest::SHA256.hexdigest(s).to_i(16) % 256).to_s(16)].pack('H*')
65
+ end
66
+ end
67
+ SimpleHashAlgorithm
68
+ end
69
+
70
+ let(:hasher) { RingSig::Hasher.new(group, hash_algorithm) }
71
+
72
+ # We test the hash_algorithm itself in this context, since we implemented our own simple hash_algorithm.
73
+ shared_examples_for 'hash algorithm' do |input, expected_value|
74
+ it 'Hashes input to expected_value' do
75
+ expect(hash_algorithm.digest(input)).to eq expected_value.force_encoding('binary')
76
+ end
77
+ end
78
+ it_behaves_like 'hash algorithm', 'a', "\xBB" # 187
79
+ it_behaves_like 'hash algorithm', '0', "\xE9" # 233
80
+ it_behaves_like 'hash algorithm', "\xE9", "\xD0" # 208
81
+ it_behaves_like 'hash algorithm', "\xD0", "\x15" # 21
82
+
83
+ describe '#hash_string' do
84
+ it 'hashes "a" to 187' do
85
+ expect(hasher.hash_string('a')).to eq 187
86
+ end
87
+
88
+ it 'hashes "0" to 21' do
89
+ expect(hasher.hash_string('0')).to eq 21
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,86 @@
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
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ describe RingSig::Signature do
4
+ group = ECDSA::Group::Secp256k1
5
+ signature = RingSig::Signature.new(group.generator, [10], [20])
6
+ sig_hex = '302d04210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798300302010a3003020114'
7
+ sig_der = "0-\x04!\x02y\xBEf~\xF9\xDC\xBB\xACU\xA0b\x95\xCE\x87\v\a\x02\x9B\xFC\xDB-\xCE(\xD9Y\xF2\x81[\x16\xF8\x17\x980\x03\x02\x01\n0\x03\x02\x01\x14".force_encoding('binary')
8
+
9
+ it 'has the right key_image' do
10
+ expect(signature.key_image).to eq group.generator
11
+ end
12
+
13
+ it 'has the right c_array' do
14
+ expect(signature.c_array).to contain_exactly 10
15
+ end
16
+
17
+ it 'has the right r_array' do
18
+ expect(signature.r_array).to contain_exactly 20
19
+ end
20
+
21
+ it 'has the right components' do
22
+ expect(signature.components).to eq group.generator.coords + [10, 20]
23
+ end
24
+
25
+ describe '#to_hex' do
26
+ it 'converts to hex correctly' do
27
+ expect(signature.to_hex).to eq sig_hex
28
+ end
29
+ end
30
+
31
+ describe '#to_der' do
32
+ it 'converts to der correctly' do
33
+ expect(signature.to_der).to eq sig_der
34
+ end
35
+ end
36
+
37
+ describe '#from_hex' do
38
+ it 'converts from hex correctly' do
39
+ expect(RingSig::Signature.from_hex(sig_hex).components).to eq signature.components
40
+ end
41
+ end
42
+
43
+ describe '#from_der' do
44
+ it 'converts from der correctly' do
45
+ expect(RingSig::Signature.from_der(sig_der).components).to eq signature.components
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,6 @@
1
+ if ENV['COVERAGE'] == 'Y'
2
+ require 'simplecov'
3
+ SimpleCov.start
4
+ end
5
+
6
+ require 'ring_sig'
metadata ADDED
@@ -0,0 +1,178 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ring_sig
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Stephen McCarthy
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: yard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: markdown
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: redcarpet
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: ecdsa
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1.1'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.1'
125
+ description: Ring Signatures allow someone to non-interactively sign a message which
126
+ can be verified against a set of chosen public keys.
127
+ email: sjmccarthy@gmail.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - ".gitignore"
133
+ - Gemfile
134
+ - LICENSE
135
+ - README.md
136
+ - Rakefile
137
+ - lib/ring_sig.rb
138
+ - lib/ring_sig/ecdsa/point.rb
139
+ - lib/ring_sig/hasher.rb
140
+ - lib/ring_sig/key.rb
141
+ - lib/ring_sig/signature.rb
142
+ - lib/ring_sig/version.rb
143
+ - ring_sig.gemspec
144
+ - spec/hasher_spec.rb
145
+ - spec/key_spec.rb
146
+ - spec/signature_spec.rb
147
+ - spec/spec_helper.rb
148
+ homepage: https://github.com/jamoes/ring_sig
149
+ licenses:
150
+ - MIT
151
+ metadata: {}
152
+ post_install_message:
153
+ rdoc_options: []
154
+ require_paths:
155
+ - lib
156
+ required_ruby_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ required_rubygems_version: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ requirements: []
167
+ rubyforge_project:
168
+ rubygems_version: 2.2.2
169
+ signing_key:
170
+ specification_version: 4
171
+ summary: This gem implements ring signatures, built on top of ECDSA, as specified
172
+ by CryptoNote
173
+ test_files:
174
+ - spec/hasher_spec.rb
175
+ - spec/key_spec.rb
176
+ - spec/signature_spec.rb
177
+ - spec/spec_helper.rb
178
+ has_rdoc: