.github
    ISSUE_TEMPLATE
        epic-template.md
        issue-template.md
    workflows
        deploy-multichain-dev-contract.yml
        deploy-prod.yml
        docker-image.yml
        integrations.yml
        multichain-contract.yml
        multichain-dev.yml
        multichain-integration.yml
        multichain-prod.yml
        terraform-dev.yml
        terraform-feature-env-destroy.yml
        terraform-feature-env.yml
        unit.yml
Cargo.toml
DEPLOY.md
README.md
contract
    .cargo
        config.toml
    Cargo.toml
    src
        lib.rs
        primitives.rs
    tests
        tests.rs
infra
    README.md
    mpc-recovery-dev
        migration.py
    scripts
        generate_cipher_keys
            Cargo.toml
            src
                main.rs
        get_sha.sh
integration-tests
    Cargo.toml
    README.md
    build.rs
    src
        env
            containers.rs
            local.rs
            mod.rs
        lib.rs
        main.rs
        mpc.rs
        multichain
            containers.rs
            local.rs
            mod.rs
            utils.rs
        sandbox.rs
        util.rs
    tests
        lib.rs
        mpc
            mod.rs
            negative.rs
            positive.rs
        multichain
            actions
                mod.rs
                wait_for.rs
            mod.rs
verify_prehashed[beg]
verify_prehashed[end]
keys
    Cargo.toml
    src
        hpke.rs
        lib.rs
load-tests
    Cargo.toml
    README.md
    src
        fastauth
            constants.rs
            mod.rs
            primitives.rs
            test_plans
                long_with_spikes.txt
                short.txt
            utils.rs
        main.rs
        multichain
            mod.rs
            test_plans
                short.txt
mpc-recovery
    Cargo.toml
    build.rs
    src
        error.rs
        firewall
            allowed.rs
            mod.rs
        gcp
            error.rs
            mod.rs
            value.rs
        key_recovery.rs
        leader_node
            mod.rs
        lib.rs
        logging.rs
        main.rs
        metrics.rs
        msg.rs
        nar.rs
        oauth.rs
        primitives.rs
        relayer
            error.rs
            mod.rs
            msg.rs
        sign_node
            aggregate_signer.rs
            migration.rs
            mod.rs
            oidc.rs
            pk_set.rs
            user_credentials.rs
        transaction.rs
        utils.rs
node
    Cargo.toml
    src
        cli.rs
        gcp
            error.rs
            mod.rs
            value.rs
        http_client.rs
        indexer.rs
        kdf.rs
        lib.rs
        main.rs
        mesh
            connection.rs
            mod.rs
        metrics.rs
        protocol
            consensus.rs
            contract
                mod.rs
                primitives.rs
            cryptography.rs
            message.rs
            mod.rs
            presignature.rs
            signature.rs
            state.rs
            triple.rs
        rpc_client.rs
        storage
            mod.rs
            secret_storage.rs
            triple_storage.rs
        test_utils.rs
        types.rs
        util.rs
        web
            error.rs
            mod.rs
rustfmt.toml
test-oidc-provider
    Cargo.toml
    README.md
    src
        main.rs

# MPC Recovery Infrastructure Overview

There are currently 3 mostly static environments for MPC
 - Mainnet (production)
 - Testnet (production)
 - Dev (development)

 ## Mainnet/Testnet

 Mainnet and Testnet infra code is in the directory `mpc-recovery-prod` and is built off of the `main` GitHub Branch
   - This environment should be deployed via the GHA pipeline `deploy-prod.yml` manually in order to prevent unwanted changes
   - Both Mainnet and Testnet are treated as production environments

 ## Dev

 The Dev environment infra code is located in the `mpc-recovery-dev` directory and is built off of the `develop` GitHub Branch
   - This should be used as the main development environment
   - Every time a pull request is opened up against the `develop` branch, a new, ephemeral environment is created with your changes
     - *Note: These environments will have the associated PR number appended to all resources*
   - When a pull request is approved and merged into the `develop` branch, a new revision is deployed to the static Dev environment with the PRs changes and the PRs ephemeral environment is destroyed
# Integration tests

## Basic guide

Running integration tests requires you to have relayer and sandbox docker images present on your machine:

```BASH
docker pull ghcr.io/near/os-relayer
docker pull ghcr.io/near/sandbox
```

For M1 you may want to pull the following image instead:

```BASH
docker pull ghcr.io/near/sandbox:latest-aarch64
```

In case of authorization issues make sure you have logged into docker using your [access token](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-with-a-personal-access-token-classic).

Build OIDC Provider test image
```bash
docker build -t near/test-oidc-provider ./test-oidc-provider
```

Set dummy AWS credentials and the correct region

