ceremonyclient/nekryptology/pkg/verenc/elgamal/decryption_key.go
2023-07-05 00:32:28 -05:00

166 lines
4.7 KiB
Go

//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package elgamal
import (
"crypto/aes"
"crypto/cipher"
"fmt"
"math/big"
"git.sr.ht/~sircmpwn/go-bare"
"source.quilibrium.com/quilibrium/monorepo/nekryptology/internal"
"source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/core"
"source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/core/curves"
)
type decryptionKeyMarshal struct {
X []byte `bare:"x"`
Curve string `bare:"curve"`
}
// DecryptionKey decrypts verifiable ciphertext and verifies proofs
type DecryptionKey struct {
x curves.Scalar
}
// EncryptionKey returns the corresponding encryption key for this decryption key
func (dk DecryptionKey) EncryptionKey() *EncryptionKey {
return &EncryptionKey{
Value: dk.x.Point().Generator().Mul(dk.x),
}
}
// MarshalBinary serializes a key to bytes
func (dk DecryptionKey) MarshalBinary() ([]byte, error) {
tv := new(decryptionKeyMarshal)
tv.X = dk.x.Bytes()
tv.Curve = dk.x.Point().CurveName()
return bare.Marshal(tv)
}
// UnmarshalBinary deserializes a key from bytes
func (dk *DecryptionKey) UnmarshalBinary(data []byte) error {
tv := new(decryptionKeyMarshal)
err := bare.Unmarshal(data, tv)
if err != nil {
return err
}
curve := curves.GetCurveByName(tv.Curve)
if curve == nil {
return fmt.Errorf("unknown curve")
}
x, err := curve.Scalar.SetBytes(tv.X)
if err != nil {
return err
}
dk.x = x
return nil
}
// Decrypt returns the resulting point from El-Gamal decryption.
// No checking is performed whether the ciphertext has been modified.
// M = H * m = C2 - C1 * x
func (dk *DecryptionKey) Decrypt(cipherText *HomomorphicCipherText) curves.Point {
if cipherText == nil {
return nil
}
return cipherText.C2.Sub(cipherText.C1.Mul(dk.x))
}
// VerifiableDecrypt the ciphertext. This performs verifiable decryption
// such that the decrypted data is checked against El-Gamal C2 value.
// If the plaintext does not match, an error is returned
// been generated by Encrypt
func (dk DecryptionKey) VerifiableDecrypt(cipherText *CipherText) ([]byte, curves.Scalar, error) {
msgBytes, msgScalar, rhs, err := dk.decryptData(cipherText)
if err != nil {
return nil, nil, err
}
h := dk.EncryptionKey().Value.Generator()
lhs := h.Mul(msgScalar)
if !lhs.Equal(rhs) {
return nil, nil, fmt.Errorf("ciphertext mismatch")
}
return msgBytes, msgScalar, nil
}
// VerifiableDecryptWithDomain the ciphertext. This performs verifiable decryption
// such that the decrypted data is checked against El-Gamal C2 value.
// If the plaintext does not match, an error is returned
// The Domain component is meant for scenarios where `msg` is used in more
// than just one setting and should be contextualized. The ciphertext must have
// been generated by EncryptWithDomain
func (dk DecryptionKey) VerifiableDecryptWithDomain(domain []byte, cipherText *CipherText) ([]byte, curves.Scalar, error) {
msgBytes, msgScalar, rhs, err := dk.decryptData(cipherText)
if err != nil {
return nil, nil, err
}
ek := dk.EncryptionKey()
genBytes := append(domain, ek.Value.ToAffineUncompressed()...)
genBytes = append(genBytes, cipherText.Nonce...)
h := ek.Value.Hash(genBytes)
lhs := h.Mul(msgScalar)
if !lhs.Equal(rhs) {
return nil, nil, fmt.Errorf("ciphertext mismatch")
}
return msgBytes, msgScalar, nil
}
func (dk DecryptionKey) decryptData(cipherText *CipherText) ([]byte, curves.Scalar, curves.Point, error) {
if cipherText == nil {
return nil, nil, nil, internal.ErrNilArguments
}
if cipherText.C1 == nil || cipherText.C2 == nil || cipherText.Nonce == nil || cipherText.Aead == nil {
return nil, nil, nil, internal.ErrNilArguments
}
// Have to check these because aesgcm will panic if not the correct length
if len(cipherText.Nonce) < 12 || len(cipherText.Aead) < 16 {
return nil, nil, nil, internal.ErrZeroValue
}
// r * Q
t := cipherText.C1.Mul(dk.x)
aeadKey, err := core.FiatShamir(new(big.Int).SetBytes(t.ToAffineCompressed()))
if err != nil {
return nil, nil, nil, err
}
block, err := aes.NewCipher(aeadKey)
if err != nil {
return nil, nil, nil, err
}
aesGcm, err := cipher.NewGCM(block)
if err != nil {
return nil, nil, nil, err
}
aad := cipherText.C1.ToAffineUncompressed()
aad = append(aad, cipherText.C2.ToAffineUncompressed()...)
// AAD = C1 || C2
msgBytes, err := aesGcm.Open(nil, cipherText.Nonce, cipherText.Aead, aad)
if err != nil {
return nil, nil, nil, err
}
msg := dk.x.New(0)
if cipherText.MsgIsHashed {
msg, err = msg.SetBytes(msgBytes)
if err != nil {
return nil, nil, nil, err
}
} else {
msg = msg.Hash(msgBytes)
}
if err != nil {
return nil, nil, nil, err
}
rhs := cipherText.C2.Sub(t)
return msgBytes, msg, rhs, nil
}