ceremonyclient/nekryptology/pkg/verenc/elgamal/proof_enc.go

213 lines
6.4 KiB
Go
Raw Normal View History

2023-07-05 05:32:28 +00:00
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package elgamal
import (
crand "crypto/rand"
"fmt"
"git.sr.ht/~sircmpwn/go-bare"
"source.quilibrium.com/quilibrium/monorepo/nekryptology/internal"
"source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/core/curves"
)
// ProofVerEnc is a proof of verifiable encryption for a discrete log
type ProofVerEnc struct {
challenge, schnorr1, schnorr2 curves.Scalar
}
type proofMarshal struct {
Challenge []byte `bare:"challenge"`
Schnorr1 []byte `bare:"schnorr1"`
Schnorr2 []byte `bare:"schnorr2"`
Curve string `bare:"curve"`
}
func (pf ProofVerEnc) MarshalBinary() ([]byte, error) {
tv := new(proofMarshal)
tv.Challenge = pf.challenge.Bytes()
tv.Schnorr1 = pf.schnorr1.Bytes()
tv.Schnorr2 = pf.schnorr2.Bytes()
tv.Curve = pf.challenge.Point().CurveName()
return bare.Marshal(tv)
}
func (pf *ProofVerEnc) UnmarshalBinary(data []byte) error {
tv := new(proofMarshal)
err := bare.Unmarshal(data, tv)
if err != nil {
return err
}
curve := curves.GetCurveByName(tv.Curve)
challenge, err := curve.Scalar.SetBytes(tv.Challenge)
if err != nil {
return err
}
schnorr1, err := curve.Scalar.SetBytes(tv.Schnorr1)
if err != nil {
return err
}
schnorr2, err := curve.Scalar.SetBytes(tv.Schnorr2)
if err != nil {
return err
}
pf.challenge = challenge
pf.schnorr1 = schnorr1
pf.schnorr2 = schnorr2
return nil
}
// VerifiableEncrypt a message using El-Gamal. This also functions as an ECIES
// encryption algorithm. The advantage here is proofs can be made about the
// ciphertext versus plain ECIES if desired and/or linked to external proofs.
func (ek EncryptionKey) VerifiableEncrypt(msg []byte, params *EncryptParams) (*CipherText, *ProofVerEnc, error) {
var err error
var proof *ProofVerEnc
var cipherText *CipherText
var h curves.Point
if msg == nil {
return nil, nil, internal.ErrNilArguments
}
if params.Blinding == nil {
params.Blinding = ek.Value.Scalar().Random(crand.Reader)
for params.Blinding.IsZero() {
params.Blinding = ek.Value.Scalar().Random(crand.Reader)
}
} else if params.Blinding.IsZero() {
return nil, nil, internal.ErrZeroValue
}
cnonce := ek.genNonce()
if cnonce == nil {
return nil, nil, fmt.Errorf("unable to generate nonce")
}
if params.Domain == nil {
// With no domain, the generator is used as h
h = ek.Value.Generator()
} else {
// If domain is provided, calculate h using domain as part of the input, then encrypt
genBytes := append(params.Domain, ek.Value.ToAffineUncompressed()...)
genBytes = append(genBytes, cnonce...)
h = ek.Value.Hash(genBytes)
}
cipherText, err = ek.encryptWithRandNonce(msg, params.MessageIsHashed, params.Blinding, h, cnonce)
if err != nil {
return nil, nil, err
}
if params.GenProof {
if params.ProofNonce == nil {
return nil, nil, internal.ErrNilArguments
}
proof, err = ek.genProof(params.ProofNonce, msg, params.MessageIsHashed, cipherText, params.Blinding, h)
if err != nil {
return nil, nil, err
}
}
return cipherText, proof, nil
}
func (ek EncryptionKey) genProof(nonce, msg []byte, msgIsHashed bool, cipherText *CipherText, blinding curves.Scalar, h curves.Point) (*ProofVerEnc, error) {
r := ek.Value.Scalar().Random(crand.Reader)
// R1 = r * G
r1 := ek.Value.Generator().Mul(r)
// R2 = r * Q + b * H
r2 := ek.Value.Mul(r).Add(h.Mul(blinding))
challengeBytes := append(cipherText.C1.ToAffineCompressed(), cipherText.C2.ToAffineCompressed()...)
challengeBytes = append(challengeBytes, r1.ToAffineCompressed()...)
challengeBytes = append(challengeBytes, r2.ToAffineCompressed()...)
challengeBytes = append(challengeBytes, nonce...)
challenge := ek.Value.Scalar().Hash(challengeBytes)
// b - cm
var msgScalar curves.Scalar
var err error
msgScalar = r.New(0)
if msgIsHashed {
msgScalar, err = msgScalar.SetBytes(msg)
if err != nil {
return nil, err
}
} else {
msgScalar = msgScalar.Hash(msg)
}
schnorr1 := blinding.Sub(challenge.Mul(msgScalar))
// r - cb
schnorr2 := r.Sub(challenge.Mul(blinding))
return &ProofVerEnc{challenge, schnorr1, schnorr2}, nil
}
// VerifyDomainEncryptProof a Proof of Verifiable Encryption
// that was generated with EncryptDomainAndProve or
// EncryptDomainAndProveBlinding.
func (ek EncryptionKey) VerifyDomainEncryptProof(nonce []byte, ciphertext *CipherText, proof *ProofVerEnc) error {
if ciphertext == nil || proof == nil {
return internal.ErrNilArguments
}
if proof.challenge == nil || proof.schnorr1 == nil || proof.schnorr2 == nil {
return internal.ErrNilArguments
}
if ciphertext.C1 == nil || ciphertext.C2 == nil {
return internal.ErrNilArguments
}
genBytes := append(nonce, ek.Value.ToAffineUncompressed()...)
genBytes = append(genBytes, ciphertext.Nonce[:]...)
h := ek.Value.Hash(genBytes)
return ek.verify(nonce, ciphertext, proof, h)
}
// VerifyEncryptProof a Proof of Verifiable Encryption
// that was generated with EncryptAndProve or
// EncryptAndProveBlinding
func (ek EncryptionKey) VerifyEncryptProof(nonce []byte, ciphertext *CipherText, proof *ProofVerEnc) error {
if ciphertext == nil || proof == nil {
return internal.ErrNilArguments
}
if proof.challenge == nil || proof.schnorr1 == nil || proof.schnorr2 == nil {
return internal.ErrNilArguments
}
if ciphertext.C1 == nil || ciphertext.C2 == nil {
return internal.ErrNilArguments
}
h := ek.Value.Generator()
return ek.verify(nonce, ciphertext, proof, h)
}
func (ek EncryptionKey) verify(nonce []byte, ciphertext *CipherText, proof *ProofVerEnc, h curves.Point) error {
// Reconstruct R1
// R1 = c * C1 + schnorr2 * G = c * ( b * G ) + (r - cb) * G
// = (cb + r - cb) * G = r * G
r1 := ciphertext.C1.Mul(proof.challenge).Add(ek.Value.Generator().Mul(proof.schnorr2))
// Reconstruct R2
// R2 = c * C2 + schnorr2 * Q + schnorr1 * H =
// c * (b * Q + m * H) + (r - cb) * Q + (b - cm) * H =
// (cb + r - cb) * Q + (cm + b - cm) * H =
// r * Q + b * H
r2 := ciphertext.C2.Mul(proof.challenge).Add(ek.Value.Mul(proof.schnorr2)).Add(h.Mul(proof.schnorr1))
challengeBytes := append(ciphertext.C1.ToAffineCompressed(), ciphertext.C2.ToAffineCompressed()...)
challengeBytes = append(challengeBytes, r1.ToAffineCompressed()...)
challengeBytes = append(challengeBytes, r2.ToAffineCompressed()...)
challengeBytes = append(challengeBytes, nonce...)
challenge := proof.challenge.Hash(challengeBytes)
if challenge.Cmp(proof.challenge) == 0 {
return nil
}
return fmt.Errorf("invalid ciphertext")
}