# Exportable Ed25519 Keys

## Intro: Limitations with exporting Ed25519 keys

One issue that often arises when using Ed25519 secrets on MPC wallets is the lack of options available when it comes to allowing a wallet user to migrate its wallet to another wallet.
This is more of a technicality that is tightly related to the way the Ed25519 signature scheme works, combined with some tasks that are inefficient to realize using MPC.
By "inefficient" we mean that they are likely to introduce poor UX due to the time it takes to accomplish these tasks.

## Sodot's Solution

We propose a middle ground that allows to bridge the gap without resorting to heavy tools; thereby retaining high quality UX and allowing for users to export their Ed25519 MPC keys to other wallets.
It is important to understand our solution and the restrictions that apply to it as we elaborate on those in the following.

At a high level the solution works by having one of the parties sample a full key.
This allows this party to compute (locally and efficiently) all information necessary to later export the key.
This party then distributes shares of this randomly sampled key to the other parties, so the key can later be exported.

:::warning
The following two noteworthy restrictions therefore apply:

1. The sampling party is essentially trusted. When designing wallets that rely on this solution it might make sense to assign the wallets' customers with sampling the key and sharing it.
2. Derivations are **impossible** with this exportable flavor of the Ed25519 key.
:::

## Key Generation and Signing

Unlike the other schemes, key generation is asymmetric: exactly one party calls `sample_key` while every other party calls `receive_key`, all using the same room, keygen IDs and `(N, T)` configuration.
The resulting signatures are standard Ed25519 signatures, verifiable with any conforming Ed25519 library.

```rust
use sodot_mpc::{ExportableEd25519, KeygenId};
use std::num::NonZeroU16;

const N: u16 = 3;
const T: u16 = 2;
const HOST_URL: &str = "us1.sodot.dev";
const API_KEY: &str = "<Your Relay API Key>";

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let exportable_ed25519 = ExportableEd25519::new(HOST_URL.to_string());

    // Your server side creates a room for 3 parties using its API_KEY
    // Creating a room uuid should always happen on the server side using your API_KEY, so that the API_KEY is never exposed to the client side
    let keygen_room_uuid = exportable_ed25519
        .create_room(NonZeroU16::new(N).unwrap(), API_KEY)
        .await?;

    // All parties call init_keygen to get (KeygenId, KeygenPrivateKey) as result
    // The KeygenId is the public part that you should pass to all other parties, the KeygenPrivateKey is the private state you should keep until the actual keygen completes.
    let (keygen_id, keygen_private_key) = exportable_ed25519.init_keygen()?;

    // All parties receive the keygenIds from all other parties
    let keygen_ids = [
        KeygenId::new("KeygenIdParty1".into()),
        KeygenId::new("KeygenIdParty2".into()),
    ];

    // Exactly one party samples the full key and joins the room with sample_key
    let (public_key, secret_share) = exportable_ed25519
        .sample_key(
            &keygen_room_uuid,
            N.try_into().expect("N is a valid NonZeroU16"),
            T.try_into().expect("T is a valid NonZeroU16"),
            &keygen_private_key,
            &keygen_ids,
        )
        .await?;

    // Every other party joins the same room with receive_key instead:
    // let (public_key, secret_share) = exportable_ed25519
    //     .receive_key(&keygen_room_uuid, ..., &keygen_private_key, &keygen_ids)
    //     .await?;

    // To sign a message, create a signing room for T parties on the server side, using your API_KEY
    let signing_room_uuid = exportable_ed25519
        .create_room(NonZeroU16::new(T).unwrap(), API_KEY)
        .await?;

    // Choose a message to sign
    // Note: ExportableEd25519 does not support derivation paths, the message is signed with the key itself
    let message = b"Hello, world!";

    // 2 parties join the signing room
    let signature = exportable_ed25519
        .sign(&signing_room_uuid, &secret_share, message)
        .await?;

    // signature is a standard 64-byte Ed25519 signature, verifiable against public_key
    println!("Signature: {:?}", signature);
    Ok(())
}
```

Key Refresh works exactly like the other schemes, see [Key Lifecycle](/rust/key_lifecycle).

## Key Export

The defining feature of the scheme: a threshold of shares can be combined, online or offline, to recover the raw 32-byte Ed25519 private key.

```rust
use sodot_mpc::{ExportableEd25519, SecretShare};

const T: u16 = 2;
const HOST_URL: &str = "us1.sodot.dev";
const API_KEY: &str = "<Your Relay API Key>";

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let exportable_ed25519 = ExportableEd25519::new(HOST_URL.to_string());
    // An original secret_share was previously generated using sample_key/receive_key
    let secret_share =
        SecretShare::<ExportableEd25519>::from("serialized_share_from_keygen".to_string());

    // Exporting the secret key material
    // Your server creates a room for T parties (2 in this case)
    let export_room_uuid = exportable_ed25519
        .create_room(T.try_into().expect("T is a valid NonZeroU16"), API_KEY)
        .await?;

    // Only one of the parties will receive the full private key, this party will retrieve its exportID:
    let export_to = exportable_ed25519.export_id(&secret_share)?;

    // It will then propagate this exportID to the other parties
    // Threshold (t) parties will now need to join the export room
    let exported_private_key = exportable_ed25519
        .export_full_private_key(&export_room_uuid, &secret_share, &export_to)
        .await?;

    // exported_private_key is 'None' except for the party with export_to as its export_id
    // For the exporting party it is a 64-byte array: the raw 32-byte Ed25519 private key
    // followed by the 32-byte public key
    let private_key_and_pubkey = exported_private_key.expect("export_to party will get the private key");
    let raw_private_key = &private_key_and_pubkey[..32];

    // raw_private_key can now be imported into any non-MPC based Ed25519 signing software
    Ok(())
}
```

Alternatively, a threshold of shares can be combined **locally** with no network communication, which is useful for offline recovery on an air-gapped device:

```rust
// Collect a threshold of serialized secret shares from the parties' secure storage
let share_1 = SecretShare::<ExportableEd25519>::from("serialized_share_party_1".to_string());
let share_2 = SecretShare::<ExportableEd25519>::from("serialized_share_party_2".to_string());

let private_key_and_pubkey = exportable_ed25519.offline_export_full_private_key(&[share_1, share_2])?;
```
