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