MPC threshold RSA signing for EGo SGX enclaves.
EGo's ego sign relies on a single RSA-3072 private key to sign enclaves. mpcegosign replaces this with threshold RSA signing where the private key is split into shares — a configurable number of parties must collaborate to produce a valid signature, but no single party can sign alone.
- t-of-n threshold signing — any t out of n parties can sign (e.g. 2-of-3, 4-of-5)
- Interactive group-chat keygen — parties exchange base64 messages via any platform (Slack, Signal, email, etc.)
- Shares encrypted in transit — X25519 ECDH + AES-256-GCM, safe to post in group chats
- EGo-compatible — delegates MRENCLAVE computation to
ego-oesignfor exact compatibility - Pure Go — no dependencies beyond the Go standard library
go install github.com/magicaltux/mpcegosign@latestAll parties join a group chat. One person initiates, others join.
Initiator (decides the parameters):
mpcegosign keygen --parties 3 --threshold 2This outputs an INIT message. Post it to the group chat.
Each other party (joiners):
mpcegosign keygenPaste the INIT message when prompted. This outputs a JOIN message. Post it to the group chat.
Back to the initiator: paste each JOIN message as it appears. Once all parties have joined, the tool generates the key, splits it into threshold shares, and outputs a FINALIZE message. Post it to the group chat.
Each joiner: paste the FINALIZE message. The tool decrypts your share and saves it locally.
At the end, each party has:
share_N.json— their secret key share (keep secure)public.pem— the shared RSA public key
Party numbers are auto-assigned: initiator is party 1, joiners are numbered in the order they join.
Signing also happens via group chat, same as keygen. One party initiates (needs the binary + EGo), others contribute their partial signatures.
Initiator (has the unsigned binary, enclave.json, EGo, and their share):
mpcegosign sign --config enclave.json --share share_1.json --out signed-binaryThis computes the MRENCLAVE, outputs a SIGN-INIT message to the group, then waits for partial signatures.
Each other signing party (has their share):
mpcegosign sign --share share_3.jsonPaste the SIGN-INIT message. The tool shows the MRENCLAVE for verification, computes the partial signature, and outputs a SIGN-PARTIAL message to post to the group.
Back to the initiator: paste each SIGN-PARTIAL. Once the threshold is reached, the tool combines the partials and writes the signed binary.
Each party independently verifies the MRENCLAVE shown in the SIGN-INIT message. If it doesn't match what they expect, they don't sign.
Interactive distributed key generation via group chat messages.
# Initiator: set number of parties and threshold
mpcegosign keygen --parties N [--threshold T] [--out-dir DIR]
# Joiner: join an existing ceremony
mpcegosign keygen [--out-dir DIR]
| Flag | Description |
|---|---|
--parties N |
Total number of parties. Only the initiator sets this. |
--threshold T |
Minimum parties required to sign. Default: N (all parties required). |
--out-dir DIR |
Directory to save share and public key. Default: current directory. |
If --parties is omitted, the tool runs as a joiner and waits for an INIT message.
Threshold examples:
| Scheme | Sub-shares per party | Total share sets |
|---|---|---|
| 2-of-3 | 2 | 3 |
| 3-of-5 | 6 | 10 |
| 4-of-5 | 4 | 5 |
| 5-of-5 | 1 | 1 |
Interactive distributed signing via group chat messages.
# Initiator (has binary + config + EGo):
mpcegosign sign --config enclave.json --share share.json [--out signed] [--ego /opt/ego]
# Signer (has their share):
mpcegosign sign --share share.json
| Flag | Description |
|---|---|
--config |
Path to EGo enclave.json. Presence of this flag makes you the initiator. |
--share |
Path to this party's key share JSON. |
--out |
Output signed binary path (initiator only). Default: <exe>.signed. |
--ego |
Path to EGo installation. Default: auto-detect. |
The initiator computes the MRENCLAVE and posts a SIGN-INIT message. Each signer verifies the MRENCLAVE, computes their partial, and posts a SIGN-PARTIAL message. Once the initiator has enough partials, the tool combines them and writes the signed binary.
Compute MRSIGNER (SHA-256 of the RSA modulus in little-endian).
mpcegosign signerid --key public.pem
mpcegosign signerid --enclave signed-binary
| Variable | Description |
|---|---|
EGO_PATH |
Path to EGo installation directory (e.g. /opt/ego). Used by sign to locate ego-oesign for MRENCLAVE computation. Falls back to /opt/ego if unset. |
SGX requires RSA-3072 with public exponent e=3. Standard threshold RSA (Shoup's scheme) requires gcd(e, n!) = 1, which fails for e=3 when n >= 3.
mpcegosign uses redundant additive sharing instead:
- For a t-of-n scheme, generate an independent additive share set for each of the C(n,t) possible t-party subsets
- Each party stores one sub-share per subset they belong to
- To sign, any t parties identify their common subset and combine their sub-shares
Additive sharing works because: m^{d_1} * m^{d_2} * ... * m^{d_t} = m^{d_1 + d_2 + ... + d_t} = m^d mod N
The SGX SIGSTRUCT (1808 bytes) contains the enclave measurement, identity, and RSA signature. mpcegosign builds the SIGSTRUCT, computes SHA-256 over the two signed regions (bytes 0-127 and 900-1027), applies PKCS#1 v1.5 padding, and signs with the MPC-combined signature.
EGo uses a dual-image layout (ego-enclave runtime + Go payload binary) for measurement. mpcegosign delegates MRENCLAVE computation to ego-oesign to ensure exact compatibility with EGo's signing flow.
The only file that persists between sessions. Each party keeps theirs secure.
{
"version": 2,
"party_index": 1,
"num_parties": 3,
"threshold": 2,
"modulus": "<base64 big-endian>",
"public_exponent": 3,
"shares": {
"1,2": "<base64 sub-share>",
"1,3": "<base64 sub-share>"
}
}All other data (hashes, partial signatures, SIGSTRUCT) is exchanged as base64 messages in the group chat during the interactive signing ceremony — no intermediate files needed.
See LICENSE file.