``` bash
aws configure set region us-east-1
aws --profile default configure set aws_access_key_id "123"
aws --profile default configure set aws_secret_access_key "456"
```

Then run the integration tests:

```BASH
cargo test -p mpc-recovery-integration-tests
```

### Alternative: Docker Builds/Tests

If instead, we need to run docker build/tests:

```BASH
docker build . -t near/mpc-recovery
```

**Note**. You will need to re-build the Docker image each time you make a code change and want to run the integration tests.

Finally, run the integration tests with the built docker image:

```BASH
cargo test -p mpc-recovery-integration-tests --features docker-test
```

## Profiling: Flamegraphs

To profile code and get a flamegraph, run the following:

```sh
cargo flamegraph --root --profile flamegraph --test lib
```

Or for a singular test like `test_basic_action`:

```sh
cargo flamegraph --root --profile flamegraph --test lib -- test_basic_action
```

This will generate a `flamegraph.svg`. Open this on a browser and inspect each of the callstacks.

## FAQ

### I want to run a test, but keep the docker containers from being destroyed

You can pass environment variable `TESTCONTAINERS=keep` to keep all of the docker containers. For example:

```bash
$ TESTCONTAINERS=keep cargo test -p mpc-recovery-integration-tests
```

### There are no logs anymore, how do I debug?

The easiest way is to run one isolated test of your choosing while keeping the containers (see above):

```bash
$ TESTCONTAINERS=keep cargo test -p mpc-recovery-integration-tests test_basic_action
```

Now, you can do `docker ps` and it should list all of containers related to your test (the most recent ones are always at the top, so lookout for those). For example:

```bash
CONTAINER ID   IMAGE                                            COMMAND                  CREATED         STATUS         PORTS                                           NAMES
b2724d0c9530   near/mpc-recovery:latest                         "mpc-recovery start-…"   5 minutes ago   Up 5 minutes   0.0.0.0:32792->19985/tcp, :::32792->19985/tcp   fervent_moore
67308ab06c5d   near/mpc-recovery:latest                         "mpc-recovery start-…"   5 minutes ago   Up 5 minutes   0.0.0.0:32791->3000/tcp, :::32791->3000/tcp     upbeat_volhard
65ec65384af4   near/mpc-recovery:latest                         "mpc-recovery start-…"   5 minutes ago   Up 5 minutes   0.0.0.0:32790->3000/tcp, :::32790->3000/tcp     friendly_easley
b4f90b1546ec   near/mpc-recovery:latest                         "mpc-recovery start-…"   5 minutes ago   Up 5 minutes   0.0.0.0:32789->3000/tcp, :::32789->3000/tcp     vibrant_allen
934ec13d9146   ghcr.io/near/os-relayer:latest                   "/usr/local/bin/entr…"   5 minutes ago   Up 5 minutes   0.0.0.0:32788->16581/tcp, :::32788->16581/tcp   sleepy_grothendieck
c505ead6eb18   redis:latest                                     "docker-entrypoint.s…"   5 minutes ago   Up 5 minutes   0.0.0.0:32787->6379/tcp, :::32787->6379/tcp     trusting_lederberg
2843226b16a9   google/cloud-sdk:latest                          "gcloud beta emulato…"   5 minutes ago   Up 5 minutes   0.0.0.0:32786->15805/tcp, :::32786->15805/tcp   hungry_pasteur
3f4c70020a4c   ghcr.io/near/sandbox:latest                      "near-sandbox --home…"   5 minutes ago   Up 5 minutes                                                   practical_elbakyan
```

Now, you can inspect each container's logs according to your needs using `docker logs <container-id>`. You might also want to reproduce some components of the test manually by making `curl` requests to the leader node (its web port is exposed on your host machine, use `docker ps` output above as the reference).

### Re-building Docker image is way too slow, is there a way I can do a faster development feedback loop?

We have a CLI tool that can instantiate a short-lived development environment that has everything except for the leader node set up. You can then seamlessly plug in your own leader node instance that you have set up manually (the tool gives you a CLI command to use as a starting point, but you can attach debugger, enable extra logs etc). Try it out now (sets up 3 signer nodes):

```bash
$ export RUST_LOG=info
$ cargo run -p mpc-recovery-integration-tests -- setup-env 3
```

### I'm getting "Error: error trying to connect: No such file or directory (os error 2)"

It's a known issue on MacOS. Try executiong the following command:

