paillier 1.2.1 → 1.2.3

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.
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