rwadurian/backend/mpc-system/coverage.html

2468 lines
92 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>crypto: Go Coverage Report</title>
<style>
body {
background: black;
color: rgb(80, 80, 80);
}
body, pre, #legend span {
font-family: Menlo, monospace;
font-weight: bold;
}
#topbar {
background: black;
position: fixed;
top: 0; left: 0; right: 0;
height: 42px;
border-bottom: 1px solid rgb(80, 80, 80);
}
#content {
margin-top: 50px;
}
#nav, #legend {
float: left;
margin-left: 10px;
}
#legend {
margin-top: 12px;
}
#nav {
margin-top: 10px;
}
#legend span {
margin: 0 5px;
}
.cov0 { color: rgb(192, 0, 0) }
.cov1 { color: rgb(128, 128, 128) }
.cov2 { color: rgb(116, 140, 131) }
.cov3 { color: rgb(104, 152, 134) }
.cov4 { color: rgb(92, 164, 137) }
.cov5 { color: rgb(80, 176, 140) }
.cov6 { color: rgb(68, 188, 143) }
.cov7 { color: rgb(56, 200, 146) }
.cov8 { color: rgb(44, 212, 149) }
.cov9 { color: rgb(32, 224, 152) }
.cov10 { color: rgb(20, 236, 155) }
</style>
</head>
<body>
<div id="topbar">
<div id="nav">
<select id="files">
<option value="file0">github.com/rwadurian/mpc-system/pkg/crypto/crypto.go (38.3%)</option>
<option value="file1">github.com/rwadurian/mpc-system/pkg/jwt/jwt.go (83.3%)</option>
<option value="file2">github.com/rwadurian/mpc-system/pkg/utils/utils.go (88.5%)</option>
<option value="file3">github.com/rwadurian/mpc-system/services/account/domain/entities/account.go (95.0%)</option>
<option value="file4">github.com/rwadurian/mpc-system/services/account/domain/entities/account_share.go (68.4%)</option>
<option value="file5">github.com/rwadurian/mpc-system/services/account/domain/entities/recovery_session.go (56.0%)</option>
<option value="file6">github.com/rwadurian/mpc-system/services/account/domain/value_objects/account_id.go (80.0%)</option>
<option value="file7">github.com/rwadurian/mpc-system/services/account/domain/value_objects/account_status.go (44.4%)</option>
<option value="file8">github.com/rwadurian/mpc-system/services/session-coordinator/domain/entities/device_info.go (28.6%)</option>
<option value="file9">github.com/rwadurian/mpc-system/services/session-coordinator/domain/entities/mpc_session.go (12.2%)</option>
<option value="file10">github.com/rwadurian/mpc-system/services/session-coordinator/domain/entities/participant.go (60.7%)</option>
<option value="file11">github.com/rwadurian/mpc-system/services/session-coordinator/domain/entities/session_message.go (0.0%)</option>
<option value="file12">github.com/rwadurian/mpc-system/services/session-coordinator/domain/value_objects/party_id.go (57.1%)</option>
<option value="file13">github.com/rwadurian/mpc-system/services/session-coordinator/domain/value_objects/session_id.go (80.0%)</option>
<option value="file14">github.com/rwadurian/mpc-system/services/session-coordinator/domain/value_objects/session_status.go (18.8%)</option>
<option value="file15">github.com/rwadurian/mpc-system/services/session-coordinator/domain/value_objects/threshold.go (55.0%)</option>
</select>
</div>
<div id="legend">
<span>not tracked</span>
<span class="cov0">no coverage</span>
<span class="cov1">low coverage</span>
<span class="cov2">*</span>
<span class="cov3">*</span>
<span class="cov4">*</span>
<span class="cov5">*</span>
<span class="cov6">*</span>
<span class="cov7">*</span>
<span class="cov8">*</span>
<span class="cov9">*</span>
<span class="cov10">high coverage</span>
</div>
</div>
<div id="content">
<pre class="file" id="file0" style="display: none">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) <span class="cov0" title="0">{
if len(masterKey) != 32 </span><span class="cov0" title="0">{
return nil, ErrInvalidKeySize
}</span>
<span class="cov0" title="0">return &amp;CryptoService{masterKey: masterKey}, nil</span>
}
// GenerateRandomBytes generates random bytes
func GenerateRandomBytes(n int) ([]byte, error) <span class="cov6" title="6">{
b := make([]byte, n)
_, err := rand.Read(b)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov6" title="6">return b, nil</span>
}
// GenerateRandomHex generates a random hex string
func GenerateRandomHex(n int) (string, error) <span class="cov0" title="0">{
bytes, err := GenerateRandomBytes(n)
if err != nil </span><span class="cov0" title="0">{
return "", err
}</span>
<span class="cov0" title="0">return hex.EncodeToString(bytes), nil</span>
}
// DeriveKey derives a key from the master key using HKDF
func (c *CryptoService) DeriveKey(context string, length int) ([]byte, error) <span class="cov0" title="0">{
hkdfReader := hkdf.New(sha256.New, c.masterKey, nil, []byte(context))
key := make([]byte, length)
if _, err := io.ReadFull(hkdfReader, key); err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov0" title="0">return key, nil</span>
}
// EncryptShare encrypts a key share using AES-256-GCM
func (c *CryptoService) EncryptShare(shareData []byte, partyID string) ([]byte, error) <span class="cov0" title="0">{
// Derive a unique key for this party
key, err := c.DeriveKey("share_encryption:"+partyID, 32)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov0" title="0">block, err := aes.NewCipher(key)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov0" title="0">aesGCM, err := cipher.NewGCM(block)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov0" title="0">nonce := make([]byte, aesGCM.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
// Encrypt and prepend nonce
<span class="cov0" title="0">ciphertext := aesGCM.Seal(nonce, nonce, shareData, []byte(partyID))
return ciphertext, nil</span>
}
// DecryptShare decrypts a key share
func (c *CryptoService) DecryptShare(encryptedData []byte, partyID string) ([]byte, error) <span class="cov0" title="0">{
// Derive the same key used for encryption
key, err := c.DeriveKey("share_encryption:"+partyID, 32)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov0" title="0">block, err := aes.NewCipher(key)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov0" title="0">aesGCM, err := cipher.NewGCM(block)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov0" title="0">nonceSize := aesGCM.NonceSize()
if len(encryptedData) &lt; nonceSize </span><span class="cov0" title="0">{
return nil, ErrInvalidCipherText
}</span>
<span class="cov0" title="0">nonce, ciphertext := encryptedData[:nonceSize], encryptedData[nonceSize:]
plaintext, err := aesGCM.Open(nil, nonce, ciphertext, []byte(partyID))
if err != nil </span><span class="cov0" title="0">{
return nil, ErrDecryptionFailed
}</span>
<span class="cov0" title="0">return plaintext, nil</span>
}
// EncryptMessage encrypts a message using AES-256-GCM
func (c *CryptoService) EncryptMessage(plaintext []byte) ([]byte, error) <span class="cov0" title="0">{
block, err := aes.NewCipher(c.masterKey)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov0" title="0">aesGCM, err := cipher.NewGCM(block)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov0" title="0">nonce := make([]byte, aesGCM.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov0" title="0">ciphertext := aesGCM.Seal(nonce, nonce, plaintext, nil)
return ciphertext, nil</span>
}
// DecryptMessage decrypts a message
func (c *CryptoService) DecryptMessage(ciphertext []byte) ([]byte, error) <span class="cov0" title="0">{
block, err := aes.NewCipher(c.masterKey)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov0" title="0">aesGCM, err := cipher.NewGCM(block)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov0" title="0">nonceSize := aesGCM.NonceSize()
if len(ciphertext) &lt; nonceSize </span><span class="cov0" title="0">{
return nil, ErrInvalidCipherText
}</span>
<span class="cov0" title="0">nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil)
if err != nil </span><span class="cov0" title="0">{
return nil, ErrDecryptionFailed
}</span>
<span class="cov0" title="0">return plaintext, nil</span>
}
// Hash256 computes SHA-256 hash
func Hash256(data []byte) []byte <span class="cov7" title="10">{
hash := sha256.Sum256(data)
return hash[:]
}</span>
// VerifyECDSASignature verifies an ECDSA signature
func VerifyECDSASignature(messageHash, signature, publicKey []byte) (bool, error) <span class="cov0" title="0">{
// Parse public key (assuming secp256k1/P256 uncompressed format)
curve := elliptic.P256()
x, y := elliptic.Unmarshal(curve, publicKey)
if x == nil </span><span class="cov0" title="0">{
return false, ErrInvalidPublicKey
}</span>
<span class="cov0" title="0">pubKey := &amp;ecdsa.PublicKey{
Curve: curve,
X: x,
Y: y,
}
// Parse signature (R || S, each 32 bytes)
if len(signature) != 64 </span><span class="cov0" title="0">{
return false, ErrInvalidSignature
}</span>
<span class="cov0" title="0">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</span>
}
// GenerateNonce generates a cryptographic nonce
func GenerateNonce() ([]byte, error) <span class="cov0" title="0">{
return GenerateRandomBytes(32)
}</span>
// SecureCompare performs constant-time comparison
func SecureCompare(a, b []byte) bool <span class="cov4" title="3">{
if len(a) != len(b) </span><span class="cov1" title="1">{
return false
}</span>
<span class="cov3" title="2">var result byte
for i := 0; i &lt; len(a); i++ </span><span class="cov10" title="20">{
result |= a[i] ^ b[i]
}</span>
<span class="cov3" title="2">return result == 0</span>
}
// ParsePublicKey parses a public key from bytes (P256 uncompressed format)
func ParsePublicKey(publicKeyBytes []byte) (*ecdsa.PublicKey, error) <span class="cov1" title="1">{
curve := elliptic.P256()
x, y := elliptic.Unmarshal(curve, publicKeyBytes)
if x == nil </span><span class="cov0" title="0">{
return nil, ErrInvalidPublicKey
}</span>
<span class="cov1" title="1">return &amp;ecdsa.PublicKey{
Curve: curve,
X: x,
Y: y,
}, nil</span>
}
// VerifySignature verifies an ECDSA signature using a public key
func VerifySignature(pubKey *ecdsa.PublicKey, messageHash, signature []byte) bool <span class="cov4" title="3">{
// Parse signature (R || S, each 32 bytes)
if len(signature) != 64 </span><span class="cov0" title="0">{
return false
}</span>
<span class="cov4" title="3">r := new(big.Int).SetBytes(signature[:32])
s := new(big.Int).SetBytes(signature[32:])
return ecdsa.Verify(pubKey, messageHash, r, s)</span>
}
// HashMessage computes SHA-256 hash of a message (alias for Hash256)
func HashMessage(message []byte) []byte <span class="cov6" title="7">{
return Hash256(message)
}</span>
// Encrypt encrypts data using AES-256-GCM with the provided key
func Encrypt(key, plaintext []byte) ([]byte, error) <span class="cov5" title="4">{
if len(key) != 32 </span><span class="cov0" title="0">{
return nil, ErrInvalidKeySize
}</span>
<span class="cov5" title="4">block, err := aes.NewCipher(key)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov5" title="4">aesGCM, err := cipher.NewGCM(block)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov5" title="4">nonce := make([]byte, aesGCM.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov5" title="4">ciphertext := aesGCM.Seal(nonce, nonce, plaintext, nil)
return ciphertext, nil</span>
}
// Decrypt decrypts data using AES-256-GCM with the provided key
func Decrypt(key, ciphertext []byte) ([]byte, error) <span class="cov3" title="2">{
if len(key) != 32 </span><span class="cov0" title="0">{
return nil, ErrInvalidKeySize
}</span>
<span class="cov3" title="2">block, err := aes.NewCipher(key)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov3" title="2">aesGCM, err := cipher.NewGCM(block)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov3" title="2">nonceSize := aesGCM.NonceSize()
if len(ciphertext) &lt; nonceSize </span><span class="cov0" title="0">{
return nil, ErrInvalidCipherText
}</span>
<span class="cov3" title="2">nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil)
if err != nil </span><span class="cov1" title="1">{
return nil, ErrDecryptionFailed
}</span>
<span class="cov1" title="1">return plaintext, nil</span>
}
// DeriveKey derives a key from secret and salt using HKDF (standalone function)
func DeriveKey(secret, salt []byte, length int) ([]byte, error) <span class="cov5" title="4">{
hkdfReader := hkdf.New(sha256.New, secret, salt, nil)
key := make([]byte, length)
if _, err := io.ReadFull(hkdfReader, key); err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov5" title="4">return key, nil</span>
}
// SignMessage signs a message using ECDSA private key
func SignMessage(privateKey *ecdsa.PrivateKey, message []byte) ([]byte, error) <span class="cov4" title="3">{
hash := Hash256(message)
r, s, err := ecdsa.Sign(rand.Reader, privateKey, hash)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
// Encode R and S as 32 bytes each (total 64 bytes)
<span class="cov4" title="3">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</span>
}
// EncodeToHex encodes bytes to hex string
func EncodeToHex(data []byte) string <span class="cov1" title="1">{
return hex.EncodeToString(data)
}</span>
// DecodeFromHex decodes hex string to bytes
func DecodeFromHex(s string) ([]byte, error) <span class="cov3" title="2">{
return hex.DecodeString(s)
}</span>
// EncodeToBase64 encodes bytes to base64 string
func EncodeToBase64(data []byte) string <span class="cov0" title="0">{
return hex.EncodeToString(data) // Using hex for simplicity, could use base64
}</span>
// DecodeFromBase64 decodes base64 string to bytes
func DecodeFromBase64(s string) ([]byte, error) <span class="cov0" title="0">{
return hex.DecodeString(s)
}</span>
// MarshalPublicKey marshals an ECDSA public key to bytes
func MarshalPublicKey(pubKey *ecdsa.PublicKey) []byte <span class="cov1" title="1">{
return elliptic.Marshal(pubKey.Curve, pubKey.X, pubKey.Y)
}</span>
// CompareBytes performs constant-time comparison of two byte slices
func CompareBytes(a, b []byte) bool <span class="cov4" title="3">{
return SecureCompare(a, b)
}</span>
</pre>
<pre class="file" id="file1" style="display: none">package jwt
import (
"errors"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
)
var (
ErrInvalidToken = errors.New("invalid token")
ErrExpiredToken = errors.New("token expired")
ErrInvalidClaims = errors.New("invalid claims")
ErrTokenNotYetValid = errors.New("token not yet valid")
)
// Claims represents custom JWT claims
type Claims struct {
SessionID string `json:"session_id"`
PartyID string `json:"party_id"`
TokenType string `json:"token_type"` // "join", "access", "refresh"
jwt.RegisteredClaims
}
// JWTService provides JWT operations
type JWTService struct {
secretKey []byte
issuer string
tokenExpiry time.Duration
refreshExpiry time.Duration
}
// NewJWTService creates a new JWT service
func NewJWTService(secretKey string, issuer string, tokenExpiry, refreshExpiry time.Duration) *JWTService <span class="cov6" title="4">{
return &amp;JWTService{
secretKey: []byte(secretKey),
issuer: issuer,
tokenExpiry: tokenExpiry,
refreshExpiry: refreshExpiry,
}
}</span>
// GenerateJoinToken generates a token for joining an MPC session
func (s *JWTService) GenerateJoinToken(sessionID uuid.UUID, partyID string, expiresIn time.Duration) (string, error) <span class="cov5" title="3">{
now := time.Now()
claims := Claims{
SessionID: sessionID.String(),
PartyID: partyID,
TokenType: "join",
RegisteredClaims: jwt.RegisteredClaims{
ID: uuid.New().String(),
Issuer: s.issuer,
Subject: partyID,
IssuedAt: jwt.NewNumericDate(now),
NotBefore: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(now.Add(expiresIn)),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(s.secretKey)
}</span>
// AccessTokenClaims represents claims in an access token
type AccessTokenClaims struct {
Subject string
Username string
Issuer string
}
// GenerateAccessToken generates an access token with username
func (s *JWTService) GenerateAccessToken(userID, username string) (string, error) <span class="cov6" title="4">{
now := time.Now()
claims := Claims{
TokenType: "access",
RegisteredClaims: jwt.RegisteredClaims{
ID: uuid.New().String(),
Issuer: s.issuer,
Subject: userID,
IssuedAt: jwt.NewNumericDate(now),
NotBefore: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(now.Add(s.tokenExpiry)),
},
}
// Store username in PartyID field for access tokens
claims.PartyID = username
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(s.secretKey)
}</span>
// GenerateRefreshToken generates a refresh token
func (s *JWTService) GenerateRefreshToken(userID string) (string, error) <span class="cov3" title="2">{
now := time.Now()
claims := Claims{
TokenType: "refresh",
RegisteredClaims: jwt.RegisteredClaims{
ID: uuid.New().String(),
Issuer: s.issuer,
Subject: userID,
IssuedAt: jwt.NewNumericDate(now),
NotBefore: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(now.Add(s.refreshExpiry)),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(s.secretKey)
}</span>
// ValidateToken validates a JWT token and returns the claims
func (s *JWTService) ValidateToken(tokenString string) (*Claims, error) <span class="cov10" title="11">{
token, err := jwt.ParseWithClaims(tokenString, &amp;Claims{}, func(token *jwt.Token) (interface{}, error) </span><span class="cov9" title="9">{
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok </span><span class="cov0" title="0">{
return nil, ErrInvalidToken
}</span>
<span class="cov9" title="9">return s.secretKey, nil</span>
})
<span class="cov10" title="11">if err != nil </span><span class="cov5" title="3">{
if errors.Is(err, jwt.ErrTokenExpired) </span><span class="cov0" title="0">{
return nil, ErrExpiredToken
}</span>
<span class="cov5" title="3">return nil, ErrInvalidToken</span>
}
<span class="cov8" title="8">claims, ok := token.Claims.(*Claims)
if !ok || !token.Valid </span><span class="cov0" title="0">{
return nil, ErrInvalidClaims
}</span>
<span class="cov8" title="8">return claims, nil</span>
}
// ValidateJoinToken validates a join token for MPC sessions
func (s *JWTService) ValidateJoinToken(tokenString string, sessionID uuid.UUID, partyID string) (*Claims, error) <span class="cov5" title="3">{
claims, err := s.ValidateToken(tokenString)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov5" title="3">if claims.TokenType != "join" </span><span class="cov0" title="0">{
return nil, ErrInvalidToken
}</span>
<span class="cov5" title="3">if claims.SessionID != sessionID.String() </span><span class="cov1" title="1">{
return nil, ErrInvalidClaims
}</span>
<span class="cov3" title="2">if claims.PartyID != partyID </span><span class="cov1" title="1">{
return nil, ErrInvalidClaims
}</span>
<span class="cov1" title="1">return claims, nil</span>
}
// RefreshAccessToken creates a new access token from a valid refresh token
func (s *JWTService) RefreshAccessToken(refreshToken string) (string, error) <span class="cov3" title="2">{
claims, err := s.ValidateToken(refreshToken)
if err != nil </span><span class="cov1" title="1">{
return "", err
}</span>
<span class="cov1" title="1">if claims.TokenType != "refresh" </span><span class="cov0" title="0">{
return "", ErrInvalidToken
}</span>
// PartyID stores the username for access tokens
<span class="cov1" title="1">return s.GenerateAccessToken(claims.Subject, claims.PartyID)</span>
}
// ValidateAccessToken validates an access token and returns structured claims
func (s *JWTService) ValidateAccessToken(tokenString string) (*AccessTokenClaims, error) <span class="cov7" title="5">{
claims, err := s.ValidateToken(tokenString)
if err != nil </span><span class="cov3" title="2">{
return nil, err
}</span>
<span class="cov5" title="3">if claims.TokenType != "access" </span><span class="cov0" title="0">{
return nil, ErrInvalidToken
}</span>
<span class="cov5" title="3">return &amp;AccessTokenClaims{
Subject: claims.Subject,
Username: claims.PartyID, // Username stored in PartyID for access tokens
Issuer: claims.Issuer,
}, nil</span>
}
// ValidateRefreshToken validates a refresh token and returns claims
func (s *JWTService) ValidateRefreshToken(tokenString string) (*Claims, error) <span class="cov1" title="1">{
claims, err := s.ValidateToken(tokenString)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov1" title="1">if claims.TokenType != "refresh" </span><span class="cov0" title="0">{
return nil, ErrInvalidToken
}</span>
<span class="cov1" title="1">return claims, nil</span>
}
// TokenGenerator interface for dependency injection
type TokenGenerator interface {
GenerateJoinToken(sessionID uuid.UUID, partyID string, expiresIn time.Duration) (string, error)
}
// TokenValidator interface for dependency injection
type TokenValidator interface {
ValidateJoinToken(tokenString string, sessionID uuid.UUID, partyID string) (*Claims, error)
}
// Ensure JWTService implements interfaces
var _ TokenGenerator = (*JWTService)(nil)
var _ TokenValidator = (*JWTService)(nil)
</pre>
<pre class="file" id="file2" style="display: none">package utils
import (
"context"
"encoding/json"
"math/big"
"reflect"
"strings"
"time"
"github.com/google/uuid"
)
// GenerateID generates a new UUID
func GenerateID() uuid.UUID <span class="cov6" title="4">{
return uuid.New()
}</span>
// ParseUUID parses a string to UUID
func ParseUUID(s string) (uuid.UUID, error) <span class="cov3" title="2">{
return uuid.Parse(s)
}</span>
// MustParseUUID parses a string to UUID, panics on error
func MustParseUUID(s string) uuid.UUID <span class="cov0" title="0">{
id, err := uuid.Parse(s)
if err != nil </span><span class="cov0" title="0">{
panic(err)</span>
}
<span class="cov0" title="0">return id</span>
}
// IsValidUUID checks if a string is a valid UUID
func IsValidUUID(s string) bool <span class="cov3" title="2">{
_, err := uuid.Parse(s)
return err == nil
}</span>
// ToJSON converts an interface to JSON bytes
func ToJSON(v interface{}) ([]byte, error) <span class="cov1" title="1">{
return json.Marshal(v)
}</span>
// FromJSON converts JSON bytes to an interface
func FromJSON(data []byte, v interface{}) error <span class="cov1" title="1">{
return json.Unmarshal(data, v)
}</span>
// NowUTC returns the current UTC time
func NowUTC() time.Time <span class="cov1" title="1">{
return time.Now().UTC()
}</span>
// TimePtr returns a pointer to the time
func TimePtr(t time.Time) *time.Time <span class="cov1" title="1">{
return &amp;t
}</span>
// NowPtr returns a pointer to the current time
func NowPtr() *time.Time <span class="cov0" title="0">{
now := NowUTC()
return &amp;now
}</span>
// BigIntToBytes converts a big.Int to bytes (32 bytes, left-padded)
func BigIntToBytes(n *big.Int) []byte <span class="cov3" title="2">{
if n == nil </span><span class="cov1" title="1">{
return make([]byte, 32)
}</span>
<span class="cov1" title="1">b := n.Bytes()
if len(b) &gt; 32 </span><span class="cov0" title="0">{
return b[:32]
}</span>
<span class="cov1" title="1">if len(b) &lt; 32 </span><span class="cov1" title="1">{
result := make([]byte, 32)
copy(result[32-len(b):], b)
return result
}</span>
<span class="cov0" title="0">return b</span>
}
// BytesToBigInt converts bytes to big.Int
func BytesToBigInt(b []byte) *big.Int <span class="cov1" title="1">{
return new(big.Int).SetBytes(b)
}</span>
// StringSliceContains checks if a string slice contains a value
func StringSliceContains(slice []string, value string) bool <span class="cov5" title="3">{
for _, s := range slice </span><span class="cov7" title="5">{
if s == value </span><span class="cov1" title="1">{
return true
}</span>
}
<span class="cov3" title="2">return false</span>
}
// StringSliceRemove removes a value from a string slice
func StringSliceRemove(slice []string, value string) []string <span class="cov3" title="2">{
result := make([]string, 0, len(slice))
for _, s := range slice </span><span class="cov8" title="6">{
if s != value </span><span class="cov7" title="5">{
result = append(result, s)
}</span>
}
<span class="cov3" title="2">return result</span>
}
// UniqueStrings returns unique strings from a slice
func UniqueStrings(slice []string) []string <span class="cov3" title="2">{
seen := make(map[string]struct{})
result := make([]string, 0, len(slice))
for _, s := range slice </span><span class="cov10" title="9">{
if _, ok := seen[s]; !ok </span><span class="cov8" title="6">{
seen[s] = struct{}{}
result = append(result, s)
}</span>
}
<span class="cov3" title="2">return result</span>
}
// TruncateString truncates a string to max length
func TruncateString(s string, maxLen int) string <span class="cov3" title="2">{
if len(s) &lt;= maxLen </span><span class="cov1" title="1">{
return s
}</span>
<span class="cov1" title="1">return s[:maxLen]</span>
}
// SafeString returns an empty string if the pointer is nil
func SafeString(s *string) string <span class="cov3" title="2">{
if s == nil </span><span class="cov1" title="1">{
return ""
}</span>
<span class="cov1" title="1">return *s</span>
}
// StringPtr returns a pointer to the string
func StringPtr(s string) *string <span class="cov1" title="1">{
return &amp;s
}</span>
// IntPtr returns a pointer to the int
func IntPtr(i int) *int <span class="cov1" title="1">{
return &amp;i
}</span>
// BoolPtr returns a pointer to the bool
func BoolPtr(b bool) *bool <span class="cov1" title="1">{
return &amp;b
}</span>
// IsZero checks if a value is zero/empty
func IsZero(v interface{}) bool <span class="cov0" title="0">{
return reflect.ValueOf(v).IsZero()
}</span>
// Coalesce returns the first non-zero value
func Coalesce[T comparable](values ...T) T <span class="cov5" title="3">{
var zero T
for _, v := range values </span><span class="cov10" title="9">{
if v != zero </span><span class="cov3" title="2">{
return v
}</span>
}
<span class="cov1" title="1">return zero</span>
}
// MapKeys returns the keys of a map
func MapKeys[K comparable, V any](m map[K]V) []K <span class="cov3" title="2">{
keys := make([]K, 0, len(m))
for k := range m </span><span class="cov5" title="3">{
keys = append(keys, k)
}</span>
<span class="cov3" title="2">return keys</span>
}
// MapValues returns the values of a map
func MapValues[K comparable, V any](m map[K]V) []V <span class="cov1" title="1">{
values := make([]V, 0, len(m))
for _, v := range m </span><span class="cov5" title="3">{
values = append(values, v)
}</span>
<span class="cov1" title="1">return values</span>
}
// Min returns the minimum of two values
func Min[T ~int | ~int64 | ~float64](a, b T) T <span class="cov5" title="3">{
if a &lt; b </span><span class="cov3" title="2">{
return a
}</span>
<span class="cov1" title="1">return b</span>
}
// Max returns the maximum of two values
func Max[T ~int | ~int64 | ~float64](a, b T) T <span class="cov5" title="3">{
if a &gt; b </span><span class="cov1" title="1">{
return a
}</span>
<span class="cov3" title="2">return b</span>
}
// Clamp clamps a value between min and max
func Clamp[T ~int | ~int64 | ~float64](value, min, max T) T <span class="cov5" title="3">{
if value &lt; min </span><span class="cov1" title="1">{
return min
}</span>
<span class="cov3" title="2">if value &gt; max </span><span class="cov1" title="1">{
return max
}</span>
<span class="cov1" title="1">return value</span>
}
// ContextWithTimeout creates a context with timeout
func ContextWithTimeout(timeout time.Duration) (context.Context, context.CancelFunc) <span class="cov0" title="0">{
return context.WithTimeout(context.Background(), timeout)
}</span>
// MaskString masks a string showing only first and last n characters
func MaskString(s string, showChars int) string <span class="cov3" title="2">{
if len(s) &lt;= showChars*2 </span><span class="cov1" title="1">{
return strings.Repeat("*", len(s))
}</span>
<span class="cov1" title="1">return s[:showChars] + strings.Repeat("*", len(s)-showChars*2) + s[len(s)-showChars:]</span>
}
// Retry executes a function with retries
func Retry(attempts int, sleep time.Duration, f func() error) error <span class="cov5" title="3">{
var err error
for i := 0; i &lt; attempts; i++ </span><span class="cov8" title="7">{
if err = f(); err == nil </span><span class="cov3" title="2">{
return nil
}</span>
<span class="cov7" title="5">if i &lt; attempts-1 </span><span class="cov6" title="4">{
time.Sleep(sleep)
sleep *= 2 // Exponential backoff
}</span>
}
<span class="cov1" title="1">return err</span>
}
</pre>
<pre class="file" id="file3" style="display: none">package entities
import (
"time"
"github.com/google/uuid"
"github.com/rwadurian/mpc-system/services/account/domain/value_objects"
)
// Account represents a user account with MPC-based authentication
type Account struct {
ID value_objects.AccountID
Username string
Email string
Phone *string
PublicKey []byte // MPC group public key
KeygenSessionID uuid.UUID
ThresholdN int
ThresholdT int
Status value_objects.AccountStatus
CreatedAt time.Time
UpdatedAt time.Time
LastLoginAt *time.Time
}
// NewAccount creates a new Account
func NewAccount(
username string,
email string,
publicKey []byte,
keygenSessionID uuid.UUID,
thresholdN int,
thresholdT int,
) *Account <span class="cov10" title="21">{
now := time.Now().UTC()
return &amp;Account{
ID: value_objects.NewAccountID(),
Username: username,
Email: email,
PublicKey: publicKey,
KeygenSessionID: keygenSessionID,
ThresholdN: thresholdN,
ThresholdT: thresholdT,
Status: value_objects.AccountStatusActive,
CreatedAt: now,
UpdatedAt: now,
}
}</span>
// SetPhone sets the phone number
func (a *Account) SetPhone(phone string) <span class="cov1" title="1">{
a.Phone = &amp;phone
a.UpdatedAt = time.Now().UTC()
}</span>
// UpdateLastLogin updates the last login timestamp
func (a *Account) UpdateLastLogin() <span class="cov1" title="1">{
now := time.Now().UTC()
a.LastLoginAt = &amp;now
a.UpdatedAt = now
}</span>
// Suspend suspends the account
func (a *Account) Suspend() error <span class="cov3" title="2">{
if a.Status == value_objects.AccountStatusRecovering </span><span class="cov1" title="1">{
return ErrAccountInRecovery
}</span>
<span class="cov1" title="1">a.Status = value_objects.AccountStatusSuspended
a.UpdatedAt = time.Now().UTC()
return nil</span>
}
// Lock locks the account
func (a *Account) Lock() error <span class="cov3" title="2">{
if a.Status == value_objects.AccountStatusRecovering </span><span class="cov1" title="1">{
return ErrAccountInRecovery
}</span>
<span class="cov1" title="1">a.Status = value_objects.AccountStatusLocked
a.UpdatedAt = time.Now().UTC()
return nil</span>
}
// Activate activates the account
func (a *Account) Activate() <span class="cov1" title="1">{
a.Status = value_objects.AccountStatusActive
a.UpdatedAt = time.Now().UTC()
}</span>
// StartRecovery marks the account as recovering
func (a *Account) StartRecovery() error <span class="cov4" title="3">{
if !a.Status.CanInitiateRecovery() </span><span class="cov1" title="1">{
return ErrCannotInitiateRecovery
}</span>
<span class="cov3" title="2">a.Status = value_objects.AccountStatusRecovering
a.UpdatedAt = time.Now().UTC()
return nil</span>
}
// CompleteRecovery completes the recovery process with new public key
func (a *Account) CompleteRecovery(newPublicKey []byte, newKeygenSessionID uuid.UUID) <span class="cov1" title="1">{
a.PublicKey = newPublicKey
a.KeygenSessionID = newKeygenSessionID
a.Status = value_objects.AccountStatusActive
a.UpdatedAt = time.Now().UTC()
}</span>
// CanLogin checks if the account can login
func (a *Account) CanLogin() bool <span class="cov5" title="4">{
return a.Status.CanLogin()
}</span>
// IsActive checks if the account is active
func (a *Account) IsActive() bool <span class="cov0" title="0">{
return a.Status == value_objects.AccountStatusActive
}</span>
// Validate validates the account data
func (a *Account) Validate() error <span class="cov5" title="5">{
if a.Username == "" </span><span class="cov1" title="1">{
return ErrInvalidUsername
}</span>
<span class="cov5" title="4">if a.Email == "" </span><span class="cov1" title="1">{
return ErrInvalidEmail
}</span>
<span class="cov4" title="3">if len(a.PublicKey) == 0 </span><span class="cov1" title="1">{
return ErrInvalidPublicKey
}</span>
<span class="cov3" title="2">if a.ThresholdT &gt; a.ThresholdN || a.ThresholdT &lt;= 0 </span><span class="cov1" title="1">{
return ErrInvalidThreshold
}</span>
<span class="cov1" title="1">return nil</span>
}
// Account errors
var (
ErrInvalidUsername = &amp;AccountError{Code: "INVALID_USERNAME", Message: "username is required"}
ErrInvalidEmail = &amp;AccountError{Code: "INVALID_EMAIL", Message: "email is required"}
ErrInvalidPublicKey = &amp;AccountError{Code: "INVALID_PUBLIC_KEY", Message: "public key is required"}
ErrInvalidThreshold = &amp;AccountError{Code: "INVALID_THRESHOLD", Message: "invalid threshold configuration"}
ErrAccountInRecovery = &amp;AccountError{Code: "ACCOUNT_IN_RECOVERY", Message: "account is in recovery mode"}
ErrCannotInitiateRecovery = &amp;AccountError{Code: "CANNOT_INITIATE_RECOVERY", Message: "cannot initiate recovery in current state"}
ErrAccountNotActive = &amp;AccountError{Code: "ACCOUNT_NOT_ACTIVE", Message: "account is not active"}
ErrAccountNotFound = &amp;AccountError{Code: "ACCOUNT_NOT_FOUND", Message: "account not found"}
ErrDuplicateUsername = &amp;AccountError{Code: "DUPLICATE_USERNAME", Message: "username already exists"}
ErrDuplicateEmail = &amp;AccountError{Code: "DUPLICATE_EMAIL", Message: "email already exists"}
)
// AccountError represents an account domain error
type AccountError struct {
Code string
Message string
}
func (e *AccountError) Error() string <span class="cov0" title="0">{
return e.Message
}</span>
</pre>
<pre class="file" id="file4" style="display: none">package entities
import (
"time"
"github.com/google/uuid"
"github.com/rwadurian/mpc-system/services/account/domain/value_objects"
)
// AccountShare represents a mapping of key share to account
// Note: This records share location, not share content
type AccountShare struct {
ID uuid.UUID
AccountID value_objects.AccountID
ShareType value_objects.ShareType
PartyID string
PartyIndex int
DeviceType *string
DeviceID *string
CreatedAt time.Time
LastUsedAt *time.Time
IsActive bool
}
// NewAccountShare creates a new AccountShare
func NewAccountShare(
accountID value_objects.AccountID,
shareType value_objects.ShareType,
partyID string,
partyIndex int,
) *AccountShare <span class="cov10" title="8">{
return &amp;AccountShare{
ID: uuid.New(),
AccountID: accountID,
ShareType: shareType,
PartyID: partyID,
PartyIndex: partyIndex,
CreatedAt: time.Now().UTC(),
IsActive: true,
}
}</span>
// SetDeviceInfo sets device information for user device shares
func (s *AccountShare) SetDeviceInfo(deviceType, deviceID string) <span class="cov1" title="1">{
s.DeviceType = &amp;deviceType
s.DeviceID = &amp;deviceID
}</span>
// UpdateLastUsed updates the last used timestamp
func (s *AccountShare) UpdateLastUsed() <span class="cov0" title="0">{
now := time.Now().UTC()
s.LastUsedAt = &amp;now
}</span>
// Deactivate deactivates the share (e.g., when device is lost)
func (s *AccountShare) Deactivate() <span class="cov1" title="1">{
s.IsActive = false
}</span>
// Activate activates the share
func (s *AccountShare) Activate() <span class="cov0" title="0">{
s.IsActive = true
}</span>
// IsUserDeviceShare checks if this is a user device share
func (s *AccountShare) IsUserDeviceShare() bool <span class="cov4" title="2">{
return s.ShareType == value_objects.ShareTypeUserDevice
}</span>
// IsServerShare checks if this is a server share
func (s *AccountShare) IsServerShare() bool <span class="cov5" title="3">{
return s.ShareType == value_objects.ShareTypeServer
}</span>
// IsRecoveryShare checks if this is a recovery share
func (s *AccountShare) IsRecoveryShare() bool <span class="cov1" title="1">{
return s.ShareType == value_objects.ShareTypeRecovery
}</span>
// Validate validates the account share
func (s *AccountShare) Validate() error <span class="cov4" title="2">{
if s.AccountID.IsZero() </span><span class="cov0" title="0">{
return ErrShareInvalidAccountID
}</span>
<span class="cov4" title="2">if !s.ShareType.IsValid() </span><span class="cov0" title="0">{
return ErrShareInvalidType
}</span>
<span class="cov4" title="2">if s.PartyID == "" </span><span class="cov1" title="1">{
return ErrShareInvalidPartyID
}</span>
<span class="cov1" title="1">if s.PartyIndex &lt; 0 </span><span class="cov0" title="0">{
return ErrShareInvalidPartyIndex
}</span>
<span class="cov1" title="1">return nil</span>
}
// AccountShare errors
var (
ErrShareInvalidAccountID = &amp;AccountError{Code: "SHARE_INVALID_ACCOUNT_ID", Message: "invalid account ID"}
ErrShareInvalidType = &amp;AccountError{Code: "SHARE_INVALID_TYPE", Message: "invalid share type"}
ErrShareInvalidPartyID = &amp;AccountError{Code: "SHARE_INVALID_PARTY_ID", Message: "invalid party ID"}
ErrShareInvalidPartyIndex = &amp;AccountError{Code: "SHARE_INVALID_PARTY_INDEX", Message: "invalid party index"}
ErrShareNotFound = &amp;AccountError{Code: "SHARE_NOT_FOUND", Message: "share not found"}
)
</pre>
<pre class="file" id="file5" style="display: none">package entities
import (
"time"
"github.com/google/uuid"
"github.com/rwadurian/mpc-system/services/account/domain/value_objects"
)
// RecoverySession represents an account recovery session
type RecoverySession struct {
ID uuid.UUID
AccountID value_objects.AccountID
RecoveryType value_objects.RecoveryType
OldShareType *value_objects.ShareType
NewKeygenSessionID *uuid.UUID
Status value_objects.RecoveryStatus
RequestedAt time.Time
CompletedAt *time.Time
}
// NewRecoverySession creates a new RecoverySession
func NewRecoverySession(
accountID value_objects.AccountID,
recoveryType value_objects.RecoveryType,
) *RecoverySession <span class="cov10" title="5">{
return &amp;RecoverySession{
ID: uuid.New(),
AccountID: accountID,
RecoveryType: recoveryType,
Status: value_objects.RecoveryStatusRequested,
RequestedAt: time.Now().UTC(),
}
}</span>
// SetOldShareType sets the old share type being replaced
func (r *RecoverySession) SetOldShareType(shareType value_objects.ShareType) <span class="cov0" title="0">{
r.OldShareType = &amp;shareType
}</span>
// StartKeygen starts the keygen process for recovery
func (r *RecoverySession) StartKeygen(keygenSessionID uuid.UUID) error <span class="cov7" title="3">{
if r.Status != value_objects.RecoveryStatusRequested </span><span class="cov0" title="0">{
return ErrRecoveryInvalidState
}</span>
<span class="cov7" title="3">r.NewKeygenSessionID = &amp;keygenSessionID
r.Status = value_objects.RecoveryStatusInProgress
return nil</span>
}
// Complete marks the recovery as completed
func (r *RecoverySession) Complete() error <span class="cov4" title="2">{
if r.Status != value_objects.RecoveryStatusInProgress </span><span class="cov0" title="0">{
return ErrRecoveryInvalidState
}</span>
<span class="cov4" title="2">now := time.Now().UTC()
r.CompletedAt = &amp;now
r.Status = value_objects.RecoveryStatusCompleted
return nil</span>
}
// Fail marks the recovery as failed
func (r *RecoverySession) Fail() error <span class="cov4" title="2">{
if r.Status == value_objects.RecoveryStatusCompleted </span><span class="cov1" title="1">{
return ErrRecoveryAlreadyCompleted
}</span>
<span class="cov1" title="1">r.Status = value_objects.RecoveryStatusFailed
return nil</span>
}
// IsCompleted checks if recovery is completed
func (r *RecoverySession) IsCompleted() bool <span class="cov0" title="0">{
return r.Status == value_objects.RecoveryStatusCompleted
}</span>
// IsFailed checks if recovery failed
func (r *RecoverySession) IsFailed() bool <span class="cov0" title="0">{
return r.Status == value_objects.RecoveryStatusFailed
}</span>
// IsInProgress checks if recovery is in progress
func (r *RecoverySession) IsInProgress() bool <span class="cov0" title="0">{
return r.Status == value_objects.RecoveryStatusInProgress
}</span>
// Validate validates the recovery session
func (r *RecoverySession) Validate() error <span class="cov0" title="0">{
if r.AccountID.IsZero() </span><span class="cov0" title="0">{
return ErrRecoveryInvalidAccountID
}</span>
<span class="cov0" title="0">if !r.RecoveryType.IsValid() </span><span class="cov0" title="0">{
return ErrRecoveryInvalidType
}</span>
<span class="cov0" title="0">return nil</span>
}
// Recovery errors
var (
ErrRecoveryInvalidAccountID = &amp;AccountError{Code: "RECOVERY_INVALID_ACCOUNT_ID", Message: "invalid account ID for recovery"}
ErrRecoveryInvalidType = &amp;AccountError{Code: "RECOVERY_INVALID_TYPE", Message: "invalid recovery type"}
ErrRecoveryInvalidState = &amp;AccountError{Code: "RECOVERY_INVALID_STATE", Message: "invalid recovery state for this operation"}
ErrRecoveryAlreadyCompleted = &amp;AccountError{Code: "RECOVERY_ALREADY_COMPLETED", Message: "recovery already completed"}
ErrRecoveryNotFound = &amp;AccountError{Code: "RECOVERY_NOT_FOUND", Message: "recovery session not found"}
)
</pre>
<pre class="file" id="file6" style="display: none">package value_objects
import (
"github.com/google/uuid"
)
// AccountID represents a unique account identifier
type AccountID struct {
value uuid.UUID
}
// NewAccountID creates a new AccountID
func NewAccountID() AccountID <span class="cov10" title="34">{
return AccountID{value: uuid.New()}
}</span>
// AccountIDFromString creates an AccountID from a string
func AccountIDFromString(s string) (AccountID, error) <span class="cov2" title="2">{
id, err := uuid.Parse(s)
if err != nil </span><span class="cov1" title="1">{
return AccountID{}, err
}</span>
<span class="cov1" title="1">return AccountID{value: id}, nil</span>
}
// AccountIDFromUUID creates an AccountID from a UUID
func AccountIDFromUUID(id uuid.UUID) AccountID <span class="cov0" title="0">{
return AccountID{value: id}
}</span>
// String returns the string representation
func (id AccountID) String() string <span class="cov1" title="1">{
return id.value.String()
}</span>
// UUID returns the UUID value
func (id AccountID) UUID() uuid.UUID <span class="cov0" title="0">{
return id.value
}</span>
// IsZero checks if the AccountID is zero
func (id AccountID) IsZero() bool <span class="cov4" title="4">{
return id.value == uuid.Nil
}</span>
// Equals checks if two AccountIDs are equal
func (id AccountID) Equals(other AccountID) bool <span class="cov3" title="3">{
return id.value == other.value
}</span>
</pre>
<pre class="file" id="file7" style="display: none">package value_objects
// AccountStatus represents the status of an account
type AccountStatus string
const (
AccountStatusActive AccountStatus = "active"
AccountStatusSuspended AccountStatus = "suspended"
AccountStatusLocked AccountStatus = "locked"
AccountStatusRecovering AccountStatus = "recovering"
)
// String returns the string representation
func (s AccountStatus) String() string <span class="cov0" title="0">{
return string(s)
}</span>
// IsValid checks if the status is valid
func (s AccountStatus) IsValid() bool <span class="cov9" title="5">{
switch s </span>{
case AccountStatusActive, AccountStatusSuspended, AccountStatusLocked, AccountStatusRecovering:<span class="cov7" title="4">
return true</span>
default:<span class="cov1" title="1">
return false</span>
}
}
// CanLogin checks if the account can login with this status
func (s AccountStatus) CanLogin() bool <span class="cov7" title="4">{
return s == AccountStatusActive
}</span>
// CanInitiateRecovery checks if recovery can be initiated
func (s AccountStatus) CanInitiateRecovery() bool <span class="cov6" title="3">{
return s == AccountStatusActive || s == AccountStatusLocked
}</span>
// ShareType represents the type of key share
type ShareType string
const (
ShareTypeUserDevice ShareType = "user_device"
ShareTypeServer ShareType = "server"
ShareTypeRecovery ShareType = "recovery"
)
// String returns the string representation
func (st ShareType) String() string <span class="cov0" title="0">{
return string(st)
}</span>
// IsValid checks if the share type is valid
func (st ShareType) IsValid() bool <span class="cov10" title="6">{
switch st </span>{
case ShareTypeUserDevice, ShareTypeServer, ShareTypeRecovery:<span class="cov9" title="5">
return true</span>
default:<span class="cov1" title="1">
return false</span>
}
}
// RecoveryType represents the type of account recovery
type RecoveryType string
const (
RecoveryTypeDeviceLost RecoveryType = "device_lost"
RecoveryTypeShareRotation RecoveryType = "share_rotation"
)
// String returns the string representation
func (rt RecoveryType) String() string <span class="cov0" title="0">{
return string(rt)
}</span>
// IsValid checks if the recovery type is valid
func (rt RecoveryType) IsValid() bool <span class="cov0" title="0">{
switch rt </span>{
case RecoveryTypeDeviceLost, RecoveryTypeShareRotation:<span class="cov0" title="0">
return true</span>
default:<span class="cov0" title="0">
return false</span>
}
}
// RecoveryStatus represents the status of a recovery session
type RecoveryStatus string
const (
RecoveryStatusRequested RecoveryStatus = "requested"
RecoveryStatusInProgress RecoveryStatus = "in_progress"
RecoveryStatusCompleted RecoveryStatus = "completed"
RecoveryStatusFailed RecoveryStatus = "failed"
)
// String returns the string representation
func (rs RecoveryStatus) String() string <span class="cov0" title="0">{
return string(rs)
}</span>
// IsValid checks if the recovery status is valid
func (rs RecoveryStatus) IsValid() bool <span class="cov0" title="0">{
switch rs </span>{
case RecoveryStatusRequested, RecoveryStatusInProgress, RecoveryStatusCompleted, RecoveryStatusFailed:<span class="cov0" title="0">
return true</span>
default:<span class="cov0" title="0">
return false</span>
}
}
</pre>
<pre class="file" id="file8" style="display: none">package entities
// DeviceType represents the type of device
type DeviceType string
const (
DeviceTypeAndroid DeviceType = "android"
DeviceTypeIOS DeviceType = "ios"
DeviceTypePC DeviceType = "pc"
DeviceTypeServer DeviceType = "server"
DeviceTypeRecovery DeviceType = "recovery"
)
// DeviceInfo holds information about a participant's device
type DeviceInfo struct {
DeviceType DeviceType `json:"device_type"`
DeviceID string `json:"device_id"`
Platform string `json:"platform"`
AppVersion string `json:"app_version"`
}
// NewDeviceInfo creates a new DeviceInfo
func NewDeviceInfo(deviceType DeviceType, deviceID, platform, appVersion string) DeviceInfo <span class="cov0" title="0">{
return DeviceInfo{
DeviceType: deviceType,
DeviceID: deviceID,
Platform: platform,
AppVersion: appVersion,
}
}</span>
// IsServer checks if the device is a server
func (d DeviceInfo) IsServer() bool <span class="cov0" title="0">{
return d.DeviceType == DeviceTypeServer
}</span>
// IsMobile checks if the device is mobile
func (d DeviceInfo) IsMobile() bool <span class="cov0" title="0">{
return d.DeviceType == DeviceTypeAndroid || d.DeviceType == DeviceTypeIOS
}</span>
// IsRecovery checks if the device is a recovery device
func (d DeviceInfo) IsRecovery() bool <span class="cov0" title="0">{
return d.DeviceType == DeviceTypeRecovery
}</span>
// Validate validates the device info
func (d DeviceInfo) Validate() error <span class="cov10" title="7">{
if d.DeviceType == "" </span><span class="cov0" title="0">{
return ErrInvalidDeviceInfo
}</span>
<span class="cov10" title="7">return nil</span>
}
</pre>
<pre class="file" id="file9" style="display: none">package entities
import (
"errors"
"time"
"github.com/google/uuid"
"github.com/rwadurian/mpc-system/services/session-coordinator/domain/value_objects"
)
var (
ErrSessionFull = errors.New("session is full")
ErrSessionExpired = errors.New("session expired")
ErrSessionNotInProgress = errors.New("session not in progress")
ErrParticipantNotFound = errors.New("participant not found")
ErrInvalidSessionType = errors.New("invalid session type")
ErrInvalidStatusTransition = errors.New("invalid status transition")
)
// SessionType represents the type of MPC session
type SessionType string
const (
SessionTypeKeygen SessionType = "keygen"
SessionTypeSign SessionType = "sign"
)
// IsValid checks if the session type is valid
func (t SessionType) IsValid() bool <span class="cov10" title="7">{
return t == SessionTypeKeygen || t == SessionTypeSign
}</span>
// MPCSession represents an MPC session
// Coordinator only manages session metadata, does not participate in MPC computation
type MPCSession struct {
ID value_objects.SessionID
SessionType SessionType
Threshold value_objects.Threshold
Participants []*Participant
Status value_objects.SessionStatus
MessageHash []byte // Used for Sign sessions
PublicKey []byte // Group public key after Keygen completion
CreatedBy string
CreatedAt time.Time
UpdatedAt time.Time
ExpiresAt time.Time
CompletedAt *time.Time
}
// NewMPCSession creates a new MPC session
func NewMPCSession(
sessionType SessionType,
threshold value_objects.Threshold,
createdBy string,
expiresIn time.Duration,
messageHash []byte, // Only for Sign sessions
) (*MPCSession, error) <span class="cov10" title="7">{
if !sessionType.IsValid() </span><span class="cov0" title="0">{
return nil, ErrInvalidSessionType
}</span>
<span class="cov10" title="7">if sessionType == SessionTypeSign &amp;&amp; len(messageHash) == 0 </span><span class="cov1" title="1">{
return nil, errors.New("message hash required for sign session")
}</span>
<span class="cov9" title="6">now := time.Now().UTC()
return &amp;MPCSession{
ID: value_objects.NewSessionID(),
SessionType: sessionType,
Threshold: threshold,
Participants: make([]*Participant, 0, threshold.N()),
Status: value_objects.SessionStatusCreated,
MessageHash: messageHash,
CreatedBy: createdBy,
CreatedAt: now,
UpdatedAt: now,
ExpiresAt: now.Add(expiresIn),
}, nil</span>
}
// AddParticipant adds a participant to the session
func (s *MPCSession) AddParticipant(p *Participant) error <span class="cov7" title="4">{
if len(s.Participants) &gt;= s.Threshold.N() </span><span class="cov1" title="1">{
return ErrSessionFull
}</span>
<span class="cov6" title="3">s.Participants = append(s.Participants, p)
s.UpdatedAt = time.Now().UTC()
return nil</span>
}
// GetParticipant gets a participant by party ID
func (s *MPCSession) GetParticipant(partyID value_objects.PartyID) (*Participant, error) <span class="cov0" title="0">{
for _, p := range s.Participants </span><span class="cov0" title="0">{
if p.PartyID.Equals(partyID) </span><span class="cov0" title="0">{
return p, nil
}</span>
}
<span class="cov0" title="0">return nil, ErrParticipantNotFound</span>
}
// UpdateParticipantStatus updates a participant's status
func (s *MPCSession) UpdateParticipantStatus(partyID value_objects.PartyID, status value_objects.ParticipantStatus) error <span class="cov0" title="0">{
for _, p := range s.Participants </span><span class="cov0" title="0">{
if p.PartyID.Equals(partyID) </span><span class="cov0" title="0">{
switch status </span>{
case value_objects.ParticipantStatusJoined:<span class="cov0" title="0">
return p.Join()</span>
case value_objects.ParticipantStatusReady:<span class="cov0" title="0">
return p.MarkReady()</span>
case value_objects.ParticipantStatusCompleted:<span class="cov0" title="0">
return p.MarkCompleted()</span>
case value_objects.ParticipantStatusFailed:<span class="cov0" title="0">
p.MarkFailed()
return nil</span>
default:<span class="cov0" title="0">
return errors.New("invalid status")</span>
}
}
}
<span class="cov0" title="0">return ErrParticipantNotFound</span>
}
// CanStart checks if all participants have joined and the session can start
func (s *MPCSession) CanStart() bool <span class="cov0" title="0">{
if len(s.Participants) != s.Threshold.N() </span><span class="cov0" title="0">{
return false
}</span>
<span class="cov0" title="0">joinedCount := 0
for _, p := range s.Participants </span><span class="cov0" title="0">{
if p.IsJoined() </span><span class="cov0" title="0">{
joinedCount++
}</span>
}
<span class="cov0" title="0">return joinedCount == s.Threshold.N()</span>
}
// Start transitions the session to in_progress
func (s *MPCSession) Start() error <span class="cov0" title="0">{
if !s.Status.CanTransitionTo(value_objects.SessionStatusInProgress) </span><span class="cov0" title="0">{
return ErrInvalidStatusTransition
}</span>
<span class="cov0" title="0">if !s.CanStart() </span><span class="cov0" title="0">{
return errors.New("not all participants have joined")
}</span>
<span class="cov0" title="0">s.Status = value_objects.SessionStatusInProgress
s.UpdatedAt = time.Now().UTC()
return nil</span>
}
// Complete marks the session as completed
func (s *MPCSession) Complete(publicKey []byte) error <span class="cov0" title="0">{
if !s.Status.CanTransitionTo(value_objects.SessionStatusCompleted) </span><span class="cov0" title="0">{
return ErrInvalidStatusTransition
}</span>
<span class="cov0" title="0">s.Status = value_objects.SessionStatusCompleted
s.PublicKey = publicKey
now := time.Now().UTC()
s.CompletedAt = &amp;now
s.UpdatedAt = now
return nil</span>
}
// Fail marks the session as failed
func (s *MPCSession) Fail() error <span class="cov0" title="0">{
if !s.Status.CanTransitionTo(value_objects.SessionStatusFailed) </span><span class="cov0" title="0">{
return ErrInvalidStatusTransition
}</span>
<span class="cov0" title="0">s.Status = value_objects.SessionStatusFailed
s.UpdatedAt = time.Now().UTC()
return nil</span>
}
// Expire marks the session as expired
func (s *MPCSession) Expire() error <span class="cov0" title="0">{
if !s.Status.CanTransitionTo(value_objects.SessionStatusExpired) </span><span class="cov0" title="0">{
return ErrInvalidStatusTransition
}</span>
<span class="cov0" title="0">s.Status = value_objects.SessionStatusExpired
s.UpdatedAt = time.Now().UTC()
return nil</span>
}
// IsExpired checks if the session has expired
func (s *MPCSession) IsExpired() bool <span class="cov4" title="2">{
return time.Now().UTC().After(s.ExpiresAt)
}</span>
// IsActive checks if the session is active
func (s *MPCSession) IsActive() bool <span class="cov0" title="0">{
return s.Status.IsActive() &amp;&amp; !s.IsExpired()
}</span>
// IsParticipant checks if a party is a participant
func (s *MPCSession) IsParticipant(partyID value_objects.PartyID) bool <span class="cov0" title="0">{
for _, p := range s.Participants </span><span class="cov0" title="0">{
if p.PartyID.Equals(partyID) </span><span class="cov0" title="0">{
return true
}</span>
}
<span class="cov0" title="0">return false</span>
}
// AllCompleted checks if all participants have completed
func (s *MPCSession) AllCompleted() bool <span class="cov0" title="0">{
for _, p := range s.Participants </span><span class="cov0" title="0">{
if !p.IsCompleted() </span><span class="cov0" title="0">{
return false
}</span>
}
<span class="cov0" title="0">return true</span>
}
// CompletedCount returns the number of completed participants
func (s *MPCSession) CompletedCount() int <span class="cov0" title="0">{
count := 0
for _, p := range s.Participants </span><span class="cov0" title="0">{
if p.IsCompleted() </span><span class="cov0" title="0">{
count++
}</span>
}
<span class="cov0" title="0">return count</span>
}
// JoinedCount returns the number of joined participants
func (s *MPCSession) JoinedCount() int <span class="cov0" title="0">{
count := 0
for _, p := range s.Participants </span><span class="cov0" title="0">{
if p.IsJoined() </span><span class="cov0" title="0">{
count++
}</span>
}
<span class="cov0" title="0">return count</span>
}
// GetPartyIDs returns all party IDs
func (s *MPCSession) GetPartyIDs() []string <span class="cov0" title="0">{
ids := make([]string, len(s.Participants))
for i, p := range s.Participants </span><span class="cov0" title="0">{
ids[i] = p.PartyID.String()
}</span>
<span class="cov0" title="0">return ids</span>
}
// GetOtherParties returns participants except the specified party
func (s *MPCSession) GetOtherParties(excludePartyID value_objects.PartyID) []*Participant <span class="cov0" title="0">{
others := make([]*Participant, 0, len(s.Participants)-1)
for _, p := range s.Participants </span><span class="cov0" title="0">{
if !p.PartyID.Equals(excludePartyID) </span><span class="cov0" title="0">{
others = append(others, p)
}</span>
}
<span class="cov0" title="0">return others</span>
}
// ToDTO converts to a DTO for API responses
func (s *MPCSession) ToDTO() SessionDTO <span class="cov0" title="0">{
participants := make([]ParticipantDTO, len(s.Participants))
for i, p := range s.Participants </span><span class="cov0" title="0">{
participants[i] = ParticipantDTO{
PartyID: p.PartyID.String(),
PartyIndex: p.PartyIndex,
Status: p.Status.String(),
DeviceType: string(p.DeviceInfo.DeviceType),
}
}</span>
<span class="cov0" title="0">return SessionDTO{
ID: s.ID.String(),
SessionType: string(s.SessionType),
ThresholdN: s.Threshold.N(),
ThresholdT: s.Threshold.T(),
Participants: participants,
Status: s.Status.String(),
CreatedAt: s.CreatedAt,
ExpiresAt: s.ExpiresAt,
}</span>
}
// SessionDTO is a data transfer object for sessions
type SessionDTO struct {
ID string `json:"id"`
SessionType string `json:"session_type"`
ThresholdN int `json:"threshold_n"`
ThresholdT int `json:"threshold_t"`
Participants []ParticipantDTO `json:"participants"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
ExpiresAt time.Time `json:"expires_at"`
}
// ParticipantDTO is a data transfer object for participants
type ParticipantDTO struct {
PartyID string `json:"party_id"`
PartyIndex int `json:"party_index"`
Status string `json:"status"`
DeviceType string `json:"device_type"`
}
// Reconstruct reconstructs an MPCSession from database
func ReconstructSession(
id uuid.UUID,
sessionType string,
thresholdT, thresholdN int,
status string,
messageHash, publicKey []byte,
createdBy string,
createdAt, updatedAt, expiresAt time.Time,
completedAt *time.Time,
participants []*Participant,
) (*MPCSession, error) <span class="cov0" title="0">{
sessionStatus, err := value_objects.NewSessionStatus(status)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov0" title="0">threshold, err := value_objects.NewThreshold(thresholdT, thresholdN)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov0" title="0">return &amp;MPCSession{
ID: value_objects.SessionIDFromUUID(id),
SessionType: SessionType(sessionType),
Threshold: threshold,
Participants: participants,
Status: sessionStatus,
MessageHash: messageHash,
PublicKey: publicKey,
CreatedBy: createdBy,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
ExpiresAt: expiresAt,
CompletedAt: completedAt,
}, nil</span>
}
</pre>
<pre class="file" id="file10" style="display: none">package entities
import (
"errors"
"time"
"github.com/rwadurian/mpc-system/services/session-coordinator/domain/value_objects"
)
var (
ErrInvalidDeviceInfo = errors.New("invalid device info")
ErrParticipantNotInvited = errors.New("participant not in invited status")
ErrInvalidParticipant = errors.New("invalid participant")
)
// Participant represents a party in an MPC session
type Participant struct {
PartyID value_objects.PartyID
PartyIndex int
Status value_objects.ParticipantStatus
DeviceInfo DeviceInfo
PublicKey []byte // Party's identity public key (for authentication)
JoinedAt time.Time
CompletedAt *time.Time
}
// NewParticipant creates a new participant
func NewParticipant(partyID value_objects.PartyID, partyIndex int, deviceInfo DeviceInfo) (*Participant, error) <span class="cov10" title="7">{
if partyID.IsZero() </span><span class="cov0" title="0">{
return nil, ErrInvalidParticipant
}</span>
<span class="cov10" title="7">if partyIndex &lt; 0 </span><span class="cov0" title="0">{
return nil, ErrInvalidParticipant
}</span>
<span class="cov10" title="7">if err := deviceInfo.Validate(); err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov10" title="7">return &amp;Participant{
PartyID: partyID,
PartyIndex: partyIndex,
Status: value_objects.ParticipantStatusInvited,
DeviceInfo: deviceInfo,
JoinedAt: time.Now().UTC(),
}, nil</span>
}
// Join marks the participant as joined
func (p *Participant) Join() error <span class="cov1" title="1">{
if !p.Status.CanTransitionTo(value_objects.ParticipantStatusJoined) </span><span class="cov0" title="0">{
return errors.New("cannot transition to joined status")
}</span>
<span class="cov1" title="1">p.Status = value_objects.ParticipantStatusJoined
p.JoinedAt = time.Now().UTC()
return nil</span>
}
// MarkReady marks the participant as ready
func (p *Participant) MarkReady() error <span class="cov1" title="1">{
if !p.Status.CanTransitionTo(value_objects.ParticipantStatusReady) </span><span class="cov0" title="0">{
return errors.New("cannot transition to ready status")
}</span>
<span class="cov1" title="1">p.Status = value_objects.ParticipantStatusReady
return nil</span>
}
// MarkCompleted marks the participant as completed
func (p *Participant) MarkCompleted() error <span class="cov1" title="1">{
if !p.Status.CanTransitionTo(value_objects.ParticipantStatusCompleted) </span><span class="cov0" title="0">{
return errors.New("cannot transition to completed status")
}</span>
<span class="cov1" title="1">p.Status = value_objects.ParticipantStatusCompleted
now := time.Now().UTC()
p.CompletedAt = &amp;now
return nil</span>
}
// MarkFailed marks the participant as failed
func (p *Participant) MarkFailed() <span class="cov1" title="1">{
p.Status = value_objects.ParticipantStatusFailed
}</span>
// IsJoined checks if the participant has joined
func (p *Participant) IsJoined() bool <span class="cov0" title="0">{
return p.Status == value_objects.ParticipantStatusJoined ||
p.Status == value_objects.ParticipantStatusReady ||
p.Status == value_objects.ParticipantStatusCompleted
}</span>
// IsReady checks if the participant is ready
func (p *Participant) IsReady() bool <span class="cov0" title="0">{
return p.Status == value_objects.ParticipantStatusReady ||
p.Status == value_objects.ParticipantStatusCompleted
}</span>
// IsCompleted checks if the participant has completed
func (p *Participant) IsCompleted() bool <span class="cov0" title="0">{
return p.Status == value_objects.ParticipantStatusCompleted
}</span>
// IsFailed checks if the participant has failed
func (p *Participant) IsFailed() bool <span class="cov0" title="0">{
return p.Status == value_objects.ParticipantStatusFailed
}</span>
// SetPublicKey sets the participant's public key
func (p *Participant) SetPublicKey(publicKey []byte) <span class="cov0" title="0">{
p.PublicKey = publicKey
}</span>
</pre>
<pre class="file" id="file11" style="display: none">package entities
import (
"time"
"github.com/google/uuid"
"github.com/rwadurian/mpc-system/services/session-coordinator/domain/value_objects"
)
// SessionMessage represents an MPC message (encrypted, Coordinator does not decrypt)
type SessionMessage struct {
ID uuid.UUID
SessionID value_objects.SessionID
FromParty value_objects.PartyID
ToParties []value_objects.PartyID // nil means broadcast
RoundNumber int
MessageType string
Payload []byte // Encrypted MPC protocol message
CreatedAt time.Time
DeliveredAt *time.Time
}
// NewSessionMessage creates a new session message
func NewSessionMessage(
sessionID value_objects.SessionID,
fromParty value_objects.PartyID,
toParties []value_objects.PartyID,
roundNumber int,
messageType string,
payload []byte,
) *SessionMessage <span class="cov0" title="0">{
return &amp;SessionMessage{
ID: uuid.New(),
SessionID: sessionID,
FromParty: fromParty,
ToParties: toParties,
RoundNumber: roundNumber,
MessageType: messageType,
Payload: payload,
CreatedAt: time.Now().UTC(),
}
}</span>
// IsBroadcast checks if the message is a broadcast
func (m *SessionMessage) IsBroadcast() bool <span class="cov0" title="0">{
return len(m.ToParties) == 0
}</span>
// IsFor checks if the message is for a specific party
func (m *SessionMessage) IsFor(partyID value_objects.PartyID) bool <span class="cov0" title="0">{
if m.IsBroadcast() </span><span class="cov0" title="0">{
// Broadcast is for everyone except sender
return !m.FromParty.Equals(partyID)
}</span>
<span class="cov0" title="0">for _, to := range m.ToParties </span><span class="cov0" title="0">{
if to.Equals(partyID) </span><span class="cov0" title="0">{
return true
}</span>
}
<span class="cov0" title="0">return false</span>
}
// MarkDelivered marks the message as delivered
func (m *SessionMessage) MarkDelivered() <span class="cov0" title="0">{
now := time.Now().UTC()
m.DeliveredAt = &amp;now
}</span>
// IsDelivered checks if the message has been delivered
func (m *SessionMessage) IsDelivered() bool <span class="cov0" title="0">{
return m.DeliveredAt != nil
}</span>
// GetToPartyStrings returns to parties as strings
func (m *SessionMessage) GetToPartyStrings() []string <span class="cov0" title="0">{
if m.IsBroadcast() </span><span class="cov0" title="0">{
return nil
}</span>
<span class="cov0" title="0">result := make([]string, len(m.ToParties))
for i, p := range m.ToParties </span><span class="cov0" title="0">{
result[i] = p.String()
}</span>
<span class="cov0" title="0">return result</span>
}
// ToDTO converts to a DTO
func (m *SessionMessage) ToDTO() MessageDTO <span class="cov0" title="0">{
toParties := m.GetToPartyStrings()
return MessageDTO{
ID: m.ID.String(),
SessionID: m.SessionID.String(),
FromParty: m.FromParty.String(),
ToParties: toParties,
IsBroadcast: m.IsBroadcast(),
RoundNumber: m.RoundNumber,
MessageType: m.MessageType,
Payload: m.Payload,
CreatedAt: m.CreatedAt,
}
}</span>
// MessageDTO is a data transfer object for messages
type MessageDTO struct {
ID string `json:"id"`
SessionID string `json:"session_id"`
FromParty string `json:"from_party"`
ToParties []string `json:"to_parties,omitempty"`
IsBroadcast bool `json:"is_broadcast"`
RoundNumber int `json:"round_number"`
MessageType string `json:"message_type"`
Payload []byte `json:"payload"`
CreatedAt time.Time `json:"created_at"`
}
</pre>
<pre class="file" id="file12" style="display: none">package value_objects
import (
"errors"
"regexp"
)
var (
ErrInvalidPartyID = errors.New("invalid party ID")
partyIDRegex = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
)
// PartyID represents a unique party identifier
type PartyID struct {
value string
}
// NewPartyID creates a new PartyID
func NewPartyID(value string) (PartyID, error) <span class="cov10" title="12">{
if value == "" </span><span class="cov1" title="1">{
return PartyID{}, ErrInvalidPartyID
}</span>
<span class="cov9" title="11">if !partyIDRegex.MatchString(value) </span><span class="cov0" title="0">{
return PartyID{}, ErrInvalidPartyID
}</span>
<span class="cov9" title="11">if len(value) &gt; 255 </span><span class="cov0" title="0">{
return PartyID{}, ErrInvalidPartyID
}</span>
<span class="cov9" title="11">return PartyID{value: value}, nil</span>
}
// MustNewPartyID creates a new PartyID, panics on error
func MustNewPartyID(value string) PartyID <span class="cov0" title="0">{
id, err := NewPartyID(value)
if err != nil </span><span class="cov0" title="0">{
panic(err)</span>
}
<span class="cov0" title="0">return id</span>
}
// String returns the string representation
func (id PartyID) String() string <span class="cov1" title="1">{
return id.value
}</span>
// IsZero checks if the PartyID is zero
func (id PartyID) IsZero() bool <span class="cov8" title="8">{
return id.value == ""
}</span>
// Equals checks if two PartyIDs are equal
func (id PartyID) Equals(other PartyID) bool <span class="cov3" title="2">{
return id.value == other.value
}</span>
</pre>
<pre class="file" id="file13" style="display: none">package value_objects
import (
"github.com/google/uuid"
)
// SessionID represents a unique session identifier
type SessionID struct {
value uuid.UUID
}
// NewSessionID creates a new SessionID
func NewSessionID() SessionID <span class="cov10" title="8">{
return SessionID{value: uuid.New()}
}</span>
// SessionIDFromString creates a SessionID from a string
func SessionIDFromString(s string) (SessionID, error) <span class="cov4" title="2">{
id, err := uuid.Parse(s)
if err != nil </span><span class="cov1" title="1">{
return SessionID{}, err
}</span>
<span class="cov1" title="1">return SessionID{value: id}, nil</span>
}
// SessionIDFromUUID creates a SessionID from a UUID
func SessionIDFromUUID(id uuid.UUID) SessionID <span class="cov0" title="0">{
return SessionID{value: id}
}</span>
// String returns the string representation
func (id SessionID) String() string <span class="cov1" title="1">{
return id.value.String()
}</span>
// UUID returns the UUID value
func (id SessionID) UUID() uuid.UUID <span class="cov0" title="0">{
return id.value
}</span>
// IsZero checks if the SessionID is zero
func (id SessionID) IsZero() bool <span class="cov4" title="2">{
return id.value == uuid.Nil
}</span>
// Equals checks if two SessionIDs are equal
func (id SessionID) Equals(other SessionID) bool <span class="cov1" title="1">{
return id.value == other.value
}</span>
</pre>
<pre class="file" id="file14" style="display: none">package value_objects
import (
"errors"
)
var ErrInvalidSessionStatus = errors.New("invalid session status")
// SessionStatus represents the status of an MPC session
type SessionStatus string
const (
SessionStatusCreated SessionStatus = "created"
SessionStatusInProgress SessionStatus = "in_progress"
SessionStatusCompleted SessionStatus = "completed"
SessionStatusFailed SessionStatus = "failed"
SessionStatusExpired SessionStatus = "expired"
)
// ValidSessionStatuses contains all valid session statuses
var ValidSessionStatuses = []SessionStatus{
SessionStatusCreated,
SessionStatusInProgress,
SessionStatusCompleted,
SessionStatusFailed,
SessionStatusExpired,
}
// NewSessionStatus creates a new SessionStatus from string
func NewSessionStatus(s string) (SessionStatus, error) <span class="cov0" title="0">{
status := SessionStatus(s)
if !status.IsValid() </span><span class="cov0" title="0">{
return "", ErrInvalidSessionStatus
}</span>
<span class="cov0" title="0">return status, nil</span>
}
// String returns the string representation
func (s SessionStatus) String() string <span class="cov0" title="0">{
return string(s)
}</span>
// IsValid checks if the status is valid
func (s SessionStatus) IsValid() bool <span class="cov0" title="0">{
for _, valid := range ValidSessionStatuses </span><span class="cov0" title="0">{
if s == valid </span><span class="cov0" title="0">{
return true
}</span>
}
<span class="cov0" title="0">return false</span>
}
// CanTransitionTo checks if the status can transition to another
func (s SessionStatus) CanTransitionTo(target SessionStatus) bool <span class="cov0" title="0">{
transitions := map[SessionStatus][]SessionStatus{
SessionStatusCreated: {SessionStatusInProgress, SessionStatusFailed, SessionStatusExpired},
SessionStatusInProgress: {SessionStatusCompleted, SessionStatusFailed, SessionStatusExpired},
SessionStatusCompleted: {},
SessionStatusFailed: {},
SessionStatusExpired: {},
}
allowed, ok := transitions[s]
if !ok </span><span class="cov0" title="0">{
return false
}</span>
<span class="cov0" title="0">for _, status := range allowed </span><span class="cov0" title="0">{
if status == target </span><span class="cov0" title="0">{
return true
}</span>
}
<span class="cov0" title="0">return false</span>
}
// IsTerminal checks if the status is terminal (cannot transition)
func (s SessionStatus) IsTerminal() bool <span class="cov0" title="0">{
return s == SessionStatusCompleted || s == SessionStatusFailed || s == SessionStatusExpired
}</span>
// IsActive checks if the session is active
func (s SessionStatus) IsActive() bool <span class="cov0" title="0">{
return s == SessionStatusCreated || s == SessionStatusInProgress
}</span>
// ParticipantStatus represents the status of a participant
type ParticipantStatus string
const (
ParticipantStatusInvited ParticipantStatus = "invited"
ParticipantStatusJoined ParticipantStatus = "joined"
ParticipantStatusReady ParticipantStatus = "ready"
ParticipantStatusCompleted ParticipantStatus = "completed"
ParticipantStatusFailed ParticipantStatus = "failed"
)
// ValidParticipantStatuses contains all valid participant statuses
var ValidParticipantStatuses = []ParticipantStatus{
ParticipantStatusInvited,
ParticipantStatusJoined,
ParticipantStatusReady,
ParticipantStatusCompleted,
ParticipantStatusFailed,
}
// String returns the string representation
func (s ParticipantStatus) String() string <span class="cov0" title="0">{
return string(s)
}</span>
// IsValid checks if the status is valid
func (s ParticipantStatus) IsValid() bool <span class="cov0" title="0">{
for _, valid := range ValidParticipantStatuses </span><span class="cov0" title="0">{
if s == valid </span><span class="cov0" title="0">{
return true
}</span>
}
<span class="cov0" title="0">return false</span>
}
// CanTransitionTo checks if the status can transition to another
func (s ParticipantStatus) CanTransitionTo(target ParticipantStatus) bool <span class="cov10" title="3">{
transitions := map[ParticipantStatus][]ParticipantStatus{
ParticipantStatusInvited: {ParticipantStatusJoined, ParticipantStatusFailed},
ParticipantStatusJoined: {ParticipantStatusReady, ParticipantStatusFailed},
ParticipantStatusReady: {ParticipantStatusCompleted, ParticipantStatusFailed},
ParticipantStatusCompleted: {},
ParticipantStatusFailed: {},
}
allowed, ok := transitions[s]
if !ok </span><span class="cov0" title="0">{
return false
}</span>
<span class="cov10" title="3">for _, status := range allowed </span><span class="cov10" title="3">{
if status == target </span><span class="cov10" title="3">{
return true
}</span>
}
<span class="cov0" title="0">return false</span>
}
</pre>
<pre class="file" id="file15" style="display: none">package value_objects
import (
"errors"
"fmt"
)
var (
ErrInvalidThreshold = errors.New("invalid threshold")
ErrThresholdTooLarge = errors.New("threshold t cannot exceed n")
ErrThresholdTooSmall = errors.New("threshold t must be at least 1")
ErrNTooSmall = errors.New("n must be at least 2")
ErrNTooLarge = errors.New("n cannot exceed maximum allowed")
)
const (
MinN = 2
MaxN = 10
MinT = 1
)
// Threshold represents the t-of-n threshold configuration
type Threshold struct {
t int // Minimum number of parties required
n int // Total number of parties
}
// NewThreshold creates a new Threshold value object
func NewThreshold(t, n int) (Threshold, error) <span class="cov9" title="11">{
if n &lt; MinN </span><span class="cov1" title="1">{
return Threshold{}, ErrNTooSmall
}</span>
<span class="cov9" title="10">if n &gt; MaxN </span><span class="cov0" title="0">{
return Threshold{}, ErrNTooLarge
}</span>
<span class="cov9" title="10">if t &lt; MinT </span><span class="cov1" title="1">{
return Threshold{}, ErrThresholdTooSmall
}</span>
<span class="cov8" title="9">if t &gt; n </span><span class="cov1" title="1">{
return Threshold{}, ErrThresholdTooLarge
}</span>
<span class="cov8" title="8">return Threshold{t: t, n: n}, nil</span>
}
// MustNewThreshold creates a new Threshold, panics on error
func MustNewThreshold(t, n int) Threshold <span class="cov0" title="0">{
threshold, err := NewThreshold(t, n)
if err != nil </span><span class="cov0" title="0">{
panic(err)</span>
}
<span class="cov0" title="0">return threshold</span>
}
// T returns the minimum required parties
func (th Threshold) T() int <span class="cov3" title="2">{
return th.t
}</span>
// N returns the total parties
func (th Threshold) N() int <span class="cov10" title="12">{
return th.n
}</span>
// IsZero checks if the Threshold is zero
func (th Threshold) IsZero() bool <span class="cov1" title="1">{
return th.t == 0 &amp;&amp; th.n == 0
}</span>
// Equals checks if two Thresholds are equal
func (th Threshold) Equals(other Threshold) bool <span class="cov0" title="0">{
return th.t == other.t &amp;&amp; th.n == other.n
}</span>
// String returns the string representation
func (th Threshold) String() string <span class="cov0" title="0">{
return fmt.Sprintf("%d-of-%d", th.t, th.n)
}</span>
// CanSign checks if the given number of parties can sign
func (th Threshold) CanSign(availableParties int) bool <span class="cov0" title="0">{
return availableParties &gt;= th.t
}</span>
// RequiresAllParties checks if all parties are required
func (th Threshold) RequiresAllParties() bool <span class="cov0" title="0">{
return th.t == th.n
}</span>
</pre>
</div>
</body>
<script>
(function() {
var files = document.getElementById('files');
var visible;
files.addEventListener('change', onChange, false);
function select(part) {
if (visible)
visible.style.display = 'none';
visible = document.getElementById(part);
if (!visible)
return;
files.value = part;
visible.style.display = 'block';
location.hash = part;
}
function onChange() {
select(files.value);
window.scrollTo(0, 0);
}
if (location.hash != "") {
select(location.hash.substr(1));
}
if (!visible) {
select("file0");
}
})();
</script>
</html>