paillier 1.0.0 → 1.2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/paillier.rb +42 -11
- data/lib/paillier/keys.rb +25 -0
- data/lib/paillier/primes.rb +29 -29
- data/lib/paillier/zkp.rb +311 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 784166f6728fa330be600b47c2be1a643ff20471b6029723083a94806681a93a
|
4
|
+
data.tar.gz: '05960d00f769c0be21772270addb3a9f247b925621b2ebc7d78d7a86e8aac5fc'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9288c168ac05ed5584d6c7878ec248b0dea02b0c3c9bd866c810c5f4a962677fc65a6338573835eb6e4e651fdb39bb4239d561f7600ec6898e3b1adfae55dff6
|
7
|
+
data.tar.gz: 84c1f5a371d5ba2e6be59c3193b9a1814882cff4a843549f5e0d10842869c2b074e1402f4fe359a71428e2d49ed07545cc92c5ad8dd218f9df04083d1d2b6cae
|
data/lib/paillier.rb
CHANGED
@@ -8,11 +8,10 @@ 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
|
|
14
|
-
KeySize = 2048
|
15
|
-
|
16
15
|
def self.gcd(u,v) # :nodoc:
|
17
16
|
while(v > 0)
|
18
17
|
u, v = v, u % v
|
@@ -100,7 +99,7 @@ module Paillier
|
|
100
99
|
while( true )
|
101
100
|
# We have to use BigMath here to make sure 'log' doesn't round
|
102
101
|
# to infinity and throw an exception
|
103
|
-
big_n = BigDecimal
|
102
|
+
big_n = BigDecimal(pub.n)
|
104
103
|
r = Primes.generateCoprime(BigMath.log(big_n, 2).round, pub.n)
|
105
104
|
if( r > 0 and r < pub.n )
|
106
105
|
break
|
@@ -123,8 +122,11 @@ module Paillier
|
|
123
122
|
#
|
124
123
|
# Arguments:
|
125
124
|
# publicKey: (Paillier::PublicKey)
|
126
|
-
# plaintext: (Int)
|
125
|
+
# plaintext: (Int, OpenSSL::BN, String)
|
127
126
|
def self.encrypt(publicKey, plaintext)
|
127
|
+
if( plaintext.is_a?(String) )
|
128
|
+
plaintext = OpenSSL::BN.new(plaintext)
|
129
|
+
end
|
128
130
|
return rEncrypt(publicKey, plaintext)[1]
|
129
131
|
end
|
130
132
|
|
@@ -136,9 +138,15 @@ module Paillier
|
|
136
138
|
#
|
137
139
|
# Arguments:
|
138
140
|
# publicKey: (Paillier::PublicKey)
|
139
|
-
# a: (Int)
|
140
|
-
# b: (Int)
|
141
|
+
# a: (Int, OpenSSL::BN, String)
|
142
|
+
# b: (Int, OpenSSL::BN, String)
|
141
143
|
def self.eAdd(publicKey, a, b)
|
144
|
+
if( a.is_a?(String) )
|
145
|
+
a = OpenSSL::BN.new(a)
|
146
|
+
end
|
147
|
+
if( b.is_a?(String) )
|
148
|
+
b = OpenSSL::BN.new(b)
|
149
|
+
end
|
142
150
|
return a.to_bn.mod_mul(b, publicKey.n_sq)
|
143
151
|
end
|
144
152
|
|
@@ -150,9 +158,15 @@ module Paillier
|
|
150
158
|
#
|
151
159
|
# Arguments:
|
152
160
|
# publicKey: (Paillier::PublicKey)
|
153
|
-
# a: (Int)
|
154
|
-
#
|
161
|
+
# a: (Int, OpenSSL::BN, String)
|
162
|
+
# n: (Int, OpenSSL::BN, String)
|
155
163
|
def self.eAddConst(publicKey, a, n)
|
164
|
+
if( a.is_a?(String) )
|
165
|
+
a = OpenSSL::BN.new(a)
|
166
|
+
end
|
167
|
+
if( n.is_a?(String) )
|
168
|
+
n = OpenSSL::BN.new(n)
|
169
|
+
end
|
156
170
|
return a.to_bn.mod_mul(modPow(publicKey.g, n, publicKey.n_sq), publicKey.n_sq)
|
157
171
|
end
|
158
172
|
|
@@ -164,7 +178,15 @@ module Paillier
|
|
164
178
|
#
|
165
179
|
# Arguments:
|
166
180
|
# publicKey: (Paillier::PublicKey)
|
181
|
+
# a: (Int, OpenSSL::BN, String)
|
182
|
+
# n: (Int, OpenSSL::BN, String)
|
167
183
|
def self.eMulConst(publicKey, a, n)
|
184
|
+
if( a.is_a?(String) )
|
185
|
+
a = OpenSSL::BN.new(a)
|
186
|
+
end
|
187
|
+
if( n.is_a?(String) )
|
188
|
+
n = OpenSSL::BN.new(n)
|
189
|
+
end
|
168
190
|
return modPow(a, n, publicKey.n_sq)
|
169
191
|
end
|
170
192
|
|
@@ -177,8 +199,11 @@ module Paillier
|
|
177
199
|
# Arguments:
|
178
200
|
# privKey: (Paillier::PrivateKey)
|
179
201
|
# pubKey: (Paillier::PublicKey)
|
180
|
-
# ciphertext: (Int)
|
202
|
+
# ciphertext: (Int, OpenSSL::BN, String)
|
181
203
|
def self.decrypt(privKey, pubKey, ciphertext)
|
204
|
+
if( ciphertext.is_a?(String) )
|
205
|
+
ciphertext = OpenSSL::BN.new(ciphertext)
|
206
|
+
end
|
182
207
|
# We want to run: x = ((cipher ** priv.l) % pub.n_sq) - 1
|
183
208
|
# But the numbers are too big, so we'll use openssl
|
184
209
|
x = ciphertext.to_bn.mod_exp(privKey.l, pubKey.n_sq) - 1
|
@@ -195,8 +220,11 @@ module Paillier
|
|
195
220
|
# Arguments:
|
196
221
|
# priv: (Paillier::PrivateKey)
|
197
222
|
# pub: (Paillier::PublicKey)
|
198
|
-
# data: (Int)
|
223
|
+
# data: (Int, OpenSSL::BN, String)
|
199
224
|
def self.sign(priv, pub, data)
|
225
|
+
if( data.is_a?(String) )
|
226
|
+
data = OpenSSL::BN.new(data)
|
227
|
+
end
|
200
228
|
hashData = hash(data)
|
201
229
|
# L(u) = (u-1)/n
|
202
230
|
numerators1 = ((hashData.to_bn.mod_exp(priv.l, pub.n_sq) - 1) / pub.n.to_bn)[0]
|
@@ -225,9 +253,12 @@ module Paillier
|
|
225
253
|
#
|
226
254
|
# Arguments:
|
227
255
|
# pub: (Paillier::PublicKey)
|
228
|
-
# message: (Int)
|
256
|
+
# message: (Int, OpenSSL::BN, String)
|
229
257
|
# sig: (Paillier::Signature)
|
230
258
|
def self.validSignature?(pub, message, sig)
|
259
|
+
if( message.is_a?(String) )
|
260
|
+
message = OpenSSL::BN.new(message)
|
261
|
+
end
|
231
262
|
hash = Digest::SHA256.hexdigest(message.to_s).to_i(16)
|
232
263
|
# We want to run (g ** s1) * (s2 ** n) % (n**2)
|
233
264
|
# But all those numbers are huge, so we approach it in stages
|
data/lib/paillier/keys.rb
CHANGED
@@ -6,6 +6,31 @@ module Paillier
|
|
6
6
|
@l = l
|
7
7
|
@m = m
|
8
8
|
end
|
9
|
+
|
10
|
+
# Serialize a private key to string form
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
# >> priv, pub = Paillier.generateKeypair(2048)
|
14
|
+
# >> priv.to_s
|
15
|
+
# => "110107191408889682017277609474037601699496910..."
|
16
|
+
#
|
17
|
+
def to_s
|
18
|
+
return "#{@l},#{@m}"
|
19
|
+
end
|
20
|
+
|
21
|
+
# De-serialize a private key string back into object form
|
22
|
+
#
|
23
|
+
# Example:
|
24
|
+
# >> s = priv.to_s
|
25
|
+
# >> newPriv = Paillier::PrivateKey.from_s(s)
|
26
|
+
# => #<Paillier::PrivateKey>
|
27
|
+
#
|
28
|
+
# Arguments:
|
29
|
+
# string (String)
|
30
|
+
def PrivateKey.from_s(string)
|
31
|
+
l,m = string.split(",")
|
32
|
+
return PrivateKey.new(l.to_i, m.to_i)
|
33
|
+
end
|
9
34
|
end
|
10
35
|
|
11
36
|
class PublicKey
|
data/lib/paillier/primes.rb
CHANGED
@@ -10,33 +10,37 @@ module Paillier
|
|
10
10
|
return int.to_s(2).length
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
13
|
+
# This is an implementation of the Rabin-Miller primality test.
|
14
|
+
# Previous versions used Little Fermat, but that is not effective
|
15
|
+
# in all cases; specifically, it can be thwarted by Carmichael
|
16
|
+
# numbers. We use 50 rounds as the default, in order to get a certainty
|
17
|
+
# of 2^-100 that we have found a prime. This implementation is adapted
|
18
|
+
# from https://rosettacode.org/wiki/Miller-Rabin_primality_test#Ruby
|
19
|
+
def self.probabilisticPrimeTest(target, k=50)
|
20
|
+
d = target-1
|
21
|
+
s = 0
|
22
|
+
while d % 2 == 0
|
23
|
+
d /= 2
|
24
|
+
s += 1
|
25
|
+
end
|
26
|
+
k.times do
|
27
|
+
a = 2 + rand(target-4)
|
28
|
+
x = a.to_bn.mod_exp(d, target)
|
29
|
+
next if x == 1 || x == target-1
|
30
|
+
(s - 1).times do
|
31
|
+
x = x.to_bn.mod_exp(2, target)
|
32
|
+
return false if x == 1
|
33
|
+
break if x == target - 1
|
34
|
+
end
|
35
|
+
return false if x != target-1
|
36
|
+
end
|
37
|
+
return true # probs prime
|
38
|
+
end
|
32
39
|
|
33
|
-
def self.isProbablyPrime?(possible, k=
|
40
|
+
def self.isProbablyPrime?(possible, k=50)
|
34
41
|
if( possible == 1 )
|
35
42
|
return true
|
36
43
|
end
|
37
|
-
if( k.nil? )
|
38
|
-
k = defaultK(bitLength(possible))
|
39
|
-
end
|
40
44
|
for i in SmallPrimes
|
41
45
|
if( possible == i )
|
42
46
|
return true
|
@@ -59,14 +63,10 @@ module Paillier
|
|
59
63
|
end
|
60
64
|
|
61
65
|
# Get a random prime of appropriate length
|
62
|
-
def self.generatePrime(bits, k=
|
66
|
+
def self.generatePrime(bits, k=50)
|
63
67
|
if( bits < 8 )
|
64
68
|
raise "Bits less than eight!"
|
65
69
|
end
|
66
|
-
if( k == nil )
|
67
|
-
k = defaultK(bits)
|
68
|
-
end
|
69
|
-
|
70
70
|
while( true )
|
71
71
|
lowerBound = (2 ** (bits-1) + 1)
|
72
72
|
size = ((2 ** bits) - lowerBound)
|
@@ -82,7 +82,7 @@ module Paillier
|
|
82
82
|
raise "Bits less than eight!"
|
83
83
|
end
|
84
84
|
|
85
|
-
# If we find a number not
|
85
|
+
# If we find a number not coprime to n then finding `p` and `q` is trivial.
|
86
86
|
# This will almost never happen for keys of reasonable size, so if
|
87
87
|
# `coprime_to` is big enough we won't bother running the expensive test.
|
88
88
|
no_test_needed = false
|
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,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: paillier
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daylighting Society
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-11-10 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: An implementation of Paillier homomorphic addition public key system
|
14
14
|
email: paillier@daylightingsociety.org
|
@@ -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.6.4
|
43
|
+
rubygems_version: 3.0.3
|
44
44
|
signing_key:
|
45
45
|
specification_version: 4
|
46
46
|
summary: Paillier Homomorphic Cryptosystem
|