rwadurian/backend/mpc-system/pkg/crypto/crypto.go

375 lines
9.2 KiB
Go

package crypto
import (
"crypto/aes"
"crypto/cipher"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"errors"
"io"
"math/big"
"golang.org/x/crypto/hkdf"
)
var (
ErrInvalidKeySize = errors.New("invalid key size")
ErrInvalidCipherText = errors.New("invalid ciphertext")
ErrEncryptionFailed = errors.New("encryption failed")
ErrDecryptionFailed = errors.New("decryption failed")
ErrInvalidPublicKey = errors.New("invalid public key")
ErrInvalidSignature = errors.New("invalid signature")
)
// CryptoService provides cryptographic operations
type CryptoService struct {
masterKey []byte
}
// NewCryptoService creates a new crypto service
func NewCryptoService(masterKey []byte) (*CryptoService, error) {
if len(masterKey) != 32 {
return nil, ErrInvalidKeySize
}
return &CryptoService{masterKey: masterKey}, nil
}
// GenerateRandomBytes generates random bytes
func GenerateRandomBytes(n int) ([]byte, error) {
b := make([]byte, n)
_, err := rand.Read(b)
if err != nil {
return nil, err
}
return b, nil
}
// GenerateRandomHex generates a random hex string
func GenerateRandomHex(n int) (string, error) {
bytes, err := GenerateRandomBytes(n)
if err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}
// DeriveKey derives a key from the master key using HKDF
func (c *CryptoService) DeriveKey(context string, length int) ([]byte, error) {
hkdfReader := hkdf.New(sha256.New, c.masterKey, nil, []byte(context))
key := make([]byte, length)
if _, err := io.ReadFull(hkdfReader, key); err != nil {
return nil, err
}
return key, nil
}
// EncryptShare encrypts a key share using AES-256-GCM
func (c *CryptoService) EncryptShare(shareData []byte, partyID string) ([]byte, error) {
// Derive a unique key for this party
key, err := c.DeriveKey("share_encryption:"+partyID, 32)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := make([]byte, aesGCM.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
// Encrypt and prepend nonce
ciphertext := aesGCM.Seal(nonce, nonce, shareData, []byte(partyID))
return ciphertext, nil
}
// DecryptShare decrypts a key share
func (c *CryptoService) DecryptShare(encryptedData []byte, partyID string) ([]byte, error) {
// Derive the same key used for encryption
key, err := c.DeriveKey("share_encryption:"+partyID, 32)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonceSize := aesGCM.NonceSize()
if len(encryptedData) < nonceSize {
return nil, ErrInvalidCipherText
}
nonce, ciphertext := encryptedData[:nonceSize], encryptedData[nonceSize:]
plaintext, err := aesGCM.Open(nil, nonce, ciphertext, []byte(partyID))
if err != nil {
return nil, ErrDecryptionFailed
}
return plaintext, nil
}
// EncryptMessage encrypts a message using AES-256-GCM
func (c *CryptoService) EncryptMessage(plaintext []byte) ([]byte, error) {
block, err := aes.NewCipher(c.masterKey)
if err != nil {
return nil, err
}
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := make([]byte, aesGCM.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
ciphertext := aesGCM.Seal(nonce, nonce, plaintext, nil)
return ciphertext, nil
}
// DecryptMessage decrypts a message
func (c *CryptoService) DecryptMessage(ciphertext []byte) ([]byte, error) {
block, err := aes.NewCipher(c.masterKey)
if err != nil {
return nil, err
}
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonceSize := aesGCM.NonceSize()
if len(ciphertext) < nonceSize {
return nil, ErrInvalidCipherText
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, ErrDecryptionFailed
}
return plaintext, nil
}
// Hash256 computes SHA-256 hash
func Hash256(data []byte) []byte {
hash := sha256.Sum256(data)
return hash[:]
}
// VerifyECDSASignature verifies an ECDSA signature
func VerifyECDSASignature(messageHash, signature, publicKey []byte) (bool, error) {
// Parse public key (assuming secp256k1/P256 uncompressed format)
curve := elliptic.P256()
x, y := elliptic.Unmarshal(curve, publicKey)
if x == nil {
return false, ErrInvalidPublicKey
}
pubKey := &ecdsa.PublicKey{
Curve: curve,
X: x,
Y: y,
}
// Parse signature (R || S, each 32 bytes)
if len(signature) != 64 {
return false, ErrInvalidSignature
}
r := new(big.Int).SetBytes(signature[:32])
s := new(big.Int).SetBytes(signature[32:])
// Verify signature
valid := ecdsa.Verify(pubKey, messageHash, r, s)
return valid, nil
}
// GenerateNonce generates a cryptographic nonce
func GenerateNonce() ([]byte, error) {
return GenerateRandomBytes(32)
}
// SecureCompare performs constant-time comparison
func SecureCompare(a, b []byte) bool {
if len(a) != len(b) {
return false
}
var result byte
for i := 0; i < len(a); i++ {
result |= a[i] ^ b[i]
}
return result == 0
}
// ParsePublicKey parses a public key from bytes (P256 uncompressed format)
func ParsePublicKey(publicKeyBytes []byte) (*ecdsa.PublicKey, error) {
curve := elliptic.P256()
x, y := elliptic.Unmarshal(curve, publicKeyBytes)
if x == nil {
return nil, ErrInvalidPublicKey
}
return &ecdsa.PublicKey{
Curve: curve,
X: x,
Y: y,
}, nil
}
// VerifySignature verifies an ECDSA signature using a public key
func VerifySignature(pubKey *ecdsa.PublicKey, messageHash, signature []byte) bool {
// Parse signature (R || S, each 32 bytes)
if len(signature) != 64 {
return false
}
r := new(big.Int).SetBytes(signature[:32])
s := new(big.Int).SetBytes(signature[32:])
return ecdsa.Verify(pubKey, messageHash, r, s)
}
// HashMessage computes SHA-256 hash of a message (alias for Hash256)
func HashMessage(message []byte) []byte {
return Hash256(message)
}
// Encrypt encrypts data using AES-256-GCM with the provided key
func Encrypt(key, plaintext []byte) ([]byte, error) {
if len(key) != 32 {
return nil, ErrInvalidKeySize
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := make([]byte, aesGCM.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
ciphertext := aesGCM.Seal(nonce, nonce, plaintext, nil)
return ciphertext, nil
}
// Decrypt decrypts data using AES-256-GCM with the provided key
func Decrypt(key, ciphertext []byte) ([]byte, error) {
if len(key) != 32 {
return nil, ErrInvalidKeySize
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonceSize := aesGCM.NonceSize()
if len(ciphertext) < nonceSize {
return nil, ErrInvalidCipherText
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, ErrDecryptionFailed
}
return plaintext, nil
}
// DeriveKey derives a key from secret and salt using HKDF (standalone function)
func DeriveKey(secret, salt []byte, length int) ([]byte, error) {
hkdfReader := hkdf.New(sha256.New, secret, salt, nil)
key := make([]byte, length)
if _, err := io.ReadFull(hkdfReader, key); err != nil {
return nil, err
}
return key, nil
}
// SignMessage signs a message using ECDSA private key
func SignMessage(privateKey *ecdsa.PrivateKey, message []byte) ([]byte, error) {
hash := Hash256(message)
r, s, err := ecdsa.Sign(rand.Reader, privateKey, hash)
if err != nil {
return nil, err
}
// Encode R and S as 32 bytes each (total 64 bytes)
signature := make([]byte, 64)
rBytes := r.Bytes()
sBytes := s.Bytes()
// Pad with zeros if necessary
copy(signature[32-len(rBytes):32], rBytes)
copy(signature[64-len(sBytes):64], sBytes)
return signature, nil
}
// EncodeToHex encodes bytes to hex string
func EncodeToHex(data []byte) string {
return hex.EncodeToString(data)
}
// DecodeFromHex decodes hex string to bytes
func DecodeFromHex(s string) ([]byte, error) {
return hex.DecodeString(s)
}
// EncodeToBase64 encodes bytes to base64 string
func EncodeToBase64(data []byte) string {
return hex.EncodeToString(data) // Using hex for simplicity, could use base64
}
// DecodeFromBase64 decodes base64 string to bytes
func DecodeFromBase64(s string) ([]byte, error) {
return hex.DecodeString(s)
}
// MarshalPublicKey marshals an ECDSA public key to bytes
func MarshalPublicKey(pubKey *ecdsa.PublicKey) []byte {
return elliptic.Marshal(pubKey.Curve, pubKey.X, pubKey.Y)
}
// CompareBytes performs constant-time comparison of two byte slices
func CompareBytes(a, b []byte) bool {
return SecureCompare(a, b)
}