From b39bd89b47dfbcbbaf4a4554395d52b94e3fa521 Mon Sep 17 00:00:00 2001 From: Prad Nukala Date: Sat, 10 Jan 2026 15:39:43 -0500 Subject: [PATCH] feat(crypto): add simple enclave implementation for MPC operations --- internal/crypto/mpc/simple.go | 219 ++++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 internal/crypto/mpc/simple.go diff --git a/internal/crypto/mpc/simple.go b/internal/crypto/mpc/simple.go new file mode 100644 index 0000000..ca21d51 --- /dev/null +++ b/internal/crypto/mpc/simple.go @@ -0,0 +1,219 @@ +package mpc + +import ( + "crypto/ecdsa" + "crypto/rand" + "encoding/hex" + "fmt" + "math/big" + + "github.com/sonr-io/crypto/core/curves" + "golang.org/x/crypto/sha3" +) + +type SimpleEnclave struct { + pubKey *ecdsa.PublicKey + pubHex string + pubBytes []byte + share1 []byte + share2 []byte + nonce []byte + curveName CurveName +} + +func NewSimpleEnclave() (*SimpleEnclave, error) { + curve := curves.K256() + ecCurve, err := curve.ToEllipticCurve() + if err != nil { + return nil, fmt.Errorf("get elliptic curve: %w", err) + } + + privKey, err := ecdsa.GenerateKey(ecCurve, rand.Reader) + if err != nil { + return nil, fmt.Errorf("generate key: %w", err) + } + + share1, share2, err := splitSecret(privKey.D, ecCurve.Params().N) + if err != nil { + return nil, fmt.Errorf("split secret: %w", err) + } + + pubBytes := make([]byte, 65) + pubBytes[0] = 0x04 + copy(pubBytes[1:33], padTo32(privKey.PublicKey.X.Bytes())) + copy(pubBytes[33:65], padTo32(privKey.PublicKey.Y.Bytes())) + + compressed := compressPubKey(pubBytes) + + nonce := make([]byte, 12) + rand.Read(nonce) + + return &SimpleEnclave{ + pubKey: &privKey.PublicKey, + pubHex: hex.EncodeToString(compressed), + pubBytes: pubBytes, + share1: share1, + share2: share2, + nonce: nonce, + curveName: K256Name, + }, nil +} + +func splitSecret(secret *big.Int, order *big.Int) ([]byte, []byte, error) { + share1Bytes := make([]byte, 32) + _, err := rand.Read(share1Bytes) + if err != nil { + return nil, nil, err + } + + share1 := new(big.Int).SetBytes(share1Bytes) + share1.Mod(share1, order) + + share2 := new(big.Int).Sub(secret, share1) + share2.Mod(share2, order) + + return padTo32(share1.Bytes()), padTo32(share2.Bytes()), nil +} + +func combineShares(share1, share2 []byte, order *big.Int) *big.Int { + s1 := new(big.Int).SetBytes(share1) + s2 := new(big.Int).SetBytes(share2) + result := new(big.Int).Add(s1, s2) + return result.Mod(result, order) +} + +func (e *SimpleEnclave) Sign(data []byte) ([]byte, error) { + curve := curves.K256() + ecCurve, err := curve.ToEllipticCurve() + if err != nil { + return nil, err + } + + privKeyD := combineShares(e.share1, e.share2, ecCurve.Params().N) + + privKey := &ecdsa.PrivateKey{ + PublicKey: *e.pubKey, + D: privKeyD, + } + + hash := sha3.New256() + hash.Write(data) + digest := hash.Sum(nil) + + r, s, err := ecdsa.Sign(rand.Reader, privKey, digest) + if err != nil { + return nil, err + } + + sig := make([]byte, 64) + copy(sig[0:32], padTo32(r.Bytes())) + copy(sig[32:64], padTo32(s.Bytes())) + return sig, nil +} + +func (e *SimpleEnclave) Verify(data []byte, sig []byte) (bool, error) { + if len(sig) != 64 { + return false, fmt.Errorf("invalid signature length: %d", len(sig)) + } + + r := new(big.Int).SetBytes(sig[:32]) + s := new(big.Int).SetBytes(sig[32:]) + + hash := sha3.New256() + hash.Write(data) + digest := hash.Sum(nil) + + return ecdsa.Verify(e.pubKey, digest, r, s), nil +} + +func (e *SimpleEnclave) PubKeyHex() string { return e.pubHex } +func (e *SimpleEnclave) PubKeyBytes() []byte { return e.pubBytes } +func (e *SimpleEnclave) IsValid() bool { return len(e.share1) > 0 && len(e.share2) > 0 } +func (e *SimpleEnclave) GetShare1() []byte { return e.share1 } +func (e *SimpleEnclave) GetShare2() []byte { return e.share2 } +func (e *SimpleEnclave) GetNonce() []byte { return e.nonce } +func (e *SimpleEnclave) GetCurve() CurveName { return e.curveName } + +func (e *SimpleEnclave) Refresh() (*SimpleEnclave, error) { + curve := curves.K256() + ecCurve, err := curve.ToEllipticCurve() + if err != nil { + return nil, err + } + + privKeyD := combineShares(e.share1, e.share2, ecCurve.Params().N) + newShare1, newShare2, err := splitSecret(privKeyD, ecCurve.Params().N) + if err != nil { + return nil, err + } + + newNonce := make([]byte, 12) + rand.Read(newNonce) + + return &SimpleEnclave{ + pubKey: e.pubKey, + pubHex: e.pubHex, + pubBytes: e.pubBytes, + share1: newShare1, + share2: newShare2, + nonce: newNonce, + curveName: e.curveName, + }, nil +} + +func ImportSimpleEnclave(pubBytes, share1, share2, nonce []byte, curveName CurveName) (*SimpleEnclave, error) { + if len(pubBytes) != 65 { + return nil, fmt.Errorf("invalid pubkey length: %d", len(pubBytes)) + } + + curve := curves.K256() + ecCurve, err := curve.ToEllipticCurve() + if err != nil { + return nil, err + } + + x := new(big.Int).SetBytes(pubBytes[1:33]) + y := new(big.Int).SetBytes(pubBytes[33:65]) + + pubKey := &ecdsa.PublicKey{ + Curve: ecCurve, + X: x, + Y: y, + } + + compressed := compressPubKey(pubBytes) + + return &SimpleEnclave{ + pubKey: pubKey, + pubHex: hex.EncodeToString(compressed), + pubBytes: pubBytes, + share1: share1, + share2: share2, + nonce: nonce, + curveName: curveName, + }, nil +} + +func padTo32(b []byte) []byte { + if len(b) >= 32 { + return b[:32] + } + padded := make([]byte, 32) + copy(padded[32-len(b):], b) + return padded +} + +func compressPubKey(uncompressed []byte) []byte { + if len(uncompressed) != 65 { + return uncompressed + } + + compressed := make([]byte, 33) + if uncompressed[64]&1 == 0 { + compressed[0] = 0x02 + } else { + compressed[0] = 0x03 + } + copy(compressed[1:], uncompressed[1:33]) + return compressed +}