forked from coinbase/kryptology
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ot.go
390 lines (333 loc) · 17.2 KB
/
ot.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
// Package simplest implements the "Verified Simplest OT", as defined in "protocol 7" of [DKLs18](https://eprint.iacr.org/2018/499.pdf).
// The original "Simplest OT" protocol is presented in [CC15](https://eprint.iacr.org/2015/267.pdf).
// In our implementation, we run OTs for multiple choice bits in parallel. Furthermore, as described in the DKLs paper,
// we implement this as Random OT protocol. We also add encryption and decryption steps as defined in the protocol, but
// emphasise that these steps are optional. Specifically, in the setting where this OT is used as the seed OT in an
// OT Extension protocol, the encryption and decryption steps are not needed.
//
// Limitation: currently we only support batch OTs that are multiples of 8.
//
// Ideal functionalities:
// - We have used ZKP Schnorr for the F^{R_{DL}}_{ZK}
// - We have used HMAC for realizing the Random Oracle Hash function, the key for HMAC is received as input to the protocol.
package simplest
import (
"crypto/rand"
"crypto/subtle"
"fmt"
"github.com/gtank/merlin"
"github.com/pkg/errors"
"golang.org/x/crypto/sha3"
"github.com/coinbase/kryptology/pkg/core/curves"
"github.com/coinbase/kryptology/pkg/zkp/schnorr"
)
const (
// keyCount is the number of encryption keys created. Since this is a 1-out-of-2 OT, the key count is set to 2.
keyCount = 2
// DigestSize is the length of hash. Similarly, when it comes to encrypting and decryption, it is the size of the
// plaintext and ciphertext.
DigestSize = 32
)
type (
// OneTimePadDecryptionKey is the type of Rho^w, Rho^0, and RHo^1 in the paper.
OneTimePadDecryptionKey = [DigestSize]byte
// OneTimePadEncryptionKeys is the type of Rho^0, and RHo^1 in the paper.
OneTimePadEncryptionKeys = [keyCount][DigestSize]byte
// OtChallenge is the type of xi in the paper.
OtChallenge = [DigestSize]byte
// OtChallengeResponse is the type of Rho' in the paper.
OtChallengeResponse = [DigestSize]byte
// ChallengeOpening is the type of hashed Rho^0 and Rho^1
ChallengeOpening = [keyCount][DigestSize]byte
// ReceiversMaskedChoices corresponds to the "A" value in the paper in compressed format.
ReceiversMaskedChoices = []byte
)
// SenderOutput are the outputs that the sender will obtain as a result of running the "random" OT protocol.
type SenderOutput struct {
// OneTimePadEncryptionKeys are Rho^0 and Rho^1, the output of the random OT.
// These can be used to encrypt and send two messages to the receiver.
// Therefore, for readability they are called OneTimePadEncryptionKeys in the code.
OneTimePadEncryptionKeys []OneTimePadEncryptionKeys
}
// ReceiverOutput are the outputs that the receiver will obtain as a result of running the "random" OT protocol.
type ReceiverOutput struct {
// PackedRandomChoiceBits is a packed version of the choice vector, the packing is done for performance reasons.
PackedRandomChoiceBits []byte
// RandomChoiceBits is the choice vector represented as unpacked int array. Initialed from PackedRandomChoiceBits.
RandomChoiceBits []int
// OneTimePadDecryptionKey is Rho^w, the output of the random OT. For the receiver, there is just 1 output per execution.
// This value will be used to decrypt one of the messages sent by the sender.
// Therefore, for readability this is called OneTimePadDecryptionKey in the code.
OneTimePadDecryptionKey []OneTimePadDecryptionKey
}
// Sender stores state for the "sender" role in OT. see Protocol 7 in Appendix A of DKLs18.
type Sender struct {
// Output is the output that is produced as a result of running random OT protocol.
Output *SenderOutput
curve *curves.Curve
// secretKey is the value `b` in the paper, which is the discrete log of B, which will be (re)used in _all_ executions of the OT.
secretKey curves.Scalar
// publicKey is the public key of the secretKey.
publicKey curves.Point
// batchSize is the number of parallel OTs.
batchSize int
transcript *merlin.Transcript
}
// Receiver stores state for the "receiver" role in OT. Protocol 7, Appendix A, of DKLs.
type Receiver struct {
// Output is the output that is produced as a result of running random OT protocol.
Output *ReceiverOutput
curve *curves.Curve
// senderPublicKey corresponds to "B" in the paper.
senderPublicKey curves.Point
// senderChallenge is "xi" in the protocol.
senderChallenge []OtChallenge
// batchSize is the number of parallel OTs.
batchSize int
transcript *merlin.Transcript
}
// NewSender creates a new "sender" object, ready to participate in a _random_ verified simplest OT in the role of the sender.
// no messages are specified by the sender, because random ones will be sent (hence the random OT).
// ultimately, the `Sender`'s `Output` field will be appropriately populated.
// you can use it directly, or alternatively bootstrap it into an _actual_ (non-random) OT using `Round7Encrypt` below
func NewSender(curve *curves.Curve, batchSize int, uniqueSessionId [DigestSize]byte) (*Sender, error) {
if batchSize&0x07 != 0 { // This is the same as `batchSize % 8 != 0`, but is constant time
return nil, errors.New("batch size should be a multiple of 8")
}
transcript := merlin.NewTranscript("Coinbase_DKLs_SeedOT")
transcript.AppendMessage([]byte("session_id"), uniqueSessionId[:])
return &Sender{
Output: &SenderOutput{},
curve: curve,
batchSize: batchSize,
transcript: transcript,
}, nil
}
// NewReceiver is a Random OT receiver. Therefore, the choice bits are created randomly.
// The choice bits are stored in a packed format (e.g., each choice is a single bit in a byte array).
func NewReceiver(curve *curves.Curve, batchSize int, uniqueSessionId [DigestSize]byte) (*Receiver, error) {
// This is the same as `batchSize % 8 != 0`, but is constant time
if batchSize&0x07 != 0 {
return nil, errors.New("batch size should be a multiple of 8")
}
transcript := merlin.NewTranscript("Coinbase_DKLs_SeedOT")
transcript.AppendMessage([]byte("session_id"), uniqueSessionId[:])
receiver := &Receiver{
Output: &ReceiverOutput{},
curve: curve,
batchSize: batchSize,
transcript: transcript,
}
batchSizeBytes := batchSize >> 3 // divide by 8
receiver.Output.PackedRandomChoiceBits = make([]byte, batchSizeBytes)
if _, err := rand.Read(receiver.Output.PackedRandomChoiceBits[:]); err != nil {
return nil, errors.Wrap(err, "choosing random choice bits")
}
// Unpack into Choice bits
receiver.initChoice()
return receiver, nil
}
// Round1ComputeAndZkpToPublicKey is the first phase of the protocol.
// computes and stores public key and returns the schnorr proof. serialized / packed.
// This implements step 1 of Protocol 7 of DKLs18, page 16.
func (sender *Sender) Round1ComputeAndZkpToPublicKey() (*schnorr.Proof, error) {
var err error
// Sample the secret key and compute the public key.
sender.secretKey = sender.curve.Scalar.Random(rand.Reader)
sender.publicKey = sender.curve.ScalarBaseMult(sender.secretKey)
// Generate the ZKP proof.
uniqueSessionId := [DigestSize]byte{}
copy(uniqueSessionId[:], sender.transcript.ExtractBytes([]byte("sender schnorr proof"), DigestSize))
prover := schnorr.NewProver(sender.curve, nil, uniqueSessionId[:])
proof, err := prover.Prove(sender.secretKey)
if err != nil {
return nil, errors.Wrap(err, "creating zkp proof for secret key in seed OT sender round 1")
}
return proof, nil
}
// Round2VerifySchnorrAndPadTransfer verifies the schnorr proof of the public key sent by the sender, i.e., step 2),
// and then does receiver's "Pad Transfer" phase in OT, i.e., step 3), of Protocol 7 (page 16) of the paper.
func (receiver *Receiver) Round2VerifySchnorrAndPadTransfer(proof *schnorr.Proof) ([]ReceiversMaskedChoices, error) {
receiver.senderPublicKey = proof.Statement
uniqueSessionId := [DigestSize]byte{}
copy(uniqueSessionId[:], receiver.transcript.ExtractBytes([]byte("sender schnorr proof"), DigestSize))
if err := schnorr.Verify(proof, receiver.curve, nil, uniqueSessionId[:]); err != nil {
return nil, errors.Wrap(err, "verifying schnorr proof in seed OT receiver round 2")
}
result := make([]ReceiversMaskedChoices, receiver.batchSize)
receiver.Output.OneTimePadDecryptionKey = make([]OneTimePadDecryptionKey, receiver.batchSize)
copy(uniqueSessionId[:], receiver.transcript.ExtractBytes([]byte("random oracle salts"), DigestSize))
for i := 0; i < receiver.batchSize; i++ {
a := receiver.curve.Scalar.Random(rand.Reader)
// Computing `A := a . G + w . B` in constant time, by first computing option0 = a.G and option1 = a.G+B and then
// constant time choosing one of them by first assuming that the output is option0, and overwrite it if the choice bit is 1.
option0 := receiver.curve.ScalarBaseMult(a)
option0Bytes := option0.ToAffineCompressed()
option1 := option0.Add(receiver.senderPublicKey)
option1Bytes := option1.ToAffineCompressed()
result[i] = option0Bytes
subtle.ConstantTimeCopy(receiver.Output.RandomChoiceBits[i], result[i], option1Bytes)
// compute the internal rho
rho := receiver.senderPublicKey.Mul(a)
hash := sha3.New256()
if _, err := hash.Write(uniqueSessionId[:]); err != nil {
return nil, errors.Wrap(err, "writing seed to hash in round 2 pad transfer")
}
if _, err := hash.Write([]byte{byte(i)}); err != nil {
return nil, errors.Wrap(err, "writing i to hash in round 2 pad transfer")
}
if _, err := hash.Write(rho.ToAffineCompressed()); err != nil {
return nil, errors.Wrap(err, "writing point to hash in round 2 pad transfer")
}
copy(receiver.Output.OneTimePadDecryptionKey[i][:], hash.Sum(nil))
}
return result, nil
}
// Round3PadTransfer is the sender's "Pad Transfer" phase in OT; see steps 4 and 5 of page 16 of the paper.
// Returns the challenges xi
func (sender *Sender) Round3PadTransfer(compressedReceiversMaskedChoice []ReceiversMaskedChoices) ([]OtChallenge, error) {
var err error
challenge := make([]OtChallenge, sender.batchSize)
sender.Output.OneTimePadEncryptionKeys = make([]OneTimePadEncryptionKeys, sender.batchSize)
negSenderPublicKey := sender.publicKey.Neg()
receiversMaskedChoice := make([]curves.Point, len(compressedReceiversMaskedChoice))
for i := 0; i < len(compressedReceiversMaskedChoice); i++ {
if receiversMaskedChoice[i], err = sender.curve.Point.FromAffineCompressed(compressedReceiversMaskedChoice[i]); err != nil {
return nil, errors.Wrap(err, "uncompress the point")
}
}
baseEncryptionKeyMaterial := make([]curves.Point, keyCount)
var hashedKey [keyCount][DigestSize]byte
uniqueSessionId := [DigestSize]byte{}
copy(uniqueSessionId[:], sender.transcript.ExtractBytes([]byte("random oracle salts"), DigestSize))
for i := 0; i < sender.batchSize; i++ {
// Sender creates two options that will eventually be used as her encryption keys.
// `baseEncryptionKeyMaterial[0]` and `baseEncryptionKeyMaterial[0]` correspond to rho_0 and rho_1 in the paper, respectively.
baseEncryptionKeyMaterial[0] = receiversMaskedChoice[i].Mul(sender.secretKey)
receiverChoiceMinusSenderPublicKey := receiversMaskedChoice[i].Add(negSenderPublicKey)
baseEncryptionKeyMaterial[1] = receiverChoiceMinusSenderPublicKey.Mul(sender.secretKey)
for k := 0; k < keyCount; k++ {
hash := sha3.New256()
if _, err = hash.Write(uniqueSessionId[:]); err != nil {
return nil, errors.Wrap(err, "writing seed to hash in round 3 pad transfer")
}
if _, err = hash.Write([]byte{byte(i)}); err != nil {
return nil, errors.Wrap(err, "writing i to hash in round 3 pad transfer")
}
if _, err = hash.Write(baseEncryptionKeyMaterial[k].ToAffineCompressed()); err != nil {
return nil, errors.Wrap(err, "writing point to hash in round 3 pad transfer")
}
copy(sender.Output.OneTimePadEncryptionKeys[i][k][:], hash.Sum(nil))
if err != nil {
return nil, errors.Wrap(err, "compute the encryption keys")
}
// Compute a challenge by XORing the hash of the hash of the key. Not a typo ;)
hashedKey[k] = sha3.Sum256(sender.Output.OneTimePadEncryptionKeys[i][k][:])
hashedKey[k] = sha3.Sum256(hashedKey[k][:])
}
challenge[i] = xorBytes(hashedKey[0], hashedKey[1])
}
return challenge, nil
}
// Round4RespondToChallenge corresponds to initial round of the receiver's "Verify" phase; see step 6 of page 16 of the paper.
// this is just the start of Verification. In this round, the receiver outputs "rho'", which the sender will check.
func (receiver *Receiver) Round4RespondToChallenge(challenge []OtChallenge) ([]OtChallengeResponse, error) {
// store to be used in future steps
receiver.senderChallenge = challenge
// challengeResponses is Rho' in the paper.
challengeResponses := make([]OtChallengeResponse, receiver.batchSize)
for i := 0; i < receiver.batchSize; i++ {
// Constant-time xor of the hashed key and the challenge, based on the choice bit.
hashedKey := sha3.Sum256(receiver.Output.OneTimePadDecryptionKey[i][:])
hashedKey = sha3.Sum256(hashedKey[:])
challengeResponses[i] = hashedKey
alternativeChallengeResponse := xorBytes(receiver.senderChallenge[i], hashedKey)
subtle.ConstantTimeCopy(receiver.Output.RandomChoiceBits[i], challengeResponses[i][:], alternativeChallengeResponse[:])
}
return challengeResponses, nil
}
// Round5Verify verifies the challenge response. If the verification passes, sender opens his challenges to the receiver.
// See step 7 of page 16 of the paper.
// Abort if Rho' != H(H(Rho^0)) in other words, if challengeResponse != H(H(encryption key 0)).
// opening is H(encryption key)
func (sender *Sender) Round5Verify(challengeResponses []OtChallengeResponse) ([]ChallengeOpening, error) {
opening := make([]ChallengeOpening, sender.batchSize)
for i := 0; i < sender.batchSize; i++ {
for k := 0; k < keyCount; k++ {
opening[i][k] = sha3.Sum256(sender.Output.OneTimePadEncryptionKeys[i][k][:])
}
// Verify
hashedKey0 := sha3.Sum256(opening[i][0][:])
if subtle.ConstantTimeCompare(hashedKey0[:], challengeResponses[i][:]) != 1 {
return nil, errors.New("receiver's challenge response didn't match H(H(rho^0))")
}
}
return opening, nil
}
// Round6Verify is the _last_ part of the "Verification" phase of OT; see p. 16 of https://eprint.iacr.org/2018/499.pdf.
// See step 8 of page 16 of the paper.
// Abort if H(Rho^w) != the one it calculated itself or
// if Xi != H(H(Rho^0)) XOR H(H(Rho^1))
// In other words,
// if opening_w != H(decryption key) or
// if challenge != H(opening 0) XOR H(opening 0)
func (receiver *Receiver) Round6Verify(challengeOpenings []ChallengeOpening) error {
for i := 0; i < receiver.batchSize; i++ {
hashedDecryptionKey := sha3.Sum256(receiver.Output.OneTimePadDecryptionKey[i][:])
w := receiver.Output.RandomChoiceBits[i]
if subtle.ConstantTimeCompare(hashedDecryptionKey[:], challengeOpenings[i][w][:]) != 1 {
return fmt.Errorf("sender's supposed H(rho^omega) doesn't match our own")
}
hashedKey0 := sha3.Sum256(challengeOpenings[i][0][:])
hashedKey1 := sha3.Sum256(challengeOpenings[i][1][:])
reconstructedChallenge := xorBytes(hashedKey0, hashedKey1)
if subtle.ConstantTimeCompare(reconstructedChallenge[:], receiver.senderChallenge[i][:]) != 1 {
return fmt.Errorf("sender's openings H(rho^0) and H(rho^1) didn't decommit to its prior message")
}
}
return nil
}
// Round7Encrypt wraps an `Encrypt` operation on the Sender's underlying output from the random OT; see `Encrypt` below.
// this is optional; it will be used only in circumstances when you want to run "actual" (i.e., non-random) OT
func (sender *Sender) Round7Encrypt(messages [][keyCount][DigestSize]byte) ([][keyCount][DigestSize]byte, error) {
return sender.Output.Encrypt(messages)
}
// Round8Decrypt wraps a `Decrypt` operation on the Receiver's underlying output from the random OT; see `Decrypt` below
// this is optional; it will be used only in circumstances when you want to run "actual" (i.e., non-random) OT
func (receiver *Receiver) Round8Decrypt(ciphertext [][keyCount][DigestSize]byte) ([][DigestSize]byte, error) {
return receiver.Output.Decrypt(ciphertext)
}
// Encrypt runs step 9) of the seed OT Protocol 7) of https://eprint.iacr.org/2018/499.pdf,
// in which the seed OT sender "encrypts" both messages under the "one-time keys" output by the random OT.
func (s *SenderOutput) Encrypt(plaintexts [][keyCount][DigestSize]byte) ([][keyCount][DigestSize]byte, error) {
batchSize := len(s.OneTimePadEncryptionKeys)
if len(plaintexts) != batchSize {
return nil, errors.New("message size should be same as batch size")
}
ciphertexts := make([][keyCount][DigestSize]byte, batchSize)
for i := 0; i < len(plaintexts); i++ {
for k := 0; k < keyCount; k++ {
ciphertexts[i][k] = xorBytes(s.OneTimePadEncryptionKeys[i][k], plaintexts[i][k])
}
}
return ciphertexts, nil
}
// Decrypt is step 10) of the seed OT Protocol 7) of https://eprint.iacr.org/2018/499.pdf,
// where the seed OT receiver "decrypts" the message it's receiving using the "key" it received in the random OT.
func (r *ReceiverOutput) Decrypt(ciphertexts [][keyCount][DigestSize]byte) ([][DigestSize]byte, error) {
batchSize := len(r.OneTimePadDecryptionKey)
if len(ciphertexts) != batchSize {
return nil, errors.New("number of ciphertexts should be same as batch size")
}
plaintexts := make([][DigestSize]byte, batchSize)
for i := 0; i < len(ciphertexts); i++ {
choice := r.RandomChoiceBits[i]
plaintexts[i] = xorBytes(r.OneTimePadDecryptionKey[i], ciphertexts[i][choice])
}
return plaintexts, nil
}