mirror of
				https://source.quilibrium.com/quilibrium/ceremonyclient.git
				synced 2025-11-04 00:27:41 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			166 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			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
 | 
						|
}
 |