ring_sig 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|