```bash
sudo ln -s $HOME/.docker/run/docker.sock /var/run/docker.sock
```
# MPC Recovery Load Tests
This directory contains load tests for the FastAuth and Multichain services. It is build using [Goose](https://book.goose.rs/title-page.html), a load testing tool written in Rust.

## Running the tests
To run the tests, you need to have Rust installed. You can install Rust using [rustup](https://rustup.rs/).
To start the tests, run the following command:
Fastauth:
```bash
RUST_LOG=info cargo run --release -- --host <host> --report-file=load_test_results.html --test-plan "$(cat ./src/fastauth/test_plans/short.txt)" --scenarios fastAuthSimpleMpcPublicKey
```
Multichain:
```bash
RUST_LOG=info cargo run --release -- --host https://rpc.testnet.near.org --report-file=load_test_results.html --test-plan "$(cat ./src/multichain/test_plans/short.txt)" --scenarios multichainSign
```
You can run Load Tests against your local development environment (check `/integration-tests` for more info) or against the staging environment by setting the `--host` parameter.

The tests are written in Rust and can be found in the `/src` directory.
You can create your own test plan or execute one of the existing test plans from `<service>/test_plans` directory.
# MPC Account Recovery (WIP)
The aim of this project is to offer NEAR users the opportunity to create and restore their accounts by utilizing OIDC protocol. By linking their NEAR account to `near.org` or other authentication provider, they can then add a new Full Access key, which will be managed by the trusted network of servers. Should they lose all the keys they possess, they can reauthorize themselves, create a new key, and add it into their NEAR account using a transaction that will be signed by MPC servers through their recovery key. All the transaction cost will be covered by a relayer server and metatransactions.

## How the MPC system will work
- The system consists of 3 trusted signing nodes and a leader node
- Each node holds a unique secret key
- Each action must be signed by all 3 nodes
- Nodes signatures are then combined into a single signature on the leader node

In the future we are planning to get rid  of the leader node to make the system more decentralized.

## External API

The recovery service is currently hosted at https://near.org

### Claim OIDC Id Token ownership

    URL: /claim_oidc
    Request parameters: {
        oidc_token_hash: [u8; 32],
        frp_public_key: String,
        frp_signature: [u8; 64],
    }
    Response: Ok {
        mpc_signature: String,
    } / Err {
        msg: String
    }

The frp_signature you send must be an Ed22519 signature of the hash:

    SALT = 3177899144
    sha256.hash(Borsh.serialize<u32>(SALT + 0) ++ Borsh.serialize<[u8]>(oidc_token_hash) ++ [0] ++ Borsh.serialize<[u8]>(frp_public_key))

signed with your on device public key.

The constant 3177899144 is a random number between 2^31 and 2^32 which as described [here](https://github.com/gutsyphilip/NEPs/blob/8b0b05c3727f0a90b70c6f88791152f54bf5b77f/neps/nep-0413.md#example) prevents collisions with legitimate on chain transactions.

If you successfully claim the token you will receive a signature in return of:

    sha256.hash(Borsh.serialize<u32>(SALT + 1) ++ Borsh.serialize<[u8]>(signature))

This will be signed by the nodes combined Ed22519 signature.

### MPC Public Key

    URL: /mpc_public_key
    Request parameters: {}
    Response: Ok {
        mpc_pk: String,
    } / Err {
        msg: String
    }

Returns the MPC public key that is used to sign the OIDC claiming response. Should not be used in production environment, as the MPC PK should be hardcoded in the client.

### User Credentials

    URL: /user_credentials
    Request parameters: {
        oidc_token: String,
        frp_signature: Signature,
        frp_public_key: String,
    }
    Response: Ok {
        public_key: String,
    } / Err {
        msg: String
    }

Returns the recovery public key associated with the provided OIDC token.
The frp_signature you send must be an Ed22519 signature of the hash:

    sha256.hash(Borsh.serialize<u32>(SALT + 2) ++ Borsh.serialize<[u8]>(oidc_token) ++ [0] ++ Borsh.serialize<[u8]>(frp_public_key))

### Create New Account

    URL: /new_account
    Request parameters: {
        near_account_id: String,
        create_account_options: CreateAccountOptions,
        oidc_token: String,
        user_credentials_frp_signature: Signature,
        frp_public_key: String,
    }
    Response:
    Ok {
        create_account_options: CreateAccountOptions,
        recovery_public_key: String,
        near_account_id: String,
    } /
    Err {
        msg: String
    }

This creates an account with account Id provided in `near_account_id`. If this name is already taken then this operation will fail with no action having been taken.

This service will send a `create_account` transaction to the relayer signed by `account_creator.near` account. If this operation is successful relayer will make an allowance for the created account.

Newly created NEAR account will have two full access keys. One that was provided by the user, and the recovery one that is controlled by the MPC system.

In the future, MPC Service will disallow creating account with ID Tokes that were not claimed first. It is expected, that PK that client wants to use for the account creation is the same as the one that was used to claim the ID Token.

The user_credentials_frp_signature you send must be an Ed22519 signature of the hash:

    sha256.hash(Borsh.serialize<u32>(SALT + 2) ++ Borsh.serialize<[u8]>(oidc_token) ++ [0] ++ Borsh.serialize<[u8]>(frp_public_key))

signed by the key you used to claim the oidc token. This does not have to be the same as the key in the public key field. This digest is the same as the one used in the user_credentials endpoint, because new_account request needs to get the recovery public key of the user that is creating the account.

### Sign

    URL: /sign
    Request parameters: {
        delegate_action: String, // Base64-encoded borsh serialization of DelegateAction
        oidc_token: String,
        frp_signature: Signature,
        user_credentials_frp_signature: Signature,
        frp_public_key: String,
    }
    Response:
    Ok {
        signature: Signature,
    } /
    Err {
        msg: String
    }

This endpoint can be used to sign a delegate action that can then be sent to the relayer. The delegate action is signed by user recovery key.

The frp_signature you send must be an Ed22519 signature of the hash:

    sha256.hash(Borsh.serialize<u32>(SALT + 3) ++
    Borsh.serialize<[u8]>(delegate_action) ++
    Borsh.serialize<[u8]>(oidc_token) ++
    [0] ++ Borsh.serialize<[u8]>(frp_public_key))

The user_credentials_frp_signature is needed to get user recovery PK. It is the same as in user_credentials endpoint.

## OIDC (OAuth 2.0) authentication

We are using OpenID Connect (OIDC) standard to authenticate users (built on top of OAuth 2.0).
Check OIDC standard docs [here](https://openid.net/specs/openid-connect-core-1_0.html#IDToken) and Google OIDC docs [here](https://developers.google.com/identity/protocols/oauth2/openid-connect)

## Front-runnig protection flow
Before transmitting your OIDC Id Token to the recovery service you must first claim the ownership of the token. This prevents a rogue node from taking your Id Token and using it to sign another request.

The expected flow for the client is next:
1. Client-side developer hardcodes the MPC PK in the client code. It should be provided by MPC Recovery service developers and compared to the one that is returned by `/mpc_public_key` endpoint. You MUST NOT fetch the MPC PK from the nodes themselves in production env.
2. Client generates a key pair that is stored in their device. It can be same key pair that is used to sign the transactions.
3. Client recieves an OIDC Id Token from the authentication provider.
4. Client claims the ownership of the token by sending a request to the `/claim_oidc_token` endpoint.
5. In reponce to the claim request, user recieves a signature that is signed by the MPC system.
6. User verifies that the signature is valid. It garantees that each node in the system has seen the token and will not accept it again with another key.
7. Now client can safely send their Id Token with `/sign` or other requests.
8. Once the token is expaired, client can claim a new one and continue using the MPC Recovery service.

Check our integration tests to see how it works in practice.

Registered ID Token will be added to the persistent DB on each Signing node and saved until expiration. Registered Id Tokens are tied to the provided PK.

## Sign flow
The expected flow for the client is next:
1. Client uses `/user_credentials` endpoint to get the recovery PK.
2. Client fetches latest nonce, block hash using obtained recovery PK.
3. Client creates a delegate action with desired actions, such as add or delete key.
4. Client serializes the delegate action and encodes it into Base64.
5. Client gets the signature from the MPC system using `/sign` endpoint.
6. Client sends the same delegate action to the relayer with obtained signature.

### Client integration

There are several ways to get and use the ID token. The flow that we are using is called the "server" flow, you can find more info [here](https://developers.google.com/identity/openid-connect/openid-connect#authenticatingtheuser). The system will be able to process any token that is following the core OpenID Connect standard. In order to receive the ID token from OpenID provider you will need to include the `openid` scope value to the Authorization Request.

### Server integration

Internally, we are identifying users by their issuer id (iss) and their unique ID (sub) retrieved form the ID token and separated by a colon: `<issuer_iss>:<user_sub>`. It means that each recovery method (like GitHub and Google) is separated from one another even if they have the same email.

### Contribute

In order to build the project, you will need to have `protoc` and `gmp` installed. Refer to your system's package manager on how to do this.

If you have [nix](https://nixos.org/) and [direnv](https://direnv.net/) installed, you can set up a development environment by running:

```BASH
direnv allow
```

Run unit tests with:
```BASH
cargo test -p mpc-recovery
```
# Test OIDC Provider
Simplistic server that can return a public key in RSA PEM format for JWT Roken validation.

## Usage
1. Build Dcoker image
```bash
docker build -t near/test-oidc-provider .
```
2. Run Integration or other type of tests
