paillier 1.2.1 → 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/lib/paillier.rb +1 -0
  3. data/lib/paillier/zkp.rb +311 -0
  4. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7249ae0f52583a0bff4159dc519f60461b2fcd09838b327ba89a7c3a2d45f342
4
- data.tar.gz: 97c7d194075a08c97cb132e1a66611d13934f86f11c99406856de21ae12c3772
3
+ metadata.gz: 7e65f1d686ff7deb6cde751ca76b2dedca5d3db08fab9cde3287cd8010694e9c
4
+ data.tar.gz: 11a9cba7134fff426ebf9ab421843eae5abf6c15782f18340c63e689098d7932
5
5
  SHA512:
6
- metadata.gz: 3cf18e9512f89d0f44d0c1f7e2f90b3866f982ed1fa15eb158526d581cb0e2b5bbf351bbae715db8751d325f7637f463f398e752584dd34af1006d9e1bfea86a
7
- data.tar.gz: 621dc6395654c2c357efe4ef8b5212502327d6d16b44e7225d91485f79a1ccb24622b844cc9aa9f1b647e8f67f29b7a0bdf768df6a4a7e00528c98c2cb1c3502
6
+ metadata.gz: 85a450def992769b0de4a549432f1a34d28a9e12fef7627e468a9c50737fd6869a367630d959c937fb1a87a1c4af05c45ac5d7847fa03e23032d60f413de946e
7
+ data.tar.gz: b36a94a9e063879794d99625ee1a02c4b6b82cc77e8fe44ae43941b6f545d3612747f27a956a5728d23f1361ed47bc65c62b82c59db8e1c6e7ced21e7db48128
@@ -8,6 +8,7 @@ require 'bigdecimal/math' # For 'log'
8
8
  require_relative 'paillier/primes'
9
9
  require_relative 'paillier/keys'
10
10
  require_relative 'paillier/signatures'
11
+ require_relative 'paillier/zkp'
11
12
 
12
13
  module Paillier
13
14
 
