How mpcium Zeroizes Key Material in Memory: Security Measures Explained
mpcium implements a three-layer zeroization strategy that explicitly overwrites big.Int fields, raw byte slices, and secure container objects to ensure cryptographic key material does not persist in heap memory beyond its intended lifecycle.
The fystack/mpcium repository provides a secure multi-party computation (MPC) framework where protecting private key material from memory-dump attacks is critical. The codebase employs a dedicated zero-knowledge security layer that combines explicit memory overwriting with Go runtime finalizers to eliminate sensitive data as soon as it leaves scope.
Three-Layer Zeroization Architecture
The security measures protecting key material in memory are implemented across three coordinated mechanisms in the pkg/security package.
Zeroing big.Int Fields in TSS Structures
The TSS (Threshold Signature Scheme) library relies heavily on math/big.Int for cryptographic operations. Because big.Int stores its value in a mutable word slice, simply dereferencing the variable does not clear the underlying memory.
In pkg/security/zeroize.go, the zeroBigInt function iterates over x.Bits() and sets every word to zero before forcing the value to zero:
// pkg/security/zeroize.go
func zeroBigInt(x *big.Int) {
if x == nil {
return
}
bits := x.Bits()
for i := range bits {
bits[i] = 0
}
x.SetInt64(0)
}
This routine is invoked by higher-level helpers like ZeroEcdsaKeygenLocalPartySaveData and ZeroEddsaKeygenLocalPartySaveData to sanitize entire TSS data structures after key generation.
Overwriting Raw Byte Slices
For general-purpose buffers that temporarily hold passwords, serialized keys, or network messages, pkg/security/memory.go provides ZeroBytes. This function walks the slice and writes zero to each element, then triggers a garbage collection pass to reduce the window where the data might exist in freed heap memory:
// pkg/security/memory.go
func ZeroBytes(data []byte) {
for i := range data {
data[i] = 0
}
runtime.GC()
}
This primitive is used throughout the codebase whenever a function receives sensitive input that must not outlive the current scope.
Finalizer-Based Secure Containers
To automate zeroization when objects go out of scope, mpcium implements SecureBytes—a wrapper that registers a Go finalizer. When the SecureBytes object is garbage-collected (or when Clear() is called explicitly), the finalizer invokes the zeroing routine:
// pkg/security/memory.go
type SecureBytes struct {
data []byte
}
func NewSecureBytes(data []byte) *SecureBytes {
sb := &SecureBytes{data: data}
runtime.SetFinalizer(sb, (*SecureBytes).zero)
return sb
}
func (sb *SecureBytes) zero() {
ZeroBytes(sb.data)
}
This pattern provides defense-in-depth: even if a developer forgets to call Clear(), the finalizer acts as a safety net when the runtime reclaims the memory.
Integration Points in the MPC Workflow
The zeroization primitives are woven into every stage where secret material is produced, stored, or transmitted.
Key Generation Sessions
During ECDSA and EdDSA key generation, the saveData structures returned by the TSS protocol contain sensitive shares. In pkg/mpc/ecdsa_keygen_session.go (lines 102–108), the code defers zeroization immediately after receiving the data:
defer security.ZeroEcdsaKeygenLocalPartySaveData(saveData)
defer security.ZeroBytes(keyBytes)
This ensures that if the function exits early due to an error or panic, the sensitive buffers are still overwritten before the stack unwinds.
Signing Sessions
Per-operation private material is handled with similar care. In pkg/mpc/ecdsa_signing_session.go (line 136), the signing routine clears the key data immediately after the signature is produced:
security.ZeroBytes(keyData)
This minimizes the window during which the ephemeral signing key exists in memory.
CLI Password Handling
At the application boundary, user-supplied passwords are sanitized before the program returns control to the shell. In cmd/mpcium/main.go (lines 382–387), the CLI zeroes password bytes after authentication:
security.ZeroBytes(passwordBytes)
Implementation Details and Code Examples
Zeroing a big.Int Used by the TSS Library
When working with threshold signature shares that use math/big.Int, always use the dedicated zeroization helper:
import "github.com/fystack/mpcium/pkg/security"
// Assume 'share' is a *big.Int obtained from a TSS round
security.ZeroBigIntForensic(share) // Calls the internal zeroBigInt routine
The implementation iterates over x.Bits() and clears each word (source).
Zeroing a Byte Slice That Holds a Serialized Key
For temporary buffers containing JSON-encoded keys or passwords:
keyBytes, _ := json.Marshal(saveData) // keyBytes now contains private key material
defer security.ZeroBytes(keyBytes) // guaranteed cleanup when the function returns
The slice is overwritten element-wise and a GC cycle is triggered (source).
Using the SecureBytes Wrapper for Temporary Secrets
For automated cleanup via finalizers:
secret := security.NewSecureBytes([]byte("my-super-secret"))
defer secret.Clear() // Explicit clear; finalizer is a safety net
// Use the raw bytes only when needed
payload := secret.Bytes()
doSomething(payload)
SecureBytes registers runtime.SetFinalizer to invoke ZeroBytes when the object is garbage-collected (source).
Summary
- Explicit Overwriting: The
zeroBigIntandZeroBytesfunctions inpkg/securitymanually overwrite memory words and byte slices to prevent data remanence. - Automated Cleanup:
SecureBytesleverages Go'sruntime.SetFinalizerto ensure zeroization occurs even when developers forget explicitClear()calls. - Workflow Integration: Deferred zeroization calls in
ecdsa_keygen_session.go,ecdsa_signing_session.go, andcmd/mpcium/main.goensure secrets are cleared during key generation, signing, and CLI password handling. - Defense in Depth: Combining immediate GC hints, explicit loops, and finalizers minimizes the attack surface for memory-dump forensic analysis.
Frequently Asked Questions
How does mpcium prevent big.Int values from lingering in memory?
mpcium prevents big.Int values from lingering by using the zeroBigInt function in pkg/security/zeroize.go. This function accesses the internal word slice via x.Bits() and iterates through each word, setting it to zero before calling x.SetInt64(0). This ensures that the underlying memory backing the arbitrary-precision integer is explicitly overwritten rather than simply dereferenced.
What is the difference between ZeroBytes and SecureBytes?
ZeroBytes is a procedural function that immediately overwrites a byte slice with zeros and triggers garbage collection, requiring the caller to manage the lifecycle manually. SecureBytes is an object-oriented wrapper that encapsulates the byte slice and registers a Go finalizer via runtime.SetFinalizer; this ensures that ZeroBytes is called automatically when the SecureBytes object is garbage collected, even if the developer forgets to explicitly call Clear().
Does mpcium zeroize key material after signing operations?
Yes, mpcium zeroizes key material immediately after signing operations. In pkg/mpc/ecdsa_signing_session.go at line 136, the code calls security.ZeroBytes(keyData) right after the signature is produced. This ensures that the ephemeral private material used for the signing session does not persist in heap memory once the cryptographic operation completes.
Are the zeroization routines tested for effectiveness?
Yes, the zeroization routines include unit tests that verify the overwriting behavior. The pkg/security/zeroize_test.go file contains tests confirming that zeroBigInt actually overwrites the backing words of the big.Int, while pkg/security/memory_test.go validates that ZeroBytes successfully clears byte slices and that SecureBytes finalizers trigger correctly during garbage collection cycles.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →