Security Model
Cryptographic primitives, share exchange encryption, threat model, and security guarantees of the NERO MPC SDK
Security Model
Trust Assumptions
The NERO MPC SDK operates under a cooperative threshold security model:
- Client + Server cooperation required: Both parties must participate in signing. A compromised server alone cannot steal funds.
- Client-side encryption: Key shares are encrypted with a device-specific key using AES-256-GCM. Physical device access alone is insufficient without the encrypted share.
- Backend integrity: The backend must correctly execute MPC protocol rounds. The cryptographic protocol provides verification of correct execution through proofs of knowledge and VSSS commitments.
- Key isolation: The full private key is never reconstructed during normal signing operations in either protocol.
Threat Mitigation
| Threat | Mitigation |
|---|---|
| Server compromise | Server holds only one share — insufficient to sign alone |
| Client device theft | Key shares encrypted with AES-256-GCM device key |
| Network interception | All communication over HTTPS/WSS; share exchange uses ECIES encryption |
| Session hijacking | Tokens encrypted at rest, auto-refresh with device binding |
| Key reconstruction | Private key is never assembled on any single device |
| Share tampering | AES-GCM 128-bit authentication tags prevent modification |
| Protocol manipulation | Schnorr proofs (DKLS) and VSSS commitments (Pedersen) verify correct execution |
Cryptographic Primitives
The SDK implements foundational cryptographic operations for browser environments, leveraging the @noble/hashes library and Web Crypto API. All primitives are exported from src/core/crypto-primitives.ts.
Encryption Functions
| Function | Purpose | Algorithm | Output |
|---|---|---|---|
encryptWithPassword | Encrypt using user password | PBKDF2 + AES-GCM | EncryptionResult |
decryptWithPassword | Decrypt password-encrypted data | PBKDF2 + AES-GCM | string (UTF-8) |
encryptWithKey | Encrypt using existing CryptoKey | AES-GCM | { ciphertext, iv } |
decryptWithKey | Decrypt using existing CryptoKey | AES-GCM | string (UTF-8) |
Key Derivation Functions
deriveKeyFromPassword — Derives a CryptoKey from a password using PBKDF2-HMAC-SHA256:
| Parameter | Value |
|---|---|
| Iterations | 100,000 |
| Salt length | 16 bytes |
| Algorithm | PBKDF2 with SHA-256 |
| Target | AES-GCM 256-bit key |
deriveKeyFromDeviceInfo — Creates deterministic keys from a combination of deviceId and userId, binding keys to specific user sessions on particular devices:
- Concatenate
deviceIdanduserIdwith a colon delimiter - Compute SHA-256 hash of the combined string
- Import the resulting hash as a raw AES-GCM 256-bit key
Hashing and Commitment Functions
hashSha256 — Computes SHA-256 hash returning a hexadecimal string. Handles both UTF-8 strings and Uint8Array inputs.
computeCommitment — Commitment scheme for MPC protocols (including Pedersen DKG) to commit to values before revelation. Logic: hashSha256("${value}:${blinding}").
Address Derivation
deriveEOAAddress — Derives standard Ethereum (EOA) addresses from uncompressed secp256k1 public keys:
- Accepts public keys with or without
0xor04prefixes - Validates public key length
- Returns a 42-character hex string starting with
0x
checksumAddress — Implements EIP-55 mixed-case checksumming to prevent transcription errors.
Share Exchange Encryption
During Distributed Key Generation (DKG), parties exchange secret polynomial shares using an ECIES-based encryption scheme combining ECDH key agreement with AES-256-GCM authenticated encryption.
EncryptedShare Structure
| Field | Type | Description |
|---|---|---|
fromPartyId | number | Identifier of the creating party |
toPartyId | number | Identifier of the intended recipient |
ephemeralPublicKey | string | Hex-encoded compressed secp256k1 public key for ECDH |
ciphertext | string | Hex-encoded AES-GCM encrypted share data |
nonce | string | Hex-encoded 12-byte random nonce for AES-GCM |
tag | string | Hex-encoded 16-byte authentication tag from AES-GCM |
ECDH Key Agreement
The deriveSharedSecret function performs three steps:
- ECDH point multiplication: Multiply the recipient's public key point by the sender's private scalar
- Extract x-coordinate: Extract the x-coordinate from the resulting elliptic curve point
- HKDF key derivation: Derive a 32-byte encryption key using HKDF-SHA256 with context string
"nero-mpc:ecdh:share-exchange"
The context string ensures that keys derived for share exchange cannot be reused elsewhere, even if the same ECDH point appears in different contexts.
Ephemeral Key Pairs
Each party generates a fresh ephemeral secp256k1 key pair for every DKG session using crypto.getRandomValues(). The function ensures the resulting scalar is non-zero and within the secp256k1 curve order. The public key is encoded in compressed format (33 bytes). Ephemeral keys are discarded after the DKG completes.
Encryption Process (encryptShare)
- Generate a new ephemeral private key using
generateSecureScalar() - Compute the ephemeral public key by multiplying the secp256k1 base point
- Derive a shared secret using ECDH with the ephemeral private key and recipient's public key
- Generate a random 12-byte nonce
- Serialize the bigint share to hex string, then to UTF-8 bytes
- Encrypt using
aesGcmEncryptwith the shared secret as key - Package all components in the
EncryptedSharestructure
Decryption Process (decryptShare)
- Derive the shared secret using ECDH with the recipient's private key and sender's ephemeral public key
- Extract ciphertext, nonce, and tag from hex strings
- Decrypt and verify authenticity using
aesGcmDecrypt(Web Crypto API automatically verifies the authentication tag) - Convert UTF-8 plaintext to hex string, then parse to bigint via
hexToScalar
AES-GCM Operations
aesGcmEncrypt:
- Import the 32-byte shared secret as an AES-GCM key using Web Crypto API
- Generate a random 12-byte nonce
- Encrypt the plaintext with AES-GCM
- Extract the ciphertext and 16-byte authentication tag
aesGcmDecrypt:
- Import the shared secret as an AES-GCM key
- Concatenate the ciphertext and authentication tag
- Decrypt using AES-GCM with the provided nonce
- Web Crypto API automatically verifies the authentication tag
Serialization Utilities
serializeEncryptedShare: Converts anEncryptedShareobject to a JSON string for storage or transmissiondeserializeEncryptedShare: Parses a JSON string back into anEncryptedShareobject
Security Properties Summary
Confidentiality
| Property | Mechanism |
|---|---|
| Share secrecy | AES-256-GCM encryption with 256-bit keys |
| Key derivation | HKDF-SHA256 with domain separation |
| Random nonces | 12-byte nonces from crypto.getRandomValues() |
| At-rest encryption | Device-key-encrypted storage via SecureKeyStorage |
Authenticity and Integrity
| Property | Mechanism |
|---|---|
| Authentication tag | AES-GCM 128-bit tag prevents tampering |
| Party identification | fromPartyId and toPartyId in encrypted structure |
| Proof of possession | Schnorr proofs during DKLS keygen |
| VSSS verification | Share verification against coefficient commitments (Pedersen) |
Protocol-Level Guarantees
- DKLS: State sanitization clears all intermediate values in
finallyblocks after each operation - Pedersen:
cleanup()method resets state to null, wipes polynomial coefficients, clears ephemeral key pairs, and removes commitment and share maps - Both protocols: Sensitive user identifiers are SHA-256 hashed before use as storage keys
Integration Points
These cryptographic primitives underpin:
- Client Key Manager: Secure storage of key shares
- Self-custody recovery: Offline key reconstruction via password-encrypted backups
- Protocol messages: Encrypted share exchange during DKG and signing
- Address management: Wallet address derivation from MPC-generated keys
Session Security
- Auth tokens are encrypted before storage using the device key
- Automatic token refresh before expiry
- Device fingerprinting for session binding
- Session state events (
"login","logout","connected","disconnected") for monitoring lifecycle