@@ -0,0 +1,311 @@
1
+
2
+ # needed for bignums
3
+ require 'set'
4
+ require 'openssl'
5
+ require 'securerandom'
6
+ require_relative 'primes'
7
+
8
+ BitStringLength = 256 #:nodoc:
9
+
10
+ module Paillier
11
+
12
+ module ZKP
13
+
14
+ # The ZKP class is used for performing zero-knowledge content proofs.
15
+ # Initialization of a ZKP object generates a commitment that the user can send along with a ciphertext to another party.
16
+ # That party can then use the commitment to confirm that the ciphertext exists in the set of pre-determined valid messages.
17
+ class ZKP
18
+
19
+ # Commitment generated by the ZKP object on initialization, used in ZKPVerify? function
20
+ attr_reader :commitment
21
+
22
+ # Ciphertext generated by the ZKP object; accessible using both myZKP.ciphertext and myZKP.cyphertext
23
+ attr_reader :ciphertext, :cyphertext
24
+
25
+
26
+ # Constructor function for a ZKP object. On initialization, generates a ZKPCommit object for use in verification
27
+ #
28
+ # Example:
29
+ # >> myZKP = Paillier::ZKP::ZKP.new(key, 65, [23, 38, 52, 65, 77, 94])
30
+ # => [#<@p = plaintext>, #<@pubkey = <key>>, #<@ciphertext = <ciphertext>>, #<@cyphertext = <ciphertext>>, #<@commitment = <commitment>>]
31
+ #
32
+ # Arguments:
33
+ # public_key: The key to be used for the encryption (Paillier::PublicKey)
34
+ # plaintext: The message to be encrypted (Integer)
35
+ # valid_messages: The set of valid messages for encryption (Array)
36
+ #
37
+ # NOTE: the order of valid_messages should be the same for both prover and verifier
38
+ def initialize(public_key, plaintext, valid_messages)
39
+ @p = plaintext
40
+ @pubkey = public_key
41
+ @r, @ciphertext = Paillier.rEncrypt(@pubkey, @p)
42
+ @cyphertext = @ciphertext
43
+ @a_s = Array.new()
44
+ @e_s = Array.new()
45
+ @z_s = Array.new()
46
+ @power = nil
47
+ @commitment = nil
48
+
49
+ # we generate a random value omega such that omega is coprime to n
50
+ while( true )
51
+ big_n = BigDecimal( @pubkey.n )
52
+ @omega = Primes.generateCoprime(BigMath.log(big_n, 2).round, @pubkey.n)
53
+ if( @omega > 0 and @omega < @pubkey.n)
54
+ break
55
+ end
56
+ end
57
+
58
+ # a_p = omega ^ n (mod n^2); 'a' calculation for the plaintext
59
+ a_p = @omega.to_bn.mod_exp( @pubkey.n, @pubkey.n_sq)
60
+
61
+ valid_messages.each_with_index do |m_k, k|
62
+ # g_mk = g ^ m_k (mod n^2)
63
+ g_mk = @pubkey.g.to_bn.mod_exp(m_k.to_bn, @pubkey.n_sq)
64
+
65
+ # u_k = c / g_mk (mod n^2)
66
+ # NOTE: this is modular algebra, so u_k = c * invmod(g_mk) (mod n^2)
67
+ u_k = @ciphertext.to_bn.mod_mul(Paillier.modInv(g_mk, @pubkey.n_sq), @pubkey.n_sq)
68
+ unless ( @p == m_k )
69
+ # randomly generate a coprime of n for z_k
70
+ while( true )
71
+ big_n = BigDecimal(@pubkey.n)
72
+ z_k = Primes::generateCoprime(BigMath.log(big_n, 2).round, @pubkey.n)
73
+ if( z_k > 0 and z_k < @pubkey.n )
74
+ break
75
+ end
76
+ end
77
+ @z_s.push(z_k.to_bn)
78
+
79
+ # generate a random e < 2^BitStringLength
80
+ e_k = SecureRandom.random_number((2 ** 256) - 1)
81
+ @e_s.push(e_k.to_bn)
82
+
83
+ # calculate z_k
84
+ # z_nth = z^n (mod n^2)
85
+ z_nth = z_k.to_bn.mod_exp(@pubkey.n, @pubkey.n_sq)
86
+ # u_eth = u^e_k (mod n^2)
87
+ u_eth = u_k.to_bn.mod_exp(e_k.to_bn, @pubkey.n_sq)
88
+ # a_k = z_nth / u_eth (mod n^2) = z_nth * invmod(u_eth) (mod n^2)
89
+ a_k = z_nth.to_bn.mod_mul( Paillier.modInv(u_eth, @pubkey.n_sq), @pubkey.n_sq )
90
+
91
+ @a_s.push(a_k.to_bn)
92
+ else
93
+ @power = k
94
+ @a_s.push(a_p.to_bn)
95
+ end
96
+ end
97
+ # attempting to craft a ZKP object with an invalid message throws exception
98
+ if(@power == nil)
99
+ raise ArgumentError, "Input message does not exist in array of valid messages.", caller
100
+ end
101
+ # we have now generated all a_s, and all e_s and z_s, save for e_p and z_p
102
+ # to generate e_p and z_p, we need to generate the challenge string, hash(a_s)
103
+ # to make the proof non-interactive
104
+ sha256 = OpenSSL::Digest::SHA256.new
105
+ for a_k in @a_s do
106
+ sha256 << a_k.to_s
107
+ end
108
+ challenge_string = sha256.digest.unpack('H*')[0].to_i(16)
109
+
110
+ # now that we have the "challenge string", we calculate e_p and z_p
111
+ e_sum = 0.to_bn
112
+ big_mod = 2.to_bn
113
+ big_mod = big_mod ** 256
114
+ for e_k in @e_s do
115
+ e_sum = (e_sum + e_k).to_bn % big_mod
116
+ end
117
+ # the sum of all e_s must add up to the challenge_string
118
+ e_p = (OpenSSL::BN.new(challenge_string) - e_sum).to_bn % big_mod
119
+ # r_ep = r ^ e_p (mod n)
120
+ r_ep = @r.to_bn.mod_exp(e_p.to_bn, @pubkey.n)
121
+ # z_p = omega * r^e_p (mod n)
122
+ z_p = @omega.to_bn.mod_mul(r_ep.to_bn, @pubkey.n)
123
+
124
+ @e_s.insert(@power, e_p.to_bn)
125
+ @z_s.insert(@power, z_p.to_bn)
126
+ @commitment = ZKPCommit.new(@a_s, @e_s, @z_s)
127
+
128
+ end
129
+ end
130
+
131
+ # Wrapper function that creates a ZKP object for the user.
132
+ # Instead of needing to call Paillier::ZKP::ZKP.new(args), the user calls Paillier::ZKP.new(args).
133
+ #
134
+ # Example:
135
+ # >> myZKP = Paillier::ZKP.new(key, 65, [23, 38, 52, 65, 77, 94])
136
+ # => [#<@p = plaintext>, #<@pubkey = <key>>, #<@ciphertext = <ciphertext>>, #<@cyphertext = <ciphertext>>, #<@commitment = <commitment>>]
137
+ #
138
+ # Arguments:
139
+ # public_key: The key to be used for the encryption (Paillier::PublicKey)
140
+ # plaintext: The message to be encrypted (Integer)
141
+ # valid_messages: The set of valid messages for encryption (Array)
142
+ #
143
+ # NOTE: the order of valid_messages should be the same for both prover and verifier
144
+ def self.new(pubkey, message, valid_messages)
145
+ return Paillier::ZKP::ZKP.new(pubkey, message, valid_messages)
146
+ end
147
+
148
+ # Function that verifies whether a ciphertext is within the set of valid messages.
149
+ #
150
+ # Example:
151
+ #
152
+ # >> Paillier::ZKP.verifyZKP?(key, ciphertext, [23, 38, 65, 77, 94], commitment)
153
+ # => true
154
+ #
155
+ # Arguments:
156
+ # pubkey: The key used for the encryption (Paillier::PublicKey)
157
+ # ciphertext: The ciphertext generated using the public key (OpenSSL::BN)
158
+ # valid_messages: The set of valid messages for encryption (Array)
159
+ # commitment: The commitment generated by the prover (Paillier::ZKP::ZKPCommit)
160
+ #
161
+ # NOTE: the order of valid_messages should be the same for both prover and verifier
162
+ def self.verifyZKP?(pubkey, ciphertext, valid_messages, commitment)
163
+ u_s = Array.new
164
+ for m_k in valid_messages do
165
+ # g_mk = g ^ m_k (mod n^2)
166
+ g_mk = pubkey.g.to_bn.mod_exp(m_k.to_bn, pubkey.n_sq)
167
+ # u_k = c / g_mk (mod n^2) = c * invmod(g_mk) (mod n^2)
168
+ u_k = OpenSSL::BN.new(ciphertext).mod_mul( Paillier.modInv(g_mk, pubkey.n_sq), pubkey.n_sq )
169
+ u_s.push(u_k)
170
+ end
171
+
172
+ # calculate the challenge_string
173
+ sha256 = OpenSSL::Digest::SHA256.new
174
+ for a_k in commitment.a_s do
175
+ sha256 << a_k.to_s
176
+ end
177
+ challenge_string = sha256.digest.unpack('H*')[0].to_i(16)
178
+
179
+ e_sum = 0.to_bn
180
+ big_mod = 2.to_bn
181
+ big_mod = big_mod ** 256
182
+ for e_k in commitment.e_s do
183
+ e_sum = (e_sum + e_k.to_bn) % big_mod
184
+ end
185
+
186
+ # first we check that the sum matches correctly
187
+ unless e_sum == OpenSSL::BN.new(challenge_string)
188
+ return false
189
+ end
190
+ # then we check that z_k^n = a_k * (u_k^e_k) (mod n^2)
191
+ for i in (0 .. (commitment.z_s.size - 1)) do
192
+ a_k = commitment.a_s[i]
193
+ e_k = commitment.e_s[i]
194
+ u_k = u_s[i]
195
+ z_k = commitment.z_s[i]
196
+ # left hand side
197
+ # z_kn = z_k ^ n (mod n^2)
198
+ z_kn = z_k.to_bn.mod_exp(pubkey.n, pubkey.n_sq)
199
+ # right hand side
200
+ # u_ke = u_k ^ e_k (mod n^2)
201
+ u_ke = u_k.to_bn.mod_exp(e_k, pubkey.n_sq)
202
+ # a_kue = a_k * u_ke (mod n^2)
203
+ a_kue = a_k.to_bn.mod_mul(u_ke, pubkey.n_sq)
204
+
205
+ # z_k ^ n ?= a_k * (u_k ^ e_k)
206
+ unless(z_kn == a_kue)
207
+ return false
208
+ end
209
+ end
210
+ # if it passes both tests, then we have validated the contents
211
+ return true
212
+ end
213
+
214
+ # Wrapper class used for containing the components of the ZKP commitment
215
+ class ZKPCommit
216
+
217
+ attr_reader :a_s, :e_s, :z_s #:nodoc:
218
+
219
+ def initialize(a_s, e_s, z_s) # :nodoc:
220
+ @a_s = a_s
221
+ @e_s = e_s
222
+ @z_s = z_s
223
+ end
224
+
225
+ # Serializes a commitment
226
+ #
227
+ # Example:
228
+ #
229
+ # >> myZKP = Paillier::ZKP.new(key, 65, [23, 38, 52, 65, 77, 94])
230
+ # => [#<@p = plaintext>, #<@pubkey = <key>>, #<@ciphertext = <ciphertext>>, #<@cyphertext = <ciphertext>>, #<@commitment = <commitment>>]
231
+ # >> myZKP.commitment.to_s
232
+ # => "<a1>,<a2>,<a3>,<a4>,<a5>,<a6>,;<e1>,<e2>,<e3>,<e4>,<e5>,;<z1>,<z2>,<z3>,<z4>,<z5>,"
233
+ def to_s()
234
+ a_s_string = @a_s.join(',')
235
+ e_s_string = @e_s.join(',')
236
+ z_s_string = @z_s.join(',')
237
+ return "#{a_s_string};#{e_s_string};#{z_s_string}"
238
+ end
239
+
240
+ # Deserializes a commitment
241
+ #
242
+ # Example:
243
+ #
244
+ # >> commit = Paillier::ZKP::ZKPCommit.from_s(commitment_string)
245
+ # => #<Paillier::ZKP::ZKPCommit: @a_s=[<a1>,<a2>, .. ,<an>], @e_s=[<e1>,<e2>, .. ,<en>], @z_s=[<z1>,<z2>, .. ,<zn>]>
246
+ #
247
+ # Arguments:
248
+ # commitment_string: Serialization of a commitment (String)
249
+ def ZKPCommit.from_s(string)
250
+ # these will hold the final result from string-parsing
251
+ a_s = Array.new
252
+ e_s = Array.new
253
+ z_s = Array.new
254
+
255
+ # separate at the semicolons
256
+ a_s_string, e_s_string, z_s_string = string.split(";")
257
+
258
+ # separate at the commas
259
+ a_s_strings = a_s_string.split(",")
260
+ e_s_strings = e_s_string.split(",")
261
+ z_s_strings = z_s_string.split(",")
262
+
263
+ # convert into arrays of bignums
264
+ for a in a_s_strings do
265
+ a_s.push(OpenSSL::BN.new(a))
266
+ end
267
+ for e in e_s_strings do
268
+ e_s.push(OpenSSL::BN.new(e))
269
+ end
270
+ for z in z_s_strings do
271
+ z_s.push(OpenSSL::BN.new(z))
272
+ end
273
+
274
+ # create the object with these arrays
275
+ return ZKPCommit.new(a_s, e_s, z_s)
276
+ end
277
+
278
+ # == operator overload to compare two ZKP commit objects
279
+ def ==(y) #:nodoc:
280
+ # if the array sizes don't match return false
281
+ if @a_s.size != y.a_s.size
282
+ return false
283
+ end
284
+ if @e_s.size != y.e_s.size
285
+ return false
286
+ end
287
+ if @z_s.size != y.z_s.size
288
+ return false
289
+ end
290
+ # if the corresponding elements in the arrays don't math return false
291
+ for i in (0 .. (@a_s.size - 1)) do
292
+ if(@a_s[i] != y.a_s[i])
293
+ return false
294
+ end
295
+ end
296
+ for i in (0 .. (@e_s.size - 1)) do
297
+ if(@e_s[i] != y.e_s[i])
298
+ return false
299
+ end
300
+ end
301
+ for i in (0 .. (@z_s.size - 1)) do
302
+ if(@z_s[i] != y.z_s[i])
303
+ return false
304
+ end
305
+ end
306
+ # else return true
307
+ return true
308
+ end
309
+ end
310
+ end
311
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paillier
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daylighting Society
@@ -20,6 +20,7 @@ files:
20
20
  - lib/paillier/keys.rb
21
21
  - lib/paillier/primes.rb
22
22
  - lib/paillier/signatures.rb
23
+ - lib/paillier/zkp.rb
23
24
  homepage: https://paillier.daylightingsociety.org
24
25
  licenses:
25
26
  - LGPL-3.0
@@ -39,8 +40,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
39
40
  - !ruby/object:Gem::Version
40
41
  version: '0'
41
42
  requirements: []
42
- rubyforge_project:
43
- rubygems_version: 2.7.6
43
+ rubygems_version: 3.0.3
44
44
  signing_key:
45
45
  specification_version: 4
46
46
  summary: Paillier Homomorphic Cryptosystem