ring_sig 0.0.1
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 +7 -0
- data/.gitignore +7 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +0 -0
- data/Rakefile +20 -0
- data/lib/ring_sig.rb +11 -0
- data/lib/ring_sig/ecdsa/point.rb +6 -0
- data/lib/ring_sig/hasher.rb +94 -0
- data/lib/ring_sig/key.rb +142 -0
- data/lib/ring_sig/signature.rb +113 -0
- data/lib/ring_sig/version.rb +3 -0
- data/ring_sig.gemspec +26 -0
- data/spec/hasher_spec.rb +93 -0
- data/spec/key_spec.rb +86 -0
- data/spec/signature_spec.rb +48 -0
- data/spec/spec_helper.rb +6 -0
- metadata +178 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
data/Gemfile
ADDED
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.
|
data/README.md
ADDED
File without changes
|
data/Rakefile
ADDED
@@ -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
|
data/lib/ring_sig.rb
ADDED
@@ -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
|
data/lib/ring_sig/key.rb
ADDED
@@ -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
|
data/ring_sig.gemspec
ADDED
@@ -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
|
data/spec/hasher_spec.rb
ADDED
@@ -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
|
data/spec/key_spec.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
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:
|