mirror of
https://source.quilibrium.com/quilibrium/ceremonyclient.git
synced 2024-12-30 18:35:18 +00:00
213 lines
6.4 KiB
Go
213 lines
6.4 KiB
Go
|
//
|
||
|
// 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")
|
||
|
}
|