paillier 1.2.1 → 1.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/paillier.rb +1 -0
- data/lib/paillier/zkp.rb +311 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7e65f1d686ff7deb6cde751ca76b2dedca5d3db08fab9cde3287cd8010694e9c
|
4
|
+
data.tar.gz: 11a9cba7134fff426ebf9ab421843eae5abf6c15782f18340c63e689098d7932
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 85a450def992769b0de4a549432f1a34d28a9e12fef7627e468a9c50737fd6869a367630d959c937fb1a87a1c4af05c45ac5d7847fa03e23032d60f413de946e
|
7
|
+
data.tar.gz: b36a94a9e063879794d99625ee1a02c4b6b82cc77e8fe44ae43941b6f545d3612747f27a956a5728d23f1361ed47bc65c62b82c59db8e1c6e7ced21e7db48128
|
data/lib/paillier.rb
CHANGED
data/lib/paillier/zkp.rb
ADDED
@@ -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.
|
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
|
-
|
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
|