//
// 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"

	"source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/core/curves"
	"source.quilibrium.com/quilibrium/monorepo/nekryptology/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, sha3.New256(), 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, sha3.New256(), 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
}