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