ring_sig 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|
+
[](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
|