mirror of
https://github.com/sonr-io/crypto.git
synced 2026-08-02 15:31:38 +00:00
No commit suggestions generated
This commit is contained in:
@@ -0,0 +1,437 @@
|
||||
//
|
||||
// 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/sonr-io/sonr/crypto/core/curves"
|
||||
"github.com/sonr-io/sonr/crypto/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
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package simplest_test
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
"github.com/sonr-io/sonr/crypto/ot/base/simplest"
|
||||
"github.com/sonr-io/sonr/crypto/ot/ottest"
|
||||
)
|
||||
|
||||
func TestOtOnMultipleCurves(t *testing.T) {
|
||||
curveInstances := []*curves.Curve{
|
||||
curves.K256(),
|
||||
curves.P256(),
|
||||
}
|
||||
for _, curve := range curveInstances {
|
||||
batchSize := 256
|
||||
hashKeySeed := [32]byte{}
|
||||
_, err := rand.Read(hashKeySeed[:])
|
||||
require.NoError(t, err)
|
||||
sender, receiver, err := ottest.RunSimplestOT(curve, batchSize, hashKeySeed)
|
||||
require.NoError(t, err)
|
||||
|
||||
for i := 0; i < batchSize; i++ {
|
||||
require.Equal(
|
||||
t,
|
||||
receiver.OneTimePadDecryptionKey[i],
|
||||
sender.OneTimePadEncryptionKeys[i][receiver.RandomChoiceBits[i]],
|
||||
)
|
||||
}
|
||||
|
||||
// Transfer messages
|
||||
messages := make([][2][32]byte, batchSize)
|
||||
for i := 0; i < batchSize; i++ {
|
||||
messages[i] = [2][32]byte{
|
||||
sha256.Sum256([]byte(fmt.Sprintf("message[%d][0]", i))),
|
||||
sha256.Sum256([]byte(fmt.Sprintf("message[%d][1]", i))),
|
||||
}
|
||||
}
|
||||
ciphertexts, err := sender.Encrypt(messages)
|
||||
require.NoError(t, err)
|
||||
decrypted, err := receiver.Decrypt(ciphertexts)
|
||||
require.NoError(t, err)
|
||||
|
||||
for i := 0; i < batchSize; i++ {
|
||||
choice := receiver.RandomChoiceBits[i]
|
||||
require.Equal(t, messages[i][choice], decrypted[i])
|
||||
require.NotEqual(t, messages[i][1-choice], decrypted[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOTStreaming(t *testing.T) {
|
||||
batchSize := 256
|
||||
curve := curves.K256()
|
||||
hashKeySeed := [32]byte{}
|
||||
_, err := rand.Read(hashKeySeed[:])
|
||||
require.NoError(t, err)
|
||||
sender, err := simplest.NewSender(curve, batchSize, hashKeySeed)
|
||||
require.Nil(t, err)
|
||||
receiver, err := simplest.NewReceiver(curve, batchSize, hashKeySeed)
|
||||
require.Nil(t, err)
|
||||
|
||||
senderPipe, receiverPipe := simplest.NewPipeWrappers()
|
||||
errorsChannel := make(
|
||||
chan error,
|
||||
2,
|
||||
) // warning: if one party errors, the other will sit there forever. add timeouts.
|
||||
go func() {
|
||||
errorsChannel <- simplest.SenderStreamOTRun(sender, senderPipe)
|
||||
}()
|
||||
go func() {
|
||||
errorsChannel <- simplest.ReceiverStreamOTRun(receiver, receiverPipe)
|
||||
}()
|
||||
for i := 0; i < 2; i++ {
|
||||
require.Nil(t, <-errorsChannel)
|
||||
}
|
||||
for i := 0; i < batchSize; i++ {
|
||||
require.Equal(
|
||||
t,
|
||||
receiver.Output.OneTimePadDecryptionKey[i],
|
||||
sender.Output.OneTimePadEncryptionKeys[i][receiver.Output.RandomChoiceBits[i]],
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package simplest
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"io"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
"github.com/sonr-io/sonr/crypto/zkp/schnorr"
|
||||
)
|
||||
|
||||
// ReceiverStreamOTRun exposes the entire seed OT process for the receiver in "stream mode" to the user.
|
||||
// what this means is that instead of calling the component methods in the process manually, and manually handling
|
||||
// the encoding and decoding of the resulting output and input structs, the user needs _only_ to pass a ReadWriter
|
||||
// (in practice this will be something like a websocket object), and this method will handle the entire process.
|
||||
// this serves the dual (though related) purpose of conveniently bundling up the entire seed OT process,
|
||||
// for use in tests, both in this package, as well as in the other packages which use this one (like cOT and mult).
|
||||
func ReceiverStreamOTRun(receiver *Receiver, rw io.ReadWriter) error {
|
||||
enc := gob.NewEncoder(rw)
|
||||
dec := gob.NewDecoder(rw)
|
||||
gob.Register(&curves.ScalarK256{})
|
||||
gob.Register(&curves.PointK256{})
|
||||
|
||||
proof := &schnorr.Proof{}
|
||||
|
||||
if err := dec.Decode(proof); err != nil {
|
||||
return errors.Wrap(err, "failed to decode proof in receiver stream OT")
|
||||
}
|
||||
|
||||
receiversMaskedChoice, err := receiver.Round2VerifySchnorrAndPadTransfer(proof)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error in round 2 in receiver stream OT")
|
||||
}
|
||||
if err = enc.Encode(receiversMaskedChoice); err != nil {
|
||||
return errors.Wrap(err, "error encoding result of round 1 in receiver stream OT")
|
||||
}
|
||||
var challenge []OtChallenge
|
||||
err = dec.Decode(&challenge)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error decoding challenge in receiver stream OT")
|
||||
}
|
||||
challengeResponse, err := receiver.Round4RespondToChallenge(challenge)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error computing round 2 challenge response in receiver stream OT")
|
||||
}
|
||||
if err = enc.Encode(challengeResponse); err != nil {
|
||||
return errors.Wrap(err, "error encoding challenge response in receiver stream OT")
|
||||
}
|
||||
var openings []ChallengeOpening
|
||||
err = dec.Decode(&openings)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error decoding challenge openings in receiver stream OT")
|
||||
}
|
||||
return receiver.Round6Verify(openings)
|
||||
}
|
||||
|
||||
// SenderStreamOTRun exposes the entire seed OT process for the sender in "stream mode" to the user.
|
||||
// similarly to the above, this means that the user needs only to pass a `ReadWriter` representing the comm channel;
|
||||
// this method will handle all encoding and decoding + writing and reading to the channel.
|
||||
func SenderStreamOTRun(sender *Sender, rw io.ReadWriter) error {
|
||||
// again a high-level helper method showing the overall flow, this time for the sender.
|
||||
enc := gob.NewEncoder(rw)
|
||||
dec := gob.NewDecoder(rw)
|
||||
gob.Register(&curves.ScalarK256{})
|
||||
gob.Register(&curves.PointK256{})
|
||||
|
||||
proof, err := sender.Round1ComputeAndZkpToPublicKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = enc.Encode(proof); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var receiversMaskedChoice []ReceiversMaskedChoices
|
||||
err = dec.Decode(&receiversMaskedChoice)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error decoding receiver's masked choice in sender stream OT")
|
||||
}
|
||||
|
||||
challenge, err := sender.Round3PadTransfer(receiversMaskedChoice)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during round 2 pad transfer in sender stream OT")
|
||||
}
|
||||
err = enc.Encode(challenge)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error encoding challenge in sender stream OT")
|
||||
}
|
||||
var challengeResponses []OtChallengeResponse
|
||||
err = dec.Decode(&challengeResponses)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error decoding challenges responses in sender stream OT")
|
||||
}
|
||||
opening, err := sender.Round5Verify(challengeResponses)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error in round 3 verify in sender stream OT")
|
||||
}
|
||||
return enc.Encode(opening)
|
||||
}
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
package simplest
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// xorBytes computes c = a xor b.
|
||||
func xorBytes(a, b [DigestSize]byte) (c [DigestSize]byte) {
|
||||
for i := 0; i < DigestSize; i++ {
|
||||
c[i] = a[i] ^ b[i]
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// initChoice initializes the receiver's choice array from the PackedRandomChoiceBits array
|
||||
func (receiver *Receiver) initChoice() {
|
||||
// unpack the random values in PackedRandomChoiceBits into bits in Choice
|
||||
receiver.Output.RandomChoiceBits = make([]int, receiver.batchSize)
|
||||
for i := 0; i < len(receiver.Output.RandomChoiceBits); i++ {
|
||||
receiver.Output.RandomChoiceBits[i] = int(
|
||||
ExtractBitFromByteVector(receiver.Output.PackedRandomChoiceBits, i),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ExtractBitFromByteVector interprets the byte-vector `vector` as if it were a _bit_-vector with len(vector) * 8 bits.
|
||||
// it extracts the `index`th such bit, interpreted in the little-endian way (i.e., both across bytes and within bytes).
|
||||
func ExtractBitFromByteVector(vector []byte, index int) byte {
|
||||
// the bitwise tricks index >> 3 == index // 8 and index & 0x07 == index % 8 are designed to avoid CPU division.
|
||||
return vector[index>>3] >> (index & 0x07) & 0x01
|
||||
}
|
||||
|
||||
type pipeWrapper struct {
|
||||
r *io.PipeReader
|
||||
w *io.PipeWriter
|
||||
exchanged int // used this during testing, to track bytes exchanged
|
||||
}
|
||||
|
||||
func (wrapper *pipeWrapper) Write(p []byte) (n int, err error) {
|
||||
n, err = wrapper.w.Write(p)
|
||||
wrapper.exchanged += n
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (wrapper *pipeWrapper) Read(p []byte) (n int, err error) {
|
||||
n, err = wrapper.r.Read(p)
|
||||
wrapper.exchanged += n
|
||||
return n, err
|
||||
}
|
||||
|
||||
func NewPipeWrappers() (*pipeWrapper, *pipeWrapper) {
|
||||
leftOut, leftIn := io.Pipe()
|
||||
rightOut, rightIn := io.Pipe()
|
||||
return &pipeWrapper{r: leftOut, w: rightIn}, &pipeWrapper{r: rightOut, w: leftIn}
|
||||
}
|
||||
Reference in New Issue
Block a user