From 84227342a12982c7bacfe10f1c6014803aa8d9dd Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Tue, 13 Jan 2026 12:26:37 +0100 Subject: [PATCH 01/44] create package --- packages/keyring-eth-mpc/CHANGELOG.md | 10 ++ packages/keyring-eth-mpc/LICENSE | 15 +++ packages/keyring-eth-mpc/README.md | 128 +++++++++++++++++++ packages/keyring-eth-mpc/jest.config.js | 32 +++++ packages/keyring-eth-mpc/package.json | 69 ++++++++++ packages/keyring-eth-mpc/src/index.ts | 1 + packages/keyring-eth-mpc/tsconfig.build.json | 11 ++ packages/keyring-eth-mpc/tsconfig.json | 9 ++ 8 files changed, 275 insertions(+) create mode 100644 packages/keyring-eth-mpc/CHANGELOG.md create mode 100644 packages/keyring-eth-mpc/LICENSE create mode 100644 packages/keyring-eth-mpc/README.md create mode 100644 packages/keyring-eth-mpc/jest.config.js create mode 100644 packages/keyring-eth-mpc/package.json create mode 100644 packages/keyring-eth-mpc/src/index.ts create mode 100644 packages/keyring-eth-mpc/tsconfig.build.json create mode 100644 packages/keyring-eth-mpc/tsconfig.json diff --git a/packages/keyring-eth-mpc/CHANGELOG.md b/packages/keyring-eth-mpc/CHANGELOG.md new file mode 100644 index 000000000..e7c894b17 --- /dev/null +++ b/packages/keyring-eth-mpc/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +[Unreleased]: https://github.com/MetaMask/accounts diff --git a/packages/keyring-eth-mpc/LICENSE b/packages/keyring-eth-mpc/LICENSE new file mode 100644 index 000000000..b5ed1b9c5 --- /dev/null +++ b/packages/keyring-eth-mpc/LICENSE @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2020 MetaMask + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/packages/keyring-eth-mpc/README.md b/packages/keyring-eth-mpc/README.md new file mode 100644 index 000000000..00bcfb153 --- /dev/null +++ b/packages/keyring-eth-mpc/README.md @@ -0,0 +1,128 @@ +# MPC Keyring + +A Keyring for Ethereum accounts that uses Multi-Party Computation (MPC) for key management and signing. +Built on top of the [MFA Wallet SDK](https://github.com/MetaMask/mfa-wallet-sdk). + +## Installation + +`yarn add @metamask/eth-mpc-keyring` + +or + +`npm install @metamask/eth-mpc-keyring` + +## The Keyring Class Protocol + +One of the goals of this class is to allow developers to easily add new signing strategies to MetaMask. We call these signing strategies Keyrings, because they can manage multiple keys. + +### Keyring.type + +A class property that returns a unique string describing the Keyring. +This is the only class property or method, the remaining methods are instance methods. + +### constructor( options ) + +As a Javascript class, your Keyring object will be used to instantiate new Keyring instances using the new keyword. For example: + +``` +const keyring = new YourKeyringClass(options); +``` + +The constructor currently receives an options object that will be defined by your keyring-building UI, once the user has gone through the steps required for you to fully instantiate a new keyring. For example, choosing a pattern for a vanity account, or entering a seed phrase. + +We haven't defined the protocol for this account-generating UI yet, so for now please ensure your Keyring behaves nicely when not passed any options object. + +## Keyring Instance Methods + +All below instance methods must return Promises to allow asynchronous resolution. + +### serialize() + +In this method, you must return any JSON-serializable JavaScript object that you like. It will be encoded to a string, encrypted with the user's password, and stored to disk. This is the same object you will receive in the deserialize() method, so it should capture all the information you need to restore the Keyring's state. + +### deserialize( object ) + +As discussed above, the deserialize() method will be passed the JavaScript object that you returned when the serialize() method was called. + +### addAccounts( n = 1 ) + +The addAccounts(n) method is used to inform your keyring that the user wishes to create a new account. You should perform whatever internal steps are needed so that a call to serialize() will persist the new account, and then return an array of the new account addresses. + +The method may be called with or without an argument, specifying the number of accounts to create. You should generally default to 1 per call. + +### getAccounts() + +When this method is called, you must return an array of hex-string addresses for the accounts that your Keyring is able to sign for. + +### signTransaction(address, transaction) + +This method will receive a hex-prefixed, all-lowercase address string for the account you should sign the incoming transaction with. + +For your convenience, the transaction is an instance of ethereumjs-tx, (https://github.com/ethereumjs/ethereumjs-tx) so signing can be as simple as: + +``` +transaction.sign(privateKey) +``` + +You must return a valid signed ethereumjs-tx (https://github.com/ethereumjs/ethereumjs-tx) object when complete, it can be the same transaction you received. + +### signMessage(address, data) + +The `eth_sign` method will receive the incoming data, alread hashed, and must sign that hash, and then return the raw signed hash. + +### exportAccount(address) + +Exports the specified account as a private key hex string. + +## Contributing + +### Setup + +- Install [Node.js](https://nodejs.org) version 18 + - If you are using [nvm](https://github.com/creationix/nvm#installation) (recommended) running `nvm use` will automatically choose the right node version for you. +- Install [Yarn v3](https://yarnpkg.com/getting-started/install) +- Run `yarn install` to install dependencies and run any required post-install scripts + +### Testing and Linting + +Run `yarn test` to run the tests once. To run tests on file changes, run `yarn test:watch`. + +Run `yarn lint` to run the linter, or run `yarn lint:fix` to run the linter and fix any automatically fixable issues. + +### Release & Publishing + +The project follows the same release process as the other libraries in the MetaMask organization. The GitHub Actions [`action-create-release-pr`](https://github.com/MetaMask/action-create-release-pr) and [`action-publish-release`](https://github.com/MetaMask/action-publish-release) are used to automate the release process; see those repositories for more information about how they work. + +1. Choose a release version. + + - The release version should be chosen according to SemVer. Analyze the changes to see whether they include any breaking changes, new features, or deprecations, then choose the appropriate SemVer version. See [the SemVer specification](https://semver.org/) for more information. + +2. If this release is backporting changes onto a previous release, then ensure there is a major version branch for that version (e.g. `1.x` for a `v1` backport release). + + - The major version branch should be set to the most recent release with that major version. For example, when backporting a `v1.0.2` release, you'd want to ensure there was a `1.x` branch that was set to the `v1.0.1` tag. + +3. Trigger the [`workflow_dispatch`](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#workflow_dispatch) event [manually](https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow) for the `Create Release Pull Request` action to create the release PR. + + - For a backport release, the base branch should be the major version branch that you ensured existed in step 2. For a normal release, the base branch should be the main branch for that repository (which should be the default value). + - This should trigger the [`action-create-release-pr`](https://github.com/MetaMask/action-create-release-pr) workflow to create the release PR. + +4. Update the changelog to move each change entry into the appropriate change category ([See here](https://keepachangelog.com/en/1.0.0/#types) for the full list of change categories, and the correct ordering), and edit them to be more easily understood by users of the package. + + - Generally any changes that don't affect consumers of the package (e.g. lockfile changes or development environment changes) are omitted. Exceptions may be made for changes that might be of interest despite not having an effect upon the published package (e.g. major test improvements, security improvements, improved documentation, etc.). + - Try to explain each change in terms that users of the package would understand (e.g. avoid referencing internal variables/concepts). + - Consolidate related changes into one change entry if it makes it easier to explain. + - Run `yarn auto-changelog validate --rc` to check that the changelog is correctly formatted. + +5. Review and QA the release. + + - If changes are made to the base branch, the release branch will need to be updated with these changes and review/QA will need to restart again. As such, it's probably best to avoid merging other PRs into the base branch while review is underway. + +6. Squash & Merge the release. + + - This should trigger the [`action-publish-release`](https://github.com/MetaMask/action-publish-release) workflow to tag the final release commit and publish the release on GitHub. + +7. Publish the release on npm. + + - Be very careful to use a clean local environment to publish the release, and follow exactly the same steps used during CI. + - Use `npm publish --dry-run` to examine the release contents to ensure the correct files are included. Compare to previous releases if necessary (e.g. using `https://unpkg.com/browse/[package name]@[package version]/`). + - Once you are confident the release contents are correct, publish the release using `npm publish`. diff --git a/packages/keyring-eth-mpc/jest.config.js b/packages/keyring-eth-mpc/jest.config.js new file mode 100644 index 000000000..afeeab368 --- /dev/null +++ b/packages/keyring-eth-mpc/jest.config.js @@ -0,0 +1,32 @@ +/* + * For a detailed explanation regarding each configuration property and type check, visit: + * https://jestjs.io/docs/configuration + */ + +const merge = require('deepmerge'); +const path = require('path'); + +const baseConfig = require('../../jest.config.packages'); + +const displayName = path.basename(__dirname); + +module.exports = merge(baseConfig, { + // The display name when running multiple projects + displayName, + + // An array of regexp pattern strings used to skip coverage collection + coveragePathIgnorePatterns: ['./src/tests'], + + // The glob patterns Jest uses to detect test files + testMatch: ['**/*.test.[jt]s?(x)'], + + // An object that configures minimum threshold enforcement for coverage results + coverageThreshold: { + global: { + branches: 100, + functions: 100, + lines: 100, + statements: 100, + }, + }, +}); diff --git a/packages/keyring-eth-mpc/package.json b/packages/keyring-eth-mpc/package.json new file mode 100644 index 000000000..5049f5fb4 --- /dev/null +++ b/packages/keyring-eth-mpc/package.json @@ -0,0 +1,69 @@ +{ + "name": "@metamask/eth-mpc-keyring", + "version": "0.0.0", + "description": "A Keyring for Ethereum accounts that uses Multi-Party Computation (MPC) for key management and signing.", + "keywords": [ + "ethereum", + "keyring" + ], + "homepage": "https://github.com/MetaMask/eth-mpc-keyring#readme", + "bugs": { + "url": "https://github.com/MetaMask/eth-mpc-keyring/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/MetaMask/eth-mpc-keyring.git" + }, + "license": "ISC", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + } + }, + "main": "./dist/index.cjs", + "types": "./dist/index.d.cts", + "files": [ + "dist/" + ], + "scripts": { + "build": "ts-bridge --project tsconfig.build.json --no-references", + "build:clean": "yarn build --clean", + "build:docs": "typedoc", + "changelog:update": "../../scripts/update-changelog.sh @metamask/eth-mpc-keyring", + "changelog:validate": "../../scripts/validate-changelog.sh @metamask/eth-mpc-keyring", + "publish:preview": "yarn npm publish --tag preview", + "test": "jest", + "test:clean": "jest --clearCache" + }, + "dependencies": { + + }, + "devDependencies": { + "@lavamoat/allow-scripts": "^3.2.1", + "@lavamoat/preinstall-always-fail": "^2.1.0", + "@metamask/auto-changelog": "^3.4.4", + "@ts-bridge/cli": "^0.6.3", + "@types/jest": "^29.5.12", + "deepmerge": "^4.2.2", + "jest": "^29.5.0" + }, + "engines": { + "node": "^18.18 || >=20" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "lavamoat": { + "allowScripts": { + "@lavamoat/preinstall-always-fail": false + } + } +} diff --git a/packages/keyring-eth-mpc/src/index.ts b/packages/keyring-eth-mpc/src/index.ts new file mode 100644 index 000000000..63aca5d0d --- /dev/null +++ b/packages/keyring-eth-mpc/src/index.ts @@ -0,0 +1 @@ +export { MpcKeyring } from './mpc-keyring'; diff --git a/packages/keyring-eth-mpc/tsconfig.build.json b/packages/keyring-eth-mpc/tsconfig.build.json new file mode 100644 index 000000000..20cc54288 --- /dev/null +++ b/packages/keyring-eth-mpc/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.packages.build.json", + "compilerOptions": { + "baseUrl": "./", + "outDir": "dist", + "rootDir": "src" + }, + "references": [], + "include": ["./src/**/*.ts"], + "exclude": ["./src/**/*.test.ts"] +} diff --git a/packages/keyring-eth-mpc/tsconfig.json b/packages/keyring-eth-mpc/tsconfig.json new file mode 100644 index 000000000..ecebaa95c --- /dev/null +++ b/packages/keyring-eth-mpc/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.packages.json", + "compilerOptions": { + "baseUrl": "./" + }, + "references": [], + "include": ["./src"], + "exclude": ["./dist/**/*"] +} From 12523b3c3068ef13035839a428fd3bdbc06a6b22 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Tue, 13 Jan 2026 16:14:20 +0100 Subject: [PATCH 02/44] init mpc keyring --- packages/keyring-eth-mpc/package.json | 7 +- packages/keyring-eth-mpc/src/mpc-keyring.ts | 156 ++++++++++++++++++++ packages/keyring-eth-mpc/tsconfig.json | 16 +- 3 files changed, 171 insertions(+), 8 deletions(-) create mode 100644 packages/keyring-eth-mpc/src/mpc-keyring.ts diff --git a/packages/keyring-eth-mpc/package.json b/packages/keyring-eth-mpc/package.json index 5049f5fb4..b2a20ecc8 100644 --- a/packages/keyring-eth-mpc/package.json +++ b/packages/keyring-eth-mpc/package.json @@ -42,13 +42,12 @@ "test": "jest", "test:clean": "jest --clearCache" }, - "dependencies": { - - }, + "dependencies": {}, "devDependencies": { "@lavamoat/allow-scripts": "^3.2.1", "@lavamoat/preinstall-always-fail": "^2.1.0", "@metamask/auto-changelog": "^3.4.4", + "@metamask/keyring-utils": "workspace:^", "@ts-bridge/cli": "^0.6.3", "@types/jest": "^29.5.12", "deepmerge": "^4.2.2", @@ -66,4 +65,4 @@ "@lavamoat/preinstall-always-fail": false } } -} +} \ No newline at end of file diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts new file mode 100644 index 000000000..a0884570a --- /dev/null +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -0,0 +1,156 @@ +import type { TypedTransaction } from '@ethereumjs/tx'; +import type { Keyring } from '@metamask/keyring-utils'; +import type { Hex, Json } from '@metamask/utils'; + +const type = 'eth-mpc'; + +export type SerializedMPCKeyringState = {}; + +export class MPCKeyring implements Keyring { + readonly type: string = type; + + constructor() {} + /** + * Return the serialized state of the keyring. + * + * @returns The serialized state of the keyring. + */ + async serialize(): Promise { + + } + + /** + * Initialize the keyring with the given serialized state. + * + * @param opts - The serialized state of the keyring. + * @returns An empty array. + */ + async deserialize( + state: Json, + ): Promise { + + } + + /** + * Add new accounts to the keyring. The accounts will be derived + * sequentially from the root HD wallet, using increasing indices. + * + * @param numberOfAccounts - The number of accounts to add. + * @returns The addresses of the new accounts. + */ + async addAccounts(numberOfAccounts = 1): Promise { + + } + + /** + * Get the addresses of all accounts in the keyring. + * + * @returns The addresses of all accounts in the keyring. + */ + async getAccounts(): Promise { + + } + + /** + * Get the public address of the account for the given app key origin. + * + * @param address - The address of the account. + * @param origin - The origin of the app requesting the account. + * @returns The public address of the account. + */ + async getAppKeyAddress(address: Hex, origin: string): Promise { + + } + + /** + * Sign a transaction using the specified account. + * + * @param address - The address of the account. + * @param tx - The transaction to sign. + * @param opts - The options for signing the transaction. + * @returns The signed transaction. + */ + async signTransaction( + address: Hex, + tx: TypedTransaction, + opts = {}, + ): Promise { + + } + + /** + * Sign a message using the specified account. + * + * @param address - The address of the account. + * @param data - The data to sign. + * @param opts - The options for signing the message. + * @returns The signature of the message. + */ + async signMessage( + address: Hex, + data: string, + opts?: Record, + ): Promise { + + } + + /** + * Sign a personal message using the specified account. + * This method is compatible with the `personal_sign` RPC method. + * + * @param address - The address of the account. + * @param msgHex - The message to sign. + * @param opts - The options for signing the message. + * @returns The signature of the message. + */ + async signPersonalMessage( + address: Hex, + msgHex: string, + opts?: Record, + ): Promise { + + } + + /** + * Sign a typed message using the specified account. + * This method is compatible with the `eth_signTypedData` RPC method. + * + * @param address - The address of the account. + * @param data - The typed data to sign. + * @param opts - The options for signing the message. + * @returns The signature of the message. + */ + async signTypedData( + address: Hex, + data: unknown[] | Record, + opts?: Record, + ): Promise { + + } + + /** + * Sign an EIP-7702 authorization using the specified account. + * This method is compatible with the EIP-7702 standard for enabling smart contract code for EOAs. + * + * @param withAccount - The address of the account. + * @param authorization - The EIP-7702 authorization to sign. + * @param opts - The options for signing the authorization. + * @returns The signature of the authorization. + */ + async signEip7702Authorization( + withAccount: Hex, + authorization: [chainId: number, contractAddress: Hex, nonce: number], + opts?: Record, + ): Promise { + + } + + /** + * Remove an account from the keyring. + * + * @param account - The address of the account to remove. + */ + removeAccount(account: Hex): void { + + } +} diff --git a/packages/keyring-eth-mpc/tsconfig.json b/packages/keyring-eth-mpc/tsconfig.json index ecebaa95c..feaa7d31e 100644 --- a/packages/keyring-eth-mpc/tsconfig.json +++ b/packages/keyring-eth-mpc/tsconfig.json @@ -3,7 +3,15 @@ "compilerOptions": { "baseUrl": "./" }, - "references": [], - "include": ["./src"], - "exclude": ["./dist/**/*"] -} + "references": [ + { + "path": "../keyring-utils" + } + ], + "include": [ + "./src" + ], + "exclude": [ + "./dist/**/*" + ] +} \ No newline at end of file From 7af2dc36aa121c3691098026dc970a8d1f0cb3e9 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Tue, 13 Jan 2026 16:46:45 +0100 Subject: [PATCH 03/44] init role --- packages/keyring-eth-mpc/src/mpc-keyring.ts | 22 ++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index a0884570a..bb5c8a349 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -4,19 +4,22 @@ import type { Hex, Json } from '@metamask/utils'; const type = 'eth-mpc'; -export type SerializedMPCKeyringState = {}; - export class MPCKeyring implements Keyring { readonly type: string = type; + #initRole: 'initiator' | 'responder' = 'initiator'; + constructor() {} + /** * Return the serialized state of the keyring. * * @returns The serialized state of the keyring. */ async serialize(): Promise { - + return { + initRole: this.#initRole, + }; } /** @@ -28,6 +31,19 @@ export class MPCKeyring implements Keyring { async deserialize( state: Json, ): Promise { + if (!state || typeof state !== 'object') { + throw new Error('Invalid state'); + } + + if ( + 'initRole' in state && + state.initRole === 'responder' + ) { + this.#initRole = 'responder'; + } + } + + async init(): Promise { } From 07654148bd2a26cf4dee7448a189c83c4a99afb3 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Tue, 13 Jan 2026 18:14:51 +0100 Subject: [PATCH 04/44] create key --- packages/keyring-eth-mpc/package.json | 7 +- packages/keyring-eth-mpc/src/mpc-keyring.ts | 97 +++++++++++++-------- packages/keyring-eth-mpc/src/types.ts | 3 + yarn.lock | 53 +++++++++++ 4 files changed, 121 insertions(+), 39 deletions(-) create mode 100644 packages/keyring-eth-mpc/src/types.ts diff --git a/packages/keyring-eth-mpc/package.json b/packages/keyring-eth-mpc/package.json index b2a20ecc8..5e28ff6c5 100644 --- a/packages/keyring-eth-mpc/package.json +++ b/packages/keyring-eth-mpc/package.json @@ -42,12 +42,17 @@ "test": "jest", "test:clean": "jest --clearCache" }, - "dependencies": {}, + "dependencies": { + "@metamask/mfa-wallet-cl24-lib": "./metamask-mfa-wallet-cl24-lib-0.0.0.tgz" + }, "devDependencies": { + "@ethereumjs/tx": "^5.4.0", "@lavamoat/allow-scripts": "^3.2.1", "@lavamoat/preinstall-always-fail": "^2.1.0", "@metamask/auto-changelog": "^3.4.4", "@metamask/keyring-utils": "workspace:^", + "@metamask/mfa-wallet-interface": "./metamask-mfa-wallet-interface-0.0.0.tgz", + "@metamask/utils": "^11.1.0", "@ts-bridge/cli": "^0.6.3", "@types/jest": "^29.5.12", "deepmerge": "^4.2.2", diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index bb5c8a349..cd94155ee 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -1,7 +1,14 @@ import type { TypedTransaction } from '@ethereumjs/tx'; import type { Keyring } from '@metamask/keyring-utils'; +import { + CL24DKM, + secp256k1 as secp256k1Curve, +} from '@metamask/mfa-wallet-cl24-lib'; import type { Hex, Json } from '@metamask/utils'; +import type { MPCKeyringOpts } from './types'; +import { CreateKeyParams, ThresholdKey } from '@metamask/mfa-wallet-interface'; + const type = 'eth-mpc'; export class MPCKeyring implements Keyring { @@ -9,7 +16,15 @@ export class MPCKeyring implements Keyring { #initRole: 'initiator' | 'responder' = 'initiator'; - constructor() {} + #networkCredentials: NetworkCredentials; + + #keyShare: ThresholdKey; + + readonly #getRandomBytes: (size: number) => Uint8Array; + + constructor(opts: MPCKeyringOpts) { + this.#getRandomBytes = opts.getRandomBytes; + } /** * Return the serialized state of the keyring. @@ -19,32 +34,56 @@ export class MPCKeyring implements Keyring { async serialize(): Promise { return { initRole: this.#initRole, + networkCredentials: serializeNetworkCredentials(this.#networkCredentials), + keyShare: serializeThresholdKey(this.#keyShare), }; } /** * Initialize the keyring with the given serialized state. * - * @param opts - The serialized state of the keyring. - * @returns An empty array. + * @param state - The serialized state of the keyring. */ - async deserialize( - state: Json, - ): Promise { + async deserialize(state: Json): Promise { if (!state || typeof state !== 'object') { throw new Error('Invalid state'); } - if ( - 'initRole' in state && - state.initRole === 'responder' - ) { + if ('initRole' in state && state.initRole === 'responder') { this.#initRole = 'responder'; } + + if ('networkCredentials' in state) { + this.#networkCredentials = deserializeNetworkCredentials(state.networkCredentials); + } + + if ('keyShare' in state) { + this.#keyShare = deserializeThresholdKey(state.keyShare); + } } async init(): Promise { - + const rng = { + generateRandomBytes: this.#getRandomBytes, + }; + const dkm = new CL24DKM('secp256k1', secp256k1Curve, rng); + + const networkCredentials = await createNetworkIdentity(); + const localId = networkCredentials.partyId; + const { partyId: cloudId, sessionId } = await initCloudKeyGen({ + localId, + }); + const custodians = [localId, cloudId]; + const threshold = 2; + + const networkSession = await initNetworkSession(custodians, sessionId); + + this.#networkCredentials = networkCredentials; + this.#keyShare = await dkm.createKey({ + custodians, + threshold, + networkSession, + }); } /** @@ -54,18 +93,14 @@ export class MPCKeyring implements Keyring { * @param numberOfAccounts - The number of accounts to add. * @returns The addresses of the new accounts. */ - async addAccounts(numberOfAccounts = 1): Promise { - - } + async addAccounts(numberOfAccounts = 1): Promise {} /** * Get the addresses of all accounts in the keyring. * * @returns The addresses of all accounts in the keyring. */ - async getAccounts(): Promise { - - } + async getAccounts(): Promise {} /** * Get the public address of the account for the given app key origin. @@ -74,9 +109,7 @@ export class MPCKeyring implements Keyring { * @param origin - The origin of the app requesting the account. * @returns The public address of the account. */ - async getAppKeyAddress(address: Hex, origin: string): Promise { - - } + async getAppKeyAddress(address: Hex, origin: string): Promise {} /** * Sign a transaction using the specified account. @@ -90,9 +123,7 @@ export class MPCKeyring implements Keyring { address: Hex, tx: TypedTransaction, opts = {}, - ): Promise { - - } + ): Promise {} /** * Sign a message using the specified account. @@ -106,9 +137,7 @@ export class MPCKeyring implements Keyring { address: Hex, data: string, opts?: Record, - ): Promise { - - } + ): Promise {} /** * Sign a personal message using the specified account. @@ -123,9 +152,7 @@ export class MPCKeyring implements Keyring { address: Hex, msgHex: string, opts?: Record, - ): Promise { - - } + ): Promise {} /** * Sign a typed message using the specified account. @@ -140,9 +167,7 @@ export class MPCKeyring implements Keyring { address: Hex, data: unknown[] | Record, opts?: Record, - ): Promise { - - } + ): Promise {} /** * Sign an EIP-7702 authorization using the specified account. @@ -157,16 +182,12 @@ export class MPCKeyring implements Keyring { withAccount: Hex, authorization: [chainId: number, contractAddress: Hex, nonce: number], opts?: Record, - ): Promise { - - } + ): Promise {} /** * Remove an account from the keyring. * * @param account - The address of the account to remove. */ - removeAccount(account: Hex): void { - - } + removeAccount(account: Hex): void {} } diff --git a/packages/keyring-eth-mpc/src/types.ts b/packages/keyring-eth-mpc/src/types.ts new file mode 100644 index 000000000..a1dcd29ce --- /dev/null +++ b/packages/keyring-eth-mpc/src/types.ts @@ -0,0 +1,3 @@ +export type MPCKeyringOpts = { + getRandomBytes: (size: number) => Promise; +}; diff --git a/yarn.lock b/yarn.lock index c1eafc528..d4808effb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1745,6 +1745,25 @@ __metadata: languageName: unknown linkType: soft +"@metamask/eth-mpc-keyring@workspace:packages/keyring-eth-mpc": + version: 0.0.0-use.local + resolution: "@metamask/eth-mpc-keyring@workspace:packages/keyring-eth-mpc" + dependencies: + "@ethereumjs/tx": "npm:^5.4.0" + "@lavamoat/allow-scripts": "npm:^3.2.1" + "@lavamoat/preinstall-always-fail": "npm:^2.1.0" + "@metamask/auto-changelog": "npm:^3.4.4" + "@metamask/keyring-utils": "workspace:^" + "@metamask/mfa-wallet-cl24-lib": ./metamask-mfa-wallet-cl24-lib-0.0.0.tgz + "@metamask/mfa-wallet-interface": ./metamask-mfa-wallet-interface-0.0.0.tgz + "@metamask/utils": "npm:^11.1.0" + "@ts-bridge/cli": "npm:^0.6.3" + "@types/jest": "npm:^29.5.12" + deepmerge: "npm:^4.2.2" + jest: "npm:^29.5.0" + languageName: unknown + linkType: soft + "@metamask/eth-qr-keyring@workspace:packages/keyring-eth-qr": version: 0.0.0-use.local resolution: "@metamask/eth-qr-keyring@workspace:packages/keyring-eth-qr" @@ -2163,6 +2182,24 @@ __metadata: languageName: node linkType: hard +"@metamask/mfa-wallet-cl24-lib@file:./metamask-mfa-wallet-cl24-lib-0.0.0.tgz::locator=%40metamask%2Feth-mpc-keyring%40workspace%3Apackages%2Fkeyring-eth-mpc": + version: 0.0.0 + resolution: "@metamask/mfa-wallet-cl24-lib@file:./metamask-mfa-wallet-cl24-lib-0.0.0.tgz#./metamask-mfa-wallet-cl24-lib-0.0.0.tgz::hash=322a56&locator=%40metamask%2Feth-mpc-keyring%40workspace%3Apackages%2Fkeyring-eth-mpc" + dependencies: + "@msgpack/msgpack": "npm:^3.1.2" + "@noble/curves": "npm:^1.9.2" + "@noble/hashes": "npm:^1.8.0" + checksum: 10/19a2de8dc2296f5f0f3dc15dbbb4cd1e66538415fba2c2b1edb967afafd2ecd7b1c3c8764b593f170e1781f09851ea4a5ce0526230e2a4e59305cd16997abd56 + languageName: node + linkType: hard + +"@metamask/mfa-wallet-interface@file:./metamask-mfa-wallet-interface-0.0.0.tgz::locator=%40metamask%2Feth-mpc-keyring%40workspace%3Apackages%2Fkeyring-eth-mpc": + version: 0.0.0 + resolution: "@metamask/mfa-wallet-interface@file:./metamask-mfa-wallet-interface-0.0.0.tgz#./metamask-mfa-wallet-interface-0.0.0.tgz::hash=7e0392&locator=%40metamask%2Feth-mpc-keyring%40workspace%3Apackages%2Fkeyring-eth-mpc" + checksum: 10/663ff097ce6b8fafd02412eb0e51e953ee816edc7db94484adb908ba542ed31d20200176979d5818be1dd6400294ae59df13a2255c3b97649dae6ca029a3a450 + languageName: node + linkType: hard + "@metamask/number-to-bn@npm:^1.7.1": version: 1.7.1 resolution: "@metamask/number-to-bn@npm:1.7.1" @@ -2484,6 +2521,13 @@ __metadata: languageName: node linkType: hard +"@msgpack/msgpack@npm:^3.1.2": + version: 3.1.3 + resolution: "@msgpack/msgpack@npm:3.1.3" + checksum: 10/cf597b7ed1fcedc2101b145885e7da591c5ed8aeba3a2579b99da8dd15ef13595c807f64f052da0a0dd74704097f6299fc2e2fb4165029a60a1d1df3843c03ce + languageName: node + linkType: hard + "@ngraveio/bc-ur@npm:^1.1.5": version: 1.1.13 resolution: "@ngraveio/bc-ur@npm:1.1.13" @@ -2533,6 +2577,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:^1.9.2": + version: 1.9.7 + resolution: "@noble/curves@npm:1.9.7" + dependencies: + "@noble/hashes": "npm:1.8.0" + checksum: 10/3cfe2735ea94972988ca9e217e0ebb2044372a7160b2079bf885da789492a6291fc8bf76ca3d8bf8dee477847ee2d6fac267d1e6c4f555054059f5e8c4865d44 + languageName: node + linkType: hard + "@noble/hashes@npm:1.4.0, @noble/hashes@npm:~1.4.0": version: 1.4.0 resolution: "@noble/hashes@npm:1.4.0" From 00a41d954bdf683cfc7794c923f79f5e921e8b85 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Wed, 14 Jan 2026 16:58:58 +0100 Subject: [PATCH 05/44] signTransaction --- package.json | 6 +- packages/keyring-eth-mpc/TODO.md | 4 + packages/keyring-eth-mpc/package.json | 10 +- packages/keyring-eth-mpc/src/mpc-keyring.ts | 131 +++++++++++++++----- packages/keyring-eth-mpc/src/network.ts | 39 ++++++ packages/keyring-eth-mpc/src/types.ts | 4 +- packages/keyring-eth-mpc/src/util.ts | 38 ++++++ yarn.lock | 33 ++++- 8 files changed, 222 insertions(+), 43 deletions(-) create mode 100644 packages/keyring-eth-mpc/TODO.md create mode 100644 packages/keyring-eth-mpc/src/network.ts create mode 100644 packages/keyring-eth-mpc/src/util.ts diff --git a/package.json b/package.json index 7f5bdf09d..03c7835d9 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,11 @@ "@types/web": "^0.0.69", "@typescript/lib-dom": "npm:@types/web@^0.0.69", "axios@1.7.3": "^1.7.7", - "ws@7.4.6": "^7.5.10" + "ws@7.4.6": "^7.5.10", + "@metamask/mfa-wallet-cl24-lib": "file:./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz", + "@metamask/mfa-wallet-dkls19-lib": "file:./packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz", + "@metamask/mfa-wallet-interface": "file:./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz", + "@metamask/mfa-wallet-util": "file:./packages/keyring-eth-mpc/metamask-mfa-wallet-util-0.0.0.tgz" }, "devDependencies": { "@lavamoat/allow-scripts": "^3.2.1", diff --git a/packages/keyring-eth-mpc/TODO.md b/packages/keyring-eth-mpc/TODO.md new file mode 100644 index 000000000..277064fef --- /dev/null +++ b/packages/keyring-eth-mpc/TODO.md @@ -0,0 +1,4 @@ +# TODO + +- Check for "not implemented" and "TODO" in code +- Add cloud factor authentication diff --git a/packages/keyring-eth-mpc/package.json b/packages/keyring-eth-mpc/package.json index 5e28ff6c5..cae28a1ca 100644 --- a/packages/keyring-eth-mpc/package.json +++ b/packages/keyring-eth-mpc/package.json @@ -43,7 +43,11 @@ "test:clean": "jest --clearCache" }, "dependencies": { - "@metamask/mfa-wallet-cl24-lib": "./metamask-mfa-wallet-cl24-lib-0.0.0.tgz" + "@ethereumjs/util": "^9.1.0", + "@metamask/eth-sig-util": "^8.2.0", + "@metamask/mfa-wallet-cl24-lib": "^0.0.0", + "@metamask/mfa-wallet-dkls19-lib": "^0.0.0", + "@metamask/mfa-wallet-util": "^0.0.0" }, "devDependencies": { "@ethereumjs/tx": "^5.4.0", @@ -51,7 +55,7 @@ "@lavamoat/preinstall-always-fail": "^2.1.0", "@metamask/auto-changelog": "^3.4.4", "@metamask/keyring-utils": "workspace:^", - "@metamask/mfa-wallet-interface": "./metamask-mfa-wallet-interface-0.0.0.tgz", + "@metamask/mfa-wallet-interface": "^0.0.0", "@metamask/utils": "^11.1.0", "@ts-bridge/cli": "^0.6.3", "@types/jest": "^29.5.12", @@ -70,4 +74,4 @@ "@lavamoat/preinstall-always-fail": false } } -} \ No newline at end of file +} diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index cd94155ee..798bde876 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -1,29 +1,46 @@ -import type { TypedTransaction } from '@ethereumjs/tx'; +import { TransactionFactory, type TypedTransaction } from '@ethereumjs/tx'; import type { Keyring } from '@metamask/keyring-utils'; import { CL24DKM, secp256k1 as secp256k1Curve, } from '@metamask/mfa-wallet-cl24-lib'; +import { DklsTssLib } from '@metamask/mfa-wallet-dkls19-lib'; +import type { + RandomNumberGenerator, + ThresholdKey, +} from '@metamask/mfa-wallet-interface'; +import { load as loadDkls19 } from '@metamask/tss-dkls19-lib'; import type { Hex, Json } from '@metamask/utils'; -import type { MPCKeyringOpts } from './types'; -import { CreateKeyParams, ThresholdKey } from '@metamask/mfa-wallet-interface'; +import { + createNetworkIdentity, + createNetworkSession, + generateSessionId, +} from './network'; +import type { MPCKeyringOpts, ThresholdKeyId } from './types'; +import { equalAddresses, publicToAddressHex } from './util'; const type = 'eth-mpc'; export class MPCKeyring implements Keyring { readonly type: string = type; + readonly #rng: RandomNumberGenerator; + + #dklsLib?: DklsTssLib; + #initRole: 'initiator' | 'responder' = 'initiator'; #networkCredentials: NetworkCredentials; - #keyShare: ThresholdKey; + #keyShare?: ThresholdKey; - readonly #getRandomBytes: (size: number) => Uint8Array; + readonly #keyId?: ThresholdKeyId; constructor(opts: MPCKeyringOpts) { - this.#getRandomBytes = opts.getRandomBytes; + this.#rng = { + generateRandomBytes: opts.getRandomBytes, + }; } /** @@ -54,7 +71,9 @@ export class MPCKeyring implements Keyring { } if ('networkCredentials' in state) { - this.#networkCredentials = deserializeNetworkCredentials(state.networkCredentials); + this.#networkCredentials = deserializeNetworkCredentials( + state.networkCredentials, + ); } if ('keyShare' in state) { @@ -63,20 +82,24 @@ export class MPCKeyring implements Keyring { } async init(): Promise { - const rng = { - generateRandomBytes: this.#getRandomBytes, - }; - const dkm = new CL24DKM('secp256k1', secp256k1Curve, rng); + this.#dklsLib = await loadDkls19(); + + const dkm = new CL24DKM('secp256k1', secp256k1Curve, this.#rng); const networkCredentials = await createNetworkIdentity(); const localId = networkCredentials.partyId; - const { partyId: cloudId, sessionId } = await initCloudKeyGen({ + const sessionId = generateSessionId(); + const { cloudId } = await initCloudKeyGen({ localId, }); const custodians = [localId, cloudId]; const threshold = 2; - const networkSession = await initNetworkSession(custodians, sessionId); + const networkSession = await createNetworkSession( + networkCredentials, + custodians, + sessionId, + ); this.#networkCredentials = networkCredentials; this.#keyShare = await dkm.createKey({ @@ -93,14 +116,23 @@ export class MPCKeyring implements Keyring { * @param numberOfAccounts - The number of accounts to add. * @returns The addresses of the new accounts. */ - async addAccounts(numberOfAccounts = 1): Promise {} + async addAccounts(numberOfAccounts = 1): Promise { + throw new Error(`addAccounts(${numberOfAccounts}): not implemented`); + } /** * Get the addresses of all accounts in the keyring. * * @returns The addresses of all accounts in the keyring. */ - async getAccounts(): Promise {} + async getAccounts(): Promise { + if (!this.#keyShare) { + return []; + } + + const addr = publicToAddressHex(this.#keyShare.publicKey); + return [addr]; + } /** * Get the public address of the account for the given app key origin. @@ -109,35 +141,70 @@ export class MPCKeyring implements Keyring { * @param origin - The origin of the app requesting the account. * @returns The public address of the account. */ - async getAppKeyAddress(address: Hex, origin: string): Promise {} + async getAppKeyAddress(address: Hex, origin: string): Promise { + throw new Error(`getAppKeyAddress(${address}, ${origin}): not implemented`); + } /** * Sign a transaction using the specified account. * * @param address - The address of the account. * @param tx - The transaction to sign. - * @param opts - The options for signing the transaction. + * @param _opts - The options for signing the transaction. * @returns The signed transaction. */ async signTransaction( address: Hex, tx: TypedTransaction, - opts = {}, - ): Promise {} + _opts = {}, + ): Promise { + if (!this.#keyShare) { + throw new Error(`keyshare not initialized`); + } else if (!this.#networkCredentials) { + throw new Error(`network credentials not initialized`); + } else if (!this.#keyId) { + throw new Error(`key id not initialized`); + } - /** - * Sign a message using the specified account. - * - * @param address - The address of the account. - * @param data - The data to sign. - * @param opts - The options for signing the message. - * @returns The signature of the message. - */ - async signMessage( - address: Hex, - data: string, - opts?: Record, - ): Promise {} + const { custodians, publicKey } = this.#keyShare; + + const addr = publicToAddressHex(publicKey); + if (!equalAddresses(address, addr)) { + throw new Error(`account ${address} not found`); + } + + const sessionId = generateSessionId(); + const message = tx.getHashedMessageToSign(); + + await initCloudSign({ + keyId: this.#keyId, + sessionId, + message, + }); + + const networkSession = await createNetworkSession( + this.#networkCredentials, + custodians, + sessionId, + ); + + const dkls19 = new DklsTssLib(this.#dklsLib, this.#rng); + const signature = await dkls19.sign({ + key: this.#keyShare, + signers: custodians, + message, + networkSession, + }); + + const { r, s, v } = parseEcdsaSignature(signature); + + return TransactionFactory.fromTxData({ + ...tx, + r, + s, + v, + }); + } /** * Sign a personal message using the specified account. diff --git a/packages/keyring-eth-mpc/src/network.ts b/packages/keyring-eth-mpc/src/network.ts new file mode 100644 index 000000000..1b49d8393 --- /dev/null +++ b/packages/keyring-eth-mpc/src/network.ts @@ -0,0 +1,39 @@ +import type { NetworkSession } from '@metamask/mfa-wallet-interface'; + +export type NetworkCredentials = { + partyId: string; +}; + +/** + * Generates a random session ID. + * + * @returns The session ID. + */ +export function generateSessionId(): string { + +} + +/** + * Creates a network identity for a MPC keyring. + * + * @returns The network credentials. + */ +export async function createNetworkIdentity(): Promise { + +} + +/** + * Creates a network session for a MPC keyring. + * + * @param networkCredentials - The network credentials. + * @param parties - The parties in the network session. + * @param sessionId - The session ID. + * @returns The network session. + */ +export async function createNetworkSession( + networkCredentials: NetworkCredentials, + parties: string[], + sessionId: string, +): Promise { + +} diff --git a/packages/keyring-eth-mpc/src/types.ts b/packages/keyring-eth-mpc/src/types.ts index a1dcd29ce..ea687bda1 100644 --- a/packages/keyring-eth-mpc/src/types.ts +++ b/packages/keyring-eth-mpc/src/types.ts @@ -1,3 +1,5 @@ export type MPCKeyringOpts = { - getRandomBytes: (size: number) => Promise; + getRandomBytes: (size: number) => Uint8Array; }; + +export type ThresholdKeyId = string; diff --git a/packages/keyring-eth-mpc/src/util.ts b/packages/keyring-eth-mpc/src/util.ts new file mode 100644 index 000000000..649d72698 --- /dev/null +++ b/packages/keyring-eth-mpc/src/util.ts @@ -0,0 +1,38 @@ +import { publicToAddress } from '@ethereumjs/util'; +import { normalize } from '@metamask/eth-sig-util'; +import type { Hex } from '@metamask/utils'; +import { add0x, assert, bytesToHex } from '@metamask/utils'; + +/** + * Convert a public key to an address. + * + * @param pubKey - The public key to convert. + * @returns The address. + */ +export function publicToAddressHex(pubKey: Uint8Array): Hex { + const addrBytes = publicToAddress(pubKey); + return bytesToHex(addrBytes); +} + +/** + * Normalize an address. + * + * @param address - The address to normalize. + * @returns The normalized address. + */ +export function normalizeAddress(address: string): Hex { + const normalized = normalize(address); + assert(normalized, 'Expected address to be set'); + return add0x(normalized); +} + +/** + * Check if two addresses are equal. + * + * @param address1 - The first address. + * @param address2 - The second address. + * @returns Whether the addresses are equal. + */ +export function equalAddresses(address1: string, address2: string): boolean { + return normalizeAddress(address1) === normalizeAddress(address2); +} diff --git a/yarn.lock b/yarn.lock index d4808effb..074987061 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1750,12 +1750,16 @@ __metadata: resolution: "@metamask/eth-mpc-keyring@workspace:packages/keyring-eth-mpc" dependencies: "@ethereumjs/tx": "npm:^5.4.0" + "@ethereumjs/util": "npm:^9.1.0" "@lavamoat/allow-scripts": "npm:^3.2.1" "@lavamoat/preinstall-always-fail": "npm:^2.1.0" "@metamask/auto-changelog": "npm:^3.4.4" + "@metamask/eth-sig-util": "npm:^8.2.0" "@metamask/keyring-utils": "workspace:^" - "@metamask/mfa-wallet-cl24-lib": ./metamask-mfa-wallet-cl24-lib-0.0.0.tgz - "@metamask/mfa-wallet-interface": ./metamask-mfa-wallet-interface-0.0.0.tgz + "@metamask/mfa-wallet-cl24-lib": "npm:^0.0.0" + "@metamask/mfa-wallet-dkls19-lib": "npm:^0.0.0" + "@metamask/mfa-wallet-interface": "npm:^0.0.0" + "@metamask/mfa-wallet-util": "npm:^0.0.0" "@metamask/utils": "npm:^11.1.0" "@ts-bridge/cli": "npm:^0.6.3" "@types/jest": "npm:^29.5.12" @@ -2182,9 +2186,9 @@ __metadata: languageName: node linkType: hard -"@metamask/mfa-wallet-cl24-lib@file:./metamask-mfa-wallet-cl24-lib-0.0.0.tgz::locator=%40metamask%2Feth-mpc-keyring%40workspace%3Apackages%2Fkeyring-eth-mpc": +"@metamask/mfa-wallet-cl24-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": version: 0.0.0 - resolution: "@metamask/mfa-wallet-cl24-lib@file:./metamask-mfa-wallet-cl24-lib-0.0.0.tgz#./metamask-mfa-wallet-cl24-lib-0.0.0.tgz::hash=322a56&locator=%40metamask%2Feth-mpc-keyring%40workspace%3Apackages%2Fkeyring-eth-mpc" + resolution: "@metamask/mfa-wallet-cl24-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz::hash=322a56&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." dependencies: "@msgpack/msgpack": "npm:^3.1.2" "@noble/curves": "npm:^1.9.2" @@ -2193,13 +2197,30 @@ __metadata: languageName: node linkType: hard -"@metamask/mfa-wallet-interface@file:./metamask-mfa-wallet-interface-0.0.0.tgz::locator=%40metamask%2Feth-mpc-keyring%40workspace%3Apackages%2Fkeyring-eth-mpc": +"@metamask/mfa-wallet-dkls19-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": + version: 0.0.0 + resolution: "@metamask/mfa-wallet-dkls19-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz::hash=3adfc7&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + dependencies: + "@metamask/mfa-wallet-util": "npm:^0.0.0" + "@noble/curves": "npm:^1.9.2" + checksum: 10/e2b19b3fe0a91448c8acd0ea9b98093a5c4e1ac73d7a9162595084f0bbc221b1c2ab5cd3cdb751c95a260f112d36ff8c98c9ccabd27bb987de86975f6cadd6f6 + languageName: node + linkType: hard + +"@metamask/mfa-wallet-interface@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": version: 0.0.0 - resolution: "@metamask/mfa-wallet-interface@file:./metamask-mfa-wallet-interface-0.0.0.tgz#./metamask-mfa-wallet-interface-0.0.0.tgz::hash=7e0392&locator=%40metamask%2Feth-mpc-keyring%40workspace%3Apackages%2Fkeyring-eth-mpc" + resolution: "@metamask/mfa-wallet-interface@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz::hash=7e0392&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." checksum: 10/663ff097ce6b8fafd02412eb0e51e953ee816edc7db94484adb908ba542ed31d20200176979d5818be1dd6400294ae59df13a2255c3b97649dae6ca029a3a450 languageName: node linkType: hard +"@metamask/mfa-wallet-util@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-util-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": + version: 0.0.0 + resolution: "@metamask/mfa-wallet-util@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-util-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-util-0.0.0.tgz::hash=bf9341&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + checksum: 10/63bc3760c49180a504b1351762ae903aaa064e8af6ba3a6afcd2a33e18f1408e3aa0bb9533a34612617a459f0d190d3d02d3876312750c4c943f8eb78b7b2702 + languageName: node + linkType: hard + "@metamask/number-to-bn@npm:^1.7.1": version: 1.7.1 resolution: "@metamask/number-to-bn@npm:1.7.1" From 51005276f27bd75e83939f46563c7aba7ae60ddd Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Thu, 15 Jan 2026 13:48:36 +0100 Subject: [PATCH 06/44] wip --- package.json | 3 +- packages/keyring-eth-mpc/package.json | 3 +- packages/keyring-eth-mpc/src/cloud.ts | 24 +++++++ packages/keyring-eth-mpc/src/mpc-keyring.ts | 75 ++++++++------------- packages/keyring-eth-mpc/src/network.ts | 46 +++++-------- packages/keyring-eth-mpc/src/types.ts | 3 + packages/keyring-eth-mpc/src/util.ts | 14 ++++ yarn.lock | 12 +++- 8 files changed, 101 insertions(+), 79 deletions(-) create mode 100644 packages/keyring-eth-mpc/src/cloud.ts diff --git a/package.json b/package.json index 03c7835d9..43ced96c8 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,8 @@ "@metamask/mfa-wallet-cl24-lib": "file:./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz", "@metamask/mfa-wallet-dkls19-lib": "file:./packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz", "@metamask/mfa-wallet-interface": "file:./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz", - "@metamask/mfa-wallet-util": "file:./packages/keyring-eth-mpc/metamask-mfa-wallet-util-0.0.0.tgz" + "@metamask/mfa-wallet-util": "file:./packages/keyring-eth-mpc/metamask-mfa-wallet-util-0.0.0.tgz", + "@metamask/tss-dkls19-lib": "file:./packages/keyring-eth-mpc/metamask-tss-dkls19-lib-0.0.0.tgz" }, "devDependencies": { "@lavamoat/allow-scripts": "^3.2.1", diff --git a/packages/keyring-eth-mpc/package.json b/packages/keyring-eth-mpc/package.json index cae28a1ca..4c15d2ab4 100644 --- a/packages/keyring-eth-mpc/package.json +++ b/packages/keyring-eth-mpc/package.json @@ -56,6 +56,7 @@ "@metamask/auto-changelog": "^3.4.4", "@metamask/keyring-utils": "workspace:^", "@metamask/mfa-wallet-interface": "^0.0.0", + "@metamask/tss-dkls19-lib": "^0.0.0", "@metamask/utils": "^11.1.0", "@ts-bridge/cli": "^0.6.3", "@types/jest": "^29.5.12", @@ -74,4 +75,4 @@ "@lavamoat/preinstall-always-fail": false } } -} +} \ No newline at end of file diff --git a/packages/keyring-eth-mpc/src/cloud.ts b/packages/keyring-eth-mpc/src/cloud.ts new file mode 100644 index 000000000..d7a0babf9 --- /dev/null +++ b/packages/keyring-eth-mpc/src/cloud.ts @@ -0,0 +1,24 @@ +/** + * Initialize a cloud keygen session + * + * @param opts - The options for the cloud keygen session + * @param opts.localId - The local ID of the device + * @returns The cloud ID of the device + */ +export async function initCloudKeyGen(opts: { + localId: string; +}): Promise<{ cloudId: string }> {} + +/** + * Initialize a cloud sign session + * + * @param opts - The options for the cloud sign session + * @param opts.keyId - The ID of the key + * @param opts.sessionId - The ID of the session + * @param opts.message - The message to sign + */ +export async function initCloudSign(opts: { + keyId: string; + sessionId: string; + message: Uint8Array; +}): Promise {} diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index 798bde876..974d1e460 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -4,21 +4,23 @@ import { CL24DKM, secp256k1 as secp256k1Curve, } from '@metamask/mfa-wallet-cl24-lib'; -import { DklsTssLib } from '@metamask/mfa-wallet-dkls19-lib'; +import { Dkls19TssLib } from '@metamask/mfa-wallet-dkls19-lib'; import type { RandomNumberGenerator, ThresholdKey, } from '@metamask/mfa-wallet-interface'; -import { load as loadDkls19 } from '@metamask/tss-dkls19-lib'; +import type { WasmLib as Dkls19WasmLib } from '@metamask/tss-dkls19-lib'; import type { Hex, Json } from '@metamask/utils'; -import { - createNetworkIdentity, - createNetworkSession, - generateSessionId, -} from './network'; +import { initCloudKeyGen, initCloudSign } from './cloud'; +import type { NetworkIdentity, NetworkManager } from './network'; +import { generateSessionId } from './network'; import type { MPCKeyringOpts, ThresholdKeyId } from './types'; -import { equalAddresses, publicToAddressHex } from './util'; +import { + equalAddresses, + parseEcdsaSignature, + publicToAddressHex, +} from './util'; const type = 'eth-mpc'; @@ -27,11 +29,13 @@ export class MPCKeyring implements Keyring { readonly #rng: RandomNumberGenerator; - #dklsLib?: DklsTssLib; + readonly #networkManager: NetworkManager; + + readonly #dkls19Lib: Dkls19WasmLib; #initRole: 'initiator' | 'responder' = 'initiator'; - #networkCredentials: NetworkCredentials; + #networkIdentity?: NetworkIdentity; #keyShare?: ThresholdKey; @@ -41,6 +45,8 @@ export class MPCKeyring implements Keyring { this.#rng = { generateRandomBytes: opts.getRandomBytes, }; + this.#dkls19Lib = opts.dkls19Lib; + this.#networkManager = {} as NetworkManager; // TODO } /** @@ -51,7 +57,7 @@ export class MPCKeyring implements Keyring { async serialize(): Promise { return { initRole: this.#initRole, - networkCredentials: serializeNetworkCredentials(this.#networkCredentials), + networkCredentials: serializeNetworkCredentials(this.#networkIdentity), keyShare: serializeThresholdKey(this.#keyShare), }; } @@ -71,7 +77,7 @@ export class MPCKeyring implements Keyring { } if ('networkCredentials' in state) { - this.#networkCredentials = deserializeNetworkCredentials( + this.#networkIdentity = deserializeNetworkCredentials( state.networkCredentials, ); } @@ -82,12 +88,11 @@ export class MPCKeyring implements Keyring { } async init(): Promise { - this.#dklsLib = await loadDkls19(); - const dkm = new CL24DKM('secp256k1', secp256k1Curve, this.#rng); - const networkCredentials = await createNetworkIdentity(); - const localId = networkCredentials.partyId; + const net = this.#networkManager; + const networkIdentity = await net.createIdentity(); + const localId = networkIdentity.partyId; const sessionId = generateSessionId(); const { cloudId } = await initCloudKeyGen({ localId, @@ -95,13 +100,13 @@ export class MPCKeyring implements Keyring { const custodians = [localId, cloudId]; const threshold = 2; - const networkSession = await createNetworkSession( - networkCredentials, + const networkSession = await net.createSession( + networkIdentity, custodians, sessionId, ); - this.#networkCredentials = networkCredentials; + this.#networkIdentity = networkIdentity; this.#keyShare = await dkm.createKey({ custodians, threshold, @@ -160,7 +165,7 @@ export class MPCKeyring implements Keyring { ): Promise { if (!this.#keyShare) { throw new Error(`keyshare not initialized`); - } else if (!this.#networkCredentials) { + } else if (!this.#networkIdentity) { throw new Error(`network credentials not initialized`); } else if (!this.#keyId) { throw new Error(`key id not initialized`); @@ -182,14 +187,14 @@ export class MPCKeyring implements Keyring { message, }); - const networkSession = await createNetworkSession( - this.#networkCredentials, + const networkSession = await this.#networkManager.createSession( + this.#networkIdentity, custodians, sessionId, ); - const dkls19 = new DklsTssLib(this.#dklsLib, this.#rng); - const signature = await dkls19.sign({ + const dkls19 = new Dkls19TssLib(this.#dkls19Lib, this.#rng); + const { signature } = await dkls19.sign({ key: this.#keyShare, signers: custodians, message, @@ -235,26 +240,4 @@ export class MPCKeyring implements Keyring { data: unknown[] | Record, opts?: Record, ): Promise {} - - /** - * Sign an EIP-7702 authorization using the specified account. - * This method is compatible with the EIP-7702 standard for enabling smart contract code for EOAs. - * - * @param withAccount - The address of the account. - * @param authorization - The EIP-7702 authorization to sign. - * @param opts - The options for signing the authorization. - * @returns The signature of the authorization. - */ - async signEip7702Authorization( - withAccount: Hex, - authorization: [chainId: number, contractAddress: Hex, nonce: number], - opts?: Record, - ): Promise {} - - /** - * Remove an account from the keyring. - * - * @param account - The address of the account to remove. - */ - removeAccount(account: Hex): void {} } diff --git a/packages/keyring-eth-mpc/src/network.ts b/packages/keyring-eth-mpc/src/network.ts index 1b49d8393..0d8677f65 100644 --- a/packages/keyring-eth-mpc/src/network.ts +++ b/packages/keyring-eth-mpc/src/network.ts @@ -1,7 +1,20 @@ -import type { NetworkSession } from '@metamask/mfa-wallet-interface'; +import type { + NetworkSession, + PartyId, + SessionId, +} from '@metamask/mfa-wallet-interface'; -export type NetworkCredentials = { - partyId: string; +export type NetworkIdentity = { + partyId: PartyId; +}; + +export type NetworkManager = { + createIdentity: () => Promise; + createSession: ( + identity: NetworkIdentity, + parties: PartyId[], + sessionId: SessionId, + ) => Promise; }; /** @@ -9,31 +22,6 @@ export type NetworkCredentials = { * * @returns The session ID. */ -export function generateSessionId(): string { - -} - -/** - * Creates a network identity for a MPC keyring. - * - * @returns The network credentials. - */ -export async function createNetworkIdentity(): Promise { - -} +export function generateSessionId(): SessionId { -/** - * Creates a network session for a MPC keyring. - * - * @param networkCredentials - The network credentials. - * @param parties - The parties in the network session. - * @param sessionId - The session ID. - * @returns The network session. - */ -export async function createNetworkSession( - networkCredentials: NetworkCredentials, - parties: string[], - sessionId: string, -): Promise { - } diff --git a/packages/keyring-eth-mpc/src/types.ts b/packages/keyring-eth-mpc/src/types.ts index ea687bda1..2d830c2af 100644 --- a/packages/keyring-eth-mpc/src/types.ts +++ b/packages/keyring-eth-mpc/src/types.ts @@ -1,5 +1,8 @@ +import type { WasmLib as Dkls19WasmLib } from '@metamask/tss-dkls19-lib'; + export type MPCKeyringOpts = { getRandomBytes: (size: number) => Uint8Array; + dkls19Lib: Dkls19WasmLib; }; export type ThresholdKeyId = string; diff --git a/packages/keyring-eth-mpc/src/util.ts b/packages/keyring-eth-mpc/src/util.ts index 649d72698..1ace7f839 100644 --- a/packages/keyring-eth-mpc/src/util.ts +++ b/packages/keyring-eth-mpc/src/util.ts @@ -36,3 +36,17 @@ export function normalizeAddress(address: string): Hex { export function equalAddresses(address1: string, address2: string): boolean { return normalizeAddress(address1) === normalizeAddress(address2); } + +/** + * Parse an ECDSA signature. + * + * @param signature - The signature to parse. + * @returns The parsed signature. + */ +export function parseEcdsaSignature(signature: Uint8Array): { + r: bigint; + s: bigint; + v: bigint; +} { + // TODO +} diff --git a/yarn.lock b/yarn.lock index 074987061..d3cc1e6a7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1760,6 +1760,7 @@ __metadata: "@metamask/mfa-wallet-dkls19-lib": "npm:^0.0.0" "@metamask/mfa-wallet-interface": "npm:^0.0.0" "@metamask/mfa-wallet-util": "npm:^0.0.0" + "@metamask/tss-dkls19-lib": "npm:^0.0.0" "@metamask/utils": "npm:^11.1.0" "@ts-bridge/cli": "npm:^0.6.3" "@types/jest": "npm:^29.5.12" @@ -2199,11 +2200,11 @@ __metadata: "@metamask/mfa-wallet-dkls19-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": version: 0.0.0 - resolution: "@metamask/mfa-wallet-dkls19-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz::hash=3adfc7&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + resolution: "@metamask/mfa-wallet-dkls19-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz::hash=adb1b2&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." dependencies: "@metamask/mfa-wallet-util": "npm:^0.0.0" "@noble/curves": "npm:^1.9.2" - checksum: 10/e2b19b3fe0a91448c8acd0ea9b98093a5c4e1ac73d7a9162595084f0bbc221b1c2ab5cd3cdb751c95a260f112d36ff8c98c9ccabd27bb987de86975f6cadd6f6 + checksum: 10/236c5bc3d8f231109d93eecce78de1cbcb6e95d3eae9c42cf43befd0b12584f5ce5b415b2c930d108c3b047d9f505768bb06bdd62900406a6404030d334ddfd1 languageName: node linkType: hard @@ -2499,6 +2500,13 @@ __metadata: languageName: node linkType: hard +"@metamask/tss-dkls19-lib@file:./packages/keyring-eth-mpc/metamask-tss-dkls19-lib-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": + version: 0.0.0 + resolution: "@metamask/tss-dkls19-lib@file:./packages/keyring-eth-mpc/metamask-tss-dkls19-lib-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-tss-dkls19-lib-0.0.0.tgz::hash=c28abe&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + checksum: 10/782d65c544eafc04ea32d5c403f0e4afe69e4e828728cc92452f32db46b84ac2d3e989f7d555d00f6e70482d930acca64d83dc64739cfe3e79bba33e0b7a474b + languageName: node + linkType: hard + "@metamask/utils@npm:^11.0.1, @metamask/utils@npm:^11.1.0, @metamask/utils@npm:^11.4.0, @metamask/utils@npm:^11.4.2, @metamask/utils@npm:^11.8.1, @metamask/utils@npm:^11.9.0": version: 11.9.0 resolution: "@metamask/utils@npm:11.9.0" From 9a87a438929b23de3b3fb7c57b6a34e3d9cdd1d6 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Thu, 15 Jan 2026 14:24:56 +0100 Subject: [PATCH 07/44] signPersonalMessage --- packages/keyring-eth-mpc/src/mpc-keyring.ts | 109 ++++++++++++-------- packages/keyring-eth-mpc/src/util.ts | 4 +- 2 files changed, 69 insertions(+), 44 deletions(-) diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index 974d1e460..7d0cada15 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -1,4 +1,5 @@ -import { TransactionFactory, type TypedTransaction } from '@ethereumjs/tx'; +import type { TypedTransaction } from '@ethereumjs/tx'; +import { hashPersonalMessage } from '@ethereumjs/util'; import type { Keyring } from '@metamask/keyring-utils'; import { CL24DKM, @@ -10,7 +11,14 @@ import type { ThresholdKey, } from '@metamask/mfa-wallet-interface'; import type { WasmLib as Dkls19WasmLib } from '@metamask/tss-dkls19-lib'; -import type { Hex, Json } from '@metamask/utils'; +import { + bigIntToBytes, + bytesToHex, + concatBytes, + hexToBytes, + type Hex, + type Json, +} from '@metamask/utils'; import { initCloudKeyGen, initCloudSign } from './cloud'; import type { NetworkIdentity, NetworkManager } from './network'; @@ -163,6 +171,60 @@ export class MPCKeyring implements Keyring { tx: TypedTransaction, _opts = {}, ): Promise { + const message = tx.getHashedMessageToSign(); + + const signature = await this.#signHash(address, message); + + const { r, s, v } = parseEcdsaSignature(signature); + + const signedTx = tx.addSignature(v, r, s); + return signedTx; + } + + /** + * Sign a personal message using the specified account. + * This method is compatible with the `personal_sign` RPC method. + * + * @param address - The address of the account. + * @param msgHex - The message to sign. + * @param _opts - The options for signing the message. + * @returns The signature of the message. + */ + async signPersonalMessage( + address: Hex, + msgHex: string, + _opts?: Record, + ): Promise { + const rawMsg = hexToBytes(msgHex); + const msgHash = hashPersonalMessage(rawMsg); + + const signature = await this.#signHash(address, msgHash); + const { r, s, v } = parseEcdsaSignature(signature); + const vRaw = bigIntToBytes(v); + + if (vRaw.length !== 1) { + throw new Error('Invalid signature'); + } + + return bytesToHex(concatBytes([vRaw, r, s])); + } + + /** + * Sign a typed message using the specified account. + * This method is compatible with the `eth_signTypedData` RPC method. + * + * @param address - The address of the account. + * @param data - The typed data to sign. + * @param opts - The options for signing the message. + * @returns The signature of the message. + */ + async signTypedData( + address: Hex, + data: unknown[] | Record, + opts?: Record, + ): Promise {} + + async #signHash(address: Hex, hash: Uint8Array): Promise { if (!this.#keyShare) { throw new Error(`keyshare not initialized`); } else if (!this.#networkIdentity) { @@ -179,7 +241,7 @@ export class MPCKeyring implements Keyring { } const sessionId = generateSessionId(); - const message = tx.getHashedMessageToSign(); + const message = hash; await initCloudSign({ keyId: this.#keyId, @@ -193,7 +255,7 @@ export class MPCKeyring implements Keyring { sessionId, ); - const dkls19 = new Dkls19TssLib(this.#dkls19Lib, this.#rng); + const dkls19 = new Dkls19TssLib(this.#dkls19Lib, this.#rng, true); const { signature } = await dkls19.sign({ key: this.#keyShare, signers: custodians, @@ -201,43 +263,6 @@ export class MPCKeyring implements Keyring { networkSession, }); - const { r, s, v } = parseEcdsaSignature(signature); - - return TransactionFactory.fromTxData({ - ...tx, - r, - s, - v, - }); + return signature; } - - /** - * Sign a personal message using the specified account. - * This method is compatible with the `personal_sign` RPC method. - * - * @param address - The address of the account. - * @param msgHex - The message to sign. - * @param opts - The options for signing the message. - * @returns The signature of the message. - */ - async signPersonalMessage( - address: Hex, - msgHex: string, - opts?: Record, - ): Promise {} - - /** - * Sign a typed message using the specified account. - * This method is compatible with the `eth_signTypedData` RPC method. - * - * @param address - The address of the account. - * @param data - The typed data to sign. - * @param opts - The options for signing the message. - * @returns The signature of the message. - */ - async signTypedData( - address: Hex, - data: unknown[] | Record, - opts?: Record, - ): Promise {} } diff --git a/packages/keyring-eth-mpc/src/util.ts b/packages/keyring-eth-mpc/src/util.ts index 1ace7f839..378ee7963 100644 --- a/packages/keyring-eth-mpc/src/util.ts +++ b/packages/keyring-eth-mpc/src/util.ts @@ -44,8 +44,8 @@ export function equalAddresses(address1: string, address2: string): boolean { * @returns The parsed signature. */ export function parseEcdsaSignature(signature: Uint8Array): { - r: bigint; - s: bigint; + r: Uint8Array; + s: Uint8Array; v: bigint; } { // TODO From 52caa1a636db3d6007a66abc52a7ab9d0f42b845 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Thu, 15 Jan 2026 17:09:46 +0100 Subject: [PATCH 08/44] signTypedData --- packages/keyring-eth-mpc/src/mpc-keyring.ts | 58 +++++++++------- packages/keyring-eth-mpc/src/util.ts | 75 ++++++++++++++++++++- 2 files changed, 104 insertions(+), 29 deletions(-) diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index 7d0cada15..e056dfaed 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -1,5 +1,11 @@ import type { TypedTransaction } from '@ethereumjs/tx'; import { hashPersonalMessage } from '@ethereumjs/util'; +import type { + TypedDataV1, + TypedMessage, + SignTypedDataVersion, + MessageTypes, +} from '@metamask/eth-sig-util'; import type { Keyring } from '@metamask/keyring-utils'; import { CL24DKM, @@ -11,14 +17,7 @@ import type { ThresholdKey, } from '@metamask/mfa-wallet-interface'; import type { WasmLib as Dkls19WasmLib } from '@metamask/tss-dkls19-lib'; -import { - bigIntToBytes, - bytesToHex, - concatBytes, - hexToBytes, - type Hex, - type Json, -} from '@metamask/utils'; +import { hexToBytes, type Hex, type Json } from '@metamask/utils'; import { initCloudKeyGen, initCloudSign } from './cloud'; import type { NetworkIdentity, NetworkManager } from './network'; @@ -26,8 +25,11 @@ import { generateSessionId } from './network'; import type { MPCKeyringOpts, ThresholdKeyId } from './types'; import { equalAddresses, + getSignedTypedDataHash, parseEcdsaSignature, + parseSignedTypedDataVersion, publicToAddressHex, + toEthSig, } from './util'; const type = 'eth-mpc'; @@ -65,7 +67,7 @@ export class MPCKeyring implements Keyring { async serialize(): Promise { return { initRole: this.#initRole, - networkCredentials: serializeNetworkCredentials(this.#networkIdentity), + networkIdentity: serializeNetworkIdentity(this.#networkIdentity), keyShare: serializeThresholdKey(this.#keyShare), }; } @@ -84,10 +86,8 @@ export class MPCKeyring implements Keyring { this.#initRole = 'responder'; } - if ('networkCredentials' in state) { - this.#networkIdentity = deserializeNetworkCredentials( - state.networkCredentials, - ); + if ('networkIdentity' in state) { + this.#networkIdentity = deserializeNetworkIdentity(state.networkIdentity); } if ('keyShare' in state) { @@ -199,14 +199,8 @@ export class MPCKeyring implements Keyring { const msgHash = hashPersonalMessage(rawMsg); const signature = await this.#signHash(address, msgHash); - const { r, s, v } = parseEcdsaSignature(signature); - const vRaw = bigIntToBytes(v); - - if (vRaw.length !== 1) { - throw new Error('Invalid signature'); - } - - return bytesToHex(concatBytes([vRaw, r, s])); + const ethSig = toEthSig(signature); + return ethSig; } /** @@ -215,14 +209,26 @@ export class MPCKeyring implements Keyring { * * @param address - The address of the account. * @param data - The typed data to sign. - * @param opts - The options for signing the message. + * @param options - The options for signing the message. * @returns The signature of the message. */ - async signTypedData( + async signTypedData< + Version extends SignTypedDataVersion, + Types extends MessageTypes, + Options extends { version?: Version }, + >( address: Hex, - data: unknown[] | Record, - opts?: Record, - ): Promise {} + data: Version extends 'V1' ? TypedDataV1 : TypedMessage, + options?: Options, + ): Promise { + const version = parseSignedTypedDataVersion(options); + + const messageHash = getSignedTypedDataHash(data, version); + + const signature = await this.#signHash(address, messageHash); + const ethSig = toEthSig(signature); + return ethSig; + } async #signHash(address: Hex, hash: Uint8Array): Promise { if (!this.#keyShare) { diff --git a/packages/keyring-eth-mpc/src/util.ts b/packages/keyring-eth-mpc/src/util.ts index 378ee7963..200c82336 100644 --- a/packages/keyring-eth-mpc/src/util.ts +++ b/packages/keyring-eth-mpc/src/util.ts @@ -1,7 +1,17 @@ -import { publicToAddress } from '@ethereumjs/util'; -import { normalize } from '@metamask/eth-sig-util'; +import { bigIntToBytes, concatBytes, publicToAddress } from '@ethereumjs/util'; +import type { + MessageTypes, + TypedDataV1, + TypedMessage, +} from '@metamask/eth-sig-util'; +import { + normalize, + SignTypedDataVersion, + TypedDataUtils, + typedSignatureHash, +} from '@metamask/eth-sig-util'; import type { Hex } from '@metamask/utils'; -import { add0x, assert, bytesToHex } from '@metamask/utils'; +import { add0x, assert, bytesToHex, hexToBytes } from '@metamask/utils'; /** * Convert a public key to an address. @@ -50,3 +60,62 @@ export function parseEcdsaSignature(signature: Uint8Array): { } { // TODO } + +/** + * Convert an ECDSA signature to an Ethereum signature. + * + * @param signature - The signature to convert. + * @returns The Ethereum signature. + */ +export function toEthSig(signature: Uint8Array): string { + const { r, s, v } = parseEcdsaSignature(signature); + const vRaw = bigIntToBytes(v); + + if (vRaw.length !== 1) { + throw new Error('Invalid signature'); + } + + return bytesToHex(concatBytes(vRaw, r, s)); +} + +/** + * Parse the version of a signed typed data object. + * + * @param opts - The options object. + * @returns The version of the signed typed data object. + */ +export function parseSignedTypedDataVersion( + opts?: Record, +): SignTypedDataVersion { + let version = opts?.version as SignTypedDataVersion | undefined; + if (!version || !Object.keys(SignTypedDataVersion).includes(version)) { + version = SignTypedDataVersion.V1; + } + return version; +} + +/** + * Get the hash of a signed typed data object. + * + * @param data - The data to hash. + * @param version - The version of the signed typed data object. + * @returns The hash of the signed typed data object. + */ +export function getSignedTypedDataHash< + Version extends SignTypedDataVersion, + MessageType extends MessageTypes, +>( + data: Version extends 'V1' ? TypedDataV1 : TypedMessage, + version: Version, +): Uint8Array { + if (version === SignTypedDataVersion.V1) { + const hash = typedSignatureHash(data as unknown as TypedDataV1); + return hexToBytes(hash); + } + + const hash = TypedDataUtils.eip712Hash( + data as TypedMessage, + version, + ); + return new Uint8Array(hash); +} From 29426eec8dc52745f1bbd39b2c3a0bca7106b8ae Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Mon, 19 Jan 2026 12:59:12 +0100 Subject: [PATCH 09/44] implement backend requests --- packages/keyring-eth-mpc/src/cloud.ts | 52 +++++++++++++++++++-- packages/keyring-eth-mpc/src/mpc-keyring.ts | 6 +++ packages/keyring-eth-mpc/src/types.ts | 1 + 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/packages/keyring-eth-mpc/src/cloud.ts b/packages/keyring-eth-mpc/src/cloud.ts index d7a0babf9..f6412ea9a 100644 --- a/packages/keyring-eth-mpc/src/cloud.ts +++ b/packages/keyring-eth-mpc/src/cloud.ts @@ -1,24 +1,70 @@ +import type { PartyId } from '@metamask/mfa-wallet-interface'; + /** * Initialize a cloud keygen session * * @param opts - The options for the cloud keygen session * @param opts.localId - The local ID of the device + * @param opts.sessionId - The ID of the session + * @param opts.baseURL - The base URL of the cloud service * @returns The cloud ID of the device */ export async function initCloudKeyGen(opts: { - localId: string; -}): Promise<{ cloudId: string }> {} + baseURL: string; + localId: PartyId; + sessionId: string; +}): Promise<{ cloudId: string }> { + const response = await fetch(`${opts.baseURL}/createKey`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + localId: opts.localId, + sessionId: opts.sessionId, + }), + }); + + if (!response.ok) { + throw new Error( + `Failed to initialize cloud keygen session: ${response.statusText}`, + ); + } + + const data = await response.json(); + return { cloudId: data.cloudId }; +} /** * Initialize a cloud sign session * * @param opts - The options for the cloud sign session + * @param opts.baseURL - The base URL of the cloud service * @param opts.keyId - The ID of the key * @param opts.sessionId - The ID of the session * @param opts.message - The message to sign */ export async function initCloudSign(opts: { + baseURL: string; keyId: string; sessionId: string; message: Uint8Array; -}): Promise {} +}): Promise { + const response = await fetch(`${opts.baseURL}/sign`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + keyId: opts.keyId, + sessionId: opts.sessionId, + message: opts.message, + }), + }); + + if (!response.ok) { + throw new Error( + `Failed to initialize cloud sign session: ${response.statusText}`, + ); + } +} diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index e056dfaed..d07e40848 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -51,12 +51,15 @@ export class MPCKeyring implements Keyring { readonly #keyId?: ThresholdKeyId; + readonly #cloudURL: string; + constructor(opts: MPCKeyringOpts) { this.#rng = { generateRandomBytes: opts.getRandomBytes, }; this.#dkls19Lib = opts.dkls19Lib; this.#networkManager = {} as NetworkManager; // TODO + this.#cloudURL = opts.cloudURL; } /** @@ -104,6 +107,8 @@ export class MPCKeyring implements Keyring { const sessionId = generateSessionId(); const { cloudId } = await initCloudKeyGen({ localId, + sessionId, + baseURL: this.#cloudURL, }); const custodians = [localId, cloudId]; const threshold = 2; @@ -253,6 +258,7 @@ export class MPCKeyring implements Keyring { keyId: this.#keyId, sessionId, message, + baseURL: this.#cloudURL, }); const networkSession = await this.#networkManager.createSession( diff --git a/packages/keyring-eth-mpc/src/types.ts b/packages/keyring-eth-mpc/src/types.ts index 2d830c2af..1d52976c5 100644 --- a/packages/keyring-eth-mpc/src/types.ts +++ b/packages/keyring-eth-mpc/src/types.ts @@ -3,6 +3,7 @@ import type { WasmLib as Dkls19WasmLib } from '@metamask/tss-dkls19-lib'; export type MPCKeyringOpts = { getRandomBytes: (size: number) => Uint8Array; dkls19Lib: Dkls19WasmLib; + cloudURL: string; }; export type ThresholdKeyId = string; From 623b0a51e16e3b74765e70805a64475b31a9e0f9 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Mon, 19 Jan 2026 15:10:45 +0100 Subject: [PATCH 10/44] serialization --- packages/keyring-eth-mpc/src/mpc-keyring.ts | 54 ++++++++++++++++----- packages/keyring-eth-mpc/src/types.ts | 16 ++++++ packages/keyring-eth-mpc/src/util.ts | 30 +++++++++++- 3 files changed, 86 insertions(+), 14 deletions(-) diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index d07e40848..861d4a719 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -22,20 +22,27 @@ import { hexToBytes, type Hex, type Json } from '@metamask/utils'; import { initCloudKeyGen, initCloudSign } from './cloud'; import type { NetworkIdentity, NetworkManager } from './network'; import { generateSessionId } from './network'; -import type { MPCKeyringOpts, ThresholdKeyId } from './types'; +import type { + InitRole, + MPCKeyringOpts, + MPCKeyringSerializer, + ThresholdKeyId, +} from './types'; import { equalAddresses, getSignedTypedDataHash, parseEcdsaSignature, + parseInitRole, parseSignedTypedDataVersion, + parseThresholdKeyId, publicToAddressHex, toEthSig, } from './util'; -const type = 'eth-mpc'; +const mpcKeyringType = 'MPC Keyring'; export class MPCKeyring implements Keyring { - readonly type: string = type; + readonly type: string = mpcKeyringType; readonly #rng: RandomNumberGenerator; @@ -43,23 +50,27 @@ export class MPCKeyring implements Keyring { readonly #dkls19Lib: Dkls19WasmLib; - #initRole: 'initiator' | 'responder' = 'initiator'; + #initRole: InitRole; #networkIdentity?: NetworkIdentity; #keyShare?: ThresholdKey; - readonly #keyId?: ThresholdKeyId; + #keyId?: ThresholdKeyId; readonly #cloudURL: string; + readonly #serializer: MPCKeyringSerializer; + constructor(opts: MPCKeyringOpts) { this.#rng = { generateRandomBytes: opts.getRandomBytes, }; this.#dkls19Lib = opts.dkls19Lib; - this.#networkManager = {} as NetworkManager; // TODO + this.#networkManager = opts.networkManager; this.#cloudURL = opts.cloudURL; + this.#serializer = opts.serializer; + this.#initRole = opts.initRole; } /** @@ -68,11 +79,21 @@ export class MPCKeyring implements Keyring { * @returns The serialized state of the keyring. */ async serialize(): Promise { - return { + const state: Json = { initRole: this.#initRole, - networkIdentity: serializeNetworkIdentity(this.#networkIdentity), - keyShare: serializeThresholdKey(this.#keyShare), }; + if (this.#networkIdentity) { + state.networkIdentity = this.#serializer.networkIdentityToJSON( + this.#networkIdentity, + ); + } + if (this.#keyShare) { + state.keyShare = this.#serializer.thresholdKeyToJSON(this.#keyShare); + } + if (this.#keyId) { + state.keyId = this.#keyId; + } + return state; } /** @@ -85,16 +106,22 @@ export class MPCKeyring implements Keyring { throw new Error('Invalid state'); } - if ('initRole' in state && state.initRole === 'responder') { - this.#initRole = 'responder'; + if ('initRole' in state) { + this.#initRole = parseInitRole(state.initRole); } if ('networkIdentity' in state) { - this.#networkIdentity = deserializeNetworkIdentity(state.networkIdentity); + this.#networkIdentity = this.#serializer.networkIdentityFromJSON( + state.networkIdentity, + ); } if ('keyShare' in state) { - this.#keyShare = deserializeThresholdKey(state.keyShare); + this.#keyShare = this.#serializer.thresholdKeyFromJSON(state.keyShare); + } + + if ('keyId' in state) { + this.#keyId = parseThresholdKeyId(state.keyId); } } @@ -125,6 +152,7 @@ export class MPCKeyring implements Keyring { threshold, networkSession, }); + this.#keyId = networkSession.sessionId; } /** diff --git a/packages/keyring-eth-mpc/src/types.ts b/packages/keyring-eth-mpc/src/types.ts index 1d52976c5..ef30d1fd9 100644 --- a/packages/keyring-eth-mpc/src/types.ts +++ b/packages/keyring-eth-mpc/src/types.ts @@ -1,9 +1,25 @@ +import type { ThresholdKey } from '@metamask/mfa-wallet-interface'; import type { WasmLib as Dkls19WasmLib } from '@metamask/tss-dkls19-lib'; +import type { Json } from '@metamask/utils'; + +import type { NetworkIdentity, NetworkManager } from './network'; export type MPCKeyringOpts = { getRandomBytes: (size: number) => Uint8Array; dkls19Lib: Dkls19WasmLib; cloudURL: string; + networkManager: NetworkManager; + serializer: MPCKeyringSerializer; + initRole: InitRole; }; export type ThresholdKeyId = string; + +export type MPCKeyringSerializer = { + networkIdentityToJSON: (identity: NetworkIdentity) => Json; + networkIdentityFromJSON: (identity: Json) => NetworkIdentity; + thresholdKeyToJSON: (key: ThresholdKey) => Json; + thresholdKeyFromJSON: (key: Json) => ThresholdKey; +}; + +export type InitRole = 'initiator' | 'responder'; diff --git a/packages/keyring-eth-mpc/src/util.ts b/packages/keyring-eth-mpc/src/util.ts index 200c82336..13f1371a4 100644 --- a/packages/keyring-eth-mpc/src/util.ts +++ b/packages/keyring-eth-mpc/src/util.ts @@ -10,9 +10,11 @@ import { TypedDataUtils, typedSignatureHash, } from '@metamask/eth-sig-util'; -import type { Hex } from '@metamask/utils'; +import type { Hex, Json } from '@metamask/utils'; import { add0x, assert, bytesToHex, hexToBytes } from '@metamask/utils'; +import type { InitRole, ThresholdKeyId } from './types'; + /** * Convert a public key to an address. * @@ -119,3 +121,29 @@ export function getSignedTypedDataHash< ); return new Uint8Array(hash); } + +/** + * Parse the init role from a JSON object. + * + * @param initRole - The init role to parse. + * @returns The parsed init role. + */ +export function parseInitRole(initRole: Json): InitRole { + if (initRole !== 'initiator' && initRole !== 'responder') { + throw new Error('Invalid init role'); + } + return initRole; +} + +/** + * Parse the key ID from a JSON object. + * + * @param keyId - The key ID to parse. + * @returns The parsed key ID. + */ +export function parseThresholdKeyId(keyId: Json): ThresholdKeyId { + if (typeof keyId !== 'string') { + throw new Error('Invalid key ID'); + } + return keyId; +} From 0a7cce463224120e1b3e2a60efe55783ced0ae5d Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Mon, 19 Jan 2026 15:19:40 +0100 Subject: [PATCH 11/44] generate session id --- packages/keyring-eth-mpc/src/mpc-keyring.ts | 4 ++-- packages/keyring-eth-mpc/src/network.ts | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index 861d4a719..a7a372c96 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -131,7 +131,7 @@ export class MPCKeyring implements Keyring { const net = this.#networkManager; const networkIdentity = await net.createIdentity(); const localId = networkIdentity.partyId; - const sessionId = generateSessionId(); + const sessionId = generateSessionId(this.#rng); const { cloudId } = await initCloudKeyGen({ localId, sessionId, @@ -279,7 +279,7 @@ export class MPCKeyring implements Keyring { throw new Error(`account ${address} not found`); } - const sessionId = generateSessionId(); + const sessionId = generateSessionId(this.#rng); const message = hash; await initCloudSign({ diff --git a/packages/keyring-eth-mpc/src/network.ts b/packages/keyring-eth-mpc/src/network.ts index 0d8677f65..4fb6e18ab 100644 --- a/packages/keyring-eth-mpc/src/network.ts +++ b/packages/keyring-eth-mpc/src/network.ts @@ -1,8 +1,10 @@ import type { NetworkSession, PartyId, + RandomNumberGenerator, SessionId, } from '@metamask/mfa-wallet-interface'; +import { bytesToHex } from '@metamask/utils'; export type NetworkIdentity = { partyId: PartyId; @@ -20,8 +22,9 @@ export type NetworkManager = { /** * Generates a random session ID. * + * @param rng - The random number generator to use. * @returns The session ID. */ -export function generateSessionId(): SessionId { - +export function generateSessionId(rng: RandomNumberGenerator): SessionId { + return bytesToHex(rng.generateRandomBytes(32)); } From ffd5eccebe8bc927583dcafca83fd9c374d62aa5 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Mon, 19 Jan 2026 16:28:02 +0100 Subject: [PATCH 12/44] recover parity bit --- packages/keyring-eth-mpc/src/mpc-keyring.ts | 14 ++-- packages/keyring-eth-mpc/src/util.ts | 74 ++++++++++++++++----- 2 files changed, 63 insertions(+), 25 deletions(-) diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index a7a372c96..c174de457 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -17,7 +17,7 @@ import type { ThresholdKey, } from '@metamask/mfa-wallet-interface'; import type { WasmLib as Dkls19WasmLib } from '@metamask/tss-dkls19-lib'; -import { hexToBytes, type Hex, type Json } from '@metamask/utils'; +import { bytesToHex, hexToBytes, type Hex, type Json } from '@metamask/utils'; import { initCloudKeyGen, initCloudSign } from './cloud'; import type { NetworkIdentity, NetworkManager } from './network'; @@ -31,7 +31,7 @@ import type { import { equalAddresses, getSignedTypedDataHash, - parseEcdsaSignature, + parseEthSig, parseInitRole, parseSignedTypedDataVersion, parseThresholdKeyId, @@ -208,7 +208,7 @@ export class MPCKeyring implements Keyring { const signature = await this.#signHash(address, message); - const { r, s, v } = parseEcdsaSignature(signature); + const { r, s, v } = parseEthSig(signature); const signedTx = tx.addSignature(v, r, s); return signedTx; @@ -232,8 +232,7 @@ export class MPCKeyring implements Keyring { const msgHash = hashPersonalMessage(rawMsg); const signature = await this.#signHash(address, msgHash); - const ethSig = toEthSig(signature); - return ethSig; + return bytesToHex(signature); } /** @@ -259,8 +258,7 @@ export class MPCKeyring implements Keyring { const messageHash = getSignedTypedDataHash(data, version); const signature = await this.#signHash(address, messageHash); - const ethSig = toEthSig(signature); - return ethSig; + return bytesToHex(signature); } async #signHash(address: Hex, hash: Uint8Array): Promise { @@ -303,6 +301,6 @@ export class MPCKeyring implements Keyring { networkSession, }); - return signature; + return toEthSig(signature, hash, publicKey); } } diff --git a/packages/keyring-eth-mpc/src/util.ts b/packages/keyring-eth-mpc/src/util.ts index 13f1371a4..fbd75bf4d 100644 --- a/packages/keyring-eth-mpc/src/util.ts +++ b/packages/keyring-eth-mpc/src/util.ts @@ -1,4 +1,9 @@ -import { bigIntToBytes, concatBytes, publicToAddress } from '@ethereumjs/util'; +import { + bigIntToBytes, + concatBytes, + ecrecover, + publicToAddress, +} from '@ethereumjs/util'; import type { MessageTypes, TypedDataV1, @@ -50,34 +55,69 @@ export function equalAddresses(address1: string, address2: string): boolean { } /** - * Parse an ECDSA signature. + * Convert an ECDSA signature in compact format (64 bytes) to a signature in + * Ethereum extended format (65 bytes). + * + * @param signature - The signature to convert. + * @param hash - The hash of the message. + * @param pubKey - The public key of the signer. + * @returns The Ethereum signature. + */ +export function toEthSig( + signature: Uint8Array, + hash: Uint8Array, + pubKey: Uint8Array, +): Uint8Array { + if (signature.length !== 64) { + throw new Error('Invalid signature length'); + } + + const rBuf = signature.slice(0, 32); + const sBuf = signature.slice(32, 64); + + const expectedAddr = publicToAddressHex(pubKey); + + for (const candidateV of [0n, 1n]) { + try { + const candidatePubKey = ecrecover(hash, candidateV + 27n, rBuf, sBuf); + if (publicToAddressHex(candidatePubKey) === expectedAddr) { + const vInt = candidateV + 27n; + return concatBytes(rBuf, sBuf, bigIntToBytes(vInt)); + } + } catch { + // Ignore errors + } + } + + throw new Error('Invalid signature'); +} + +/** + * Parse an extended ECDSA signature. * * @param signature - The signature to parse. * @returns The parsed signature. */ -export function parseEcdsaSignature(signature: Uint8Array): { +export function parseEthSig(signature: Uint8Array): { r: Uint8Array; s: Uint8Array; v: bigint; } { - // TODO -} + if (signature.length !== 65) { + throw new Error('Invalid signature length'); + } -/** - * Convert an ECDSA signature to an Ethereum signature. - * - * @param signature - The signature to convert. - * @returns The Ethereum signature. - */ -export function toEthSig(signature: Uint8Array): string { - const { r, s, v } = parseEcdsaSignature(signature); - const vRaw = bigIntToBytes(v); + const rBuf = signature.slice(0, 32); + const sBuf = signature.slice(32, 64); + const vByte = signature[64]; - if (vRaw.length !== 1) { - throw new Error('Invalid signature'); + // This check is technically redundant because length is 65, but satisfies TS + if (vByte === undefined) { + throw new Error('Invalid signature v value'); } + const vInt = BigInt(vByte); - return bytesToHex(concatBytes(vRaw, r, s)); + return { r: rBuf, s: sBuf, v: vInt }; } /** From 72fc13bf90715689223e106edb3914f746b90760 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Mon, 19 Jan 2026 16:35:56 +0100 Subject: [PATCH 13/44] ensure low s --- packages/keyring-eth-mpc/src/util.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/keyring-eth-mpc/src/util.ts b/packages/keyring-eth-mpc/src/util.ts index fbd75bf4d..b0c29cd26 100644 --- a/packages/keyring-eth-mpc/src/util.ts +++ b/packages/keyring-eth-mpc/src/util.ts @@ -20,6 +20,11 @@ import { add0x, assert, bytesToHex, hexToBytes } from '@metamask/utils'; import type { InitRole, ThresholdKeyId } from './types'; +const SECP256K1_N = BigInt( + '0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', +); +const SECP256K1_HALF_N = SECP256K1_N / 2n; + /** * Convert a public key to an address. * @@ -73,7 +78,20 @@ export function toEthSig( } const rBuf = signature.slice(0, 32); - const sBuf = signature.slice(32, 64); + let sBuf = signature.slice(32, 64); + + const sInt = BigInt(add0x(bytesToHex(sBuf))); + if (sInt > SECP256K1_HALF_N) { + const newSInt = SECP256K1_N - sInt; + const newSBytes = bigIntToBytes(newSInt); + + if (newSBytes.length < 32) { + sBuf = new Uint8Array(32); + sBuf.set(newSBytes, 32 - newSBytes.length); + } else { + sBuf = new Uint8Array(newSBytes); + } + } const expectedAddr = publicToAddressHex(pubKey); From c315fd81acf682140f0a30fddf311e979b3d98aa Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Mon, 19 Jan 2026 16:52:29 +0100 Subject: [PATCH 14/44] add comments --- packages/keyring-eth-mpc/src/mpc-keyring.ts | 2 ++ packages/keyring-eth-mpc/src/util.ts | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index c174de457..d17446ab7 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -301,6 +301,8 @@ export class MPCKeyring implements Keyring { networkSession, }); + // NOTE: We could create an Ethereum compliant signature more efficiently if + // the signing library provided the parity bit. return toEthSig(signature, hash, publicKey); } } diff --git a/packages/keyring-eth-mpc/src/util.ts b/packages/keyring-eth-mpc/src/util.ts index b0c29cd26..41d2d722f 100644 --- a/packages/keyring-eth-mpc/src/util.ts +++ b/packages/keyring-eth-mpc/src/util.ts @@ -77,6 +77,8 @@ export function toEthSig( throw new Error('Invalid signature length'); } + // Enforce low S value + const rBuf = signature.slice(0, 32); let sBuf = signature.slice(32, 64); @@ -93,6 +95,8 @@ export function toEthSig( } } + // Recover parity bit + const expectedAddr = publicToAddressHex(pubKey); for (const candidateV of [0n, 1n]) { From d378b7303df7081b2436d5649b668af2ef712c10 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Mon, 19 Jan 2026 17:42:11 +0100 Subject: [PATCH 15/44] cleanup --- packages/keyring-eth-mpc/src/mpc-keyring.ts | 2 -- packages/keyring-eth-mpc/src/util.ts | 25 ++++++++++++--------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index d17446ab7..c174de457 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -301,8 +301,6 @@ export class MPCKeyring implements Keyring { networkSession, }); - // NOTE: We could create an Ethereum compliant signature more efficiently if - // the signing library provided the parity bit. return toEthSig(signature, hash, publicKey); } } diff --git a/packages/keyring-eth-mpc/src/util.ts b/packages/keyring-eth-mpc/src/util.ts index 41d2d722f..8ae68b6fc 100644 --- a/packages/keyring-eth-mpc/src/util.ts +++ b/packages/keyring-eth-mpc/src/util.ts @@ -77,7 +77,7 @@ export function toEthSig( throw new Error('Invalid signature length'); } - // Enforce low S value + // Enforce low `s` const rBuf = signature.slice(0, 32); let sBuf = signature.slice(32, 64); @@ -95,23 +95,26 @@ export function toEthSig( } } - // Recover parity bit + // Recover `v` + // --------------------------------------------------------------------------- + // NOTE: If the signing library provided the parity of R.y, we could compute + // `v` directly and skip the costly ecrecover operation. + // --------------------------------------------------------------------------- const expectedAddr = publicToAddressHex(pubKey); - for (const candidateV of [0n, 1n]) { + const checkParity = (parity: bigint): boolean => { try { - const candidatePubKey = ecrecover(hash, candidateV + 27n, rBuf, sBuf); - if (publicToAddressHex(candidatePubKey) === expectedAddr) { - const vInt = candidateV + 27n; - return concatBytes(rBuf, sBuf, bigIntToBytes(vInt)); - } + const candidatePubKey = ecrecover(hash, parity, rBuf, sBuf); + return publicToAddressHex(candidatePubKey) === expectedAddr; } catch { - // Ignore errors + return false; } - } + }; - throw new Error('Invalid signature'); + // Ethereum defines `v = parity(R.y) + 27`. + const vInt = checkParity(0n) ? 27n : 28n; + return concatBytes(rBuf, sBuf, bigIntToBytes(vInt)); } /** From d8337a981bce6eadfd120918108b7fbc7215d0f9 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Mon, 19 Jan 2026 21:56:17 +0100 Subject: [PATCH 16/44] f --- packages/keyring-eth-mpc/src/util.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/keyring-eth-mpc/src/util.ts b/packages/keyring-eth-mpc/src/util.ts index 8ae68b6fc..f880d895c 100644 --- a/packages/keyring-eth-mpc/src/util.ts +++ b/packages/keyring-eth-mpc/src/util.ts @@ -95,7 +95,7 @@ export function toEthSig( } } - // Recover `v` + // Compute `v` // --------------------------------------------------------------------------- // NOTE: If the signing library provided the parity of R.y, we could compute // `v` directly and skip the costly ecrecover operation. @@ -112,8 +112,12 @@ export function toEthSig( } }; - // Ethereum defines `v = parity(R.y) + 27`. - const vInt = checkParity(0n) ? 27n : 28n; + const parity = checkParity(0n) ? 0n : 1n; + + // Ethereum's recovery value: `v = parity(R.y) + 27` + const vInt = parity + 27n; + + // Ethereum's extended signature format: `[r | s | v]` return concatBytes(rBuf, sBuf, bigIntToBytes(vInt)); } From 577bbaaa3eac9cf7de3b35f84b575f8d49f5db55 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Tue, 20 Jan 2026 09:09:50 +0100 Subject: [PATCH 17/44] wip build --- packages/keyring-eth-mpc/package.json | 2 +- packages/keyring-eth-mpc/src/mpc-keyring.ts | 4 ++++ tsconfig.build.json | 1 + tsconfig.json | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/keyring-eth-mpc/package.json b/packages/keyring-eth-mpc/package.json index 4c15d2ab4..def2e5699 100644 --- a/packages/keyring-eth-mpc/package.json +++ b/packages/keyring-eth-mpc/package.json @@ -75,4 +75,4 @@ "@lavamoat/preinstall-always-fail": false } } -} \ No newline at end of file +} diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index c174de457..116b9db6d 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -41,6 +41,10 @@ import { const mpcKeyringType = 'MPC Keyring'; +export const uninitializedResponderState: Json = { + initRole: 'responder', +}; + export class MPCKeyring implements Keyring { readonly type: string = mpcKeyringType; diff --git a/tsconfig.build.json b/tsconfig.build.json index 60377db1b..551af2f73 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -7,6 +7,7 @@ { "path": "./packages/keyring-eth-simple/tsconfig.build.json" }, { "path": "./packages/keyring-eth-trezor/tsconfig.build.json" }, { "path": "./packages/keyring-eth-hd/tsconfig.build.json" }, + { "path": "./packages/keyring-eth-mpc/tsconfig.build.json" }, { "path": "./packages/keyring-snap-bridge/tsconfig.build.json" }, { "path": "./packages/keyring-snap-sdk/tsconfig.build.json" }, { "path": "./packages/keyring-snap-client/tsconfig.build.json" }, diff --git a/tsconfig.json b/tsconfig.json index cd24f44bf..31200c342 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,7 @@ { "path": "./packages/keyring-eth-simple" }, { "path": "./packages/keyring-eth-trezor" }, { "path": "./packages/keyring-eth-hd" }, + { "path": "./packages/keyring-eth-mpc" }, { "path": "./packages/keyring-snap-bridge" }, { "path": "./packages/keyring-snap-client" }, { "path": "./packages/keyring-internal-snap-client" }, From b63be6a2896a04a56ad80c273aadaa67ce59ecfb Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Tue, 20 Jan 2026 09:19:12 +0100 Subject: [PATCH 18/44] wip --- packages/keyring-eth-mpc/src/index.ts | 2 +- packages/keyring-eth-mpc/tsconfig.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/keyring-eth-mpc/src/index.ts b/packages/keyring-eth-mpc/src/index.ts index 63aca5d0d..b8e51392b 100644 --- a/packages/keyring-eth-mpc/src/index.ts +++ b/packages/keyring-eth-mpc/src/index.ts @@ -1 +1 @@ -export { MpcKeyring } from './mpc-keyring'; +export { MPCKeyring } from './mpc-keyring'; diff --git a/packages/keyring-eth-mpc/tsconfig.json b/packages/keyring-eth-mpc/tsconfig.json index feaa7d31e..0e0f886cd 100644 --- a/packages/keyring-eth-mpc/tsconfig.json +++ b/packages/keyring-eth-mpc/tsconfig.json @@ -1,7 +1,8 @@ { "extends": "../../tsconfig.packages.json", "compilerOptions": { - "baseUrl": "./" + "baseUrl": "./", + "skipLibCheck": true }, "references": [ { From f707b24dc10e999f62778749e765e8cd13ae513a Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Tue, 20 Jan 2026 09:27:13 +0100 Subject: [PATCH 19/44] wip --- packages/keyring-eth-mpc/tsconfig.build.json | 4 +++- yarn.lock | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/keyring-eth-mpc/tsconfig.build.json b/packages/keyring-eth-mpc/tsconfig.build.json index 20cc54288..94c953bd9 100644 --- a/packages/keyring-eth-mpc/tsconfig.build.json +++ b/packages/keyring-eth-mpc/tsconfig.build.json @@ -5,7 +5,9 @@ "outDir": "dist", "rootDir": "src" }, - "references": [], + "references": [ + { "path": "../keyring-utils/tsconfig.build.json" } + ], "include": ["./src/**/*.ts"], "exclude": ["./src/**/*.test.ts"] } diff --git a/yarn.lock b/yarn.lock index d3cc1e6a7..0424f7cb8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2189,12 +2189,12 @@ __metadata: "@metamask/mfa-wallet-cl24-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": version: 0.0.0 - resolution: "@metamask/mfa-wallet-cl24-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz::hash=322a56&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + resolution: "@metamask/mfa-wallet-cl24-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz::hash=8ee1d0&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." dependencies: "@msgpack/msgpack": "npm:^3.1.2" "@noble/curves": "npm:^1.9.2" "@noble/hashes": "npm:^1.8.0" - checksum: 10/19a2de8dc2296f5f0f3dc15dbbb4cd1e66538415fba2c2b1edb967afafd2ecd7b1c3c8764b593f170e1781f09851ea4a5ce0526230e2a4e59305cd16997abd56 + checksum: 10/ba0a676cdb75fa4aed3cefb12c28b36e29ee384f2664190cc9898af3e61b83b4c9cab815631fe18d06996c9b3fafefd35967d93dac98689f9fcb72c9f0b499a6 languageName: node linkType: hard From 6e37655ef0d7133574e6ca3d7235647ca747eb89 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Tue, 20 Jan 2026 09:35:33 +0100 Subject: [PATCH 20/44] wip --- packages/keyring-eth-mpc/tsconfig.build.json | 5 ++++- packages/keyring-eth-mpc/tsconfig.json | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/keyring-eth-mpc/tsconfig.build.json b/packages/keyring-eth-mpc/tsconfig.build.json index 94c953bd9..b34e78e32 100644 --- a/packages/keyring-eth-mpc/tsconfig.build.json +++ b/packages/keyring-eth-mpc/tsconfig.build.json @@ -3,7 +3,10 @@ "compilerOptions": { "baseUrl": "./", "outDir": "dist", - "rootDir": "src" + "rootDir": "src", + // NOTE: @msgpack/msgpack uses Uint8Array which requires TypeScript 5.7+. + // skipLibCheck bypasses type-checking of .d.ts files in node_modules. + "skipLibCheck": true }, "references": [ { "path": "../keyring-utils/tsconfig.build.json" } diff --git a/packages/keyring-eth-mpc/tsconfig.json b/packages/keyring-eth-mpc/tsconfig.json index 0e0f886cd..a30efa086 100644 --- a/packages/keyring-eth-mpc/tsconfig.json +++ b/packages/keyring-eth-mpc/tsconfig.json @@ -2,6 +2,8 @@ "extends": "../../tsconfig.packages.json", "compilerOptions": { "baseUrl": "./", + // NOTE: @msgpack/msgpack uses Uint8Array which requires TypeScript 5.7+. + // skipLibCheck bypasses type-checking of .d.ts files in node_modules. "skipLibCheck": true }, "references": [ From 7a028a7cddc1d1b7dc19f81d509e7172e44f5549 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Tue, 20 Jan 2026 10:30:33 +0100 Subject: [PATCH 21/44] export types --- packages/keyring-eth-mpc/src/index.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/keyring-eth-mpc/src/index.ts b/packages/keyring-eth-mpc/src/index.ts index b8e51392b..fa45ecbe1 100644 --- a/packages/keyring-eth-mpc/src/index.ts +++ b/packages/keyring-eth-mpc/src/index.ts @@ -1 +1,9 @@ export { MPCKeyring } from './mpc-keyring'; +export { uninitializedResponderState } from './mpc-keyring'; + +export type { + InitRole, + MPCKeyringOpts, + MPCKeyringSerializer, + ThresholdKeyId, +} from './types'; From 84c4c4b5c87592e91f46134f6bf39ff862b728a1 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Fri, 23 Jan 2026 23:13:39 +0100 Subject: [PATCH 22/44] integrate network --- package.json | 2 + packages/keyring-eth-mpc/package.json | 2 + packages/keyring-eth-mpc/src/mpc-keyring.ts | 44 +++++++++----- packages/keyring-eth-mpc/src/types.ts | 19 +++--- yarn.lock | 66 +++++++++++++++++++-- 5 files changed, 103 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index 43ced96c8..b7acf05da 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,9 @@ "ws@7.4.6": "^7.5.10", "@metamask/mfa-wallet-cl24-lib": "file:./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz", "@metamask/mfa-wallet-dkls19-lib": "file:./packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz", + "@metamask/mfa-wallet-e2ee": "file:./packages/keyring-eth-mpc/metamask-mfa-wallet-e2ee-0.0.0.tgz", "@metamask/mfa-wallet-interface": "file:./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz", + "@metamask/mfa-wallet-network": "file:./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz", "@metamask/mfa-wallet-util": "file:./packages/keyring-eth-mpc/metamask-mfa-wallet-util-0.0.0.tgz", "@metamask/tss-dkls19-lib": "file:./packages/keyring-eth-mpc/metamask-tss-dkls19-lib-0.0.0.tgz" }, diff --git a/packages/keyring-eth-mpc/package.json b/packages/keyring-eth-mpc/package.json index def2e5699..03e5d7166 100644 --- a/packages/keyring-eth-mpc/package.json +++ b/packages/keyring-eth-mpc/package.json @@ -47,6 +47,8 @@ "@metamask/eth-sig-util": "^8.2.0", "@metamask/mfa-wallet-cl24-lib": "^0.0.0", "@metamask/mfa-wallet-dkls19-lib": "^0.0.0", + "@metamask/mfa-wallet-e2ee": "^0.0.0", + "@metamask/mfa-wallet-network": "^0.0.0", "@metamask/mfa-wallet-util": "^0.0.0" }, "devDependencies": { diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index 116b9db6d..f8cbe3a43 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -9,6 +9,7 @@ import type { import type { Keyring } from '@metamask/keyring-utils'; import { CL24DKM, + CL24ThresholdKeySerializer, secp256k1 as secp256k1Curve, } from '@metamask/mfa-wallet-cl24-lib'; import { Dkls19TssLib } from '@metamask/mfa-wallet-dkls19-lib'; @@ -16,11 +17,15 @@ import type { RandomNumberGenerator, ThresholdKey, } from '@metamask/mfa-wallet-interface'; +import type { MfaNetworkIdentity } from '@metamask/mfa-wallet-network'; +import { + MfaNetworkIdentitySerializer, + MfaNetworkManager, +} from '@metamask/mfa-wallet-network'; import type { WasmLib as Dkls19WasmLib } from '@metamask/tss-dkls19-lib'; import { bytesToHex, hexToBytes, type Hex, type Json } from '@metamask/utils'; import { initCloudKeyGen, initCloudSign } from './cloud'; -import type { NetworkIdentity, NetworkManager } from './network'; import { generateSessionId } from './network'; import type { InitRole, @@ -50,13 +55,13 @@ export class MPCKeyring implements Keyring { readonly #rng: RandomNumberGenerator; - readonly #networkManager: NetworkManager; + readonly #networkManager: MfaNetworkManager; readonly #dkls19Lib: Dkls19WasmLib; #initRole: InitRole; - #networkIdentity?: NetworkIdentity; + #networkIdentity?: MfaNetworkIdentity; #keyShare?: ThresholdKey; @@ -71,10 +76,22 @@ export class MPCKeyring implements Keyring { generateRandomBytes: opts.getRandomBytes, }; this.#dkls19Lib = opts.dkls19Lib; - this.#networkManager = opts.networkManager; this.#cloudURL = opts.cloudURL; - this.#serializer = opts.serializer; this.#initRole = opts.initRole; + this.#serializer = { + thresholdKey: new CL24ThresholdKeySerializer(), + networkIdentity: new MfaNetworkIdentitySerializer(), + }; + this.#networkManager = new MfaNetworkManager({ + url: opts.relayerURL, + randomBytes: { + getRandomValues: (array: Uint8Array): Uint8Array => { + const bytes = opts.getRandomBytes(array.length); + array.set(bytes); + return array; + }, + }, + }); } /** @@ -87,12 +104,12 @@ export class MPCKeyring implements Keyring { initRole: this.#initRole, }; if (this.#networkIdentity) { - state.networkIdentity = this.#serializer.networkIdentityToJSON( + state.networkIdentity = this.#serializer.networkIdentity.toJson( this.#networkIdentity, ); } if (this.#keyShare) { - state.keyShare = this.#serializer.thresholdKeyToJSON(this.#keyShare); + state.keyShare = this.#serializer.thresholdKey.toJson(this.#keyShare); } if (this.#keyId) { state.keyId = this.#keyId; @@ -115,13 +132,13 @@ export class MPCKeyring implements Keyring { } if ('networkIdentity' in state) { - this.#networkIdentity = this.#serializer.networkIdentityFromJSON( + this.#networkIdentity = this.#serializer.networkIdentity.fromJson( state.networkIdentity, ); } if ('keyShare' in state) { - this.#keyShare = this.#serializer.thresholdKeyFromJSON(state.keyShare); + this.#keyShare = this.#serializer.thresholdKey.fromJson(state.keyShare); } if ('keyId' in state) { @@ -130,7 +147,7 @@ export class MPCKeyring implements Keyring { } async init(): Promise { - const dkm = new CL24DKM('secp256k1', secp256k1Curve, this.#rng); + const dkm = new CL24DKM(secp256k1Curve, this.#rng); const net = this.#networkManager; const networkIdentity = await net.createIdentity(); @@ -144,11 +161,7 @@ export class MPCKeyring implements Keyring { const custodians = [localId, cloudId]; const threshold = 2; - const networkSession = await net.createSession( - networkIdentity, - custodians, - sessionId, - ); + const networkSession = await net.createSession(networkIdentity, sessionId); this.#networkIdentity = networkIdentity; this.#keyShare = await dkm.createKey({ @@ -293,7 +306,6 @@ export class MPCKeyring implements Keyring { const networkSession = await this.#networkManager.createSession( this.#networkIdentity, - custodians, sessionId, ); diff --git a/packages/keyring-eth-mpc/src/types.ts b/packages/keyring-eth-mpc/src/types.ts index ef30d1fd9..ddc141b0c 100644 --- a/packages/keyring-eth-mpc/src/types.ts +++ b/packages/keyring-eth-mpc/src/types.ts @@ -1,25 +1,26 @@ import type { ThresholdKey } from '@metamask/mfa-wallet-interface'; +import type { MfaNetworkIdentity } from '@metamask/mfa-wallet-network'; import type { WasmLib as Dkls19WasmLib } from '@metamask/tss-dkls19-lib'; import type { Json } from '@metamask/utils'; -import type { NetworkIdentity, NetworkManager } from './network'; - export type MPCKeyringOpts = { getRandomBytes: (size: number) => Uint8Array; dkls19Lib: Dkls19WasmLib; cloudURL: string; - networkManager: NetworkManager; - serializer: MPCKeyringSerializer; + relayerURL: string; initRole: InitRole; }; export type ThresholdKeyId = string; -export type MPCKeyringSerializer = { - networkIdentityToJSON: (identity: NetworkIdentity) => Json; - networkIdentityFromJSON: (identity: Json) => NetworkIdentity; - thresholdKeyToJSON: (key: ThresholdKey) => Json; - thresholdKeyFromJSON: (key: Json) => ThresholdKey; +type JsonSerializer = { + toJson: (value: Value) => Json; + fromJson: (value: Json) => Value; }; export type InitRole = 'initiator' | 'responder'; + +export type MPCKeyringSerializer = { + thresholdKey: JsonSerializer; + networkIdentity: JsonSerializer; +}; diff --git a/yarn.lock b/yarn.lock index 0424f7cb8..01b134ace 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1758,7 +1758,9 @@ __metadata: "@metamask/keyring-utils": "workspace:^" "@metamask/mfa-wallet-cl24-lib": "npm:^0.0.0" "@metamask/mfa-wallet-dkls19-lib": "npm:^0.0.0" + "@metamask/mfa-wallet-e2ee": "npm:^0.0.0" "@metamask/mfa-wallet-interface": "npm:^0.0.0" + "@metamask/mfa-wallet-network": "npm:^0.0.0" "@metamask/mfa-wallet-util": "npm:^0.0.0" "@metamask/tss-dkls19-lib": "npm:^0.0.0" "@metamask/utils": "npm:^11.1.0" @@ -2189,12 +2191,12 @@ __metadata: "@metamask/mfa-wallet-cl24-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": version: 0.0.0 - resolution: "@metamask/mfa-wallet-cl24-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz::hash=8ee1d0&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + resolution: "@metamask/mfa-wallet-cl24-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz::hash=4ec4ea&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." dependencies: "@msgpack/msgpack": "npm:^3.1.2" "@noble/curves": "npm:^1.9.2" "@noble/hashes": "npm:^1.8.0" - checksum: 10/ba0a676cdb75fa4aed3cefb12c28b36e29ee384f2664190cc9898af3e61b83b4c9cab815631fe18d06996c9b3fafefd35967d93dac98689f9fcb72c9f0b499a6 + checksum: 10/cb58aed5eca46f0fb25fc66e83d5609818e2fb9c6a1b6766ba6d9e4bd49becc5eade784bf4d28a04c3967b70633be54f33764bf34dae81e1f7684dcf64fb4252 languageName: node linkType: hard @@ -2208,6 +2210,18 @@ __metadata: languageName: node linkType: hard +"@metamask/mfa-wallet-e2ee@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-e2ee-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": + version: 0.0.0 + resolution: "@metamask/mfa-wallet-e2ee@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-e2ee-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-e2ee-0.0.0.tgz::hash=7e9304&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + dependencies: + "@msgpack/msgpack": "npm:^3.1.2" + "@noble/ciphers": "npm:^1.3.0" + "@noble/curves": "npm:^1.9.2" + "@noble/hashes": "npm:^1.8.0" + checksum: 10/13ccf57849813e2ae7622d7d8cd056836571dbaccd39a52c9d729170c3933689a348b330af5dfe28a125fb22407653e847c458ba5edec8c1d7e07659d91c6065 + languageName: node + linkType: hard + "@metamask/mfa-wallet-interface@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": version: 0.0.0 resolution: "@metamask/mfa-wallet-interface@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz::hash=7e0392&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." @@ -2215,6 +2229,18 @@ __metadata: languageName: node linkType: hard +"@metamask/mfa-wallet-network@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": + version: 0.0.0 + resolution: "@metamask/mfa-wallet-network@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz::hash=a24145&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + dependencies: + "@metamask/mfa-wallet-e2ee": "npm:^0.0.0" + "@msgpack/msgpack": "npm:^3.1.2" + "@noble/hashes": "npm:^1.8.0" + centrifuge: "npm:^5.5.2" + checksum: 10/48143c67a62c02dd890ef67fecba94a6b45eb13aaa2f5ec360e2b52d4c12ec95b306fce96ae99ed00ab11d62d001006a8d399d99a6f25758e3b1b2b74abc2d2b + languageName: node + linkType: hard + "@metamask/mfa-wallet-util@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-util-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": version: 0.0.0 resolution: "@metamask/mfa-wallet-util@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-util-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-util-0.0.0.tgz::hash=bf9341&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." @@ -2502,8 +2528,8 @@ __metadata: "@metamask/tss-dkls19-lib@file:./packages/keyring-eth-mpc/metamask-tss-dkls19-lib-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": version: 0.0.0 - resolution: "@metamask/tss-dkls19-lib@file:./packages/keyring-eth-mpc/metamask-tss-dkls19-lib-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-tss-dkls19-lib-0.0.0.tgz::hash=c28abe&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." - checksum: 10/782d65c544eafc04ea32d5c403f0e4afe69e4e828728cc92452f32db46b84ac2d3e989f7d555d00f6e70482d930acca64d83dc64739cfe3e79bba33e0b7a474b + resolution: "@metamask/tss-dkls19-lib@file:./packages/keyring-eth-mpc/metamask-tss-dkls19-lib-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-tss-dkls19-lib-0.0.0.tgz::hash=083b6a&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + checksum: 10/647a9337ad540994f1c0d2dcdfb00b559075c668a2e0ed93433210e407a1dabf28ca0b9767ab44c90e07ad7eaa7a9c5633de85e68695abdfd2a2f26ff36ac65c languageName: node linkType: hard @@ -2572,7 +2598,7 @@ __metadata: languageName: node linkType: hard -"@noble/ciphers@npm:1.3.0": +"@noble/ciphers@npm:1.3.0, @noble/ciphers@npm:^1.3.0": version: 1.3.0 resolution: "@noble/ciphers@npm:1.3.0" checksum: 10/051660051e3e9e2ca5fb9dece2885532b56b7e62946f89afa7284a0fb8bc02e2bd1c06554dba68162ff42d295b54026456084198610f63c296873b2f1cd7a586 @@ -5602,6 +5628,16 @@ __metadata: languageName: node linkType: hard +"centrifuge@npm:^5.5.2": + version: 5.5.3 + resolution: "centrifuge@npm:5.5.3" + dependencies: + events: "npm:^3.3.0" + protobufjs: "npm:^7.2.5" + checksum: 10/2074551adc0ea421d8d4880236b956d6fde0d652c3da2d82124b49c1d6659817069bb58f8f8525794382915d5bff3310592bb023a4afa35fc3ca2b69158ba412 + languageName: node + linkType: hard + "chalk-template@npm:1.1.0": version: 1.1.0 resolution: "chalk-template@npm:1.1.0" @@ -10373,6 +10409,26 @@ __metadata: languageName: node linkType: hard +"protobufjs@npm:^7.2.5": + version: 7.5.4 + resolution: "protobufjs@npm:7.5.4" + dependencies: + "@protobufjs/aspromise": "npm:^1.1.2" + "@protobufjs/base64": "npm:^1.1.2" + "@protobufjs/codegen": "npm:^2.0.4" + "@protobufjs/eventemitter": "npm:^1.1.0" + "@protobufjs/fetch": "npm:^1.1.0" + "@protobufjs/float": "npm:^1.0.2" + "@protobufjs/inquire": "npm:^1.1.0" + "@protobufjs/path": "npm:^1.1.2" + "@protobufjs/pool": "npm:^1.1.0" + "@protobufjs/utf8": "npm:^1.1.0" + "@types/node": "npm:>=13.7.0" + long: "npm:^5.0.0" + checksum: 10/88d677bb6f11a2ecec63fdd053dfe6d31120844d04e865efa9c8fbe0674cd077d6624ecfdf014018a20dcb114ae2a59c1b21966dd8073e920650c71370966439 + languageName: node + linkType: hard + "proxy-from-env@npm:^1.1.0": version: 1.1.0 resolution: "proxy-from-env@npm:1.1.0" From 33a9d553e135de883c3c5c24d575e9fe74d796e7 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Mon, 26 Jan 2026 10:40:46 +0100 Subject: [PATCH 23/44] use createScopedSessionId --- packages/keyring-eth-mpc/src/cloud.ts | 12 ++++++------ packages/keyring-eth-mpc/src/mpc-keyring.ts | 12 +++++++----- packages/keyring-eth-mpc/src/network.ts | 12 ------------ yarn.lock | 4 ++-- 4 files changed, 15 insertions(+), 25 deletions(-) diff --git a/packages/keyring-eth-mpc/src/cloud.ts b/packages/keyring-eth-mpc/src/cloud.ts index f6412ea9a..579854226 100644 --- a/packages/keyring-eth-mpc/src/cloud.ts +++ b/packages/keyring-eth-mpc/src/cloud.ts @@ -5,14 +5,14 @@ import type { PartyId } from '@metamask/mfa-wallet-interface'; * * @param opts - The options for the cloud keygen session * @param opts.localId - The local ID of the device - * @param opts.sessionId - The ID of the session + * @param opts.sessionNonce - The nonce of the session * @param opts.baseURL - The base URL of the cloud service * @returns The cloud ID of the device */ export async function initCloudKeyGen(opts: { baseURL: string; localId: PartyId; - sessionId: string; + sessionNonce: string; }): Promise<{ cloudId: string }> { const response = await fetch(`${opts.baseURL}/createKey`, { method: 'POST', @@ -21,7 +21,7 @@ export async function initCloudKeyGen(opts: { }, body: JSON.stringify({ localId: opts.localId, - sessionId: opts.sessionId, + sessionNonce: opts.sessionNonce, }), }); @@ -41,13 +41,13 @@ export async function initCloudKeyGen(opts: { * @param opts - The options for the cloud sign session * @param opts.baseURL - The base URL of the cloud service * @param opts.keyId - The ID of the key - * @param opts.sessionId - The ID of the session + * @param opts.sessionNonce - The nonce of the session * @param opts.message - The message to sign */ export async function initCloudSign(opts: { baseURL: string; keyId: string; - sessionId: string; + sessionNonce: string; message: Uint8Array; }): Promise { const response = await fetch(`${opts.baseURL}/sign`, { @@ -57,7 +57,7 @@ export async function initCloudSign(opts: { }, body: JSON.stringify({ keyId: opts.keyId, - sessionId: opts.sessionId, + sessionNonce: opts.sessionNonce, message: opts.message, }), }); diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index f8cbe3a43..b278f1a71 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -21,12 +21,12 @@ import type { MfaNetworkIdentity } from '@metamask/mfa-wallet-network'; import { MfaNetworkIdentitySerializer, MfaNetworkManager, + createScopedSessionId, } from '@metamask/mfa-wallet-network'; import type { WasmLib as Dkls19WasmLib } from '@metamask/tss-dkls19-lib'; import { bytesToHex, hexToBytes, type Hex, type Json } from '@metamask/utils'; import { initCloudKeyGen, initCloudSign } from './cloud'; -import { generateSessionId } from './network'; import type { InitRole, MPCKeyringOpts, @@ -152,15 +152,16 @@ export class MPCKeyring implements Keyring { const net = this.#networkManager; const networkIdentity = await net.createIdentity(); const localId = networkIdentity.partyId; - const sessionId = generateSessionId(this.#rng); + const sessionNonce = bytesToHex(this.#rng.generateRandomBytes(32)); const { cloudId } = await initCloudKeyGen({ localId, - sessionId, + sessionNonce, baseURL: this.#cloudURL, }); const custodians = [localId, cloudId]; const threshold = 2; + const sessionId = createScopedSessionId(custodians, sessionNonce); const networkSession = await net.createSession(networkIdentity, sessionId); this.#networkIdentity = networkIdentity; @@ -294,12 +295,13 @@ export class MPCKeyring implements Keyring { throw new Error(`account ${address} not found`); } - const sessionId = generateSessionId(this.#rng); + const sessionNonce = bytesToHex(this.#rng.generateRandomBytes(32)); + const sessionId = createScopedSessionId(custodians, sessionNonce); const message = hash; await initCloudSign({ keyId: this.#keyId, - sessionId, + sessionNonce, message, baseURL: this.#cloudURL, }); diff --git a/packages/keyring-eth-mpc/src/network.ts b/packages/keyring-eth-mpc/src/network.ts index 4fb6e18ab..42deb202e 100644 --- a/packages/keyring-eth-mpc/src/network.ts +++ b/packages/keyring-eth-mpc/src/network.ts @@ -1,10 +1,8 @@ import type { NetworkSession, PartyId, - RandomNumberGenerator, SessionId, } from '@metamask/mfa-wallet-interface'; -import { bytesToHex } from '@metamask/utils'; export type NetworkIdentity = { partyId: PartyId; @@ -18,13 +16,3 @@ export type NetworkManager = { sessionId: SessionId, ) => Promise; }; - -/** - * Generates a random session ID. - * - * @param rng - The random number generator to use. - * @returns The session ID. - */ -export function generateSessionId(rng: RandomNumberGenerator): SessionId { - return bytesToHex(rng.generateRandomBytes(32)); -} diff --git a/yarn.lock b/yarn.lock index 01b134ace..5f0937447 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2231,13 +2231,13 @@ __metadata: "@metamask/mfa-wallet-network@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": version: 0.0.0 - resolution: "@metamask/mfa-wallet-network@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz::hash=a24145&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + resolution: "@metamask/mfa-wallet-network@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz::hash=437af8&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." dependencies: "@metamask/mfa-wallet-e2ee": "npm:^0.0.0" "@msgpack/msgpack": "npm:^3.1.2" "@noble/hashes": "npm:^1.8.0" centrifuge: "npm:^5.5.2" - checksum: 10/48143c67a62c02dd890ef67fecba94a6b45eb13aaa2f5ec360e2b52d4c12ec95b306fce96ae99ed00ab11d62d001006a8d399d99a6f25758e3b1b2b74abc2d2b + checksum: 10/68f59188d011bdc719d9d5be0be179e153e08719c6241d862c19315cce4bc5ed865af56076fc14636f5a15e92085d55e7a803c22c8b798dd19d2f1fe82383332 languageName: node linkType: hard From 6c34914cc56c7f1c866582d34222a95df56b6d4c Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Mon, 26 Jan 2026 16:10:25 +0100 Subject: [PATCH 24/44] cleanup session --- packages/keyring-eth-mpc/package.json | 3 ++- packages/keyring-eth-mpc/src/mpc-keyring.ts | 4 ++++ yarn.lock | 12 ++++++------ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/keyring-eth-mpc/package.json b/packages/keyring-eth-mpc/package.json index 03e5d7166..dd319fd15 100644 --- a/packages/keyring-eth-mpc/package.json +++ b/packages/keyring-eth-mpc/package.json @@ -74,7 +74,8 @@ }, "lavamoat": { "allowScripts": { - "@lavamoat/preinstall-always-fail": false + "@lavamoat/preinstall-always-fail": false, + "@metamask/mfa-wallet-network>centrifuge>protobufjs": false } } } diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index b278f1a71..6283feb9d 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -171,6 +171,8 @@ export class MPCKeyring implements Keyring { networkSession, }); this.#keyId = networkSession.sessionId; + + await networkSession.disconnect(); } /** @@ -319,6 +321,8 @@ export class MPCKeyring implements Keyring { networkSession, }); + await networkSession.disconnect(); + return toEthSig(signature, hash, publicKey); } } diff --git a/yarn.lock b/yarn.lock index 5f0937447..25ac2f2b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2202,11 +2202,11 @@ __metadata: "@metamask/mfa-wallet-dkls19-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": version: 0.0.0 - resolution: "@metamask/mfa-wallet-dkls19-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz::hash=adb1b2&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + resolution: "@metamask/mfa-wallet-dkls19-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz::hash=aef3b3&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." dependencies: "@metamask/mfa-wallet-util": "npm:^0.0.0" "@noble/curves": "npm:^1.9.2" - checksum: 10/236c5bc3d8f231109d93eecce78de1cbcb6e95d3eae9c42cf43befd0b12584f5ce5b415b2c930d108c3b047d9f505768bb06bdd62900406a6404030d334ddfd1 + checksum: 10/bd9c8f0fd1ff8afe8595c93859a298545a2625065229d693794f0f6bf2b1852816353027b899a88082032ffc6e09e435620b0c3c68ad74ae7e517e3f01a15489 languageName: node linkType: hard @@ -2224,8 +2224,8 @@ __metadata: "@metamask/mfa-wallet-interface@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": version: 0.0.0 - resolution: "@metamask/mfa-wallet-interface@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz::hash=7e0392&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." - checksum: 10/663ff097ce6b8fafd02412eb0e51e953ee816edc7db94484adb908ba542ed31d20200176979d5818be1dd6400294ae59df13a2255c3b97649dae6ca029a3a450 + resolution: "@metamask/mfa-wallet-interface@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz::hash=f21c99&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + checksum: 10/eba9cab297a83b7deb50027bb7321d394beb18ee806cfc0f103e00c0ebca6ed92364c80aeace7247c546afc0cfb176182b09d230823fd574a87e090e095a4131 languageName: node linkType: hard @@ -2243,8 +2243,8 @@ __metadata: "@metamask/mfa-wallet-util@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-util-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": version: 0.0.0 - resolution: "@metamask/mfa-wallet-util@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-util-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-util-0.0.0.tgz::hash=bf9341&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." - checksum: 10/63bc3760c49180a504b1351762ae903aaa064e8af6ba3a6afcd2a33e18f1408e3aa0bb9533a34612617a459f0d190d3d02d3876312750c4c943f8eb78b7b2702 + resolution: "@metamask/mfa-wallet-util@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-util-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-util-0.0.0.tgz::hash=24be98&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + checksum: 10/aef5061019305e5a112753396aad5d05390dc877eb22d8e74df58c5b6180564f49a85e5bac573c91397b54081e4c30cc8c6b6d593b6c2319f4473a19bd243bcd languageName: node linkType: hard From aa82621b6a7b064c19a5377cd333a161bac79cfc Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Mon, 26 Jan 2026 17:47:09 +0100 Subject: [PATCH 25/44] revise cloud api --- packages/keyring-eth-mpc/src/cloud.ts | 14 ++++++++++---- packages/keyring-eth-mpc/src/mpc-keyring.ts | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/keyring-eth-mpc/src/cloud.ts b/packages/keyring-eth-mpc/src/cloud.ts index 579854226..f53fd6ba0 100644 --- a/packages/keyring-eth-mpc/src/cloud.ts +++ b/packages/keyring-eth-mpc/src/cloud.ts @@ -1,4 +1,5 @@ import type { PartyId } from '@metamask/mfa-wallet-interface'; +import { bytesToBase64 } from '@metamask/utils'; /** * Initialize a cloud keygen session @@ -20,8 +21,9 @@ export async function initCloudKeyGen(opts: { 'Content-Type': 'application/json', }, body: JSON.stringify({ - localId: opts.localId, - sessionNonce: opts.sessionNonce, + custodianId: opts.localId, + nonce: opts.sessionNonce, + keyType: 'secp256k1', }), }); @@ -41,12 +43,14 @@ export async function initCloudKeyGen(opts: { * @param opts - The options for the cloud sign session * @param opts.baseURL - The base URL of the cloud service * @param opts.keyId - The ID of the key + * @param opts.localId - The local ID of the device * @param opts.sessionNonce - The nonce of the session * @param opts.message - The message to sign */ export async function initCloudSign(opts: { baseURL: string; keyId: string; + localId: PartyId; sessionNonce: string; message: Uint8Array; }): Promise { @@ -57,8 +61,10 @@ export async function initCloudSign(opts: { }, body: JSON.stringify({ keyId: opts.keyId, - sessionNonce: opts.sessionNonce, - message: opts.message, + custodianId: opts.localId, + nonce: opts.sessionNonce, + message: bytesToBase64(opts.message), + protocol: 'dkls19', }), }); diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index 6283feb9d..493095e68 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -303,6 +303,7 @@ export class MPCKeyring implements Keyring { await initCloudSign({ keyId: this.#keyId, + localId: this.#networkIdentity.partyId, sessionNonce, message, baseURL: this.#cloudURL, From dfa4ad3ca841df9171383a1500b26ce33a9aacd7 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Wed, 28 Jan 2026 00:28:04 +0100 Subject: [PATCH 26/44] compatibility --- packages/keyring-eth-mpc/src/cloud.ts | 8 ++++++-- packages/keyring-eth-mpc/src/mpc-keyring.ts | 20 ++++++++++++++++---- packages/keyring-eth-mpc/src/types.ts | 2 ++ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/packages/keyring-eth-mpc/src/cloud.ts b/packages/keyring-eth-mpc/src/cloud.ts index f53fd6ba0..9c05b33cf 100644 --- a/packages/keyring-eth-mpc/src/cloud.ts +++ b/packages/keyring-eth-mpc/src/cloud.ts @@ -15,7 +15,7 @@ export async function initCloudKeyGen(opts: { localId: PartyId; sessionNonce: string; }): Promise<{ cloudId: string }> { - const response = await fetch(`${opts.baseURL}/createKey`, { + const response = await fetch(`${opts.baseURL}/create-key`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -24,6 +24,7 @@ export async function initCloudKeyGen(opts: { custodianId: opts.localId, nonce: opts.sessionNonce, keyType: 'secp256k1', + tssVerifierId: opts.localId, // TODO: remove this once we have updated API }), }); @@ -34,7 +35,7 @@ export async function initCloudKeyGen(opts: { } const data = await response.json(); - return { cloudId: data.cloudId }; + return { cloudId: data.serverCustodianId }; } /** @@ -65,6 +66,9 @@ export async function initCloudSign(opts: { nonce: opts.sessionNonce, message: bytesToBase64(opts.message), protocol: 'dkls19', + tssVerifierId: opts.localId, // TODO: remove this once we have updated API + kid: opts.keyId, // TODO: remove this once we have updated API + keyType: 'secp256k1', // TODO: remove this once we have updated API }), }); diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index 493095e68..21961bdb7 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -1,5 +1,5 @@ import type { TypedTransaction } from '@ethereumjs/tx'; -import { hashPersonalMessage } from '@ethereumjs/util'; +import { hashPersonalMessage, pubToAddress } from '@ethereumjs/util'; import type { TypedDataV1, TypedMessage, @@ -40,7 +40,6 @@ import { parseInitRole, parseSignedTypedDataVersion, parseThresholdKeyId, - publicToAddressHex, toEthSig, } from './util'; @@ -91,6 +90,8 @@ export class MPCKeyring implements Keyring { return array; }, }, + ...(opts.getToken && { getToken: opts.getToken }), + ...(opts.webSocket === undefined ? {} : { websocket: opts.webSocket }), }); } @@ -147,6 +148,10 @@ export class MPCKeyring implements Keyring { } async init(): Promise { + if (this.#keyShare && this.#networkIdentity && this.#keyId) { + return; + } + const dkm = new CL24DKM(secp256k1Curve, this.#rng); const net = this.#networkManager; @@ -196,7 +201,7 @@ export class MPCKeyring implements Keyring { return []; } - const addr = publicToAddressHex(this.#keyShare.publicKey); + const addr = this.#address(); return [addr]; } @@ -292,7 +297,7 @@ export class MPCKeyring implements Keyring { const { custodians, publicKey } = this.#keyShare; - const addr = publicToAddressHex(publicKey); + const addr = this.#address(); if (!equalAddresses(address, addr)) { throw new Error(`account ${address} not found`); } @@ -326,4 +331,11 @@ export class MPCKeyring implements Keyring { return toEthSig(signature, hash, publicKey); } + + #address(): Hex { + if (!this.#keyShare) { + throw new Error(`keyshare not initialized`); + } + return bytesToHex(pubToAddress(this.#keyShare.publicKey, true)); + } } diff --git a/packages/keyring-eth-mpc/src/types.ts b/packages/keyring-eth-mpc/src/types.ts index ddc141b0c..7bd0257bc 100644 --- a/packages/keyring-eth-mpc/src/types.ts +++ b/packages/keyring-eth-mpc/src/types.ts @@ -9,6 +9,8 @@ export type MPCKeyringOpts = { cloudURL: string; relayerURL: string; initRole: InitRole; + getToken?: (partyId: string) => string; + webSocket?: unknown; }; export type ThresholdKeyId = string; From cad523f7cd05e748131aeea25bc9acdaed6cb193 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Mon, 2 Feb 2026 15:05:18 +0100 Subject: [PATCH 27/44] use publicKeyToAddressHex in toEthSig --- packages/keyring-eth-mpc/src/mpc-keyring.ts | 5 +++-- packages/keyring-eth-mpc/src/util.ts | 13 ++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index 21961bdb7..b3b5b4539 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -1,5 +1,5 @@ import type { TypedTransaction } from '@ethereumjs/tx'; -import { hashPersonalMessage, pubToAddress } from '@ethereumjs/util'; +import { hashPersonalMessage } from '@ethereumjs/util'; import type { TypedDataV1, TypedMessage, @@ -40,6 +40,7 @@ import { parseInitRole, parseSignedTypedDataVersion, parseThresholdKeyId, + publicKeyToAddressHex, toEthSig, } from './util'; @@ -336,6 +337,6 @@ export class MPCKeyring implements Keyring { if (!this.#keyShare) { throw new Error(`keyshare not initialized`); } - return bytesToHex(pubToAddress(this.#keyShare.publicKey, true)); + return publicKeyToAddressHex(this.#keyShare.publicKey); } } diff --git a/packages/keyring-eth-mpc/src/util.ts b/packages/keyring-eth-mpc/src/util.ts index f880d895c..117b37c79 100644 --- a/packages/keyring-eth-mpc/src/util.ts +++ b/packages/keyring-eth-mpc/src/util.ts @@ -3,6 +3,7 @@ import { concatBytes, ecrecover, publicToAddress, + pubToAddress, } from '@ethereumjs/util'; import type { MessageTypes, @@ -101,7 +102,7 @@ export function toEthSig( // `v` directly and skip the costly ecrecover operation. // --------------------------------------------------------------------------- - const expectedAddr = publicToAddressHex(pubKey); + const expectedAddr = publicKeyToAddressHex(pubKey); const checkParity = (parity: bigint): boolean => { try { @@ -216,3 +217,13 @@ export function parseThresholdKeyId(keyId: Json): ThresholdKeyId { } return keyId; } + +/** + * Convert a public key to an address. + * + * @param publicKey - The public key to convert. + * @returns The address. + */ +export function publicKeyToAddressHex(publicKey: Uint8Array): Hex { + return bytesToHex(pubToAddress(publicKey, true)); +} From d69bd18df15665429ba8c4366942a73f67713d03 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Mon, 2 Feb 2026 15:15:52 +0100 Subject: [PATCH 28/44] update API --- packages/keyring-eth-mpc/src/cloud.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/keyring-eth-mpc/src/cloud.ts b/packages/keyring-eth-mpc/src/cloud.ts index 9c05b33cf..dded19c84 100644 --- a/packages/keyring-eth-mpc/src/cloud.ts +++ b/packages/keyring-eth-mpc/src/cloud.ts @@ -23,8 +23,8 @@ export async function initCloudKeyGen(opts: { body: JSON.stringify({ custodianId: opts.localId, nonce: opts.sessionNonce, - keyType: 'secp256k1', - tssVerifierId: opts.localId, // TODO: remove this once we have updated API + protocol: 'cl24-secp256k1', + // verifierIds: [], // TODO add this once we get verifier ids from frontend }), }); @@ -66,9 +66,7 @@ export async function initCloudSign(opts: { nonce: opts.sessionNonce, message: bytesToBase64(opts.message), protocol: 'dkls19', - tssVerifierId: opts.localId, // TODO: remove this once we have updated API - kid: opts.keyId, // TODO: remove this once we have updated API - keyType: 'secp256k1', // TODO: remove this once we have updated API + // token: 'TODO', // TODO add this once we get token from frontend }), }); From 0ee93a1488316a16bed469a7a52e7b9f1a9818d2 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Thu, 5 Feb 2026 22:28:13 +0100 Subject: [PATCH 29/44] add support for verifiers --- packages/keyring-eth-mpc/TODO.md | 2 +- packages/keyring-eth-mpc/src/cloud.ts | 8 +- packages/keyring-eth-mpc/src/mpc-keyring.ts | 95 +++++++++++++++++---- packages/keyring-eth-mpc/src/types.ts | 6 +- packages/keyring-eth-mpc/src/util.ts | 53 +++++++++--- yarn.lock | 16 ++-- 6 files changed, 135 insertions(+), 45 deletions(-) diff --git a/packages/keyring-eth-mpc/TODO.md b/packages/keyring-eth-mpc/TODO.md index 277064fef..94be42dae 100644 --- a/packages/keyring-eth-mpc/TODO.md +++ b/packages/keyring-eth-mpc/TODO.md @@ -1,4 +1,4 @@ # TODO - Check for "not implemented" and "TODO" in code -- Add cloud factor authentication +- Add support for managing verifiers: add/remove diff --git a/packages/keyring-eth-mpc/src/cloud.ts b/packages/keyring-eth-mpc/src/cloud.ts index dded19c84..b846d5465 100644 --- a/packages/keyring-eth-mpc/src/cloud.ts +++ b/packages/keyring-eth-mpc/src/cloud.ts @@ -8,12 +8,14 @@ import { bytesToBase64 } from '@metamask/utils'; * @param opts.localId - The local ID of the device * @param opts.sessionNonce - The nonce of the session * @param opts.baseURL - The base URL of the cloud service + * @param opts.verifierIds - The IDs of the verifiers * @returns The cloud ID of the device */ export async function initCloudKeyGen(opts: { baseURL: string; localId: PartyId; sessionNonce: string; + verifierIds: string[]; }): Promise<{ cloudId: string }> { const response = await fetch(`${opts.baseURL}/create-key`, { method: 'POST', @@ -24,7 +26,7 @@ export async function initCloudKeyGen(opts: { custodianId: opts.localId, nonce: opts.sessionNonce, protocol: 'cl24-secp256k1', - // verifierIds: [], // TODO add this once we get verifier ids from frontend + verifierIds: opts.verifierIds, }), }); @@ -47,6 +49,7 @@ export async function initCloudKeyGen(opts: { * @param opts.localId - The local ID of the device * @param opts.sessionNonce - The nonce of the session * @param opts.message - The message to sign + * @param opts.token - The token for the verifier */ export async function initCloudSign(opts: { baseURL: string; @@ -54,6 +57,7 @@ export async function initCloudSign(opts: { localId: PartyId; sessionNonce: string; message: Uint8Array; + token: string; }): Promise { const response = await fetch(`${opts.baseURL}/sign`, { method: 'POST', @@ -66,7 +70,7 @@ export async function initCloudSign(opts: { nonce: opts.sessionNonce, message: bytesToBase64(opts.message), protocol: 'dkls19', - // token: 'TODO', // TODO add this once we get token from frontend + token: opts.token, }), }); diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index b3b5b4539..6f669e7e4 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -28,7 +28,6 @@ import { bytesToHex, hexToBytes, type Hex, type Json } from '@metamask/utils'; import { initCloudKeyGen, initCloudSign } from './cloud'; import type { - InitRole, MPCKeyringOpts, MPCKeyringSerializer, ThresholdKeyId, @@ -37,9 +36,10 @@ import { equalAddresses, getSignedTypedDataHash, parseEthSig, - parseInitRole, + parseSelectedVerifierIndex, parseSignedTypedDataVersion, parseThresholdKeyId, + parseVerifierIds, publicKeyToAddressHex, toEthSig, } from './util'; @@ -59,25 +59,28 @@ export class MPCKeyring implements Keyring { readonly #dkls19Lib: Dkls19WasmLib; - #initRole: InitRole; - #networkIdentity?: MfaNetworkIdentity; #keyShare?: ThresholdKey; #keyId?: ThresholdKeyId; + #verifierIds?: string[]; + + #selectedVerifierIndex?: number; + readonly #cloudURL: string; readonly #serializer: MPCKeyringSerializer; + readonly #getVerifierToken: (verifierId: string) => Promise; + constructor(opts: MPCKeyringOpts) { this.#rng = { generateRandomBytes: opts.getRandomBytes, }; this.#dkls19Lib = opts.dkls19Lib; this.#cloudURL = opts.cloudURL; - this.#initRole = opts.initRole; this.#serializer = { thresholdKey: new CL24ThresholdKeySerializer(), networkIdentity: new MfaNetworkIdentitySerializer(), @@ -91,9 +94,12 @@ export class MPCKeyring implements Keyring { return array; }, }, - ...(opts.getToken && { getToken: opts.getToken }), + ...(opts.getTransportToken && { + getToken: opts.getTransportToken, + }), ...(opts.webSocket === undefined ? {} : { websocket: opts.webSocket }), }); + this.#getVerifierToken = opts.getVerifierToken; } /** @@ -102,9 +108,7 @@ export class MPCKeyring implements Keyring { * @returns The serialized state of the keyring. */ async serialize(): Promise { - const state: Json = { - initRole: this.#initRole, - }; + const state: Json = {}; if (this.#networkIdentity) { state.networkIdentity = this.#serializer.networkIdentity.toJson( this.#networkIdentity, @@ -116,6 +120,12 @@ export class MPCKeyring implements Keyring { if (this.#keyId) { state.keyId = this.#keyId; } + if (this.#verifierIds) { + state.verifierIds = this.#verifierIds; + } + if (this.#selectedVerifierIndex) { + state.selectedVerifierIndex = this.#selectedVerifierIndex; + } return state; } @@ -129,10 +139,6 @@ export class MPCKeyring implements Keyring { throw new Error('Invalid state'); } - if ('initRole' in state) { - this.#initRole = parseInitRole(state.initRole); - } - if ('networkIdentity' in state) { this.#networkIdentity = this.#serializer.networkIdentity.fromJson( state.networkIdentity, @@ -146,11 +152,54 @@ export class MPCKeyring implements Keyring { if ('keyId' in state) { this.#keyId = parseThresholdKeyId(state.keyId); } + + if ('verifierIds' in state) { + this.#verifierIds = parseVerifierIds(state.verifierIds); + } + + if ('selectedVerifierIndex' in state) { + this.#selectedVerifierIndex = parseSelectedVerifierIndex( + state.selectedVerifierIndex, + ); + } + } + + getVerifierIds(): string[] { + if (!this.#verifierIds) { + throw new Error('Verifier IDs not initialized'); + } + return this.#verifierIds; + } + + selectVerifier(verifierIndex: number): string { + if (!this.#verifierIds) { + throw new Error('Verifier IDs not initialized'); + } else if (verifierIndex < 0 || verifierIndex >= this.#verifierIds.length) { + throw new Error('Invalid verifier index'); + } + this.#selectedVerifierIndex = verifierIndex; + return this.#verifierIds[verifierIndex] as string; + } + + getSelectedVerifierId(): string { + if (!this.#verifierIds) { + throw new Error('Verifier IDs not initialized'); + } else if (!this.#selectedVerifierIndex) { + throw new Error('Selected verifier index not initialized'); + } else if ( + this.#selectedVerifierIndex < 0 || + this.#selectedVerifierIndex >= this.#verifierIds.length + ) { + throw new Error('Invalid selected verifier index'); + } + return this.#verifierIds[this.#selectedVerifierIndex] as string; } - async init(): Promise { - if (this.#keyShare && this.#networkIdentity && this.#keyId) { - return; + async setup({ verifierIds }: { verifierIds: string[] }): Promise { + if (this.#keyShare || this.#networkIdentity || this.#keyId) { + throw new Error('Keyring already setup'); + } else if (verifierIds.length < 1) { + throw new Error('At least one verifier ID is required'); } const dkm = new CL24DKM(secp256k1Curve, this.#rng); @@ -163,6 +212,7 @@ export class MPCKeyring implements Keyring { localId, sessionNonce, baseURL: this.#cloudURL, + verifierIds, }); const custodians = [localId, cloudId]; const threshold = 2; @@ -177,6 +227,8 @@ export class MPCKeyring implements Keyring { networkSession, }); this.#keyId = networkSession.sessionId; + this.#verifierIds = verifierIds; + this.#selectedVerifierIndex = 0; await networkSession.disconnect(); } @@ -294,6 +346,15 @@ export class MPCKeyring implements Keyring { throw new Error(`network credentials not initialized`); } else if (!this.#keyId) { throw new Error(`key id not initialized`); + } else if (!this.#verifierIds) { + throw new Error('Verifier IDs not initialized'); + } else if (!this.#selectedVerifierIndex) { + throw new Error('Selected verifier index not initialized'); + } + + const verifierId = this.#verifierIds[this.#selectedVerifierIndex]; + if (!verifierId) { + throw new Error('Selected verifier index out of bounds'); } const { custodians, publicKey } = this.#keyShare; @@ -306,6 +367,7 @@ export class MPCKeyring implements Keyring { const sessionNonce = bytesToHex(this.#rng.generateRandomBytes(32)); const sessionId = createScopedSessionId(custodians, sessionNonce); const message = hash; + const token = await this.#getVerifierToken(verifierId); await initCloudSign({ keyId: this.#keyId, @@ -313,6 +375,7 @@ export class MPCKeyring implements Keyring { sessionNonce, message, baseURL: this.#cloudURL, + token, }); const networkSession = await this.#networkManager.createSession( diff --git a/packages/keyring-eth-mpc/src/types.ts b/packages/keyring-eth-mpc/src/types.ts index 7bd0257bc..b66bd13f8 100644 --- a/packages/keyring-eth-mpc/src/types.ts +++ b/packages/keyring-eth-mpc/src/types.ts @@ -8,8 +8,8 @@ export type MPCKeyringOpts = { dkls19Lib: Dkls19WasmLib; cloudURL: string; relayerURL: string; - initRole: InitRole; - getToken?: (partyId: string) => string; + getTransportToken?: () => Promise; + getVerifierToken: (verifierId: string) => Promise; webSocket?: unknown; }; @@ -20,8 +20,6 @@ type JsonSerializer = { fromJson: (value: Json) => Value; }; -export type InitRole = 'initiator' | 'responder'; - export type MPCKeyringSerializer = { thresholdKey: JsonSerializer; networkIdentity: JsonSerializer; diff --git a/packages/keyring-eth-mpc/src/util.ts b/packages/keyring-eth-mpc/src/util.ts index 117b37c79..cc4e83bcc 100644 --- a/packages/keyring-eth-mpc/src/util.ts +++ b/packages/keyring-eth-mpc/src/util.ts @@ -19,7 +19,7 @@ import { import type { Hex, Json } from '@metamask/utils'; import { add0x, assert, bytesToHex, hexToBytes } from '@metamask/utils'; -import type { InitRole, ThresholdKeyId } from './types'; +import type { ThresholdKeyId } from './types'; const SECP256K1_N = BigInt( '0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', @@ -192,19 +192,6 @@ export function getSignedTypedDataHash< return new Uint8Array(hash); } -/** - * Parse the init role from a JSON object. - * - * @param initRole - The init role to parse. - * @returns The parsed init role. - */ -export function parseInitRole(initRole: Json): InitRole { - if (initRole !== 'initiator' && initRole !== 'responder') { - throw new Error('Invalid init role'); - } - return initRole; -} - /** * Parse the key ID from a JSON object. * @@ -218,6 +205,44 @@ export function parseThresholdKeyId(keyId: Json): ThresholdKeyId { return keyId; } +/** + * Parse verifier IDs from a JSON object. + * + * @param verifierIds - The verifier IDs to parse. + * @returns The parsed verifier IDs. + */ +export function parseVerifierIds(verifierIds: Json): string[] { + if (!Array.isArray(verifierIds)) { + throw new Error('Invalid verifier IDs: expected an array'); + } + for (const id of verifierIds) { + if (typeof id !== 'string') { + throw new Error('Invalid verifier ID: expected a string'); + } + } + return verifierIds as string[]; +} + +/** + * Parse the selected verifier index from a JSON object. + * + * @param selectedVerifierIndex - The selected verifier index to parse. + * @returns The parsed selected verifier index. + */ +export function parseSelectedVerifierIndex( + selectedVerifierIndex: Json, +): number { + if (typeof selectedVerifierIndex !== 'number') { + throw new Error('Invalid selected verifier index: expected a number'); + } + if (!Number.isInteger(selectedVerifierIndex) || selectedVerifierIndex < 0) { + throw new Error( + 'Invalid selected verifier index: expected a non-negative integer', + ); + } + return selectedVerifierIndex; +} + /** * Convert a public key to an address. * diff --git a/yarn.lock b/yarn.lock index 25ac2f2b3..7371b2014 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2191,12 +2191,12 @@ __metadata: "@metamask/mfa-wallet-cl24-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": version: 0.0.0 - resolution: "@metamask/mfa-wallet-cl24-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz::hash=4ec4ea&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + resolution: "@metamask/mfa-wallet-cl24-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz::hash=6f19f0&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." dependencies: "@msgpack/msgpack": "npm:^3.1.2" "@noble/curves": "npm:^1.9.2" "@noble/hashes": "npm:^1.8.0" - checksum: 10/cb58aed5eca46f0fb25fc66e83d5609818e2fb9c6a1b6766ba6d9e4bd49becc5eade784bf4d28a04c3967b70633be54f33764bf34dae81e1f7684dcf64fb4252 + checksum: 10/3779f3d5d370082ea2dd27163670f6085f9863049a35f605019d4fc8883c7101f92726c37a5b12b2d6ab62e768b0f8cd297cd5076a9864a608c24c739cb132d1 languageName: node linkType: hard @@ -2212,32 +2212,32 @@ __metadata: "@metamask/mfa-wallet-e2ee@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-e2ee-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": version: 0.0.0 - resolution: "@metamask/mfa-wallet-e2ee@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-e2ee-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-e2ee-0.0.0.tgz::hash=7e9304&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + resolution: "@metamask/mfa-wallet-e2ee@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-e2ee-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-e2ee-0.0.0.tgz::hash=adbb1d&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." dependencies: "@msgpack/msgpack": "npm:^3.1.2" "@noble/ciphers": "npm:^1.3.0" "@noble/curves": "npm:^1.9.2" "@noble/hashes": "npm:^1.8.0" - checksum: 10/13ccf57849813e2ae7622d7d8cd056836571dbaccd39a52c9d729170c3933689a348b330af5dfe28a125fb22407653e847c458ba5edec8c1d7e07659d91c6065 + checksum: 10/ce481bf5a802dc15f0d30fbb906c6157668485a532474eaf306019f19dc97b46e71fee6f716278143e2c59a2a7dcee2df1508e769d307657a37d3c54bc33f106 languageName: node linkType: hard "@metamask/mfa-wallet-interface@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": version: 0.0.0 - resolution: "@metamask/mfa-wallet-interface@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz::hash=f21c99&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." - checksum: 10/eba9cab297a83b7deb50027bb7321d394beb18ee806cfc0f103e00c0ebca6ed92364c80aeace7247c546afc0cfb176182b09d230823fd574a87e090e095a4131 + resolution: "@metamask/mfa-wallet-interface@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz::hash=42e214&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + checksum: 10/f2396f9d3694c06c8e64f0f12e7c1eccf23cd90694d8214745e08d24c3b1540e4beb39d4b56577bf0ffa1ea55a76980bb7f9b275d2323712a6dc654617235ad4 languageName: node linkType: hard "@metamask/mfa-wallet-network@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": version: 0.0.0 - resolution: "@metamask/mfa-wallet-network@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz::hash=437af8&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + resolution: "@metamask/mfa-wallet-network@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz::hash=702a02&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." dependencies: "@metamask/mfa-wallet-e2ee": "npm:^0.0.0" "@msgpack/msgpack": "npm:^3.1.2" "@noble/hashes": "npm:^1.8.0" centrifuge: "npm:^5.5.2" - checksum: 10/68f59188d011bdc719d9d5be0be179e153e08719c6241d862c19315cce4bc5ed865af56076fc14636f5a15e92085d55e7a803c22c8b798dd19d2f1fe82383332 + checksum: 10/60f759c20b21cd9c56fe78336a7a67cbffabe8a5a76801d504c6e3324285c80c4e5a3a20e1389c82f031b080b65da8ae56cd5df483e5860e267d3327e73c8be5 languageName: node linkType: hard From 26b059990b7c9a70dabd12a3c38d7fab0fa79bae Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Fri, 13 Feb 2026 12:51:33 +0100 Subject: [PATCH 30/44] fixup --- packages/keyring-eth-mpc/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/keyring-eth-mpc/src/index.ts b/packages/keyring-eth-mpc/src/index.ts index fa45ecbe1..e88986aaf 100644 --- a/packages/keyring-eth-mpc/src/index.ts +++ b/packages/keyring-eth-mpc/src/index.ts @@ -2,7 +2,6 @@ export { MPCKeyring } from './mpc-keyring'; export { uninitializedResponderState } from './mpc-keyring'; export type { - InitRole, MPCKeyringOpts, MPCKeyringSerializer, ThresholdKeyId, From 0e650db5c5d8f5df7533d963b86b3ee3e914046d Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Fri, 13 Feb 2026 12:51:46 +0100 Subject: [PATCH 31/44] update network --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7371b2014..06760275b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2231,13 +2231,13 @@ __metadata: "@metamask/mfa-wallet-network@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": version: 0.0.0 - resolution: "@metamask/mfa-wallet-network@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz::hash=702a02&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + resolution: "@metamask/mfa-wallet-network@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz::hash=f6cd5b&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." dependencies: "@metamask/mfa-wallet-e2ee": "npm:^0.0.0" "@msgpack/msgpack": "npm:^3.1.2" "@noble/hashes": "npm:^1.8.0" centrifuge: "npm:^5.5.2" - checksum: 10/60f759c20b21cd9c56fe78336a7a67cbffabe8a5a76801d504c6e3324285c80c4e5a3a20e1389c82f031b080b65da8ae56cd5df483e5860e267d3327e73c8be5 + checksum: 10/2594c89bb7e161caabe19f9eacad08aa75259236cce7d9442d9c3ac880cc1be16b2182a27f881ade04cf77d26e60f1c44295a8be5951b56a15ce6c35a0cdd69c languageName: node linkType: hard From 541d9a171c0f425d57e3b245045b357fb4d28ef1 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Fri, 13 Feb 2026 16:10:58 +0100 Subject: [PATCH 32/44] add getCustodianId --- packages/keyring-eth-mpc/src/mpc-keyring.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index 6f669e7e4..869362ee2 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -164,6 +164,18 @@ export class MPCKeyring implements Keyring { } } + /** + * Get the custodian identifier from the network identity. + * + * @returns The network identity party ID. + */ + getCustodianId(): string { + if (!this.#networkIdentity) { + throw new Error('Network identity not initialized'); + } + return this.#networkIdentity.partyId; + } + getVerifierIds(): string[] { if (!this.#verifierIds) { throw new Error('Verifier IDs not initialized'); From d7a261d13598bc6b2f52854e5d38eae65314555a Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Fri, 13 Feb 2026 16:12:30 +0100 Subject: [PATCH 33/44] add getCustodians --- packages/keyring-eth-mpc/src/index.ts | 2 ++ packages/keyring-eth-mpc/src/mpc-keyring.ts | 27 +++++++++++++++++ packages/keyring-eth-mpc/src/types.ts | 7 +++++ packages/keyring-eth-mpc/src/util.ts | 32 ++++++++++++++++++++- 4 files changed, 67 insertions(+), 1 deletion(-) diff --git a/packages/keyring-eth-mpc/src/index.ts b/packages/keyring-eth-mpc/src/index.ts index e88986aaf..386a0776d 100644 --- a/packages/keyring-eth-mpc/src/index.ts +++ b/packages/keyring-eth-mpc/src/index.ts @@ -2,6 +2,8 @@ export { MPCKeyring } from './mpc-keyring'; export { uninitializedResponderState } from './mpc-keyring'; export type { + Custodian, + CustodianType, MPCKeyringOpts, MPCKeyringSerializer, ThresholdKeyId, diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index 869362ee2..6eaec7f3f 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -28,6 +28,7 @@ import { bytesToHex, hexToBytes, type Hex, type Json } from '@metamask/utils'; import { initCloudKeyGen, initCloudSign } from './cloud'; import type { + Custodian, MPCKeyringOpts, MPCKeyringSerializer, ThresholdKeyId, @@ -35,6 +36,7 @@ import type { import { equalAddresses, getSignedTypedDataHash, + parseCustodians, parseEthSig, parseSelectedVerifierIndex, parseSignedTypedDataVersion, @@ -65,6 +67,8 @@ export class MPCKeyring implements Keyring { #keyId?: ThresholdKeyId; + #custodians?: Custodian[]; + #verifierIds?: string[]; #selectedVerifierIndex?: number; @@ -120,6 +124,9 @@ export class MPCKeyring implements Keyring { if (this.#keyId) { state.keyId = this.#keyId; } + if (this.#custodians) { + state.custodians = this.#custodians; + } if (this.#verifierIds) { state.verifierIds = this.#verifierIds; } @@ -153,6 +160,10 @@ export class MPCKeyring implements Keyring { this.#keyId = parseThresholdKeyId(state.keyId); } + if ('custodians' in state) { + this.#custodians = parseCustodians(state.custodians); + } + if ('verifierIds' in state) { this.#verifierIds = parseVerifierIds(state.verifierIds); } @@ -176,6 +187,18 @@ export class MPCKeyring implements Keyring { return this.#networkIdentity.partyId; } + /** + * Get the custodians associated with the current threshold key. + * + * @returns The custodians with their party IDs and types. + */ + getCustodians(): Custodian[] { + if (!this.#custodians) { + throw new Error('Custodians not initialized'); + } + return this.#custodians; + } + getVerifierIds(): string[] { if (!this.#verifierIds) { throw new Error('Verifier IDs not initialized'); @@ -239,6 +262,10 @@ export class MPCKeyring implements Keyring { networkSession, }); this.#keyId = networkSession.sessionId; + this.#custodians = [ + { partyId: localId, type: 'user' }, + { partyId: cloudId, type: 'cloud' }, + ]; this.#verifierIds = verifierIds; this.#selectedVerifierIndex = 0; diff --git a/packages/keyring-eth-mpc/src/types.ts b/packages/keyring-eth-mpc/src/types.ts index b66bd13f8..88de0c44b 100644 --- a/packages/keyring-eth-mpc/src/types.ts +++ b/packages/keyring-eth-mpc/src/types.ts @@ -15,6 +15,13 @@ export type MPCKeyringOpts = { export type ThresholdKeyId = string; +export type CustodianType = 'user' | 'cloud'; + +export type Custodian = { + partyId: string; + type: CustodianType; +}; + type JsonSerializer = { toJson: (value: Value) => Json; fromJson: (value: Json) => Value; diff --git a/packages/keyring-eth-mpc/src/util.ts b/packages/keyring-eth-mpc/src/util.ts index cc4e83bcc..c31d89e68 100644 --- a/packages/keyring-eth-mpc/src/util.ts +++ b/packages/keyring-eth-mpc/src/util.ts @@ -19,7 +19,7 @@ import { import type { Hex, Json } from '@metamask/utils'; import { add0x, assert, bytesToHex, hexToBytes } from '@metamask/utils'; -import type { ThresholdKeyId } from './types'; +import type { Custodian, ThresholdKeyId } from './types'; const SECP256K1_N = BigInt( '0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', @@ -243,6 +243,36 @@ export function parseSelectedVerifierIndex( return selectedVerifierIndex; } +/** + * Parse custodians from a JSON object. + * + * @param custodians - The custodians to parse. + * @returns The parsed custodians. + */ +export function parseCustodians(custodians: Json): Custodian[] { + if (!Array.isArray(custodians)) { + throw new Error('Invalid custodians: expected an array'); + } + for (const custodian of custodians) { + if ( + !custodian || + typeof custodian !== 'object' || + Array.isArray(custodian) + ) { + throw new Error('Invalid custodian: expected an object'); + } + if (typeof custodian.partyId !== 'string') { + throw new Error('Invalid custodian partyId: expected a string'); + } + if (custodian.type !== 'user' && custodian.type !== 'cloud') { + throw new Error( + "Invalid custodian type: expected 'user' or 'cloud'", + ); + } + } + return custodians as Custodian[]; +} + /** * Convert a public key to an address. * From 87405ca409ee48f59a83ed666093154817323747 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Fri, 13 Feb 2026 16:33:38 +0100 Subject: [PATCH 34/44] addCustodian --- packages/keyring-eth-mpc/src/cloud.ts | 41 +++++++++++++ packages/keyring-eth-mpc/src/mpc-keyring.ts | 68 ++++++++++++++++++++- 2 files changed, 108 insertions(+), 1 deletion(-) diff --git a/packages/keyring-eth-mpc/src/cloud.ts b/packages/keyring-eth-mpc/src/cloud.ts index b846d5465..bc0e1127b 100644 --- a/packages/keyring-eth-mpc/src/cloud.ts +++ b/packages/keyring-eth-mpc/src/cloud.ts @@ -40,6 +40,47 @@ export async function initCloudKeyGen(opts: { return { cloudId: data.serverCustodianId }; } +/** + * Initialize a cloud key update session + * + * @param opts - The options for the cloud key update session + * @param opts.baseURL - The base URL of the cloud service + * @param opts.keyId - The ID of the key + * @param opts.onlineCustodians - The party IDs of the online custodians + * @param opts.newCustodians - The party IDs of the new custodian set + * @param opts.sessionNonce - The nonce of the session + * @param opts.token - The token for the verifier + */ +export async function initCloudKeyUpdate(opts: { + baseURL: string; + keyId: string; + onlineCustodians: PartyId[]; + newCustodians: PartyId[]; + sessionNonce: string; + token: string; +}): Promise { + const response = await fetch(`${opts.baseURL}/update-custodians`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + keyId: opts.keyId, + onlineCustodians: opts.onlineCustodians, + newCustodians: opts.newCustodians, + nonce: opts.sessionNonce, + protocol: 'cl24-secp256k1', + token: opts.token, + }), + }); + + if (!response.ok) { + throw new Error( + `Failed to initialize cloud key update session: ${response.statusText}`, + ); + } +} + /** * Initialize a cloud sign session * diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index 6eaec7f3f..dd582998b 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -26,7 +26,7 @@ import { import type { WasmLib as Dkls19WasmLib } from '@metamask/tss-dkls19-lib'; import { bytesToHex, hexToBytes, type Hex, type Json } from '@metamask/utils'; -import { initCloudKeyGen, initCloudSign } from './cloud'; +import { initCloudKeyGen, initCloudKeyUpdate, initCloudSign } from './cloud'; import type { Custodian, MPCKeyringOpts, @@ -199,6 +199,72 @@ export class MPCKeyring implements Keyring { return this.#custodians; } + /** + * Add a new custodian to the keyring. + * + * @param custodianId - The party ID of the custodian to add. + */ + async addCustodian(custodianId: string): Promise { + if (!this.#keyShare) { + throw new Error('Key share not initialized'); + } else if (!this.#networkIdentity) { + throw new Error('Network identity not initialized'); + } else if (!this.#keyId) { + throw new Error('Key ID not initialized'); + } else if (!this.#custodians) { + throw new Error('Custodians not initialized'); + } else if (this.#keyShare.threshold !== 2) { + throw new Error('Key threshold must be 2'); + } + + const localId = this.#networkIdentity.partyId; + const cloudCustodian = this.#custodians.find( + (custodian) => custodian.type === 'cloud', + ); + if (!cloudCustodian) { + throw new Error('Cloud custodian not found'); + } + + const onlineCustodians = [localId, cloudCustodian.partyId]; + const newCustodians = [...onlineCustodians, custodianId]; + + const sessionNonce = bytesToHex(this.#rng.generateRandomBytes(32)); + + const verifierId = this.getSelectedVerifierId(); + const token = await this.#getVerifierToken(verifierId); + + await initCloudKeyUpdate({ + keyId: this.#keyId, + onlineCustodians, + newCustodians, + sessionNonce, + baseURL: this.#cloudURL, + token, + }); + + const sessionId = createScopedSessionId(newCustodians, sessionNonce); + const networkSession = await this.#networkManager.createSession( + this.#networkIdentity, + sessionId, + ); + + const dkm = new CL24DKM(secp256k1Curve, this.#rng); + const newKey = await dkm.updateKey({ + key: this.#keyShare, + onlineCustodians, + newCustodians, + networkSession, + }); + + await networkSession.disconnect(); + + this.#keyShare = newKey; + this.#custodians = [ + ...this.#custodians, + { partyId: custodianId, type: 'user' }, + ]; + } + getVerifierIds(): string[] { if (!this.#verifierIds) { throw new Error('Verifier IDs not initialized'); From 2f320a4d969fa35ddf8d5a6c87684a8d8ac26d40 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Fri, 13 Feb 2026 16:36:27 +0100 Subject: [PATCH 35/44] update update API --- packages/keyring-eth-mpc/src/cloud.ts | 14 +++++++------- packages/keyring-eth-mpc/src/mpc-keyring.ts | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/keyring-eth-mpc/src/cloud.ts b/packages/keyring-eth-mpc/src/cloud.ts index bc0e1127b..924e067f7 100644 --- a/packages/keyring-eth-mpc/src/cloud.ts +++ b/packages/keyring-eth-mpc/src/cloud.ts @@ -46,16 +46,16 @@ export async function initCloudKeyGen(opts: { * @param opts - The options for the cloud key update session * @param opts.baseURL - The base URL of the cloud service * @param opts.keyId - The ID of the key - * @param opts.onlineCustodians - The party IDs of the online custodians - * @param opts.newCustodians - The party IDs of the new custodian set + * @param opts.custodianId - The party ID of the calling custodian + * @param opts.newCustodianId - The party ID of the custodian to add * @param opts.sessionNonce - The nonce of the session * @param opts.token - The token for the verifier */ export async function initCloudKeyUpdate(opts: { baseURL: string; keyId: string; - onlineCustodians: PartyId[]; - newCustodians: PartyId[]; + custodianId: PartyId; + newCustodianId: string; sessionNonce: string; token: string; }): Promise { @@ -66,10 +66,10 @@ export async function initCloudKeyUpdate(opts: { }, body: JSON.stringify({ keyId: opts.keyId, - onlineCustodians: opts.onlineCustodians, - newCustodians: opts.newCustodians, + custodianId: opts.custodianId, + newCustodianId: opts.newCustodianId, nonce: opts.sessionNonce, - protocol: 'cl24-secp256k1', + dkmProtocol: 'cl24-secp256k1', token: opts.token, }), }); diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index dd582998b..203e8fb6d 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -235,8 +235,8 @@ export class MPCKeyring implements Keyring { await initCloudKeyUpdate({ keyId: this.#keyId, - onlineCustodians, - newCustodians, + custodianId: localId, + newCustodianId: custodianId, sessionNonce, baseURL: this.#cloudURL, token, From c80d20f4139cfa832d75cbead68c90f7ad4682f5 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Fri, 13 Feb 2026 17:16:46 +0100 Subject: [PATCH 36/44] setup join mode --- packages/keyring-eth-mpc/src/mpc-keyring.ts | 140 +++++++++++++++++++- 1 file changed, 133 insertions(+), 7 deletions(-) diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index 203e8fb6d..463b602be 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -14,6 +14,8 @@ import { } from '@metamask/mfa-wallet-cl24-lib'; import { Dkls19TssLib } from '@metamask/mfa-wallet-dkls19-lib'; import type { + PartyId, + PartialThresholdKey, RandomNumberGenerator, ThresholdKey, } from '@metamask/mfa-wallet-interface'; @@ -230,6 +232,28 @@ export class MPCKeyring implements Keyring { const sessionNonce = bytesToHex(this.#rng.generateRandomBytes(32)); + // Send join data to the new custodian + const joinSessionId = createScopedSessionId([custodianId, localId], 'join'); + const joinSession = await this.#networkManager.createSession( + this.#networkIdentity, + joinSessionId, + ); + + const partialKeyJson = this.#serializer.thresholdKey.toJson(this.#keyShare); + const joinData = JSON.stringify({ + cloudCustodian: cloudCustodian.partyId, + nonce: sessionNonce, + partialKey: partialKeyJson, + keyId: this.#keyId, + }); + joinSession.sendMessage( + custodianId, + 'join-data', + new TextEncoder().encode(joinData), + ); + await joinSession.disconnect(); + + // Notify the cloud custodian const verifierId = this.getSelectedVerifierId(); const token = await this.#getVerifierToken(verifierId); @@ -242,6 +266,7 @@ export class MPCKeyring implements Keyring { token, }); + // Run the key update protocol const sessionId = createScopedSessionId(newCustodians, sessionNonce); const networkSession = await this.#networkManager.createSession( this.#networkIdentity, @@ -296,18 +321,46 @@ export class MPCKeyring implements Keyring { return this.#verifierIds[this.#selectedVerifierIndex] as string; } - async setup({ verifierIds }: { verifierIds: string[] }): Promise { - if (this.#keyShare || this.#networkIdentity || this.#keyId) { + /** + * Create or retrieve the network identity. + * + * @returns The party ID of the network identity. + */ + async setupIdentity(): Promise { + if (!this.#networkIdentity) { + this.#networkIdentity = await this.#networkManager.createIdentity(); + } + return this.#networkIdentity.partyId; + } + + async setup( + opts: { verifierIds: string[] } & ( + | { mode?: 'create' } + | { mode: 'join'; initiator: PartyId } + ), + ): Promise { + const { verifierIds } = opts; + const mode = 'mode' in opts ? opts.mode ?? 'create' : 'create'; + + if (this.#keyShare || this.#keyId) { throw new Error('Keyring already setup'); } else if (verifierIds.length < 1) { throw new Error('At least one verifier ID is required'); } + if (mode === 'join') { + const { initiator } = opts as { initiator: PartyId }; + await this.#setupJoin({ verifierIds, initiator }); + } else { + await this.#setupCreate(verifierIds); + } + } + + async #setupCreate(verifierIds: string[]): Promise { const dkm = new CL24DKM(secp256k1Curve, this.#rng); + const localId = await this.setupIdentity(); + const { networkIdentity } = this.#assertNetworkIdentity(); - const net = this.#networkManager; - const networkIdentity = await net.createIdentity(); - const localId = networkIdentity.partyId; const sessionNonce = bytesToHex(this.#rng.generateRandomBytes(32)); const { cloudId } = await initCloudKeyGen({ localId, @@ -319,9 +372,11 @@ export class MPCKeyring implements Keyring { const threshold = 2; const sessionId = createScopedSessionId(custodians, sessionNonce); - const networkSession = await net.createSession(networkIdentity, sessionId); + const networkSession = await this.#networkManager.createSession( + networkIdentity, + sessionId, + ); - this.#networkIdentity = networkIdentity; this.#keyShare = await dkm.createKey({ custodians, threshold, @@ -338,6 +393,70 @@ export class MPCKeyring implements Keyring { await networkSession.disconnect(); } + async #setupJoin(opts: { + verifierIds: string[]; + initiator: PartyId; + }): Promise { + const { verifierIds, initiator } = opts; + const myId = await this.setupIdentity(); + const { networkIdentity } = this.#assertNetworkIdentity(); + + // Open a network session with initiator tagged 'join' + const joinSessionId = createScopedSessionId([myId, initiator], 'join'); + const joinSession = await this.#networkManager.createSession( + networkIdentity, + joinSessionId, + ); + + // Receive join data from initiator + const joinDataBytes = await joinSession.receiveMessage( + initiator, + 'join-data', + ); + await joinSession.disconnect(); + + const joinData = JSON.parse(new TextDecoder().decode(joinDataBytes)); + const { + cloudCustodian, + nonce, + partialKey: partialKeyJson, + keyId, + } = joinData; + + const partialKey = this.#serializer.thresholdKey.fromJson( + partialKeyJson, + ) as PartialThresholdKey; + + const onlineCustodians = [initiator, cloudCustodian]; + const newCustodians = [...onlineCustodians, myId]; + + const sessionId = createScopedSessionId(newCustodians, nonce); + const networkSession = await this.#networkManager.createSession( + networkIdentity, + sessionId, + ); + + const dkm = new CL24DKM(secp256k1Curve, this.#rng); + const key = await dkm.updateKey({ + key: partialKey, + onlineCustodians, + newCustodians, + networkSession, + }); + + await networkSession.disconnect(); + + this.#keyShare = key; + this.#keyId = keyId; + this.#custodians = [ + { partyId: initiator, type: 'user' }, + { partyId: cloudCustodian, type: 'cloud' }, + { partyId: myId, type: 'user' }, + ]; + this.#verifierIds = verifierIds; + this.#selectedVerifierIndex = 0; + } + /** * Add new accounts to the keyring. The accounts will be derived * sequentially from the root HD wallet, using increasing indices. @@ -501,6 +620,13 @@ export class MPCKeyring implements Keyring { return toEthSig(signature, hash, publicKey); } + #assertNetworkIdentity(): { networkIdentity: MfaNetworkIdentity } { + if (!this.#networkIdentity) { + throw new Error('Network identity not initialized'); + } + return { networkIdentity: this.#networkIdentity }; + } + #address(): Hex { if (!this.#keyShare) { throw new Error(`keyshare not initialized`); From c99a07d5f4cc27d83eb045a1661aec465e51cb3f Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Sat, 14 Feb 2026 01:09:06 +0100 Subject: [PATCH 37/44] fixup --- packages/keyring-eth-mpc/src/cloud.ts | 4 ++-- packages/keyring-eth-mpc/src/mpc-keyring.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/keyring-eth-mpc/src/cloud.ts b/packages/keyring-eth-mpc/src/cloud.ts index 924e067f7..d0fc74b8d 100644 --- a/packages/keyring-eth-mpc/src/cloud.ts +++ b/packages/keyring-eth-mpc/src/cloud.ts @@ -59,7 +59,7 @@ export async function initCloudKeyUpdate(opts: { sessionNonce: string; token: string; }): Promise { - const response = await fetch(`${opts.baseURL}/update-custodians`, { + const response = await fetch(`${opts.baseURL}/update-key`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -69,7 +69,7 @@ export async function initCloudKeyUpdate(opts: { custodianId: opts.custodianId, newCustodianId: opts.newCustodianId, nonce: opts.sessionNonce, - dkmProtocol: 'cl24-secp256k1', + protocol: 'cl24-secp256k1', token: opts.token, }), }); diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index 463b602be..bf573eda2 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -132,7 +132,7 @@ export class MPCKeyring implements Keyring { if (this.#verifierIds) { state.verifierIds = this.#verifierIds; } - if (this.#selectedVerifierIndex) { + if (this.#selectedVerifierIndex !== undefined) { state.selectedVerifierIndex = this.#selectedVerifierIndex; } return state; @@ -251,7 +251,6 @@ export class MPCKeyring implements Keyring { 'join-data', new TextEncoder().encode(joinData), ); - await joinSession.disconnect(); // Notify the cloud custodian const verifierId = this.getSelectedVerifierId(); @@ -282,6 +281,7 @@ export class MPCKeyring implements Keyring { }); await networkSession.disconnect(); + await joinSession.disconnect(); this.#keyShare = newKey; this.#custodians = [ @@ -310,7 +310,7 @@ export class MPCKeyring implements Keyring { getSelectedVerifierId(): string { if (!this.#verifierIds) { throw new Error('Verifier IDs not initialized'); - } else if (!this.#selectedVerifierIndex) { + } else if (this.#selectedVerifierIndex === undefined) { throw new Error('Selected verifier index not initialized'); } else if ( this.#selectedVerifierIndex < 0 || @@ -572,7 +572,7 @@ export class MPCKeyring implements Keyring { throw new Error(`key id not initialized`); } else if (!this.#verifierIds) { throw new Error('Verifier IDs not initialized'); - } else if (!this.#selectedVerifierIndex) { + } else if (this.#selectedVerifierIndex === undefined) { throw new Error('Selected verifier index not initialized'); } From 4a966bae7b1806cd3e0bcfbcdf9db3c51afa2c76 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Mon, 16 Feb 2026 10:31:21 +0100 Subject: [PATCH 38/44] join with joinData --- packages/keyring-eth-mpc/src/mpc-keyring.ts | 140 +++++++++++++++----- 1 file changed, 108 insertions(+), 32 deletions(-) diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index bf573eda2..77827d8a3 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -202,11 +202,11 @@ export class MPCKeyring implements Keyring { } /** - * Add a new custodian to the keyring. + * Add a new custodian to the keyring using serialized join data. * - * @param custodianId - The party ID of the custodian to add. + * @param joinData - The serialized join data from {@link createJoinData}. */ - async addCustodian(custodianId: string): Promise { + async addCustodian(joinData: string): Promise { if (!this.#keyShare) { throw new Error('Key share not initialized'); } else if (!this.#networkIdentity) { @@ -227,32 +227,57 @@ export class MPCKeyring implements Keyring { throw new Error('Cloud custodian not found'); } - const onlineCustodians = [localId, cloudCustodian.partyId]; - const newCustodians = [...onlineCustodians, custodianId]; + // Deserialize join data to get ephemeral joiner identity and nonce + const { joinerIdentity: joinerIdentityJson, nonce } = JSON.parse(joinData); + const ephemeralJoinerIdentity = + this.#serializer.networkIdentity.fromJson(joinerIdentityJson); + const ephemeralJoinerId = ephemeralJoinerIdentity.partyId; + + // Session 1: establish with ephemeral joiner identity and nonce, + // receive the actual static joiner identity + const joinSession1Id = createScopedSessionId( + [ephemeralJoinerId, localId], + nonce, + ); + const joinSession1 = await this.#networkManager.createSession( + this.#networkIdentity, + joinSession1Id, + ); + + const staticJoinerIdBytes = await joinSession1.receiveMessage( + ephemeralJoinerId, + 'static-id', + ); + const custodianId = new TextDecoder().decode(staticJoinerIdBytes); + await joinSession1.disconnect(); + // Session 2: establish with static joiner identity, + // send partial key, key id, and fresh nonce const sessionNonce = bytesToHex(this.#rng.generateRandomBytes(32)); - // Send join data to the new custodian - const joinSessionId = createScopedSessionId([custodianId, localId], 'join'); - const joinSession = await this.#networkManager.createSession( + const joinSession2Id = createScopedSessionId([custodianId, localId], nonce); + const joinSession2 = await this.#networkManager.createSession( this.#networkIdentity, - joinSessionId, + joinSession2Id, ); const partialKeyJson = this.#serializer.thresholdKey.toJson(this.#keyShare); - const joinData = JSON.stringify({ + const joinPayload = JSON.stringify({ cloudCustodian: cloudCustodian.partyId, nonce: sessionNonce, partialKey: partialKeyJson, keyId: this.#keyId, }); - joinSession.sendMessage( + joinSession2.sendMessage( custodianId, 'join-data', - new TextEncoder().encode(joinData), + new TextEncoder().encode(joinPayload), ); // Notify the cloud custodian + const onlineCustodians = [localId, cloudCustodian.partyId]; + const newCustodians = [...onlineCustodians, custodianId]; + const verifierId = this.getSelectedVerifierId(); const token = await this.#getVerifierToken(verifierId); @@ -281,7 +306,7 @@ export class MPCKeyring implements Keyring { }); await networkSession.disconnect(); - await joinSession.disconnect(); + await joinSession2.disconnect(); this.#keyShare = newKey; this.#custodians = [ @@ -326,17 +351,38 @@ export class MPCKeyring implements Keyring { * * @returns The party ID of the network identity. */ - async setupIdentity(): Promise { + async #setupIdentity(): Promise { if (!this.#networkIdentity) { this.#networkIdentity = await this.#networkManager.createIdentity(); } return this.#networkIdentity.partyId; } + /** + * Generate join data for a new custodian. + * Creates a fresh ephemeral joiner identity and session nonce, + * and serializes them along with the initiator's public ID. + * + * @returns Serialized join data string. + */ + async createJoinData(): Promise { + const initiatorId = await this.#setupIdentity(); + const ephemeralJoinerIdentity = await this.#networkManager.createIdentity(); + const nonce = bytesToHex(this.#rng.generateRandomBytes(32)); + + return JSON.stringify({ + initiatorId, + joinerIdentity: this.#serializer.networkIdentity.toJson( + ephemeralJoinerIdentity, + ), + nonce, + }); + } + async setup( opts: { verifierIds: string[] } & ( | { mode?: 'create' } - | { mode: 'join'; initiator: PartyId } + | { mode: 'join'; joinData: string } ), ): Promise { const { verifierIds } = opts; @@ -349,8 +395,8 @@ export class MPCKeyring implements Keyring { } if (mode === 'join') { - const { initiator } = opts as { initiator: PartyId }; - await this.#setupJoin({ verifierIds, initiator }); + const { joinData } = opts as { joinData: string }; + await this.#setupJoin({ verifierIds, joinData }); } else { await this.#setupCreate(verifierIds); } @@ -358,7 +404,7 @@ export class MPCKeyring implements Keyring { async #setupCreate(verifierIds: string[]): Promise { const dkm = new CL24DKM(secp256k1Curve, this.#rng); - const localId = await this.setupIdentity(); + const localId = await this.#setupIdentity(); const { networkIdentity } = this.#assertNetworkIdentity(); const sessionNonce = bytesToHex(this.#rng.generateRandomBytes(32)); @@ -395,42 +441,72 @@ export class MPCKeyring implements Keyring { async #setupJoin(opts: { verifierIds: string[]; - initiator: PartyId; + joinData: string; }): Promise { - const { verifierIds, initiator } = opts; - const myId = await this.setupIdentity(); + const { verifierIds, joinData } = opts; + + // Deserialize join data to get initiator id, ephemeral joiner identity, nonce + const { + initiatorId: initiator, + joinerIdentity: joinerIdentityJson, + nonce, + } = JSON.parse(joinData); + const ephemeralJoinerIdentity = + this.#serializer.networkIdentity.fromJson(joinerIdentityJson); + + // Setup own static identity + const myId = await this.#setupIdentity(); const { networkIdentity } = this.#assertNetworkIdentity(); - // Open a network session with initiator tagged 'join' - const joinSessionId = createScopedSessionId([myId, initiator], 'join'); - const joinSession = await this.#networkManager.createSession( + // Session 1: establish with initiator using ephemeral joiner identity, + // send own static identity (public id) + const joinSession1Id = createScopedSessionId( + [ephemeralJoinerIdentity.partyId, initiator], + nonce, + ); + const joinSession1 = await this.#networkManager.createSession( + ephemeralJoinerIdentity, + joinSession1Id, + ); + + joinSession1.sendMessage( + initiator, + 'static-id', + new TextEncoder().encode(myId), + ); + await joinSession1.disconnect(); + + // Session 2: establish with initiator using static identity, + // receive partial key, key id, and nonce + const joinSession2Id = createScopedSessionId([myId, initiator], nonce); + const joinSession2 = await this.#networkManager.createSession( networkIdentity, - joinSessionId, + joinSession2Id, ); - // Receive join data from initiator - const joinDataBytes = await joinSession.receiveMessage( + const joinPayloadBytes = await joinSession2.receiveMessage( initiator, 'join-data', ); - await joinSession.disconnect(); + await joinSession2.disconnect(); - const joinData = JSON.parse(new TextDecoder().decode(joinDataBytes)); + const joinPayload = JSON.parse(new TextDecoder().decode(joinPayloadBytes)); const { cloudCustodian, - nonce, + nonce: sessionNonce, partialKey: partialKeyJson, keyId, - } = joinData; + } = joinPayload; const partialKey = this.#serializer.thresholdKey.fromJson( partialKeyJson, ) as PartialThresholdKey; + // Create DKM update session and run the protocol const onlineCustodians = [initiator, cloudCustodian]; const newCustodians = [...onlineCustodians, myId]; - const sessionId = createScopedSessionId(newCustodians, nonce); + const sessionId = createScopedSessionId(newCustodians, sessionNonce); const networkSession = await this.#networkManager.createSession( networkIdentity, sessionId, From f81de13a1d1420fb6e151194125f5fd14a411a77 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Mon, 16 Feb 2026 10:35:12 +0100 Subject: [PATCH 39/44] bug fix --- packages/keyring-eth-mpc/src/mpc-keyring.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index 77827d8a3..e4ff7276c 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -306,6 +306,8 @@ export class MPCKeyring implements Keyring { }); await networkSession.disconnect(); + // We disconnect session 2 after receiving message from custodian to avoid + // a bug where messages are not sent when disconnecting immediately. await joinSession2.disconnect(); this.#keyShare = newKey; @@ -474,7 +476,6 @@ export class MPCKeyring implements Keyring { 'static-id', new TextEncoder().encode(myId), ); - await joinSession1.disconnect(); // Session 2: establish with initiator using static identity, // receive partial key, key id, and nonce @@ -489,6 +490,9 @@ export class MPCKeyring implements Keyring { 'join-data', ); await joinSession2.disconnect(); + // We disconnect session 1 after receiving message from initiator to avoid + // a bug where messages are not sent when disconnecting immediately. + await joinSession1.disconnect(); const joinPayload = JSON.parse(new TextDecoder().decode(joinPayloadBytes)); const { From 62a97bc37a166519581dbd4f59cd9afa7b378744 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Mon, 16 Feb 2026 12:16:41 +0100 Subject: [PATCH 40/44] update cl24 and mpc interface --- package.json | 2 +- packages/keyring-eth-mpc/package.json | 2 +- packages/keyring-eth-mpc/src/mpc-keyring.ts | 19 ++++++++---- packages/keyring-eth-mpc/src/types.ts | 10 +++++-- yarn.lock | 32 ++++++++++----------- 5 files changed, 38 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index b7acf05da..7712014a3 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@metamask/mfa-wallet-interface": "file:./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz", "@metamask/mfa-wallet-network": "file:./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz", "@metamask/mfa-wallet-util": "file:./packages/keyring-eth-mpc/metamask-mfa-wallet-util-0.0.0.tgz", - "@metamask/tss-dkls19-lib": "file:./packages/keyring-eth-mpc/metamask-tss-dkls19-lib-0.0.0.tgz" + "@metamask/mpc-libs-interface": "file:./packages/keyring-eth-mpc/metamask-mpc-libs-interface-0.0.0.tgz" }, "devDependencies": { "@lavamoat/allow-scripts": "^3.2.1", diff --git a/packages/keyring-eth-mpc/package.json b/packages/keyring-eth-mpc/package.json index dd319fd15..fa5a11a23 100644 --- a/packages/keyring-eth-mpc/package.json +++ b/packages/keyring-eth-mpc/package.json @@ -58,7 +58,7 @@ "@metamask/auto-changelog": "^3.4.4", "@metamask/keyring-utils": "workspace:^", "@metamask/mfa-wallet-interface": "^0.0.0", - "@metamask/tss-dkls19-lib": "^0.0.0", + "@metamask/mpc-libs-interface": "^0.0.0", "@metamask/utils": "^11.1.0", "@ts-bridge/cli": "^0.6.3", "@types/jest": "^29.5.12", diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index e4ff7276c..efd60d9dc 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -9,6 +9,7 @@ import type { import type { Keyring } from '@metamask/keyring-utils'; import { CL24DKM, + CL24PartialThresholdKeySerializer, CL24ThresholdKeySerializer, secp256k1 as secp256k1Curve, } from '@metamask/mfa-wallet-cl24-lib'; @@ -25,7 +26,7 @@ import { MfaNetworkManager, createScopedSessionId, } from '@metamask/mfa-wallet-network'; -import type { WasmLib as Dkls19WasmLib } from '@metamask/tss-dkls19-lib'; +import type { Dkls19Lib } from '@metamask/mpc-libs-interface'; import { bytesToHex, hexToBytes, type Hex, type Json } from '@metamask/utils'; import { initCloudKeyGen, initCloudKeyUpdate, initCloudSign } from './cloud'; @@ -61,7 +62,7 @@ export class MPCKeyring implements Keyring { readonly #networkManager: MfaNetworkManager; - readonly #dkls19Lib: Dkls19WasmLib; + readonly #dkls19Lib: Dkls19Lib; #networkIdentity?: MfaNetworkIdentity; @@ -89,6 +90,7 @@ export class MPCKeyring implements Keyring { this.#cloudURL = opts.cloudURL; this.#serializer = { thresholdKey: new CL24ThresholdKeySerializer(), + partialThresholdKey: new CL24PartialThresholdKeySerializer(), networkIdentity: new MfaNetworkIdentitySerializer(), }; this.#networkManager = new MfaNetworkManager({ @@ -261,7 +263,13 @@ export class MPCKeyring implements Keyring { joinSession2Id, ); - const partialKeyJson = this.#serializer.thresholdKey.toJson(this.#keyShare); + const partialKey: PartialThresholdKey = { + custodians: this.#keyShare.custodians, + shareIndexes: this.#keyShare.shareIndexes, + threshold: this.#keyShare.threshold, + }; + const partialKeyJson = + this.#serializer.partialThresholdKey.toJson(partialKey); const joinPayload = JSON.stringify({ cloudCustodian: cloudCustodian.partyId, nonce: sessionNonce, @@ -502,9 +510,8 @@ export class MPCKeyring implements Keyring { keyId, } = joinPayload; - const partialKey = this.#serializer.thresholdKey.fromJson( - partialKeyJson, - ) as PartialThresholdKey; + const partialKey = + this.#serializer.partialThresholdKey.fromJson(partialKeyJson); // Create DKM update session and run the protocol const onlineCustodians = [initiator, cloudCustodian]; diff --git a/packages/keyring-eth-mpc/src/types.ts b/packages/keyring-eth-mpc/src/types.ts index 88de0c44b..b12165fe2 100644 --- a/packages/keyring-eth-mpc/src/types.ts +++ b/packages/keyring-eth-mpc/src/types.ts @@ -1,11 +1,14 @@ -import type { ThresholdKey } from '@metamask/mfa-wallet-interface'; +import type { + PartialThresholdKey, + ThresholdKey, +} from '@metamask/mfa-wallet-interface'; import type { MfaNetworkIdentity } from '@metamask/mfa-wallet-network'; -import type { WasmLib as Dkls19WasmLib } from '@metamask/tss-dkls19-lib'; +import type { Dkls19Lib } from '@metamask/mpc-libs-interface'; import type { Json } from '@metamask/utils'; export type MPCKeyringOpts = { getRandomBytes: (size: number) => Uint8Array; - dkls19Lib: Dkls19WasmLib; + dkls19Lib: Dkls19Lib; cloudURL: string; relayerURL: string; getTransportToken?: () => Promise; @@ -29,5 +32,6 @@ type JsonSerializer = { export type MPCKeyringSerializer = { thresholdKey: JsonSerializer; + partialThresholdKey: JsonSerializer; networkIdentity: JsonSerializer; }; diff --git a/yarn.lock b/yarn.lock index 06760275b..872369750 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1762,7 +1762,7 @@ __metadata: "@metamask/mfa-wallet-interface": "npm:^0.0.0" "@metamask/mfa-wallet-network": "npm:^0.0.0" "@metamask/mfa-wallet-util": "npm:^0.0.0" - "@metamask/tss-dkls19-lib": "npm:^0.0.0" + "@metamask/mpc-libs-interface": "npm:^0.0.0" "@metamask/utils": "npm:^11.1.0" "@ts-bridge/cli": "npm:^0.6.3" "@types/jest": "npm:^29.5.12" @@ -2191,22 +2191,22 @@ __metadata: "@metamask/mfa-wallet-cl24-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": version: 0.0.0 - resolution: "@metamask/mfa-wallet-cl24-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz::hash=6f19f0&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + resolution: "@metamask/mfa-wallet-cl24-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz::hash=c5dc05&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." dependencies: "@msgpack/msgpack": "npm:^3.1.2" "@noble/curves": "npm:^1.9.2" "@noble/hashes": "npm:^1.8.0" - checksum: 10/3779f3d5d370082ea2dd27163670f6085f9863049a35f605019d4fc8883c7101f92726c37a5b12b2d6ab62e768b0f8cd297cd5076a9864a608c24c739cb132d1 + checksum: 10/7b6e99196366ef2dabc3ce1db8ee91467bc2ba0b1eab5965a7044f9dafb687433d586bf4ecd84f287849183583f1999d848c52a95e972cfc17cf89a86f0c7c2e languageName: node linkType: hard "@metamask/mfa-wallet-dkls19-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": version: 0.0.0 - resolution: "@metamask/mfa-wallet-dkls19-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz::hash=aef3b3&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + resolution: "@metamask/mfa-wallet-dkls19-lib@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz::hash=4b0e4f&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." dependencies: "@metamask/mfa-wallet-util": "npm:^0.0.0" "@noble/curves": "npm:^1.9.2" - checksum: 10/bd9c8f0fd1ff8afe8595c93859a298545a2625065229d693794f0f6bf2b1852816353027b899a88082032ffc6e09e435620b0c3c68ad74ae7e517e3f01a15489 + checksum: 10/c6171512e90737102f190c152ae636ad5fade75ea0225eb0d008e19dd108e5b0cc0b2348a7301df6b0ef0df7661f2664662a4b947d2f06d865bf6535d8a60619 languageName: node linkType: hard @@ -2224,8 +2224,8 @@ __metadata: "@metamask/mfa-wallet-interface@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": version: 0.0.0 - resolution: "@metamask/mfa-wallet-interface@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz::hash=42e214&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." - checksum: 10/f2396f9d3694c06c8e64f0f12e7c1eccf23cd90694d8214745e08d24c3b1540e4beb39d4b56577bf0ffa1ea55a76980bb7f9b275d2323712a6dc654617235ad4 + resolution: "@metamask/mfa-wallet-interface@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz::hash=97a2c3&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + checksum: 10/7da4a2d06e67729b6386b7bc4d5aebd43090a4db23618aafc079e51f890cf00cf1aea336b1dc7877a8e138c89c63d54af7c164fa4b908cb5b5d23b9bc4c4e9cb languageName: node linkType: hard @@ -2243,8 +2243,15 @@ __metadata: "@metamask/mfa-wallet-util@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-util-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": version: 0.0.0 - resolution: "@metamask/mfa-wallet-util@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-util-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-util-0.0.0.tgz::hash=24be98&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." - checksum: 10/aef5061019305e5a112753396aad5d05390dc877eb22d8e74df58c5b6180564f49a85e5bac573c91397b54081e4c30cc8c6b6d593b6c2319f4473a19bd243bcd + resolution: "@metamask/mfa-wallet-util@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-util-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-util-0.0.0.tgz::hash=9f8f8e&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + checksum: 10/3c81433065dbc1cf597ee71b0a447f7ec7b9c389b9b70070de6eee2871be863f6ae03220d5b1ea5f98740ec83781df527e0c5396a88267f1840feef27aca39e3 + languageName: node + linkType: hard + +"@metamask/mpc-libs-interface@file:./packages/keyring-eth-mpc/metamask-mpc-libs-interface-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": + version: 0.0.0 + resolution: "@metamask/mpc-libs-interface@file:./packages/keyring-eth-mpc/metamask-mpc-libs-interface-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mpc-libs-interface-0.0.0.tgz::hash=c606bf&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + checksum: 10/737f6c569e66f21898262a2304bff7048b5a58ae93c4d42688081a8cf6c7d4fbaf669cc90b08fec09e75c959ce798ad5ba021e04e53f1567d6f05b2c101b4a9d languageName: node linkType: hard @@ -2526,13 +2533,6 @@ __metadata: languageName: node linkType: hard -"@metamask/tss-dkls19-lib@file:./packages/keyring-eth-mpc/metamask-tss-dkls19-lib-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": - version: 0.0.0 - resolution: "@metamask/tss-dkls19-lib@file:./packages/keyring-eth-mpc/metamask-tss-dkls19-lib-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-tss-dkls19-lib-0.0.0.tgz::hash=083b6a&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." - checksum: 10/647a9337ad540994f1c0d2dcdfb00b559075c668a2e0ed93433210e407a1dabf28ca0b9767ab44c90e07ad7eaa7a9c5633de85e68695abdfd2a2f26ff36ac65c - languageName: node - linkType: hard - "@metamask/utils@npm:^11.0.1, @metamask/utils@npm:^11.1.0, @metamask/utils@npm:^11.4.0, @metamask/utils@npm:^11.4.2, @metamask/utils@npm:^11.8.1, @metamask/utils@npm:^11.9.0": version: 11.9.0 resolution: "@metamask/utils@npm:11.9.0" From f4295270e62b4b65db4dc35e750f8c35602cc58e Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Mon, 16 Feb 2026 13:25:45 +0100 Subject: [PATCH 41/44] fix selection of signers --- packages/keyring-eth-mpc/src/mpc-keyring.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index efd60d9dc..37be1ecb2 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -668,21 +668,34 @@ export class MPCKeyring implements Keyring { throw new Error('Selected verifier index out of bounds'); } - const { custodians, publicKey } = this.#keyShare; + if (!this.#custodians) { + throw new Error('Custodians not initialized'); + } + + const { publicKey } = this.#keyShare; const addr = this.#address(); if (!equalAddresses(address, addr)) { throw new Error(`account ${address} not found`); } + const localId = this.#networkIdentity.partyId; + const cloudCustodian = this.#custodians.find( + (custodian) => custodian.type === 'cloud', + ); + if (!cloudCustodian) { + throw new Error('Cloud custodian not found'); + } + + const signers = [localId, cloudCustodian.partyId]; const sessionNonce = bytesToHex(this.#rng.generateRandomBytes(32)); - const sessionId = createScopedSessionId(custodians, sessionNonce); + const sessionId = createScopedSessionId(signers, sessionNonce); const message = hash; const token = await this.#getVerifierToken(verifierId); await initCloudSign({ keyId: this.#keyId, - localId: this.#networkIdentity.partyId, + localId, sessionNonce, message, baseURL: this.#cloudURL, @@ -697,7 +710,7 @@ export class MPCKeyring implements Keyring { const dkls19 = new Dkls19TssLib(this.#dkls19Lib, this.#rng, true); const { signature } = await dkls19.sign({ key: this.#keyShare, - signers: custodians, + signers, message, networkSession, }); From 5cc9e9a06cec3704a092ad7c3a2d3dd5f2fc8bfe Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Tue, 17 Feb 2026 10:10:25 +0100 Subject: [PATCH 42/44] update wallet network --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 872369750..0db8fc309 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2231,13 +2231,13 @@ __metadata: "@metamask/mfa-wallet-network@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz::locator=%40metamask%2Faccounts-monorepo%40workspace%3A.": version: 0.0.0 - resolution: "@metamask/mfa-wallet-network@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz::hash=f6cd5b&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." + resolution: "@metamask/mfa-wallet-network@file:./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz#./packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz::hash=82b49e&locator=%40metamask%2Faccounts-monorepo%40workspace%3A." dependencies: "@metamask/mfa-wallet-e2ee": "npm:^0.0.0" "@msgpack/msgpack": "npm:^3.1.2" "@noble/hashes": "npm:^1.8.0" centrifuge: "npm:^5.5.2" - checksum: 10/2594c89bb7e161caabe19f9eacad08aa75259236cce7d9442d9c3ac880cc1be16b2182a27f881ade04cf77d26e60f1c44295a8be5951b56a15ce6c35a0cdd69c + checksum: 10/857028df344faf5cd487703dadac8655068c0fbe542c85ddd0fbcfdd6c5d2ba4e925180d5232a5ad6b694eb9a3225cf7ebc6fbec113119dbe443f55fbfe06d70 languageName: node linkType: hard From 0b5969a2f6d25fc428a92d0b957658876a46d0b8 Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Tue, 17 Feb 2026 10:10:54 +0100 Subject: [PATCH 43/44] add local packages --- .../metamask-mfa-wallet-cl24-lib-0.0.0.tgz | Bin 0 -> 71764 bytes .../metamask-mfa-wallet-dkls19-lib-0.0.0.tgz | Bin 0 -> 14341 bytes .../metamask-mfa-wallet-e2ee-0.0.0.tgz | Bin 0 -> 51985 bytes .../metamask-mfa-wallet-interface-0.0.0.tgz | Bin 0 -> 5509 bytes .../metamask-mfa-wallet-network-0.0.0.tgz | Bin 0 -> 57875 bytes .../metamask-mfa-wallet-util-0.0.0.tgz | Bin 0 -> 8680 bytes .../metamask-mpc-libs-interface-0.0.0.tgz | Bin 0 -> 1651 bytes 7 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz create mode 100644 packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz create mode 100644 packages/keyring-eth-mpc/metamask-mfa-wallet-e2ee-0.0.0.tgz create mode 100644 packages/keyring-eth-mpc/metamask-mfa-wallet-interface-0.0.0.tgz create mode 100644 packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz create mode 100644 packages/keyring-eth-mpc/metamask-mfa-wallet-util-0.0.0.tgz create mode 100644 packages/keyring-eth-mpc/metamask-mpc-libs-interface-0.0.0.tgz diff --git a/packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz b/packages/keyring-eth-mpc/metamask-mfa-wallet-cl24-lib-0.0.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..1588a67edd2eae91c90ec809976185419824445e GIT binary patch literal 71764 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≧̸G7A0f2F(Rh^#ZyGd;zNM{zWTK`!y4 zte1R;yQ%6sq*OT@0`{z&`1J6S{jjkLzme4*`?EwF7i1>w z7=yIq;w5S1Vh9vRWNP*6M^XI4IrO5Lbd4#0cTMnjw2!j8{6hz_?Sf^`CRQCoj99Hr zMrVu!d^DU-Ci+yw#eo*e^$lJP771dbaM)_JZjR*|hXgih!zMvhO>Bd`B*H_SFvlse z3fnDJOy{2lmBisRs7qa$5*U9XI9Azowe2Lbzc1HL3kmGL6w{9PKdfLA53c=qkhFGo zn%{q{OqehKHCnd)cW0|v=70JO@*knz90AD`Z)o(dLMrS4WKg$FRAqRmuuz^J!-wOn1T{D1d+8 zqO*BOMPYu#%!)@fxhM@37DX=xg17}pL-i9TB_o(OGM)80NXJ^biXcoC@r%dphoWm_h+r0-OHxCtN7Ukn)-kjG2?NK{N{M$t4-u& z!hJmE(vFK|DaYxH%?ZKMi&1erg% zsdP|IvdM?^ak}By5BpFCffV!52hhWWb(aHaIbt5|3?N0ZT?`&|P?3#?j^g@}=VfuW zfZsHS2?Ctn$R&6R2YQimp6w#{Voz4{aCg;J(aC`r@h#Y$F!6#i3?v2+Y55P`;p;O) zKM>pZVa-Y=)-y|eY01NkowEVgp}V7n8jEnL_V>~0G)RBYpYIQpl|4YeqX)`b$Y%-1 zgTL!@WO`KYFSQ@Jox1nzzz+*8TKxp;*A=(g;Xhaacr%LRA0H2f=>S+5ihN|P2!|0> z@hP`a2JL99U}eM^-j{{(rucGx90;2Fi_fkae>jI2;C@@bq8KEZ7JTX)<(4Eh!R!vD z$t?TNU5bKB{6AX{cP#r)t5xcMzjpusBVCZvi)Z*335n<4oI*MoKd#&I6wf4b5sWNB zNR`y~*-SsshQ_7Eqsu>VfABVSJ- zBiBY1@{8)#N*rcL?myRL7g039zRiHBxXc&ia>;=2L7UuG;o#Bv+Lu}BS+bo#x#$YL zt*o6>aAnaKr(<<&qvMWk+qP|+H@4a7bewc-+qTuQ-LdbT{0CJtHS;nx^Kxs|ty6W* z!>PUM?DhS=4dx@3UD`r92^q@QFL@NQFK#!p+W{cfJ_q4~@~Dd5pF#H5>;%Z5*##ho zXB%|x$35Cs`pimHxJCSV9Qx~xr*p)QSm5Z4DNeD$HF*XDB~MV0EoHxvtKPA?&S1sq zFJyBDqIeaKLu@udwPNPWfSQO3X8CDZtt|F(W!$DFY7svkWgf@MomuUPB*MkW&2rbM zEdVQt^|;k}eOAnD1cTCx5)W?fttI1O+R$MooTBY$)gQ~|BM*9O>yfA~48(QyQoG`K z271JM?PbG#p6L?CO;27BF{>I`oLG90b2GI+;RWg&;EdZ%%XJqWR9n4eaPh%KMlI|Eyt(=S2phopLB+OS zD?BP`+FPc63bDcD{alAS8Jnr5oOsaIiIs_%;wgjBqE)0A=cv6#maEDk!LK-hs`Db7Xy~5m0tmL}i?=Yuw>BZNC!lGihnD3K^nP5yM&ArQ$IA zLwkND4u%UudgZEcMubX>=A^vF^-(0kDg-dDdadFP;EVI1$Zc_W=)@$Z&kcXTG;MS^ zu%i@=NN6`Zs8%6X4GPY_wKZ6Uji~y_LL?^?8t;vy&r^FsRpLVZO^W?v%Kr@sRDsy9 zhG8w+&!z@c8^FI-h5hPW4mV9zAGLaV7QoWkTj0AE#0=2)4pmB7KAt_TfMr-<2siIl z>*fJ!*cQ3K;!@k;;AFL%=mkXtldFoL_Z*}Z6uQ*>xwDMILo|K!$UYOaS?p#nA<%hJiCbwpz zb6-Mg0_JWe6ayH!L|D4?1yyLiL1aCtL1zaenbWg7{%n58GulYr67x~K!(AK=wa0puQv;lz#fkynmXGKVn44BQ8rk6EBN>@*M zdN%`CiSTM{{t1P}3a=`n?PiLoNuoEo`(J>uY=c`E#Wtskrx58TYxU#YbM>BF557|B z?)%2focp51)c<+j--Ke3k!9aM50{JMYw=BWET$x)OJhJifIl;E`$IsFh~4$dQ&N;T z9=pOJ09OB-)%c3lKAzOX(7s9>i?19Aw4%-To?e1kN+OP?Dj0VzSrED5ULLluEEWT$ zf=DxEj;D3(B|~@Jo>_Dsr!jQc9cUc%*)6&{RNiTPL>51Bm533cW?DGTVGPb~CC4Fe0-rPREtvwbp9ef}E(&Z0$Zro+Rl z7=a%RikShU!GM63Z=eE7Ort$%q|;wqh;&u+&ftOq>j1vtW|kj|&m!suY%TqBl8tbj z5upFftqVly+lMXZhca6T3qDu5A{pcD(c=BQuIJwq`t0}nTGlAeRHywIFLS?DQwOxF zaHmvkqz~LT681IE+ECQRd?fOkqFZ^B&Wob2sSK6fAKx^xs!K@ZV8Yqrn=YAIRqL`2 zeV6)2gQ#k$icTQ9xMLzUed|6SeY~$)cXww&A+9+(DFzWK&fu+++;|tRp}`Z)aG-*U zY`yOIA*f;HfrVi%)~Ml3&p!t?zbj?C)z8wSoj=qYqDNlb zPj%0Hx(KU`xVPrvF23}LS{1spcsZLIj@8;2(h|+eR3r@ttJ(?m%{+RB2y0{kRY*T$xv7~jG4fnXny5#{*j3BX36 z>EH`0mdpZOG>!sz>Df8>R!@j5VP4Qi`nDK9F8FI>puEe zt{+R3g>(Kx^Ra|)^rlcHc!}QZtZ(`QslctrlVK8C3$Mg@xT+Aki;dk>a-0qAMb^kB zA-(e3EP_?KDyf}9tU{UZY}^K{=8Ij*7TsuVyKpfbqSn0X)e_-?-83zpWq2=RIhII3 zV2nnrGpVV}INWdZ2c19Abr)w1x31@shZ*s60SD2F!eU8?tg_bFO78B`o)D$FZa*w>tktC?u4Y|$s100MNe(L18^Y@RNlk+ilKfpL#>IipuB;=k^;v4hdInSLZ6{~2JUkg1Ou9Kn(}{vyz%?tgjnL3xSp8B z{^N8?>X9?188UODCZaF`g1n`4G9lpC$dRZH4cn6c!TRn5x^2@WR)+K7s@3L|UnBaz zT@{G`)D3j}MTHo+U>_6-^iR=6D!qXd+}%NP>Y4HXj8#}-;#4upvR24)?5?R-*id?x zBt?%J=3#vG!1KQtG!DTUpo*u!T5K#e2AUGf!!R5EvezF1hm#^8Xyh)e$B_|2i0m&@ z)Lpg-8yUe$U@C;#cqF^v-&lhyipNE9i5nC=wk{-Zg;mK5`sR#MCpBg-U$ej$k>tLlydF$6}Y6=p?~Llam&n)z|^JULhS{-n3BU& zamx?4p07EGl2p!uZS5!QkeE5JOL|o%j`CH68ob*8y55wVvbifE`3LvFB0s_CtR`f` z79}r0kQK++up)HMB7u%8y{&O{ z)qC_KNu#rQ@|^yr@Y}d=;Oz*&QVpk-?7~QHA|QYK;W%2Q-5NtqBEXsnI;83;%u;0X z(3;76*%E+Wx8sE=4WIhge{}2}pu00zyZLbfKEh_&OeN&Fs)v4Sn;Gdkz`m{wSXT`+ zv5y~^+o)*O9Z@1^9QVfzBcLC!7uyb~t3(Q*=`i@W3EC!ejwTx2qk&t_K`sI}5Wes9 zz2|#)zd5~mX9yR^eLqEoVeEYU6jU6$WRJ+1RdgFcMH7hv`{?xL&hC*r|H=caU;9Z4 zP0R_q2pojaO4^FEYag^8Pj5`?zW#k{xat*@`-ze=PSi8twgafnv2+_<0cG6u`uT#! zakez$$FeT2JkpU@>@L)G+^{9ieEkgk6<0QEh1q5HdnkUh>i`VMZ)9FxJsbjLHTcJU z{Q{hvX73HXo39d{*?HFPyA*wuvpxBl&MtinxvrKH1a8`n{c?@`E`1X1iXdkRh?aPB zVUc*X=*{5Q~_l{Y>CUstQiw>%GAe5`0h@H}>@y=l$f z`yS5~v%QY-dSyp8lh5)MrlRMMYnvmPhIJctA&-*TFl4@56r6neN`t7(p1Nk<8RPt| z3m?YryEgXw?jicZR+EFGN{Xu%ql>Cv?S*WAMIFo>rPA>^MbXjcbx16gKS;$ zgWCOeeR@-nVo_eFa!&lKW_n+>bAtO0$pgAGVS}3M21g=LdWlfy_GZ}@kbxbfvEjXe z6uW~)V(>hlaD3oDKj(kPgF(fQo5QB??%wl!N?V@$pz?K6ct}0@Oyii> zh(zDoZ>r?|-u3UN>Nyxlk=H0>sf-+qp6;b(q~hiTaCw z7e7ta9C7UXdfn@CmugV>Z+;0s0)@%k8}bTir9xKI!x+nR91za#ellOp2d_pi8=9Al z*dzzN7+OW5jW9W%A|6WrO3LwgS>s7Pkx=ak6WPW=#}Az2LQ(h$=X%LN?oQQe6w-fF z=S2h^n=&lRd}u@%e2~y?dZ$toK@z@RAhVd#C_8i;;7p!S+f9ZhXtG+MA9o5T*ULNS zyy#QNtQGG&Mgeu7@~0H`U;BjzF|j$3wht<0*JbTYZ+d$V-@mS#G5sA9ZOrFb5$6w7 zd1M;#8ny-U*5<><%G9k``G(Z*GRW&5qg(HXWr(~<)qe>!uEj?*+qrGQuV2?LeslX! zePxyLLHsA*us;}2)Z=+`xxQDA5QPuRiBdIUsGsS}#Su3KI)D6uwe(AvtW1!pN*R`u ziA+@}%f<4=O@}F_&Q>hC_1a22LLbq@0n>4mU%jyR7|M&3(?;|w2ir%XFc-ucJYqdc z&e$0bes$g<`4O3YUzC^LyEbkD$vVEuz1NHS^%%iqm$EQ(x4el@{7iwKa46kIdC9F? zd&B`*0v1e-#6GRj4wsjxAIa3PbI0G-q>U-}f1>}&d)N~punJNj|(cXm(t!AOB zI6#?sj{Z}O`#%-2h7OI5D#M zbk1)*SvOn}^AESYNPB*J>L<0hWcM4sf5~g^AkNZO+(+??^NCUct7dM z_(rJUdh(cFOlPu;nO5pI6}bg{1B{h4J@_obdCmM&!V>2p4rOg{3u4x=T#lZXyryxN zEV}h`UeX_=m@|Y_G`Urvun{l227XHee#`04>m;F|9_b;Hrf;_#cp8TW7Di8(!onpy z@J!gWV%3Z*^qx?p#!GPEVbBhN?^gg(HpgJtgJfmE`MVrp+#@RUsni=|ypeTsOEw5T z>m55@A9aao00+z!yL*>9Hfe;pFB8tih;cMogr^>{SVU78GAF5oQHo^k&iijq*H)pnO4f_aUY(?J+w-h9@=IO|?LIoo$#jTyoo62!2ybtrv zBD)4Xidf^L{^+rl{K(d#Gk%$Q=szACCR)SJC>4l60%RD2C`oH;fJ{TodG36smB^Y@ z?fK)|l=#U-_f)$$wZQYA70H`iADH<-$x}fo(#TkVqP(Rg^kailr5|q#%nv8)P8|uy zpt_$5sK4*R`{mLtgSQuXzUMFS6X>=vzaNiiW7LcBt_VM?5tSR(+QBKqxG#GAS97T1 zkGFyfYHwD4e041JoxkEzV(#%WBlf(~@mO|XGvef#WLNk%!U5hYUwXF=G?)rTX=Qp)7puUG-jHem$(k}Ir2s&G+>el&CntYnuqGxhJGQ^-vS{mybufj^Lqs}&iG0alU zz(Z`F)O{N1;Siil=9mRu-oEt(1zm73EBe0L6cj`8jt(8h=rI+OhZzx{rieoz*}Yf? z8&ROM+IV$Kutl4Ca5Xh+x%tQn&DvasCyhQ|^6fJ0tu0QV(DlEwqq+Q2ntiz!krQL5Qra)dQiRSwm~~ zEuz@uJdTAILjfNxX>zsqDKsr_v_mLM{WmO>{R5Wo2dy-(WRBe*F#C9NRCt!;F=mUGobwh@asqWrBU)=?77wOF* zjf`I;-*qF}_7N5B*U($U+5RT)YU5N15F$gRk($Ivi5uYj(1$HL;>Nwyrjb*B$ws%U z$N?PTh{5UH{yqGS|4K2pop9X$8sex<=J5BzL-+lsoTk;rvx^EZqZCeK5dti^-`eUV zm|k8KcuNLLA7xc7+sLO}b5|oiT8&WKGPmPh%P|2M2YmCOQazh8MBDPQIS#iv7La2d z0odPf#N9$TzlZ0t$ALpyc^e=x_3GWDw31VA$m*r>V1wC>B#jUUEho8~#7 zycZ*%Hm#Hck=HCEIlw^8MLS?||JrmRqTqcOU*YQZM}WC3yT#ljW%o&8L4OWa=2qp* zNNdyW-1KT#sZpWUIT|-(uryPKcvnv_`X$4E)|B$h@s>(8NE1vIkGN~NS0#8@ zi`%elx_z!XEsZ*+x_F?#538$2^Pb&;h}uddV8FBQ9>?w%3l(H1S4s^3A7Az9+x{8_ z4(h0}E3DzdkQiVnK6IyfZ__G4GFTeZ10wb5Ga{sDbNmMdnS~nr&rtCjC1(Ub@#K4j zqhMDU65(`)tJkPNkO8|oCr57qf2=3p_l$oz%}>pxhY@VtGMJTk5+(*yH#rXM0%aGq zPO@I-LQ$aStr3%xQ4ptB{z}mvsC$v-qSqfBxI+(hTf^Tc;A~53>3@B8SF~S!aF>&| z!*Y{QvzkSOnwJ2lKlb?&)PhYsWvqjOC55cy(RJK|z_uJI)&Wg~b_024T=1DfOj85- zlynR3CE^PF%dhO;ebh#DhDxt+le-$=mB0A`5K#7C2SC`{I9`nYg5V>&C@8WxwZ`M^KnJ|Yixj2w^|Uyv$eoeH_r{@5C!lk1XrtQ! zg8t31QZ>cFGnk1^{``L{K>=|fuC(FKgAF0M&))25Nw?JpkYjU8t1Red_82sMmY;OP zWz$uBo%l9;-(l#`df>BzruXzrbVtVf_rA->GO}57^qz4~%u3WiS?X}`XP`84G=OUy z|B_>jXJYWok9D3+rhp`B2bD7OK|O zLw-xZuFvMbJ(soeX%y1HXzhBIwY6y4cif%>s@wJ8;gTmLCa>mLa>ijHlN*eCtp-rq&3g*xvz1S-a zK5~*Md?=5IxBk!Tzt*v0l=WgjDsj6GP3N2FlOgXc+!Bk{m9CtmrFpgK!Yd6aK*hh< z0P-Q~n_94jv{Rwk{!P^jEItb!Ytx`}!XhCOiS8cSLc8P}Pd%eBFhZ`tUHkh^GRV2N z*((FK9Pv-mHZN~9_#aNbfz|sRb3Dv|q18&*T+;{)*5FjcX_x2h?C9|lBDN94K62o?{ zUfsX5ZaC*zc2X}w!L#ENegYjIR7l-Dg7cb*k-@G4mtO4OvpoJ2_!jBs=nF7^bKm3s zN8RuAR#CTcq(Z1L&WLqEU{;77`}Gmh4I&IQ`B>kU`*hw_Dnu>1Sj(F(>zzs`ft}~y zb8@oW7H4ZYhBA18C(fs#FjQ0=rFsdjc@-=`3XC^+S}0+HYv+jere>%Q7jy!(od; zf6o$bl}z8#%h*g=V%arP08oHHfFb9wEiuN`Jtv|X6@x#+riV|^6Er-8f zjy$njwlBqeX6;%O2_NyK-3=BiSC=_emnBWfbUy8VRH=7g_IXsf3X4b~=Nh0^D<-_@ zltmc@VYSBB?D|d-cz$(=y=k4jc(>FcjdJ^Bmw@dVsl~eEa<%p;Z{$+(Y3r$K??K~~ zeeGXC#k2{$gCPWeSkq0w77Xy(Tha*5%MLOnTS;7rVp!=jGS>G-8sc$D+dL%^ASYIS zvc`3A;f;=9|G2(gN}JeF1<7gncxAuW3Ij{4iS$5v$DsGix1IfVP=;+#b(fN%7S(Ie zs^{qaaelAB#+l_wWC~dVeOvT#NJ?~pD0w2B8ja`pX0B9hF3tQSdru9ahG@mZU@B-RNU#+ zdi8Y$mihHc53YTEda?FFi(l(+<|{~02$Yxc(R&D@1c6d-L1zK)fF@mrO^}&Y=OHLB z^J^gw2f?}klkmQ4uLzj9W)m}KqOMc$?ABn&VmMQlsA6^)^9%$6xPxuRzil}uG{ z+Ir3E5(zR0riIlJ6Q-pUVy>AK8bYQ=HXUYk7LN{^3wR_yj24VXATtPbrc>xS6!>TF z{JgGkH9Q6yfrcLDD?0+Sd_q<6$lcok;tOM|KZ^OewTmfAXfB8(aWJ}M99zSby$e0? zls28`|eu+QXbO#e3WF~X-%w3c|%&RCg*QEZ3h_Al*>I^ZG-Rbvy zc<)aewXtaCe`-QZ@DBf-nCt$Rn%~P38)}mT^S>KTS*d1rK`d0{Zy4S$j8R?CwRf$| zdz-u!50M_DQD?YPxBJyoew!6H>eCdc%usSbVW2#D#xJmvB=uzA$Xf|iZB1PIA*#IExEPbcVh?|2k1TaRH@_8oa3k$;hlH?iI)0gab{D}rmbvw zS9AcXE#&sEp0$gQU_}wqLxXi&mEhQxn@UU=bG?FM-2?LFv55-N_yWr~m6WKu!@w#O zv{P0!{}Ca24yYA3c18%u$sAqRgXofUQq}}9GlO4W$J8e-_-V{%e^VKF@KqnFqC{txh?n( zL@+J!mT(=;X}v33Ud@90CZ~Uvpl*)Nb8i{EP z*1elp9nFNDgscNEy>X!3(F^6>ZSu3XYZ8{sqjjZP=XD$TA8huGSj``S6A;X~v~SUu zggtYvl4f9i##^L|5bmvXxiP?<6v~X{)uusBpHJfqo@fn$)vG78EzN`EhSqWI_bz49@Vbj5)E^% zD|b02VVZotw(t2+R&%ArRi-r~k^B|~7}OGlR<4{C%u&}{8p7&zlo|@2!PjjUanUa3 z64&hOBNG~v_`9yGn%uu7E86h?@$Q(M$nEG>LcJPXHJA;O{vF$>pGka;MMaZjZHb=x z@jRcr*K!|VbrkUN5`WHL_|vd~cEX7DTFz9}S`>v%6paKU;XOn=V0_?SRfUv;5r&Qx za6I-36y8(1voiEDjPS+_Kgr$_gWI=v=ktqUwYaJGs%SSngMGS1#v3P+u~Kf0%j5e# zAFtX?zL%bCsVZXnA?0kN>ht*}6%5tlR;3yPk8Y6A(3#}apT+e2E#ww8N<=*dHSA8k zjB303>#(Qo0D|wP>gb=*NSH1%7XKtX91=fe2ZfvfryzHggn^Sg7>RGBD$~nAckdPz zcq6s*`?vL0NC~+|Tvli1F!PkdZ=xx#@Gv61q-K_?mud_&x5;tu-;kjY6QOx732CgP zX|8t8CZhNJ&^-zWRAhwfRDOdLr+l&Z2}dI-dmH zVG>LB`jNH(WtBzbpMk5P5a*)a$HamHf!O*(f$|*eO@bF{RWLBQ_Z8|&EDmWU{d?fS zzR!#*%%?l`5TYYvtq4OOZGEzM5@$6wVal~PN*^!DWonjOb8==(DE?h}q$7#nBoPNl zON^c$ChmQWRO@9HOAs8&m6~MclhPV9)FYrY=Llt~kEFhT-BmJpsrSbqE?JCiz+FoV zr5BEI*-XI-o<~H;;Rt?*E&L`LV?ZFbgm+ z`}b&wE`mj(>3+~q2PW65m5bA!@940t#KnwQ9`Tn5=AJsNezJ!qh%d%z7Za4c<+e|1 znurpQOPIb%r)6xF*TCe*R9w0G1>rWqO@5&MXcjf~9Qz|n34_5XNl8M3XH`X;2)C42 z$LWEtsF~?)f@L~|!l8#waZXB4{S?QW5rTW#N`!M&63G93-b^5);;3#Ht zKKS8^vt*_JjPNM4+#4%~ML|;%M@3y_pSGg0Qsvel*To!jX$b=nD60ETu~d{CLO{t* z)1E82WdA*>;EI{<$9NfB5+EAKL4h!At674Fd_c9Gh6iCI2!M03YF8#&MA7<}%|Wfo z@kh4+#^L1kTy_k5k0IY;!{B_5$rIZhcqzBy9CJ)`SP_a$jbmA2LlO0~ZweUrHho6pYaAzSu; zHKgSCRfSxA2~VMA-0D@A^wT}{FaxXCV#0>id|3zSe%&J+CVSj23{)g%SE}31W?U&{Z7?nQBJcHcX>P@U zPr%%Q;upS7=sbkvjBbib2kP-pgCoBrz^Dw(oq*0M3}zzD3CAx#zHR1LbbV@K+`q|7 zvOPnwwkt@6wW}uZGli$TWgJogk1e_n-N@!lLx~;WGRqpVIn&AGcx*2uwH!);-V~N4 zS3~?CwFHOM*YQhgqo_k*talrk#1voMYIG|Tq@4i@cs3bK26b0~K72lfV!r-51~--P zcMrq_#};xG)3ho(w8&IJx(!|s22#59i0SGPv)sJ|jQ*B7j$2(AVk?q3u@ba-7Ci_IdfWiN?1PcmpdH&LSs+N~74(4d0>C)Ap)3(FoVqS_ zl(JgxAhP{RC2lfmxnX0yMSo7`g!2eyRkrhPO8I2*RrsbE9s{75+#-dH&MbgfzFATw znXf1RSiNG$Smk@&89}l+lLo-U1P6wI2@t8^Q3-+1nWSMya;D?q|4nIB3F4aGN95~_ zhF$$SQbU)IZ~8`vliIikB-Plw$^I$i>DiiH0vfft2QhPjK#k8Wn+^wK8>b+BXsg*k z>bs`zdJRO>u6~v@vYz~BLoUDFy;8yT0>5ql`;3WRPjk<%?;pmpM*b&TeB?Nt54j0p&s4F0*YMrHJHR6YHH(|=X0r^ zxxopi$!P*k?>ebFP~+k^V-&bG4BYn)@z@V3UVMZWwMQXls0$)iD9>MI!D&jQN8U&| zLBBnkaLuL(f^8K=)d0q8%R7CCFX&>o_y{@5sidix!c7@u2f9^|MqE4u{@bgGkQn~K zH1)6>=3=9c%M)6hhVHHkWg$q_V70w)6G)oIf3y;<&l&$wQ zjc*qOR@%GGk83V_(0|E(pfiL;`Xq;ciV`xE1z^>n&V!*4c-*~n$rmT}$A<{{grJl; z=-Y*6F37h=I(Pb{H{=4BvNK@@F7aApOlQyaOHMR!n?b2{b_@diXq5S_lpXVZ=lGlO z?{HV00#7kWWGr_Lun_?`Om+?qYYY(va@5&x`)_PRX}Wl_DWh8jPpWxLo}`(92|V;##0t} z)gMKzYFfK9{RTc@3sZ$)4@hGx zWWbOyWtg!sf`6{5RrnfDOnQ3Twy|~cw00vRm`MDZ$vAA91`b$k3U zCCx1#FSmmu=FY{gXH}fKNb%)yL?|+PcR(d9A@(OjPd_HSqFbHNfM<;ftH~)Z)nDY> zRV@-+vKmX>N`}w78k_owatlc_1XfSrN{y&>0SH_MmqZtJG$*~j%YHyxuNxl%kHh+e z_82#&SV4I+@f;>Q7*|*ik4ISL9{O4PmbicS8pGQ70^SMGpB;|`HdQD1 z5M;^gr*JlTAJPHEkH#|7bk^%R$IqG%y+*hg=8C}8<&|IMpn$f9y?wx6w9lIXov&5} zdHMccQe3yM^;e~4-7eoIHvjkC@4axhpxlGu<>;tvBhZ0U7ciTS5jdEm12Ss`_TD@S z0V|$%mYd@nK39(bopcv~4~45<>yM+B(p~D;3+Tn3qPt)3r6HAs6w-baRYMb|B_B8z zx;B+Z-m`cKIeXe{O`j@PBYrdX{tS&QCE{)aAbrTY?aZ~rXU7@;u4n27;>~@Mt+*|2 zcgq6_?>U|LoaMFu^AD)&&4|pfXKC+*_ibl+<@~&&9Ax0&!0-OHqqhiB71XAItNJib z_=O`DEQH_>fF88vfONl#?r(-w2SXt<{?YYnA<$>a;NQ4WT?-9VoDpsC+(iMDL}ERh zAM`z|#fb{+RlpH1#*M^esB~FkpXNaG|J+o=nqoLKQOGiks;GGW;QENEb(cB6rr)PS z-o3()2sD(sVzG7fokotnDNVDSC@k4;D|e2Soazk_`uR|W#^lgH7Jq&H-mMW>yR-Oc zqpi$2J4n2dCaO+Jsg7u+?dFBnoTeI=Mn1*a`6ok`mgKms;-`Xe>3Q*F8LK<7tdJjm z*{N#-u%AJVq_VtfdR@noxzTg2!6JK0anMLKaC>aZIIP1CL|0oEDZEiWq}&Z>n^L$qh%@b9pZGQzP(g~YGyY|5PnL_ zZ!3O%kUOT&J5eu>v53(zvvh;sMgJ9&@6E3Z&*@)NJvp-61s?IvI5x-C>v6mgwEG))Q5 zEX4~pvR@mTGdt$RKD0(~m@@{qrxDjq6>)zww_J1>&%msc7;5Wqy3x8*{O?T`Rrl_9 z8lfdolME^AZZla*4mfpE*dGvn|3rrukt9oQ)f)OZ14S1Cly@iv)Ic?&fa^)-Y*!XR zGCP$GggzR+t2ZDf&asZxH;1?(X%QHcv{&sXXJGtr{;>SD!^JXVpWlQr{fKu_$~Qcf zm(^nO8O(oL^Ybf+F-YFIb?ZYr?A%@WeAy~2>M*T*qbEef$VRS(g_R+bu|lw!_j6t8 zT}arI8O+MGvPeB@F71>4tBB&1^zuC>scN0#*r+fm{;<)|OJ?;x6h!aW^;#{9srGy= zL{P98&*1rY>hw}QoRb=qIX`~9!Y~$A^@_}cdBN3#H+~i^X=F)547AgMCwoW>{1|(k z(;nel5sa;gz8xVS9{Mx8#F z7&7+KfgTka;$`;^`E>$6Btm#aGNfXQD1S&*^2+wIcw&2|b@SCn!s1-Khh0zpKyT;W zjul6|VBh6dY8y`Bj7mBbN~Mb~I}49n2Q_k-_ueSwdR&|s2nArz{rk)(WPO_8OkZOGYU-`&TLM1eUUVzgdPnm5N1 zk5`3Z{5}5_3HkWVh%f2UXqHr+CI-8Puf}AWQM|N(q!QIL zR1?9C><{|+bWTgPd3!WI@NDFMLAMFqmRBC3{xgvX2SzRgQ}T}$q?HIj&JbN{oE2jm zQ@S%*(*;;#OZiJ6&(G*#UqaH{#G|fJ%n3kzcN9$Ek2G-4yY7-N9nXKIpSb_WT`@_` z01OLa9(i?Xhp7`QhqJW|^1}94Ao%`NcAXPmge%H!bYfjx*gEf|oTdsnO8 zdb;@PF;4mhd=Om@R+ASLoOFu;%zudw-Mp_EV`} zIb}F<@jHGL@UHA5{njz>ru#fNn0cl+M(ehm66#d+$pUHr1-;xF|4@2i8IkAovw)cJX5xbq)(SvcjxyNH^hU&Vg7rW+) za_S7-h4dBS9FT8Prw8+wt7HOZ_M#PlDB>UrLyG%>~Kot(2Z*jN$3|%xbrK4gZJQAkL zA-j*1PmySG@nl+;M9U9?9Cz04O?=uLOXV0wP7XdOzZ#=LlKy1UpzRM44Eu^zP9IV7v4;8}1u<}t^}OnN#E}K6fK{^Bui}#;YiGj#If7J`VnfWQ z^&dS;mZm8sXJ~j*zPKubQPw}Z6gtaBkgX6Pe6^mmGmuJgj96l+ES&N z8ke%iF7k?TqFq;g$@Bz|^iXNYvG1$19+z(NfEGbbrixI^Y~fwh9-i-tD|}PUy zt6sDONp=@FWVR|V&*M52c9HT57n5cD&sp4QQ#f_#T@1Nm zwjygi2(apL#9~%t`9)hh+ykmuBOPe`%_Cih+EtQ2CGZn2fKX_tlR1}Ae>g39x!3RE zZBMrn?B0DS!a-0EqQZC6zYVLLzRhPwG>zN+QirGCw@e74t=;nXje6I}k^9k*I2lOa zm2qL%HRtNc(D2)Z^gO+`eXc?+A!R`*UuD_BbRBcbf0dI9)Bk60><++fec{c1J%vRQ zyC~OgDr3F_1ea3m!638HkxOASok3Bu>N$SvmNqPvsc}$~*rv<6albI2vivKhq~M<9 zdpr(r4sJ2~3p^`Ae)VTLb#mUlJ9!DE zUIZ}q+{=X$g{|Qj7sfvn#@J}hd{voM79KFiKhJt>IRx#!c9ZH) ztKVbGm*C2$Z~SL__$ISza~6xoH^=X^^p;##0;p1iG1wT-nXo?}Wg(N!h%JYeg|-aG z@;G~vfTSP`xy68EhS+W1X<1016)nu>l#l^6n!lNQHp2Uv(Z%2%CGcL%*0({GJ?<^q9biuqJzDlR+ir0;?ZhnIV9%7@VGQwqEnp4 zFq@(kLxOAN;rc2RElK1_ znp9$f96SP+hXLJ?KtSwppurmC+_Tpsb7KE2!p}~&Gq~s0-RcjIvJfFjol#-qfYYhR z+4o@-!;2noLa9oy{tKdg&(QRZeH-0XF9nHxgWzXVkn$f6#!(j)J|+t++fWJ8Rd1G?9s{|1jkGr!!!GA9Mne1l=$ zJ?fPJgM2*8fj8(|_5HUc!9WUm1c9v%O|KkxYKqmS4b7+-9ILTwisf%rkMI&e_Tm;v z`!j&G5r!roHmVafJ4oxa5WTGoYDY{x#0euxxU--W6{=AT6;~e+zYBRjX*W!yEEQol zO04YlT#M<^7mn{-0Ha?9j$`}(yKGYc0oeQhI^XTI1AC`eWG5d$b^`h?KtSsWi2Bu- z>_kY@*YS9#Z&bfKv5#$LmiMvQ;U6p}P){(8TRiTPB|rD0QSo!WM-vpj59M>@J!){thaY}nuU z&dBkelLq1<_^=54h-_|l{fJC|#G(c{=>VCCKP-UAK|NnE2V__xszo^R2m=^EB%vQ} zTj&pC)On_JCJYH>_M{6jm^luDe{tacLCT_ifkXJ!31UsN#7a8wHlUJbD?*F{7t#6Q^8KxIvgMZh#C)YkZ z2cL=echl!!R+FIS^hei^S`Hl0jpmWiUMTc4*zIstfN_RbzU!!;NSx9vM;weByBPDk zoBQbAH6<;U00>`7f*bjM@#t3NWtX2YzHmR7szjle%`&gj%}a<0jMfGxhwb;P?>Mvk zoAEy)@ql|fT11_Dck0*w!rEKLHWswonqg+f4l{E)&|&6un30Zr9qw$YdR&7hxwzs{j-g?$r!<)%9@J0~}4xn0(JSF%bRVGW%Y8rq4 ziS1F`A#NKjr{Xnsyud7zwdu8u`1BgL6Ih+wIoHtwq&M3}oP8>XTsEO(0&J_d{Vj&X zmx$pclqjW)APhVFTi@Dkb|5#QouCA0(0+mh%I=y?c-hM8MB2r6W}TaMz<(wfJet@O zU<+vZn%^V?{=NV%|5E7=_F;;>0Fyt9mUk{AAvh3insSr5Y<&ZO)?`>+nUqD`k$UUl ztZL=-o=|8pdgdzlR3<&kTN#AaD zFG%;B#O<fE#nb z)};|d9mtb2|JBvC=?7qEH1KQeC-6HQMLQtXzxPVm;E&|aLAY;EtL~RzjRXcP@&hUS z$H+Z5p{#`AD)$6~xiue?xAu;q&ve}Iaa(mF!_H=W^^rhKp+_N>5f1^v<`0oqM;KDD zui=U2Q?*(;0y3YinvU?|HGQrO7~dL4ZCqczr~_I?J9a6b$A@5;Pn#6giGJ$c@-XaNZ53u8~f?YPp0AC*C=tCl2qEfOx;N{>UeLkf^n( zsD>FCWiG-{DC(glE}BwWsArqjZ_&?&5Xx>v{-pg0p5cJPvrVz>?5JZ#vPU7C&Vm@7 z)goiT!j++yco~*Mu^G~&k|@J+4!*1HM5Mb%;%Z(Z%LD;j^14TH$V9BVKfL6WE#ivz z96We?;jPyJU$)bSzoT%%lLLwkmF`Sdn0|0zklt)wgz!vH^>ol&cy}@vzvSAq#T`ml_aj3OZh)Vn zdxtU+dnzv2CHG;i$n{V=lKf&Lnu zKmi@cSD?WB+YLNBNC|j>mgK8;?pM&0-qS|BIcy-A#?4f`ff*%Qz@Pc0G}*F&<7_lg zaTT4<5`ctcEFvvC_J;{ZdS3Y~U1qZaWr?h$6IJk<79dxeG{pmvd9qI~B6FzVVqsQP zDjdY0U3xdE*ODnL$H@$Pl4NxGniN42l0$t47xejfRt`Lmu2I!eOfJCj5(onJReB9P z-0A^x=>pceNPuJ4fSYUJ|13cM9pG4-1JJ&<<6)QRVu{KQ|Eh6u1xg*!dI~Z94$&&jS6^ANu~+@xwV{;Mm*Uj07-; z4oFm6lX4)?@`LBi-}^rAVL&*Oi25UxRBy@01HNQ-6})*Zwoi4mZ-b@=dycmzy0fy| zS3Ja>Ev{SDMvVfo>3GaCD`_(qs@#?c$?vnx{QDw9=B%+qPSFaGoOe?9`^8<41P@fU zOHYN11h`UH+U*id0;ZL!dwpgvreh4}f7#ExbvG{UTFlW*0P*7iyy}2DE}1(4?aP{Q zdBASvr~dYX{SJ}=+udeN`>Dl+$fx!ieVqZ}GB<+ZCv${%y%sAHT>vKhIkei8snrPo z^(&xZFw4dTMp0%cE_;0@_xPrxMwx?2h+T+ZLU~FMRj~8SUKP6-x7j3{oEYDj9CASd ze$nQ`2OA}Q1%v~#mnJ#WwYpf1Y~E}p==m$`%i9R{#O>Z1aoi~HFhIqPS1{p%Xqnw6 z5qQ`|3|!OsT-!z#m|CLlfsFm~>r4LI#u1ezd(IW2Jp zWZd^{=xEYy_^W4C$pf9gKk(ZDVebHH0WRo0wlr|&;~z0u-w$5EHB$CoUcV>Z5j3mc zg(|lX?66t;6$p~a+;_i=v2Z%svZSfw=7n9O%ZXU|1sWW(_Xc{X_B_=>hd4zcr!v87 zu)zwhi_NOSz`jFd?=|cc^q0tXC(q}#@jcuSwu)!T9q9fnT(RkE-VB!?u^oRU|5L;8 zOET)H_-?*`UZJS}%*C(ky+9(mv7}0r_T^t!3Gc!<=H;pS|1hNWQOUe`;Ur~@mUEYv zYH!MPshQ95uC=ceR&WQ%llyrTTfNJ1YGN0!sIfu)G%IlG%+?;MLI1SsyF|%@5pX7} z-|mxmQbWI*2UFY?oIu0L1}bo+e^3GZ#58^gz&qwV8i0j>pB9k&W^ojLay&blozp`o z9I4nmWHHKzUKbjAC>*l6T+g4dp5Nm$)n>*0w29(+9R#6L9s(ErJBk2})zwbzA3e8R z_oFkSD(wy50Z!#_u0)wYxGc_vYA*=Fpbfmn&z#No@?T9)Wf%VK#mcIGI%Q8RgFVT> zHw5;rU!UmSN=%A-bN6l0()lLU{tV38CuAksBL{4agnsPaJN5LOe^mXoYVZ0{z zE7$7Gcg#Bm$|y4tPAs_d3czdp;OQj%YV5dt@uN_o)hu|gJ+UO3P!B>J^8zprxnqCw zZnKvgw~SI=WW3r56HO9AhnX9s*1N125<>6IL6E%+7f*(tANKkE3nKWU(3r;rw>LX; zRT8IOt1+;H6J9|B+pu%a+nWZE%0xHk#f=|d(w~mgCEn|#{gh^lC*s-*Q?JiH3IG{R z=9mx$`rN42{reY3RTM#S9rh{a8Zc*^dA~ARnK;lb0QazcR&J4&^S8lTFI}I~mnn;q zBaN2;z4Y|oBncW(@7{3?>OG$zp6NOFY!T7913-kF${hDtz*6y`6BOmSwwpnIq7;xS z`c_s1Dj~Fnnb21|@YRsEJxpEI`M%9zCUc24n?hSke4bPc<@r44k0#9-i;PUoqFqee z9=~ig|3^l&@U8$g?*RT=dPKUy#mu#_g9>!`?E_J9a8C8T0T+CdRJojl@}tg0FZ(~c zi;iHi5d}SUw7qBojpgZPk(})}D$zG@(CRWyS80DXwC>fqnQ=Wdm*Wqb#@1Zak;j{)t0jrkRg+8adpHfWx9iCIulPc7uv4x(9id);7EEsIY9~G|-`t`}n5dUdNL#5n=feuzKfU-1@ zLc+k5YZkRs!ATTtQ5GuBX;&DfMf&h%^iMBk9aVmXMpHPawC#=FWr~dKsk|IcHj1*^ z$+qd;7Ln5}Mut}}I~O6>*O;6=_|&8u*FjcaAE^S8tGkQ=GMw|&5rzigCCF$e6=y4I z-u2sM&PcBUU#!X_`$$s>{xbI+loUlvEi)AcwhdBA?C!;v!ft)k(1G&^(ah(#?)20} zgV}o0Ygu`lTk;v8mo}^^Wj+l8zOCd97$)-Qc#N&Cn(p<`afW5#*vYBSUY>g215v%Y@kc>sCxBn|nvKAkTAt4r!g(NL z9H@>cxJ~pImERZnf&5ds6P*X0KOwEhxcuq@=2$*kTIQjCJ(!%#rz{2j5A@K6AMbGN z{L(b}7_ET<|NO4jn~_clt2XyzD`?ZCVemdopuOgpK0oH{k+~Hc0Ap?4fVJ!|pnNs3 zK^wTc|Gnz%{c``iFJS+@f>5w?dJ=euyw<1LCc!i78~ibC>orS#6*K6MS9HrXOL=88 zI7|6?%(=f9D9fN^4wq{V=k}~`4s`uLmff2j0Q|nbJs-rn&BxrRSJf4;<8J;K*e&-} zw-*e#(x>55`!biSuMh77Y=-{xf~xK=$J;A#428dJa_@byRB-G?1jm3blf|x%*&Xx( zIB``Opxsi^r0L%7Ddl`YW1E`j>WimUOj~2T%%qL@VKuufV)%|WQCI(4`Yr{!H;?VF zTz(^3#%4?-<8N|9biA?$q~Ao!GvT!$VEl%tC?ol~vQa5&OZlbZR1ue=sqZS(D~vqD zIO7?GWl7dF$N=90Xjat$`Wmc@DBjgiCR5ehLnPde5tV_V{>pN7LbN0K&SRtoKRoaX z6@NA;G^bRD8rNI#Rbrd8O7qN4H{-C*)@mEoqR?udYi2i)sGme^W=Ar_9*ix;e;YG7 zJY+VF($rN-T*oDG;Tx3m%+BJ(dW5a5%;1W71`DGBNI#$OH@5HcEaS+GMm6+^a|7== z%*Za^24GoF@aOS*InZ`c3R4Ef<&Ts2)$w#J2Gk$_6gY6+k<^<8m zZMVH+x)@D$%{>=DU#&vyrWkEz&8{DJ!z~tRZyT?lerk>MF{j4Vg*hUPoy|`f&vOO! z@hstERg9GzWlGneJ!jD4oobK~jO;%jmq?U1R4XbcMRw%IE%FqtDJ$ve|I}OyzE!`Z z#a?8xa>a!cp$lHAz6g$Vtf^_|PDaxj;uM<}NXoRE=#r&07fxBXE%5rRhm0Pu7q>2wdz|nEsa7!;e^LD7TS`%kJvr0U; zQD{q@WZ9yZzNN)Tia-=*IK8iycK;7UuvVDX?9suRKfM}@im#U4)Hmn|@c`PVYE0pa3ye7LeI-)W zVpT-Z_N_xN*@^H;Ieq3u$#XkN&S5*12j z@32a{;}kM-(VQ7R*ff_YhR-i=n~{987)!|1otU2ENxvUa)@*l~{1|8Uz^Rq`5NRg*RZ{M!jQ<+%lR;N@?e=|4qNja^r0^XYLgaPRqs zfm7minPWwAlzS&y6DB_t<~>YcL;9kP_0?s9a?4i!KM|Q4LhC@@P8`w??g#` zyz%eJtM5ZgCy4ohr^Egl+TP!BqSudoZ56-;O5nqNUlr|1f6%#q@9x9pQf(i4QQ3u* zA`tWu$02cpq1`N$w@W%yYPb7W#jtGe?o{UmRBy-SO-wt|am5V~glq1f5(x26fv~E> zS+M|j$2{G-J+~=i0>0qzgH5&ZW!;|I>&7ptM$7L+`IkE5IAC&J04J3W6$0g-pejitYza^9W3DAwyhLJ)Lro zfA3Ze#F*Iii8`hni(#Go{PCeGCgsH)wH`^TkAz$x+o$nM{kXy^ahGR zjfO}~dnlc$($HX{Mq>g1b)Bf$%X!_+11m z787a&v`}QO+<58E711L}+)14Aph$JfPwdeKU-TvxfQmUVS+LGfENA^#sBFolYqY*m z+u4$cSS0b54G%;wb38N5j4Nb*^ZC*8sAn!A*F;f`-WhQag;a56H(0{ke zD%^3xJN}4{6}>Y5B4DA=m)FwsUcN;tl=S1IkCNKa(~G0TMX)A#ZW7NVWNK5>vYv`3 z60nhll3>4At}3|8Xb2^>l-U6`I7u1bn6|wnvHZyUxjvy^;xh_mh^nllm`PQVS(4>f zq$2h0P2!5I*D}M=Iedj!=~=q))ZOO|3N-|ZPrS9^fIc?c0Elt|RH^rz*15gpwkhS& zwi&7v3^toBgj*t*4U#^+50(2@HV;>yaK9*PdaYu!8UeT)s7 zR8OmShwO@24``K5#%#N*nV5^I?R^T=qjucbjITA4{FZK3{Fi4fckLg^(&6vidIZQD zPoXF5v)hg!_=$f{%6BF^J{xlNe=hz==uVnIy648T(R+~TRbVTp7RC38Ze~UUowDBb z2f?Dx{7dfDeLWf4h<57)&@w>XB>2f%;iDI87%%6D9rqY3`5z^bSAJq6B0^75X;r)^ z!y%M_E3O&pww3ILL^CQ8;p2fbz!Pz4+P(w&x}H+|cSG~KbkE$7yS_3zxEzinbNNwJ z)T;>91dDU|#uB*VE5M3I4y>;hZwbiF$-|oVl{^eYSKrzu@xp;*E2`}3MWo05$VeGH z1ug$RYHXM5Y}Vqvd;Mcpki0w9?8jumE0;q)#KCD#VpzJoyku2+%ky-b>dGAg`}`Aq zb08>{n(*nVEOi)I^fSttlmvE%}KnjYlBp zSZ`-WbSOQ7WgRw$QeUB-c*FzH28cgy%|%zidsDmPgo0PQDCC&&=x1YXPN&#}6 ztMBZk>E(ubydC!S-0}XRE{2(ane?eKLR=29*Bfl% zSY)!;1qUL;>BEW}l>F93T@QF1DZ^{lF6&E1sED2YHD3lznOz#(lpP4W`9EW788OQy z(blF^PB&G$H+NDbr%v74{C1wa*`7M`_^=ELXAfPLdv?T7DI7{+hyf4_=O<$h+d3z} z*(Fn2LNlpInnG%Vh{G^!uOjZTh@Tz}{Vt9LT%jK!*n^>k7(9W0yz^_RUK4o+>IWau z$aWE5p;hmYOoXl^XHznT>+Y|6#FmEb@w>Gccu9VzB+QdxX6A_(1s>zah83K6GTD)p zWKk>JQ+fw1<uM* zszxSEw3>S?oxYin$X}|!r=)WW(NZuDpFhde{P(xK%YEDCgkAPZF7gs&Uulsf!4T+8 zH-cquI79*wNkRmadXBj#$nO>3`4c@umixzInPPg}JjhJW_DLs>s6(B&BVsI3uYVc? ziREUAg{U3eL1}=)%Asj`eVcx_<5f>X?Hr zTIoXi#j2l?_Via9ix*~_1WY>hiAgR=7pROS#XdC3_9#*$BB6TAO;L&xCH{%4&ieX!L1`Irb4?-Sp^Y}eHm(8G?5ar z7pA6mF8Pc|{EwVMQuD+-e;}q@!)~|H(%{u*8D164>in&dw0MIL@IpM zVl8hde|6!h<%}(ttc&)*!2W|*6g?B#UjA(>X z!Ei~oRCKfHnK~&}&1k?r0v{^X&2*z{!bLCEyl=ks!Ms-%kh#bU*dM%VNNuI7(QQOj zkHF|rQU8+5$?S|;-9Hxh z)Ub?_{9Hn;@ShxTMw8!x(@kP56Gade-EJfxbZCRw8LLAa{!1JFH({Sdyl*#0 zeaXPxqE8}!vd=lKsu-YW`^gcIFada27YAH$3)1`Pm}#b$nk9luUQCb=s1_uN`i|Rr zE){mhb)@c8&NCUpaNa-F1(4 zy9=N?kzM<1yt^PUkKqUeVblM9LO9cZ*Z;)WtuNfbN%!2wMX9ergmAXs)e*9J;TJWV z`&UcuGSKG=lCO95_YZxUrOHuKcYZ7O-Eog3;jLL8MtwGO#*)y~z}j{!MaYC|fVdX^ zUY6k>bh#@~jVOT5*F4rCFJMn7b@CTz3qk&MxEBN5?;tt zM>1(cD4`=!zLB5w`FM*O#91R?7x=M|@-A44B{uZ+j?Ul1Xvyu;nu0x8^!5LFA5Oh4 z9(Z;cdX(3Ap1ryDoy#Bw^BQcMT;LWH4~w|cRZ(x}Z(Am1wn_xZ!RJQVSP0>zU&NCd zGs-UXPvt#`xIpID$n}>M*{SD5e()xQ*=n^DdS*nQui0ITV z#oV8dl0E0?xp)<9{N`DrAGzS#G+_* z9VrXZui?Eytz`wrmamtAoo{J}AS^YRUx|bk(YkA@&NYjL-gT(vFg+x=A}mr7d@XQJ z=`9E|*o%e!KxDZKw@r)S=Oz+DczvmCX?RgPkiFC9LHbVb*Tg|neMIgzqA|oA-R$nU zJXd%i#?vXg=tvM32R{6}$>(Y%DJ4FD3km+Hf?~@oZ@jp6JE|a}x zf0?8UZmwMMuEd58Flk)M-QcDolG>L?bMfHVD&nZ36(X_AOGtLzJ9n!ahN`pKON(%n4p4<<8Lq)1W1C}C;@C~z~F_MFpe8>bO{tCzjUbz*hW|b z(e4%W_M$3zYWUPxKE`cJiD~ZEVP(0DSuvz84r$GT3)8H6{0$N2vmrqsmoQ;y^mMbm z9i6b^4g&v}?QqXJ*HcZLCCa8KL50e}X=0oi%XSnu(Q)-P7;qu+YF(~_6us#=yXT{*eII_xfc1S|aR^Cj_{h~$o-)7{sf<+iD>wat#L{%o z&6oT1z%#S>J@N*r&3#MCmL&Ua%)U9F6@sh5HQ}R;J+8@}38%~dA$P&R6la46yJKEr ztWjwC3mmBg!Jd`LPu7;Yv2_tkcQmBF(E|A-8{ygb8ACWFh_F2d?I)asr->6E8*FJf z)ecr2@pR8OQ91)90H*>?r97;uZf5xNPfYdWttbOL`O&YcgWm7u)yCw-RKW#2pEu`0 zwqf+%(8WrUb6@o;7=gs_{*{7W@^P8*SziW}~0m#H_4`ad-M+s+ud1SrcM0ixC7zJ z5Hd&7I!o5MCDX0lRD*A&Ro{1dz|-XE!(?)0Xu243b((^CuD0zL;Pdb@WaU{kz5y%h zG}{bFEYTiCw~v@}RA~*wg66@`Qq8^e*taISv5D2WOgW9?VEgt1ss!IUA59;;5Rp&i zIk?Vq-Nv#D3{lw^u%msRA_V}eN1fHd4FGOo;t$+cl?Ky?OS=nYp&l%7F{S6>Fa18q^qH z;wzvhj*4cenJYnVJUErZ$XmnZ;LK2njDizav7Jv6fj0th9%w|6;3?}|+DRF#^B^{> ze9yIgv%Deyux zw{?Jz$_&CgVYTn0E);;9Bx1m#oO`)o6coR3;8r>0A(&YO{F7Iht<` z`sFrr#imtSVdxNRmM1JslGY9L&umimjI1#d_}QVYfB9Z_u3>lm>b+?&xp9hd1k=SZ z&xbcSC-UmLDb#w2GXu+rDrH!So%2tDA$C@L>~j`pYQsKu+e>SusP^e;I^Hnik7-$u zt@Rv&wz(;pQG+i+jHnVbYG0JTeOChB>CE(o(KvT`Rb*4B4yc2!Z7VUd?2T>9?e88F zhf{mDiq>UyaXY4H2HeUw9G@9j70r zMmd^l9^NlUM2`Y-tt3^%h9qMfJpjg*jeXH`?PacuATsI2M?m6)#^Z0gSXC6_xaBwO zH#bI!OLac5dR1pwbg6qeso`?d^n?eGztK0G#CIs8te^!YY(*W96d%l(Ekp#Vb`u$_ zY`3+hBMWJ+3yx{JHzKKTQ?G7(yuPzJYugL4WcLo>l((>lI!^hkbbnu+E&1~fNPN2Q zX_*W9R<-GmVe#)zTzw-VNMU9fV|tDVr+hnC1-i{j_CreACI>P5BBYz_hD2YQ1TWc8 z@qr|bA`OV5?Iu=pcakNcwUw-@VgNW355iTHeT;0hLLl$y!@v5aP}{S1wg;RNih=DR za0g`F!84T*J{c>8qh8Sv2TN8HBxQW(N^~&_-YQb(lC@%)>nJu#Z95sO548=D5z$-- z@+bPF8mH4uBnu;ZowD71YZ{O@lPbJ(9yT}ODah>R2dFh?3Lz*g{d?q@drOBbrFR;F z+LI>qZHYCo!(UoPRB}I+M&#$_A57M6a$L;Hf?A46?A*pxCXWu)Y|1#*H4R8LTuGuC zX`h=2ipxF?0=`!y5HWg?g2#4$OhMn!JI;rnI>fub!vFu9qh*q$9C_GZ$o=!-;7--m))#0D=l^Bo4qROi%&I{F3h42 za%diW533@bWiEGMAegEVR$?I?xAiQPY#}IJDxNEwVqx2pCoUo3C=kb-Oo2}pbrceA zm47><`cw08O0WLqb8ls8ZJ_FJ&*}dZLczg4vNspvd{0KOwz8LCOV8(dgVzOt*?<)&u-_* z0`SOox*yx#aXLz;{W*U%9Y@&_Jz(s24MM>FD@5u)rn zWnLTTroFP_WF}fe`W&I8j*UB1{UjpR`l z83z_Ur26bxubWg%(f8-&0m}=gN>HA84eU-1vFRrTidh{0K}0fNWB73uI*1RS=uu4= zKL1i4-wNeCNn&)N3H-v9>-pESU3KYt#eZ{`oiBr^_KG~|aU2t*%HU&ry~P{Nn(Nm+ z9nOd935mb>o5ENyEkTzzyWSLXEUuTwcZc1+(LeEL0}it`s)G2RmMkMFN|Xbk6bwg4 zPutc{8ot@VBkucKMrJ9Af=O+ zOEUj{yWtoXAIWUC#gyu+B60$llqVeZUPBAjV|CaSgTeK0_X`-*?*u9!G-Hu+J9YL| z^sMUGBXusi+*mb4dBByUTL0pRO*pcM_Ai^h{gnE$%0F-3fZnd=EK6VV!pU`%)Z&ig zQ``4tAhX8g2%ylau-jtlR>A<|q}yjjVm5-ag}D`-y!I+`;5LjN*&;>D%d>BJn1ajI z=Yx-Y*Mv%14=(~>l&Ujy3^B($cQ*RHGt{P*CYYCmQ6`_K{|%)u^q%w1y7(dvib9it zRTE$I8?+FpDEtbUpEV;9N8(;c3S?qJj{@`_uh_`MuL7@+zLMU%xA9TDFZgMc$`mv= ze7we&M6|do}SEPhTP7A~Nj|GLUo)(^zdo zU&@{Kb7sKWq8zPtMY>~9Ig(^cUhWY8ibiE18qykNV7P9oIlM*N!YKBUG|bLkff19jn*HAK@@MWRpSnuYFJ7-*$7b@6HuTPFD2(B-6A;w9 z_^q@SslNyTEx^CdwfUEv(Id^i*{&*!>%5gms1)pf+I^&qO<|Bsq zFn}7`s#2)In7%?+g13zgho&6GIPHO6n z632WvgkNYyI7jb^C2j8(8kemI<4V~Gxe}*V>{f0)r3A>f?9%!Y9s9EjI$mw}e*38P zw}TvGf;#v%-Qe0su46TssW!fKtME<1KaZK+UgtAnsOymmgD$^~NrmxM3fuw;jXI{R zTKQ_1Lw#$BD8a*gpmo8uOHPM8Fg$%KqQOVvW11;uvIG+raKb$0)LM!Qh8mJ4*ol(B z;HctQ6&7#7$&6S$8wlgY;iaS6hjBU+Ucr7DH9my4pgO+RVMwm5Xm?n$(>9>hbuq}& zX2+U2q>I-B{?eNrrt-s6WuNATCKH7chWkGw_hv*R6$xWf~ehV9X(d4!ykbo>wyd}SF*;nJ1gIZ9^77eDzf`+$?FAeco zvpD;~pjR|-^XF!TWtf~2dw3p{6$@Th(X2pBZAO0BPA_|7c!fbu+!4*P`W+El=@>}8OuwGM%SHCKHK&W7_UN*B0 z&nbQQ^B{1GBqQ$RLO%BwnX8fVl~tx)B8DP~t@XhUwNG%JW0+`p5 zS{e=X-2w*qr?Ai zPl4B!z&VKIxm}a%n+||LO{|*p$Rp6wmQTkSAkcOKEP6Gl1xN7$)*m>t`G5F*8SdQ_ z?}haUoC~Ve{Oum-<#(}B6hh`Eg0P3A84#Sskbu-21Zwma^+tose6BytT3^tUV>&-^4)%A*F^Py>J4DV-ehFufS9}0 zD2Mrj4S^UzsgKvw5gr3=<*|6~bd(iPp3)UC!O5OW>aswe$Eb0(?I}D+hd}wb_@Q3D zcIskc{S53P_~EZTi2l;xCJzyz%XbRnRuRKzs_+-cv@fbkJOtuEQQBADm1al!7!W%^ zcDgLGUG@ur*1t!~8wBRU2cBbuAS25yFp+${dP(LS6#RKrflumxBg)NVA!q+4%b(#) zfGChQ$Oh@DNEOXhiv>4`^rpqaBnBmOpf4oLf};xKw9#F?M29}PE;U=DvI%UpjRD?z zgG6AGHQm1wzN1mDPZ|~$ermDMWByX9pQ6Tg;efOkTl3y}k|5mm52+Ejwg~+7iG}}6 z$}Ft7x1UPo{7|7kE?0 zj9r3zFtL8%uIf)af(}dmvkn$A+*?`Y~nur zM>l%7g9VkM#Y6D7mRh-LkTg^tD@Aw__3E^vF#M)fN6}Yxp$Im+`Zt5aD%h^Rajvt_ z7whep%>Kq3qC2{4%!1pA)F0;3Zi8_UNofoiP-ARJBZjtRcUXbDDL}seXpEszxD{g$ z;>o~q0iTaeEqUkXP3l_MyE4L+rh(T0t+?7L!+v)r#1TOx>yK}ttN>4WiL7pw=#BhQ zX*-j=$l5lG%`9fqd1+kAK;@=x=Smj*tcd1d`5CB-;)nrExb}~^fs+^9@g`DDJ*#1i zr&dY0%B9h^QN|^aT=b@=RldhL^0_<_LTpPbRGnQWtJb{Y5#z>g?7)COiS2i>QyBhNO!?T4{B^Y`8S!dH5Nf*Jvzc z>^8BLInYEjnbMWIgOz!xN6-py0V0C#@kP>qWn>Dz-f5@PQZV6#_@;N7?DJoN>7hYe zZN^$)x7W2`k6EdfTbe+wz8#%#6%8CNLfQ zq|=ii#H*>IB-6yPRT)sI;qZv$z#5{CMq=vW;{%6RL))j8MGr)sT(o4h@aPI<-WA0L zA&!b(pl}mW@vv>9E=^g1mRqdAKZdcteP1sUs~VY%sjT>l^JQbERBZeH9U10snM9>S zjsA|QW$u%G*W;8+A;jZvHj5%xxU|^9_>~L2cScV*=J`lgGswL2olrxv_1L1fHnZf) z6dCov&dVR+G#sJpsf~7A&DX86g06{phf)-_N@uuv^6xjw`kaneqGeHg+kZP@QQMq5 z#+3>qb_!M~@poW6+t)ZR+ElzWBuW$&{RQPPPP6Xp75hz=jK^jQ{lebF?1K;I?F?He zl+UpF$x+xu-Blc>-8A*J;6kiuBqP(*saOjAt}K__@_UZLh^9pi+c!Oo4C?g|p{TR; zB_{um>4xT!e&4YZG&R}6KZF7dtOWRhlx5p>*f&Le)2s8O)->8SgU8&;ASim-fZhvR z2#fy2dz5ZA?2!*S-{rz+Qfa7@2{3>)J03di>aCQWbQ4-C^a{A8f5}hs3o?>y=JMB# z74G9kq%aatW%q&WSJJ z#fumht`h&l(u;seWQjeN5w%c&yeNG>+4gvg16*_au*SyET6)bbcn3pF3Fc|KjD89` z=n9^1DV@3f++q`gi%8($RA^Uq3-K&nHm#>R!mvdxmb)~nk7|_-T%Kf8`nDG?Ci@*$ zXm4ex6u0)VzstGHo2w|m0Tt;=AFkcgVJSKeIA=T^IgMb}lG!W|=H#8tgN+05#pZk4ugC#&o;q@8E|e8(q9W@nkIv@X{a=B^O;5G zjV8@9#4Sk;WUMG+{_K=h6K}VV%>`q#DJ*I19kf%M`V&1*3Z#tLsiWFA|80}&)3-NZ zW|cLv%$vSeP1*yn-4+->f+omx272((RWj|*7cPOk&k2WI!}>DrE$!=Ky!V(TV>lU$nmj_Q0|3CIIQ^ zu0Q=R@BMw8f*19VzQiqOv)h@ooWl+3o8*0?K)kPsYkzU4^)?M5NukRt!zE(u5`(yU zv_x7%v+M$m^qsG94+v&+hTe3Yxz%AEdxzI_C`29YPG)C5owuhg3%3Ny!rz!?2R^lV z(!WW-Q>|;AsT}LKII&OeIj2)i0Q3|6>Du64bk3ytHQH>>c1N=QT8@s63jibTXx4rE zEsl5B@s#)4XDsKY{ejTE_wfJYAn*u4WbWyAH~~C)_ph$2wokbS*0g2=cmKWlJN(iZ ze*>RnOnSRP4aR?m;ey!qLzn=7$bTh}boc;gzL8~Pynf}BojHZ*_++`+kR2aK4+2u9u0c+qxsoaT%T#4?%W z`R+vBONcaKcOgT0;5I{SyVQ{9(gt?Zw7lHuvKtoY{ zNrz40qE=k?sPwC=Aoo~LMQ6o^JIYDXnKU6<_g;nn{4yzU-2YB^Pmzg0q^-l_$iG+o z2m}2MoZpbe6<42t8u~jfI4E>CMtFcy!)sYCT!nBH*XM8g70rvb4>fFq@5dJ%u+@O> z=F)Y+{pUbh_G0f9t@EoN88GvAh^H3_F8rD%gR%g3X17XlecF8tLJ|N4qg?YtTR=?U z*Z7TB@(&?YydPHlCd^NYdvHu=BKzmB3rNiLED!kADtC=~=;7F#IFRNqh+wZH3bSL- zJVZrA1MR0D#wpoSa|c`zq}XFpo|HJgizY{h*QC)xFq3AUkPaz4&G46Y8eqEm(IUG( zxWU(3I2hqIe9Di_%Ai_6oD?D}hmCuf8h0${8anJg*3&-CgohD&f9l}Xm1Y?r+8J;I z!M_k$5&Z!Y=E8FC!&1;Hzo-uJW-J%eUfb;dqU@Z4GzqsZUAEa}+eVjd+qP}nwz_QF zc9(72)n$BB=S<98{t^Gpdy$cmcbSoUueF|aZ)o64han+oEP6JTY`-{2c3)$2H?l=5 z_m@JKGo+elpvgkr1B3rCd>-^4xFSox3FiAt|N1{FBY*d=o$)xT{MfSAKjrZ0L{KjC zJ%1`=pL(J%yN0i|0-xgki77?ye<}e*Ele)@x*1Zcdmo%m(QolTpL^K3kV3P3f#g6iT?+^yv@ zZ}k*WY>5A=L%zq!CGyokFe}VeaCSmUU{jF?kb36$zrdwVYs;h0P^9}0s=oCID zz1qAV9UBVgH$()g@M%3tT^S?Sb#*S@MZ)oTwEEHc=jBPXEzj=5Th*7B5cLtxtw7hc zfVH%Jd7pUt#DoKSoEYj2!>*Rsf=#(Wj~XnJdmgWRq;br0^DN1&A2yc5M8`D7SA$h= zJbzO+<8bGy*#*DJPMYVlZ}g4nEI;k74?7Q^DJXXeB(SR6Cka2U&{W0oBw}c`<^K`y z2*LukAcp;-4R57)paPdNQC>ho^6aJ2d0fST;7>sGU{@U#42A=U9?1eav$tNA)8(}W zLrN~NPRG1n{+kgPgYr)FE}aVUuzeR5G2geq#+Z+Gamg$`rU@6xP2R_O0Nr6@A97Y|Lt+mclFi!(Y^WMii`;}KeJiJu&~%ubVJDt zp-rNmwOD4h%aHwyB}GGdY_(3M+jH`5U}OU`OXOF`}43HE4e z&}?0#{wan{SSXM%J7{(G3_+73L)HW> zm{7DMItIqRp}R)Do9aXM+l}~e+@Xs zT`KyAL^$5+O^`7kewrrd=yb-0riEBC>LjOFO=}pFgY#T0 zf4UmX)kATd7@1RotTebE(t0CLBrI`-StJkqPqqSR-q)$NdD`5d5(7lBg63uls*F4P zc?3E6zU%y&{O-mSUZ}PNPKzCoF3ICDu20>Px+@1Aer8{ktJGw4X|REqBIOhCZ~-0mp*io=TBUs6pQ^QJP~zQl2M&l15|X3%d_U=-bTh z`lqi4G8`%Ap7q8g@5g5w?;7nq5mCfb1y1HXfWv$K#YPYUu@;Faa69;@ZDgcjWY9P> zQkTMy<>bcSOPu44V9xIWuh!z}VI*P-=}$I@ta0$9MN|jKV4$Bp+&}oEaKRp_P|Kb9 zDALUv7Ee*a&Q@Sce0W7hiJGAX3`t7%<3-f*)-pf1bzPyvMs;o9JV6Lu-LenvwUBB^SK~c zec|o(*>k_Gg1qUgO}+k5XH>%hy{ext2NQiKME$mz`ZmBi@EM?@dryqxB`~J86A+8D z+}x$vWL$!zvkc5Z?TPy$s(^+jkFeza*?gZX4cVJhJQ|NOv@LxhPYFgti+d5H+5Fz- zU{w!TZmr`A2zGU3mkAa+DJJmi@qo~azX&oQ;>O*6cEQ{Z)G|8aB1S*+src4BK1sKGGQT(6AG;Oo17N>18PG-x7?b<{j|m#!P7WB` z-2ybJ&H$FRbafpc0BS!@>7&x(zRAxLI$iv}ZS^+_IHG~>_4efQ8VG^8R_-lF6?6rs zvp+V(=Se0;Te5M-q4ngbAF2X@nMhURwB*KqIDMw~b%z!2GNw`vHFzg`3)d2*WFLM!s?gfMDO693-@{#%N_?(9m2>?oALV<3zbi~z!m|F{?$D|FzLOzT zV2s3JA9fu?QqP+EgHbFcMM=e;sz39o7TuwL$?@;5yQtBN@0)40#GH6$R5Iy}Pc3n7 z(Ou|Y^nBNx zUT-&t{G~B6qJd5c9R@j_oq?}kGrmueH9U&O21>z%t}cm+pc7s)Z}WpQ@sTQ0dkLBZ zM=#QYsBekO%O8v_^C1KJMWL6C#&FXENO*x)l*@b9$Kmj{pxDs5A)3>T;gK%iQwH)u zP|FGgpkTO63{1T&-cAn(nIP0Gf-6-OODqtO6sgO%$Vn{WI)G0QkYtO(rZR+q(|#ok zG!JOSw2Apfr!@h~0awg_6|Oi|Hp4K0K!a>0Ub$1Ag5GTk&Pwsaadz`EUqGgeBg37r z2W}d;CuCBet6G#WH}RRN2s!g3GcvHZAyP6w!C?cV>`1kb_KnmX%VDB;e$CAn$T_FI zE<(Ez)q9bwt+d?7)L!n0Dd?XY_SWkHO|B9=`2di=?=x6_#jS+r{nR?_Q(rq~Acc>p zJTx9)4op?y(cD^g`&b;8O5rnOwiK{~%azh9PZjlY{eGBf|>1M#oQ0?FfU zZhR_4X^wKaHFrBw`{Kz8maA1xRPZ?XVl=#LVWWTUNRXk+`Hu-M{S&hc#Q7^1wV7K+2m4>B zZd&R=$BcaX`1nD{(|rpxbVSy>ROmh(MU=Eur1MFj<_owSHxbsOf#C<#5|>})3UI6 zm*5-{;;{L29$Q8>?PIN$3|Z_Iof#2dDx9!>i>Vc)rWF-WR?)68XR>{$YyK%cWUsrj zy*sw*{tH>vr%(KubLeYpo7qP!6YoOQ>7;mdd!q-vBN=Hl2z-L^u zz<|wC11h!-lUlmmKENNCIzIXHgXDo0zD{O!ed0TI$Bmf6L*>gTY;BuL_pCeIDx1Oq z7U`|90986;H2P{XNum+c4VfhpB%hYf1@W9dJ|%w$J)@FDj!0Sv9rYlqwbZ#p)botJ)B2gaEk-rW( z(C3q*|_vyeCe zMeA3`2k+w_)fxVjr#bU1jYttJsP4w+7P+vWA(|+3d#4L0W}s5Eq%K7y3Mz9|7&KcN ziB=bmS4Ab%jjyPhzGM_ocOCgqy-7|gEpM?DmLNgYd46Di3G1?z-jc>vN9>Spr@K#@ zJb;q&J@>X&rEoA)2@_6Nj(^E@Xon6(UyaWI`p=&?O?t*WuQ1#vT+h2L zsc97eeNBB`=XZep{jPq`ZVkugcX`XR0UE)#-)$ZoGtPmLk$DwyG-4SN|0!$dzfnUJ zd}CxJu3x(BF9M_p-K_UPhABL+&<9O1Vu)JS4=ztXUq^F8zYE^Y=N|of&D(;HX2;u| zsWZyS!eWBsYws6)kK1W%E2Jy8b6?r3+OPKAh#|ifiKfHj2`AL=+kF(DiI=r;@WwxP_r(K^h%iAD{O3v+@qdHJ+%Itgq#w$t=<2=D;PvoDxpi zHRx{n%DZ<%knnnv7#~cEae>?6G5ln`6=ZZBG0DG>W&VE)9T?-%uwQb%)HnQx27nG> zgU@fZf0lk$s7AaQZ~E#w-?#P;1d+*lxIgSCd@`A^9logKfyXl)-fDFbyRwGCkc9_- z3l+C!3j3X8c&k5+43|9 z1E2%4;X@YKoFTccUmLO_;a#Wu>CVBmZ3RaUymUvMgu)}bePzVu$^DmCN!qJH$qe^{ zti_&k&LAF^AW{>U#CBdNxyHt=0_O+Vx|NDzx)hEpmjC!mSBU>sQtvqxCK3vj4e^FT z$oHKl0w$4{6fxm{gH*{tFpeawh9kpjfwPcYcJ~#Rw=;L9D1?c!r3?&fDf}z>`*7OL zv6BWVeiO;neam6qLfN-iv}SE8#b!}tv1F>7-4bQ+rX^a^3tvdf7Y#8fQy?othU{9C zam4&cg^b#YXe2GVT+!_xxWANZ}2KSbMTj zGe^l5IdzR63pQm!#cELGOI|tczIhA5t{KSy3y=wgd z{bi|BULd_CPFC}r)V5V?V$(^wxok7zc`?~ZGWV9NVh6b_-AX!hTsqKF&GL+3J4BI7 z>pHS#Zc!QA>AKi4GhEIC(MmR;j3I+~dP0-I#0D$H6Wy*D=(V|Vw_s4^WyyZ^&MX-R z&2grCJE`4jn|S^p9$!Wme0%wKv3!HfQ$qM=iiqs~xn)oULv1**6>^n51aJkl>H2l1 zP64kl@dq*Mr(6(=DOR7_$l|idN^GtccgZ?@uGQ7S()q+ib!23xd19L>_EH8?nt<-V zxbLF+3?_s6gSQM%Z7q>KtU~cPC&>(?-+^+l(%b_T0Y!N{i5xd{qk}oV&;z6IMaXU~ zWYWRgYWWnkLwhV4+7p`BlS%`-?k>(rIPXEfm3;?dqwHll>Ot4p39DEPd*WOr$o*( z1gWbdX7t!fv4%I0L7lf5{?5p-R+-? zl=P`$O@k522pHGY&*;n=pEU@fE|F&o<1twDg`rjA2ecJ=5%o7gK|g-Fl9CSUynbpn zFg=nO@kG%Q(D1GYo_QPl(W5!t#1mwsES`2D;#C1P)@yLl$?2TwS4~$UGmv6vxuWMKAp~g#J zp3zks&7y5iftwmm^{}oEipW>-UT2 z6~DotcEfeq&UaHw%ki-PiyMCJHNgGhmUgBno}i=TKsGODgzQk#8i7#IbP421_um*l z3|n5`uX>6hQtgL_iBP=Ydzmihl}Nc{m!-%!rudNj_JjM&^Htm7T7hbG+Dq$obym}3 zNp_H4uuS>c$KnvQ4t?np5uE70Fl2vMokbHK+{VK3hcg~};>=JMZu!Y%8@(Wev{bSw zN5s7a1W5wvJPJrJrKWH0cRAfoE)Jgv=Du%iUsqiC!n)V|y`z$fMFhAT$(%~)Ajq;T z9*(#GP3qXjB6%APu~Q1of#N{Rlo(D;F&obPAMt)D?Waw1#(mz=9cmV%NyR62W1f)v zME@;+KJS$aJL230R*hSA;{m|a>Hc-5XF%h-YeVC!-Pb+Kyd;cPm zeOXby)Z;eNcJ=GX@4oqAX==p1Z38QsVqnMVd%gDG;xq;fuWIPPVD02WGAG_(m-BGg z?1RlDZ#CXwhZ9zF)$fjpZZQ`s9)c7{?kEb)$-+mTyuTqW=)}g2o`&X)Ph5^)YrFTq z+V;&oai0ttyG;kB>P4d1;ZDi>VYmmed|)HLEohc8rvxQ+7012JCl*XVrvN^_pHD%{ zBWW*nC4?g0k!BrVA%OS_>KzfGHW4~Je7c+?;BS_t5d=u}+C(EHgCoAdfma*&{s{PN zHlm1A_JPACY}Nv*BP~V7mXjGGcx76j0)KuL=S6&&lJsz$KZUvAk%G-y9@kFO(?e4y z$8UXYzO{A>jG0%~uWnQ>!RIvr{i{3Mtbb>$VlS3->~MZ5;Gp}d4o635WV@gonJ6`O zhfaeH0>hg~E{Xp>vMK(jGwDT`LpSbt|7*OPoy0n-W#;#4t=AfMYWzR3?n>^l+8<4p zb9D7(PJrYl9`!4Eq>SNEvr0A$={>`l0_x#BQQx+VmDf6TTMJ;m?+-n4N4*@de9!+y zhxo1TZ=DOcwfpLm)zbKi`P9zBlU*Y}6$jtNJ^J!wjtzIyOvG|W<=ZbLHnb{W^kGF* zHKYKs#gtdT{?|QIiqpeU*fUhrE)j;)#FI&G>NE<2|KhY3i;p?tGZ|GbAw~>I^T}a8 zXTSq&J5>@V!WFWE(4}bYvGv4CS?GxnTj$=_wOLw9fj>=b}jub66l|;uvme!jc9f&l#U7m2xZ?bp%Y`6C=88fyk#x2~SLM=ccbG!3?$H z(le&sO}5fllyz~pt~7#*QQnLDu^-|2W!ge>ZwjogwGirQlIx3>}h-Qj308+KQ z1SSnvlmZemQ>xJiz1=>6L}oVHQ&3uY?QQ3DqIH?O|70y_;0v&ilPha8_^uiPZ28{J zH2|)kSD*V@zU*xQ+M9rt=RSsw@0WigQ}LWq)@&zTyD50|lF3RwTm^#31HUw=Zae#7 zkQAPNZXif5!o5>u6*6FjG_R&6*;>S8z-$^?hjnM6$=c3RmpxVOQpK5}4l)%=V?q{t z2?~Ac0*|fzSEny6?2B&7E_I+1+RA;gHdq#2Q{Scg=BttWJ!sbeOrgFlMyM4@?df`+ zaP!g`2|cFkp^@5(V`4)HmTs$3P|M|I&by9zh4PHp@W8h0|2WUqK%{nEMH30`o-Day zL;p!3^Ng3(z0pv1weYlG+lsri#*?j-=+SxkMds&=vbJiP6R&BxD!J}iKIny6BoFE> ziMg|KKwe>*E^RZAtvF8fP1V(G%bq$$fk+L#r46~r0wlr-OcXjmf;+*ZcS7=2+)N!JQtdu_2eVG%WZGvt|na=9JY|@ZXyK+f3KT2-)=o#$t%q@DUP)7 zm?TfJW|Q9!pCu0+At4(=R45BLT7h_;jz#UkEsrgK&+l}%?NK$)W}PJgzGPk~`PxA5=aZlqPsNF>qgCqs6-zPV zzX-_tCd5u1`KaH!x_bI{mdF)s0sgQ3v4Hh!+nOhU^%p=3(6<7pdjjZJfBX9n*Fhqf zekJP1YgEwlxh(R@#{#5YoI(@1^#;s(>JSB4 zCo|fYquyfdT~zVl_{7ua)zSml(3oducJ!K4Hws{hI_G*2B5AwlylKd*WFdt}zY=s4 zxF=yCmjWXdE@-)wz%BNZCW1pf>H)==j$0JJgxzf5hloibZg<}mM4!pVYM~=5XegRr z6Faeh)A{%%5>L|{mpvxm&+9$Dv`%J61S7tpIdHp|AzZ_cZ{rc4kF(`Vh_P6?4GQHL z5!0$n&29b__0O=SC)aZZ*`~!(eo|5+6pp#|VFVlpG$@kJdzKLPY}X0RhtgByivES7 zM4O+oVa+$0S46>7oE%L)qU6egB9jgbtKYsA3d7Y={?pc^iD0r6@%>fvJ#(TDcn3-GC^0n}{y7!7Qx(AJg)k2Bakq{+FU^YVId)X(~FuIY>D z>%c<->Z#>yJn7f(*2lRnlHzxJHvOM&-Y(TaYg44k5e1b*xIXGULEg)iOogfC4;GGC zf|zw4r&N-KurP28d>wosnwOEM_Wkfr*L}?5hn|YahFX#~@12SGZDE8#muuIGYt5S- zVA?Nhw-g-6p7-E^_nKApc7j7hX<0@6YF8YWw|9at> z+&6!|tLtZ-Pr&td%lE0>AwOqQiKmKeG}BeA?3?}pLyJSXMDi=5t_G#uZ~R;D&9ce% zy2JOtPcZwJ{BA$jyPIoy`5!Hc=XL*n^DBN2y7lm#nT%mbAQdo0v%>3if-qOWDj2bQ??GuUYqAm^T0 z=s8&mhi=^Akmoh%zySG2AxO^PqM0sUKL7qS;ue9noqNf>aceuhkAQ8**IQq`+4Q7d z7^z%VUBP3^jmw0?3i8Df0@)}Zb4EFFc8#3W6YK(;0B6(VDxL>cm3PDS4f=!_wyV~X zag_p7jg?k#*rgZ}2|idr{`UrT!rVj$^LLe98TjP+N!p*$;#Mcm0HZ^ zVPT1G&&1a?h@69(pEj4DB!L~+aE?BKTR1S>V$M0xMGDubp`W=(let%uR9s9=jP~!z zAu3xa^?t5+Qq?CWOKyq5FufiDWD;6Ci7NcnwJ1){25?k>2j{t@)dP$gd@8@m9CZdO zjG>usG^?ja!1EhH?I4{ltYwG68x{_p8gGaLJu=VEOPB-!qS8h}V*;~d{1)2HpmsDaQaB{)s|KvbF>Bz&wbIKXx0wz zZ`}Q~x&|2!tgtiHj-4TvQgp(y^GS&>p4`8N@z9F@;HNL4za$*9-==wg_r1&f$X&W? zR+;9{kvI?MKAv=kvYt^c$br^>r9Jk$B3_K%6f~?28q9ZO5NE}u$-L@MRr9w5t0yc< zpz#gpa3fodT~+fh2L2GH=UF4bfn&QfZJPtLzQ6=AgrDA!1z|4pj+J1HxKX5bVL%T? z+#V2LA{Kt_OP(ln&h4KO%E)!Klg~0-sqIZH$0ftEZzM1ymB$wTVl{Dv9m!7V$f%L0 z>$1t9Q!&Y%qaE3r_F9V0nJ|5NIYR^ zm+Jq|ewg2>VQhJlbNrj@=0JKM<4bI-&>z+A4|RA)@H96PIU!jt7sZh~z>?|jrcCi= zCY`#lx)eh3R4u^9kWiX{;WZzUIQod2he_-T^1a zu)v8kh{Kah&B1}#zQ_FOXoa0&`DFrfH}3?VNAF0rSniL%Ahn1oEj`Bux1SvM-3?{B zYL`+|N-d<<0!A{t()uLu!3ppmk|az5rx9CreTu;=?i?1;kZX-}>NJc9SRi%k?|!Gz zCZM=6E*xPRp1FaT(5+dCqEfsL$Lwd_JdDR2;j!rhW-cjmNl&UJ7a?166-?%hG`@*t zhiVx%jH0P^3qF$?H6C28qkj(E6V8YGNN+?mrIfVhTkB;5m3Jz*@eX&$XQCl*$jN$Q zjUXWVA|V@A#u+$IW+&B+o3!lOSN-s)j?w369PQ=DrAp}`W(M(1gE(7VQAFxbrH1WI zC8%>(u#3@;aguunFb4$a2tX(kze%)`D>WxPV#U-PB}(E_D`!sSYwAg6(pvo5x|^jq zktOYxLQ+Pl&DuQWKYZw4AKbBe;3SwU96|>4Brs4Tf*XR041eMhuzc6vjio~Vy zkt8oY_BSJU#^720wow#%x3jN5c9HPZLUof<2;3k^Jml-=x zEXI2vS7NBu7|+d_x> z>%v01`u5)c_JhE2L$xtzWELm~RSQeN}_mz;MAuclXuv{8lLOy%E4@X%JUPI2rkrxE|r% zJQNYYCYX*d1|RMMnprF4^(p7=&$zQ6K7gKrG@9V`(_d)uqIu%vEH~blMmi0rZDKbY zYvc04-{ISn6=s}b89V^fgQO5Ct@mC$EHv=QPe}U1=qkjb4$xYv2QeJ6={`{xpSmjl|(yDZ)OAaCrF#SyK8Vp(@|-U z!BsIKw`3c9s7va0hOU#2&Op)ow=)YBoaBlU5XM{rI^YhaSswoaQ!X(D=5p{BsW9}pf1P{Z3_Bx?J(yHG-S z`c_+BcLg?^_keU;Z#@j+s_#K9();YAo#8=Mkxu>|WVIZP`lCVZ6XXFJCX#Jxgf5Yn zf(}OSB1B<&zX$%c=0>9SWsfTLG&-AnJcu5I`qZmistRWRtAkM$zr<{02;|#&cWf9^mT5>R7(7@y}|8YHQH(^32wOG2;AG{ z*R;O7zWl8}Puad7u>qd3fNr?&$G}dKPpG-kbQbz)0^I!;<8QI{x%W0H6ANAH2>7oT zv$eTSA{aPX@rym-MM(~!i064r&ln4R>$U(C*{%w~m8bX$CMB2lcd3R#b+(dGk9pbC zWjLmdoHFgfnAWpHUMSiAB6ZQzY#<4Bq)T$71@xj+hCs&U-e0umQIcC^SuVVE_71&B zh0S#;@TJY`ych+|w=?zg#o4$;gw-yGEFL62mR0McjElehH6hHrP8sqM|8us%vwXRO zvRrTCNgK&%H_l<3zVG6hc1{fF|FX-naO{R5Q3nV=Jkk5xOjYr&-+1b08<{)G=P=wTdk+~2V-(2xl1wa56R#MtK35yb&`vH0bs zd&Cy}jW3O=UdGiGs{eSA4H#m*W+4@puK@>_k2L7hQWp%3@;{Qe>c>3#Nmw)G%lJ5< zzlbPgi?IU3j24-64=9a?;-sExHUI-D2-TufK8a=?(?(I8-kT!-Z6u~b6?n-bHRUpF zBnMWRSzx(C3Z+wxq6KA+719ym@9}QEPcLVSS)`Q(GSzLz$#3WmNSE65gGlA#U;(9+ z%f!1o`3Z&KU*dI9f}1JqSkcIb6i!!cF~n^XCJEJz*~+<8X!%U4L@zAa0soxcZ#X0y-tr~9TM#LWHVi58Q|H*wjalGd=1>vY|3{tJ62 zyige2-~rx<^~iFBGQwG_f=|-H2C!$3FLe&gbWe*(f!94Ej+hoMtTCavku7;zi?ar( zAIN;aTZhb+8%Z8iS61x4bbNWsS6?pnbzpswE@EaRgPyXQgOeJ~F|ZZ%YA_KP6Bbz` z*X?_cKJoepq$B}T{-DpHV1)Y4h*qATZoFN&h5Iwt{Hehq@OHAea6q9H=-sp(!+&>K z@h?vOt}iRWLGL*QFKf7vZk#LBWqZQtG`5mj$z`X;T5p62GD0PW=RSy%_&lS;YwQpL zul@7qu5vXZ5!q7*BN7dxQJm|it}%7H_|NNe7zY@XfeS|hD1mx2O^3TTpP zX#uRBE&*y%*#Wgu*^EjX;tm7g2(a!7strM1$bAOs07}B3ePg=*tVJhT%lSZB(htX6 zUq7Jg3skpz`o|OEly&gb+4vgi$qu-1al&-zaRdJ0bBjTkgyPp|Ug;tdwLF%z1#ZI6 zufx5kDtK^mI-7*WwFTVBWkls=l6GXpnu$h7eH_k85ru(g{y%jP!rQ=OZOpotDtO{oF`d9Vf zU#9J1Br~jXter?(orX>e%qz(B8a1Uxs@-}i9PLIbEa~5jemPSpQPHeTuEOHkD|obW zdlg~gqhelG>h7lOJ&TENF2>YnB*Nj(P@2rNg7YK|?5UW(s@B6|{_f{sD4)e-!;_*y zGC8OBB0Dmysa6!kSBLVP^H+fH7l4RmsnAQQ<3t%Yl6qBQGLA$Iygo!UR2|>NmPlA` z=6l0zC{AIS~v z{OBo$pWiKqPYEv}Ko<*)=erMPU^^i&n>Ztw2} zQVSqJ^z-<10~|i8%fA4?@wv2EzxBc_9XK?T%5h1LIk73sXJLW(MS_?roBwlmSMQBDl$F_hNjcCJ5ywz63Hl?@3I641)}2^zAwua6 z`mZ!zIg%<|Y?S1{?%Tp5&kVqIeuR#9;fJ&dh@%|y6or)@RKeH|76acN*oWXw@U{w3V^>MI#oAr zx#E0!S}-6nt^8@x@7sv4-PBdQX=_o{A;=f_XzS9~%Wu%(UiEg5gX}w(vb(kp-T!BC zyeajWeoLeGqi@$4a43%Nh5f!5^T-)H&man6z80u_a&qa49689Yjd^6E>FK&Th}@wG zH3y)Pt}{zAd7ZCxld_GJ(2XiKJE}6}`Da4-{KjPFymWRUVGmc>77L?Q5JZ7^x!mpe z^$Q`nT6lcU(cFxQe-32toq7)~1vvfYHkH!YgF>Q+DeOTP$8(MKF?P`(tmlXh+irHvR2b;dw{9bbUK-mH#|BQC2 zk<>@s=b>ZW-mVb&8(OEGhXUs1>3cD}HM-?S2IW`Ax2kUG&jizH;ay%z=F-*H8oEUp z9$NtcO0k@TC>wT7!UKo~=V#->8$id?6Dxx3SL9ePv9N+bi_`Mq8yrrt}I;&0|=LV$s_9^ZDJ9 zyQ*_dB(?l{6U?EFP9l3QuRQ+_=@zb-4VmZ-gu1Yp)i;EtqU!NUuW)CGmnCK~Q9vv7 zg$eNXNt#(Nk;m)W<}d8M+RDHNbn~kU$GS@~v%6uvn^Ul)dyc_Nz<-6Ll5b`hbUtM#~xEE4#6?n;IJ2uIg|5qP{0Ytlng)4yl*^+;V^TH9SOkXoCHMW z_n`MJ+IUV%z}H;mj)`fiutF#dXKi9dV+4ujU9e@oK4Uv%7*W1D$&)I#cU_>Fq|eAs z5|m&iHEgHeTh6E9-o@J}6bq+JX~4i1-cO~~Gd6J9%QOEW%&SM~_pWRA7F0B%U9!Eu z7GIe)|Ir-HXYk%XiW%HUk8*F!*^#9Sh5&?zU6C#A2N z$RL7qVP{lb{M?5HdDpx6Nzjw4yZXI)`?C55xM2r`2gz{shK(fC(h+vD|#} ziDL;KAip@ar@RTcG(Fp{bQl&Qn|Bje2;063Iev@Vfb28Q@vC@i#;E!w&7b=S`)yMm zoql{*hkvG=<%F|dj~`Be;U*m*$17q+ATD0PefNJpGz4TMVEJn(rt>A5v1B&-&|Z#X zaL*Z!{WEVYMx$Pb_CfOtpfwjyS47SSj+X-`2k@xgPVi)x71{mkR@sWt?KftjqDYr} zNCAiI3TjbsIu}f$fh_S;;DQi2Ne!zJ(rz~N;flm2;W&&ret%t@-nnW^MhH7fst@Tj zO;Vvx&OkXWW)vZ(h7Z2uQ0BnfauT^{2{S1zRF+IyKc8BiNMq*m<7=chZfhXBHDxve zq;_{;^^h}1%kYz$5Q(pqF_2~HiFz?%1rC??J1`Sar3!TIB+lYtPSfzT?t$r_x7nW9 zCV|$&e8p|anq;}8Nhrs5$5@v(zCu+Bh>SGXxQI{n6x-z~P|e9w+9rLOEkc5GyzlS< z+sXZo_#kLVG|6=fA{9tP`KcwvVxfI!$~gU8GJi)c5$_{W(uRk;ml&7AU-}<@i6};lp8ga+N6kt*Do1Q3 z_ej&0q5`WkI0jTz+(6K!do@G8n>2Hxn#Y(dTeK%o=JiK{~IjL)nrY!V%gGeD(X8*E2+M zN@})8;6D?q&5t-Hj^@{m;d&_ypS8!CsN14ytIN8~+Dqc#iT~wQ7W%18Yvt{8=&mMk#VW<9aU^gzZ*1x=S zn|}~kUPV4P+~%d+&benX`Gaz2cW>0t$4N!Yy}tKG>u5QCcB;vN&Y8v8hLf;gz*66w ztd%2nrj?%2s3N<*<`Ex$m!Q2cSCEOtqi4{T3ae>)rw{USu1`Pz^gb<7&1|G_Q`g?<_GCGFm7R)@exY6q>4+BAM);+Lx95@mnjG~z-U>q!&(z0NvG160BKfl zl(M(ByPv_Crgb-=%Jd&zotS9n-P?6NpG5sX0}ig+!N)+od67JV~>PNrr`mav47P1WGRR{VGEkE=5Y$aWOIf{j8yi21F3}4FHc-*vg+GPW zI=nNdLcYUJ{)?r!kE3oJ9M0??StSV1eL^|3l~@hlF0IiekhfUY-!Y2iFswv%AFdwP z{(ofGda*u={ti55$3Q_jE1gfdX)qOBiz8l_e?@r-2F^KCZDf{4zrflYoi)&|h-#w1KCI z4D~J8K9M)nQ=`H|Q{a*e+o?tUPPVte&exRqJoLx@nM#o_lg*o=XvzVC@M#ms7rt@ig7-v8bGO<9vjl$9jPKsj3+awYXEIJUd2Q|Q125oMZ++m>IA2WdhX z+_4~P@jzYXoLfxP;Z}2JFM@9Ls_;~n?k6K3pzC@Os%+2`-p=R*&Y9=l2*gc?vL5xi z4q$2$#YXZjLiO}ZsdIb}rA)n|=#X!2mTTdj-&eck!kM7IUf0b_mX>Qb{fE@%=8trZ zD9=17UechZyXufOl6tL6NzNS6#ugQ>7yVcsGZCoHSh}-hX5!_(AT+ z{9I!o5TMGP8YLOncZj7$pVf}4hJ?o1vE{+e-mojk-55ax&$`SAPHmxCio8jC#l3E{ z_Sc!xybjK4A@547bRb+G#MG*yC1Fh!1sXqdRXo8!DWlWO!6;_T&CKEO+X_!CG<-v9 zMmG@L0b;FoodR(2$2i?*2LtA@3}VS{yI)%ILe{vt9Zg{#(1wCbe;&r>#upAWqZ5%V zSpV$D!n6&2qExFUn*112q}LgWqXXP_qqCT}sL=Gnp17%U*rq(!vFj0*bdidnk1t-W zoRnNTq#7)aDMY(av;>GWVE~`4&Mr^Pn;<~}C!!!D`Jb1=BcSdz1(9~`5HujJLcsD> z2WwC~2Q;TuPW)kkP%?Bi?Z{aVv*ML9y(`H*gF1v`KZ#3Ir_T zPnRdWvTCHu^D#gAYyTv+R#H9wpZc&WJQn0+Wff}I!c+N?$K=b6Jbqo1S}lg+U-5R0 z*5Rd>%$50*-7P%2zOE};3d%%1DHwGU61ge#yItV?A;Am9(C07N3YQ%5fC^h=cVocj z#x<|U-hn@##u#9`=8M6`wF%yCw7|rgLR=EK5uY-0%rP&8LK$@|EmdZ;fsEMYS`#H5 z2a@8Rb)u+=QyvIL88{+v@J|)$0H1W!{sgI{=c^We893pwv)XSS$zQK#sh&A8@{{m4 z$s_cWJost%$g>rl202-08-{i7r;NTJ}*+Y3Yz`K)XiQbk~n~Yl=$7w#A(_LuJX$aHTBE(9<_erqRij048xQydl+Z4~fb`NjMFB0?A<=z4#t*CP>9 znJ2(|y$R9LV5a(8g5F{E;cJH}`mpCA%&INY4aZX#vU!F*H2!Hbx6#38&Mo;Z$4kMV z2IoItR!*rlqI}i(e*!%d!t9t1vJ$slU=(Ms8nE)Et0}8E310);7iBp|^W$UiH)!+@ zCQAov@KchOe-Wt7fTklF<9i^UUwo*@x2&HP>!;xt`~K4)9;5EGysP(Le`m|_|Li~9 zS-=18^8E*yQ=%(?cjOyD27^ZjBxG3f`vV*=u5t_8Yvc-#)UMcEir?UZOwJ z#cta2E4josZnoMm5Vi=giy7lKH*rntKdJdu7gy0fvbg@!D(c(*%XNA@`XTfP3eGJU+CLzCCi>OKQ`&+k>GgNF~D_>YIX>-+yR-2Xyf zD4=;{nwu;~F(Issr*Uz*IX#Xy&f(owZs;4{L_!3Fi6=tC@jY?=RUc&pm*u*vcA2OZrBPWe2e>o2n&GK|S@Gp*hu2GL_eQo+^U3~}79jfWZLIkaD zFyxp*nLfX~FfLh+_k$jSXO~p>uDE?bkf`d-Cu@B zujKA>?SDdMS>-iQ$N%B2XW9RD*6+XHApe)_jyDEcI3vwQdq{IsRq*wUr6NcKmzRuL z6a&dq`8fFBPuX;%_eKo*5YjFTH)eS zc#^YtWZ*6jp|`_s)EXXlJq?y8EXyws&5Nzy-ECV*-#{ZMQ@%;%;84wlyd%=FdP0aK zZW4ojtk%Mp%uUmnM?VSrRZHZe4p+qv5S=UUnSoX!s{RJn(g}_y2QcujE6%X4pC>VD zH0YV-I9Or2$xdY%M1#NrEczI0g=gKVnQ&0QPH#c{ui^jO z54Lwa^8Y&i`!n$Wr!QXqm>lJCe%X=nelM67y>W)?j-N{?sT{7eU%7AWN8vLw25@RXwI|BHxT%rzIYiW=88g%t@tG{RLiEt zO=}^Mtcllv$p5&ywW8J4nObdK-CBQC-Ec%%d8U)3Kg+WzG;+!=2V~ztb$9 z>;pzNz(?!Rz3VnzA#;k-W1yY`k}eJ}Uk{h6@Qu&*WjrfLyC5@>V+_)ckC&90k0A&e z@viaD&$9fRcTh{9ty*3F>g(X!XfFy^`Hl{9;2X;$ce7Fo$<694a`aYifjByzPbRc0 z?wKJHZN?U_28#r@QFv@Mq?%{C)**pK+HkiZRuj9!t_f@~*}vUW;-uWFRZLf(2E4`L zG~lT&yb~mU;xxu=x;^l2vTy6P(^>+%E5)?u{SPzPWTW#q9~J%G-QHhsDic=Ae_MUm z{=2)~-(AapcQ60(-CH3b+2V}|PDNWq9)Q5^b~n}98RSc{|0pf;-THs@AA0;h9&WAA z|J|Q|ziq~PSm6XMwQnin7BSLDy(6X4k`Js(*9e;ZCb29no#<#@a-1Wjuj3A^@r>nN z@I!@m4%u*HbUJobb-iw1fWN8|TsvsK?{saDEBdsv2u7VSI0@k2pQ5+(REWarh}kJ{ zI%ZO2C^E=fHw1AM0DtsP#?uN&Uux0oc~YJbG=(HBN*43C#3w-fA@a6r(aCknm#Tor zDy03n^p2pqY6^ zw3Zughb?h`s+0S#gztj6^tmzGQv|e~rW8DC ztwQ)EV1r?L9-vn3f(>W=-3O;Ve^ve2H#Gc!*YVtWhkx_D@o^DPNMx?09Usd=$LWjZ zSJwGg%f3<`P-p*n=-U5#Yy1D_kpJH(Sfp20+K!bvhN)0R=}&Ge9neX(`4DYqKRt=l zAzF8E8QEL)D)&5@edYY8q(C7PoG_(8BuhD%p7yh>y$Ng_LhN z8~EXng@E=uUl&|zkN;qU!P`+J-`#u|wgX_}QB)Ia!Esno6}P#JCTNGSf`tiZd0#fB z8}jA6*${~OOZAS8KZ-+)*l<9vC^?d#1z$Kv`6Wprn5|Hn4B3BHDGJv3fA$~lI`*Ib z&RYNbTlfEeP6a8NA|gI2co_QTG#kw!`r4JJ6r{;fGIj(ZEUE3up1u(UtxHP<#a}6Z z@IfRZon_z@k5iYTmWaa_@$ogqRABueYKic1m+~aD{P?*0LR<{T}`HgIf4G``UYx zy=agp2lK?IFTQ_k!L-XaF|}5>F6-C+hWa%PaqGpv;pSGU*EPQILQvFc6Q1B#rHtxD z?G6xO(38>dS!*so9~}6JBCDx;wd%1tTx3Xr{vBEltH>C5qEyZ#Kmby=UsE?c+t7ui zx#ho2{x=zPx9U5fhX426_^J`GUfj#>J_IW zbz*Zx##8k;sWj6Q7P6Mne{1|7^pY!o0@U#T?Y?XO-`RP%=Kpuk|EXE55^G^0{x4dp z_3=x1CAJ&dqpVTk({2RxaRmp)h{$JZ} z{)e63&V%*&zr*u?741XVBUrqtH&r+fyhmg-oFj~_rBmoQ2+bTJa}KJ&MREg~?OK%+ zN8*83hlYCbkhLPPJqSYBP}=RMWqt|%TRP_88(QG~{hW=#Up~M8;oJ9HA2e}Z7UMVZ z@Kl7L76PdS76T+Lp{@1HvYZWzTwA&)nG3%6sH*f6>80R~ztNc9WYt6B-|Z3gt?=h4 zZq|pr6|^x?g*_k??Ge+9iI$%3j^!q1Fm(9fc|lIK79G?e^4K`FX}DcY^Pftdx`a$g zZ5c7j;tna&gbs8zH=~6ngL0lh3!{d}L!FBGOr%}V$3wupoB6GpLX3>4;$cZ$Q|ll; z79yAQ8z8A)2hlu*3>|6PwMoe=r5^jdtm8w^NUfr6&Pm8#vMWIN;oq${&paYDH=!9KJM0XLj%G(`g(^*_5 zCjHkY5!P@gFb!kkpC3(<;R|K~%S)~eu|UFNj6VuRmPv$)T@5JIe8|FH<@PxxIv`U3O*hoensDXkG!DEd4C>D0E z|4p*O6Iju~-5kUa$YACBBL59OFg*HSo{#?S_uZ{R}trji##L84Km zd#YFY`gZ@x>sJTw-v0FD-SgM4J|6t=`0f73=dYgb|0~*ywzROU$OxVnG_wUyWF?^? zmjXj0J zp>wCIX0RIt4Yk=95{gF+sR5$opqP|pe8}%+ulW0}C+Az4$Wzt&Mh@)TmY9>;ogM%9 zRaBGw6BKgd|GhUxjs3HDG8c441O~rIxwmE>4L_ONM#Jf<%1Jt#E7D$U46`gBB`J&m zC%45F7DeBM8F`jFlCC_~v?_F*k~D_?Ey@4{69NZJJ}31<#}oF|HZ1G6LwHwp9BBV; zsQ>zm<Qq7e`EuFOi6Q_pZ^! z6HVE|Lv6iC#-&=-ymV_`lA7Y(CdN^;aaA#%f0+h+>s)4B+gfRHnIkPqlvB?p9o+hOwT*C9Hxp+)7MqJ=p1$RS9H0wMziu@h0^K+ zKCDWL4~EV_u<9*K{#-!s9ouUAN_4N|7U;a!8-o$Gw6Rp z2v1J*A4PC$7!0zCe~d zjI)7{!n(t9c61pP@fqA2sR%Zir^!$}fN}`O?nckKx*mdc*o8oUo?JR{I}2!=1oN_z zgs4H1ORxb-bY-j17Ht?`m!ig~K~fT7(DIzQ+DN4EIlY~%?m^_YCG)$#K0dyUPOfY7 ztKb`MEgabQ|5;JoJ^oAop&S3x-|DaNzdn2Xr^iCmaj`yEhz#&E%ah|I26MB-8&qbK z*3SnA?Z)5$RT&$8J~*i7ubxTX2oT^cQuyHN{#uhOLiqeN%NF4wL`}*FRs`zQX#w1Py0X2Byb+aYBdBjY*9i~;nINM= zpm;{lpNhx;5IEou?#6PQT(Hr`gr(#1Bm$RvZ`_e=;`vAiTB|0zw)$3=QTFIsvW$``VuI#oYd&i`HkNMG?%`f>d~1jg3g`G55LKL79D`u_h6_y4WCeKd9M zc#~&m?CFo$xTr}_JA;>Q9w+0Ia)T>z*#*Zsjg7DT0uOG{wZq}`&-LC1 z|M}ugb}9*~WiNZ_+Z1HZz5|<5b@54&1ijTkPNZlcP}L{L$Qton$uaT#BAFzR7Mf?Q zudc?&s`}yo-zwh#0?M}np_Ar4NzRc8fdf{9c%WNn{VresTmM`CTmQR*fBzpsd$xT5 G%m@H>9)(>1 literal 0 HcmV?d00001 diff --git a/packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz b/packages/keyring-eth-mpc/metamask-mfa-wallet-dkls19-lib-0.0.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..8322d2cdb75a642681fcee5e7a42f1764cd02637 GIT binary patch literal 14341 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%0Ad@tj~&dPHRrE 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 H0N4NkVNVAq literal 0 HcmV?d00001 diff --git a/packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz b/packages/keyring-eth-mpc/metamask-mfa-wallet-network-0.0.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..a071c742254267fb99b68df2da11665153b7dc5d GIT binary patch literal 57875 zcmV)oK%BoHiwFP!00002|Lnc%d)qd$IJ}?zS73d9&xzF1@;zzgx;Yf3B-+H6*iO@& z-JUEhK@ww%R7uK?lluMJ-)9DQ5R_!cxpb?3*mWpiFc<)X!CWU{?;;%1^%vcsb9mbM z+voUiV`Jmt&JJ;X-`LpL+Sq!fZ%AQlunIuE~UEd76;)ya=NtN`@pP zy>xQDk`72QigJ>tgW@X8C<&833G+PdMPWhvq@VVtW119U0pks#nC7Hbj3`+>Pw~rIU!}1m~)-!#05W z1PEGi66NsaYLt%6nMC=@V45XSKBCyCpOQSq5#P~X0bii`AdTbn3Z~XelYRs<%9|@I zXCq3&^YoJ9T-d@UX%Y1pxp4s|YE}79`6!HIa!xtHXrDw03AO2DFnnHwNfCuHnWPzx zXHT=SvU2vULr#yLoW1r>I;49_j!%w$>9#vf^KEZhYA@PH=?$IGkBRD!dJMn`vkDMKyoXM`QyQdwG_$S>{Ajp%GqXTaR$a(Yx znsg7L-(iPA0a6oVMUGDJ->*(P5+G@J{1@HBr>D>vruetzfB)J4P5Au}{A|6KCWC0$ z=)KFo;Qhb(Xy>7Q|8H$=Y%cHrFY({%G^d2!lB;_w>)(Ax{5U3;VLYWdF#d5A$5Bq< zUdbEeyY-bLym=aZI*1Z_oTU?*71uTT9`1qMBdc%U()=LpPh-03k&g^glN8yMlAHS8 z3UAoxWi%}4AuX=b>>|K@NA}5O8uiJ>-byb`@`AKGPyAOe&fcDN4?0J$&Q8fb`M5#| zfm<%2y`o7rfz}>=>qoi#yeq%LP1Vby30zUK`HelmN$2e3jf6f9-}^;D$CDy&l3fPp zJj`jE#^E&rcY@%&R)L8U-eXf>GY;SLPumrkJnNpG9i6;+`=oOgJbQ~*8`*~;acx$} zcjN#Bk(^Az46onxl4e=dr#VVBq)!LoG%oVS3c=R@GYPYBthHJr$Gj&y>>xs*Y}3Hz z87-z+l9M1EPhwh7lCTxxt)bES%3zwHOhp`vSW`polaDubP7DT@{{#LjqA^XUK;e(1 z(P-F9?ZHP0sv%mF8&5*WXpvo8-EkqUzSz#}C^w4LnJ-}p0B^4Y8AW-KX4g+>(Hp%0 zIkKsU+$jA>9zUM7#0>1s-pb9K^Wpfqx3a$eUnEbbS&trslSz~eU!At&ks;xNyn zK}7omw>(~K&{B5&=PK{8`Wx9NtFI29A0E9vT-}rKo`g}%o@D%rIj8vMg$Hnw(AWi1 zlI2u%VNlo@XD9yQ>G9FY+1t}st<&J7+v>c1;&)$k+RkAoVV2_?Egi$dZ%p$%9KusD z8;8Zr(T}00d*oyPWjLyL&W1KN*_WYIz6zM|49ag^}umVjE!Dd8?>j`aGy~T>Hk!RC!n5<(l-srIf>LH6Teo4YH{PMt@`UBO9v%;Jf z$5~pWFsGwI4f=XEy(XdoAO1$W$4jix{7`XwtBZK{?S0ShzbNU`_xFDN&$<71w>Qi8 z|Hk(6{{Qpd|8C(`{CY45`A=PBAjS15&7v@lK2Tj&U~;?qHv@XwODD8{N>OFm?Q6f& zB%fx~mN;0iGxmNceSZ;txIUrm_-Q{bB)^Dnuj%<|+Pk2I?4|uUq(wW*C`u+_9I(5p zgvRmKG5^W$hm*n}!@(frvvrd2AWXs`c98PaItkK@inb^yjajR=n`ED4^!I6$(b{Tb zoqvWCzgnNa`lV5E6VPNjrWx>Zwi7%uq(ze`d8|%8iua$yqdbE>a)Wya(9PL|ZFKqP zTcFD+cWXQ)0vh>s1SeaQw_Nae8?q&{k)3Q!)_9XO-egh4D>lWgXW_)MYRie~%|p() zqwfhdCv2VWSM)r`v-VcXQ#RdYlQr37QM#8;@~!9PM9{$^^hvSH-kygN0Tw}BGNxN& zEJjznZPA@za>=_7Y)M2KWu`?Gmk5I&Z+Db*Hh*h8ITDT=V^KmGchDz}&!H?jw00@p zd0-93?PhG<)t@+X3)-TnpcrxIp|Q7lH3?Crd^?F>ZhxPa1))1!Sqib4B9=ZIrX{pz zqI8FgA2CDNyEXCx50~U9@O3_viduM6?`wH(r z;0bTvqC)97i!Q^0K8a}DKc(=dLFEVe@Bd~B6nC>aD9qFm&uEWEmo)Q8UWDK(gD*yp zTto@@3<*6>G$1wjME3Xh$?ABuPKr^MUNPO&^@QC$wN*YA8Pj5v_Q4A&Negn7MFnt~ zRjz$8frYDp@!tn?K!&{@&GR%PS7A;VtLsZYp^)UX00CSZ0n|O|fK7{yBxb!z9uqNd z^7s9H-sX=#$lnK~N&b%iY>dKOQm0;lTo5K=f|N|;n+1QWz zNR~eB#ppp)2(&q`HHq_>3>v*Kj=5KBMQmcFjmy7Z!d z8ztZ+)}y)S`jy@J?w_*jNs%%|le=?&g)aw{(kAQ<(doF)hMzm|v`q2jLoPwg$~$bFlu7 zN7nx&>3K}oM`1pqIeT!a_MfKvU@#U_@UXU@_U30WPM`TFo%UO0^m*O^e~$%%^%y(YW3Nm6v-9D+J@(V$j*wNUnjjF?2TyM1nm?kIKUbFL;U~hVD(d=nJj14 z1lr8UCeVA-Yjqui8~oo8X3Bjw#5=5}?;L)h7>>^=XuDx8UPVdqgP&#Lb!~fV_MDzB zLLtDbk%dV=9k;FvidyLt{O2#|=T%b}XrnRYF6i}f7y;J_ufnLfH4eb2ar_}oXvPKs zfV0K~-==HS?ZYyNr}#ObewIF?@9Q;=RRit8Jrjm-H4%;X5wTQ2iCwNr1 zUtX~a*kqrG?%x6#3+tjI`?nreyTMn-tGBpR>r9kjfH|>B2vYmj{G1q;u4n^AzCPt` z1n1FZ4XDZO;i=6~lthct6depgc`h2v0jyu@UyfzH^JdbdB-~R)ctLBfD^eo|qKl)+ z-j5qP=Bnw9TILYEMa)U8uaoXDK?4;kfUR!2?c62%l=MSc(_22YaBbYm_*JiIQ_&8zn*N@2(OeisAPG`&)}pvN)~vPo)bkAwHrMg?#40-% z*WSO2nzm}@d7ox+^V1}kX%>41$RRDx(hHh=B3uE+1TLrfiO?k+6Fm2){zTv$iUNJ3 z9RiC@HykXQZ!Fn{@9uil_O7@>7h*W2UP5}ac6lwQ@c<6pkQS%-E(@Z`2-Fb9=3HM# z5B6))ZRZ3O9V7aFjV9pY)o1>JJ-E$tkK_;?h#p{lG%Cl})(!_Tn2yte9t$y29@$#P zeh|FTK*8cPLO>nS_h%^z*mbMBb~ic&jPgt4n9@uU&N@<@P$oV~yl>vyL%|#Zv_D8C zSiGhXnMy~~BB}X1yKV1)q4_)mL`wtfo2Gm+ZctO@G$LtckH4H5$+Z74FUqy^>0oe= zW}w;re>=43L(VEL;Ng^+f&yird0L{MyCb4BO- z6W&BM5S;}QQO?_wt0<0zgf)Wjxc3E)sJr5u^DC>6P35s;1vE|SKYuf{uda1*wPufL zK&-~id8M~o7%@lFIajf3L@p`hqiA4=m2lV?+`1($v9FDc=4pIMIZiH>0gZEN^}P*( zVh1CB+VuBIY6hi$LG?$OKzzaX?>@ehJ{&ACn`y$iSKTdKvZgL7X|m&SmX4#G)@q!# z9?2-2Qj}TmzP3m^@Xs-TlAF4*!L*2y6V?E7M@(qK@@(Lu^T<>u`lAQKi1 zvPK|v1^c^5#n=GG3=m;I_;FmrwmKPO`gyka)hLST++7Z6ah&!XE$$a3hvLj)u*SbZ zVdmJgE-jS{3%gyFOkENsmmXT*y!>BbxBa~SUz;17ySDtlv$M0z|MX{%|CtrlQWY0t zx;D)B|K=j&Q*IT5fX2RpD}qA_M*E>04l*R=w+fT`tW*iuYK@$-b`!2@YG^Hrl&nda z(b*ygS7iW#|6imyem)6%6t6*nRgE@=4XI=%-FDqf!tjsNqeH3M(x+mjVQ><&1`~l4 zSsp{j*0yPit9%r0?LI`qDh%0x?T`$k!FBDQ4<=ia2ONS&1jye&3Q*Tg)ndBCIb@)$ z3_(5KI+oMpY=;!1upn7Fg%)EW+QZ9hIvkM+m{NHU`2gsIy^-t=sm0J=oCReo5;w$U z$bx7=Jb~9hMu$;e(2Vv0R2+VYVoY8p*de|@=wJ}_BAPJ7@FBro0ZLUK(@Pp#J4*e! zMp|L6H=pJJg;VCh4B^)I6Ds^rEQ(m_kLw4y2)~Wk7G7ZQ35w0#c*=xFuw+<4P7`N}kP<7XX*LR>_cgXD_Y)F<^8G;I&cqKcdT2faOy)e*^1W2HMZxSD zTmB%XVKSYFwrFx=Z9#(MJCCmoh3JB5J{Z?>VUv^IhrWmCM%&LS7?al=7U zlOR>ras1BaTeCX5C|m}r@22|gfZOw#CaxJZ5bc_{JCZb=%$%j;L9Zod(sy~TwxTES ztOR{iua*6V*rrrl%dz1agD45(_`2rWZ_X3ar;rHAv7-7earQJOWFl8G*O@;{lZ7*P z(vWc=X4a$qT7~*Rt)$jiLtoiQ*a-yDsOHG9pa`5!i(wii!#P*HwB(7b4uxRuGyO># zh=4qWO?xUGey)MiQ@_s;a|^+ibK+C_J!kz@YQFSC0&nX|K{bCWw;E^_M299AG)n^2l{`|i^& zayrf9vJ8eTKf740Et1x(A!Z#FZ9{x9tZlC6SZI)2t7jpYmS9Y`6NDVYaYi?L#QEoG zcK;iD6&x7>T}qXzj1F$iY=x3Ho3x|M)UN zo8?3i74e&{hWRgH9QDO7|H#wiGXy|7vxUty7*xbEawW6|3k25kV0_hBmPRJ+vVYiwLqEGVN(@c#F)M;5_G%g_h3_LM++t1DsER*(V(n zK^3|^6GoQ`GrkCe;+FhKgT!c8sQi$IwWBlu$>=1bxhMsqy5T7l`7I+>dqR%GQv*1s zW4aI&DeP7m_N7y|q<6u$1as+a%CQ^%T0xfKv0H6k?a;czgHGKElaIg_%mA?dT0jYSg^hh!kr?pQ8aF68X5&{3jMFLFg zmgfBLi+U2z=5KcB$zn{-2q8o4?3aeRE7(2ppvx!kIA$94AJk`f{Zwp782@&gQFmBp zy=QL3n?-r@%tvqSTo~SNq=_(ee{Nmk_9Z7$@W2YV=cy5YhSN+iZ|Z^%h%ywY{h~2Y-MK>pH+?o^E-1L zwqQVD9nF$s{+>_I`JWc(C~r%%Ad7-gzX=l()KoBk;dWU2qXK3A!pyZC ze+%*O3gDMMa#ur(mg(y#6L4Vjm_KV_<;;EC{JBqKSUukTK6N!JSNZ*Bsp9rFz9~wH z%uC+pGxJ-yok$O!_(Y%&%AoNyE}}K&-`PvY<7pE0gc9OAT^%GG5;#?_rBVosbn(($ zP()LB+*#@$G3cBs9e`iHAb!R}U%B&vN=h9!^n7H-qrUKY#A>S0%{Gf!{Ss-hvm;Uzxz@3k2BpdDy$CE}NodsSyU(SxIUEIL~Gl3BE;cKzG}SMzStI->h&MJN5zvc^J{N<}xm1s0u< zzENiNK~XQ)3U|oAOKtGn@ADmdi=1wyJ6Zm5%j9tGd))lj9YJbBkgkg>XP2+n$|se( z^l+9_=`mYQ5ygAd%tf&L4;G$-TL^P`=g!s6Iz&dPi*?FO^~QOWaAs4tS3!vB3lD2- z5SN?CEoowkon>#mJ0j+q=gmx?Pu)$`?v3tzlEK#JhugYmtX~`RwzA%K?k3h#Bc_*8 zAC=114hGIp>3$=+HKHUcm=?3<_Dlqq)27P#BA%FIU9LuNaW|Icu=ldr`76LMEzCk} z&xzEH=lsk8HC1{qkbn%Zb7s0|N~#5?%HKA|Y+Xn#JjxiE58<0V$HHj=89)o+O)P@;=jSP?xE3Gl2s2=} z$(HNZ?O=>OJZCh_HK^KGg9J0dElWak&~Ray)?OMzq-vs^OhoTi%CuOhvFpiN zPYarjqa@0U2v8MC?!F^53D2Q2FNyM$mH9O^b3zk{a3#p9b0#$fs0C9>IH%4`_szki z#U=1uJH*mm%U^5yFy$;bgJ~R}>WJ(w8ai%1H9ccGztu~@d#Fn6FOmV;smPa!PHlv( z)4+zQI0@`W*G7k+Uqw-snQ`xK%*8&q?vM(mQtjvr;?5(u0xYbYiAuhjRHnV0$+v8L zT>_L-M1p@mNi&Qyua&cfTN_0nOO1ip9A@-JZ?PUvDe8XU*!=w8oG@Tl8vxTlcJlu| z@oEZ)Uc*;8l9)WTGQ+DDbz+&)5{u9x!` z--C7@;(<_Ox#sEJQ3&+NCrMmoCc*`-8GPjCE>dQBpxnW&WKxE5vR?i}#oedb3h;8_ zYYPS+5l5AAZH!Zrs)&fZcaf<&N5H~*CwmxHT+n24<%s?Db)81JX3;`>*R6vuKVjiz zaHt-pDb^kKvbebQMXAqM)-F1}GobNa=Ujfr+!=kxlV}G1pCCH3G=So5itC^f^zGeG zA%-f>c#E+Y9>cAqx8BX42Hezk-x~C7j0=(+E^+3Ql=L$pFIe(IZ3^|fs2%Tk9#*{# zt+;8P`ZOIZzAn;d#$+@Od5617s1%A8<}-Ctcf_Z3a1~UpK5?g=&@JKnw~-U7?RB4E z2AsKJ^rnv+bJ8^G?lM}T`4uOtaF-D&^RwCQ_=3!CszzRfiJENt|V4H4%xaw5WsM1lFOm`E(NCFztJ){Jdo)umfsC#M(DL>)h9fWgDjKtHHaPQn;sC$0(2AuuR{ zau6#OitA6%7fdL<(07(<@z|k{b_AAdvn^j7YMT2M#b{yrD4b(6{iM~(wlY9af^^_w zZ!zOd^Fa1|;92%hH*bgkWS<7#t`)zQnY~%V!bPl`IeWEg$CVmFDKbF~F!7v=?st6P z0<*c0rC8D8z#8ZzmxEyGFDtIW%-vtRi>BE`DQ75od{`H4&v{*dn^{6p6-~`nJnOV| zkkN{7-ZyT}R_ZPG!k6M{SoT67JS#X_2Fm`)BcqM@Ux;qY?>qW;UjCQx)BajalVLG}a>e3DR{i7jnZLHR z`_NiRuI~kJJ_QPPleIP@DgfZGB!pe0#<12$9*3H)`gN3q(esqScXl55ajLW42BGwW zCjXX1IZW5miF!X2Daof3$m^DqX~HZX=%0yh;>;w8-jZRKM9EN=c#+eVCO8&Ijfaq* ze9L;Sm=gaT{!il7K^}s6@21H`l3t0VOA&tK3C#E|LCSXrBq|;#9fN4)5rJD&kT741 zSjNC3@TR_sa_Wg}YS@5f?_l6DdRlInQo1{RQ;6z#uMAvOrZ)H!f$H^)c#N43p>C?c?-8v0~ZN3>V0t)3MyZP&5%MAi~;8T4gk! z#)V63i(}RX5Pxk}DgdQK9Ogwwkp&$D;5!sR9Z%qj+}xA@_-S8&Xz)+D{PFwm4FzV& zMT(P}M~o(JLp`fnQZBf-_)1HkS}`wJAT_@kQ7~Et7B-BPfsKC^nb+nGs`inkhbYC8 zEF6{N25)cE+2vd0y6%_P(lsTfiMDo$8~pk@T=;1M?&D!h;6SAVw8UWcitrlJxMwuX zX%6;}kVjpDL1K$-W(rldm?*|^ayh{Or~|Nur>clbcul?|Tjaa%)C||irqkcXRo9d| z#uZ#JROL?jVD*X!K8rdg|9HTTG_X9TsaFg{h@1HTj-q7+7f{8m#R`_;)s>TQ0GZkI z3GIQGexHn^IF54KOIh5(cjRE2v-Ayw`@{g}nPga=2$Z8k;2xqvnJwf2FB0-`lo-IB zvLKr{|F^-hj?QIeRXGviGMY*DaDPdwN56pi$22@WpX@ zWw_oN=~$wSz(6Xgh*g+TCUb!i(kvQAOqP+ET~Kn$F4<#=%_$k;UNc|{fY<`%DRuqB zA@1>Y!i>UVHJGeksJWSPxbc={w?4-!qXU{z009(~%gh7-mSD_TGEawClb-V&XZ@%N z6jgOPqc~e>(+gO7IjQE1$Xcu@Cde?eY_&F=iB$DnC#use3fEb8?#6KijnbIq4fuHD ztS!PyW-L;KuDSr60>>mfX{_d}BvTx?qu1Bh$x#wt^CU_U&uavgD+@SLu-C#~fwk^A zl_rkBQx`Qsa$p2#@GkWkA^mc3_xU_3g6&skj42~}`KuhmvYV@4wp5#(;6WiRU(X3z`6&MbOyeIrMzn88Ia$;7xTrAaWjbrG+fTo?6|8kmU%>9=CyOGCH z4?-65G%Ku1th@))XOwKb20)`@FCCx5UXeXbNZ$i;dv{b=nXpY&L6x@J`Mtu(B`qg^ zHY1l||GA>)dD^?6#hO-5?=#nb-`U>X*ecb3f3&--|Fg{hyv+Z+%>TU1|NJM(|NNSs zpW@{q)6E)rpdCN=Xi{X+V2X*5UB9EIz2Y}k!H@?VRSYnU)AMjn8@=S`Ts*C;(yq2F z*jf~hbT{Jp4)lVg?uJQPM1yOQVA|2ZPABbX!X5OUMLE1h@6E*v!b?yrkxsw3Mcc8X{A)yxIWGVR(edH&tIWsR0}(45vm z|CG@%UIVQmSC4XIEW{S%B#@J;YRk=n(;^**MTD7-m`AylKH9MibJ9q=5Y|kbu1qw_ z1QzJVFR9(HOh4?XyFHUnIP(b4JdrcA)K>D8TlX@#&Ai0Db6L^6Y`pXM)E)Y}nV1F$ zf{qLNzPNQZjd^^Fh5jl>TfkTx#D?snnULW-{xeAXw84$tXg~-28rY^G75(nK9CG`! zjeMA5z9g{QgRXiQFuSX^<8)N@L14S?xi`NZF1bn-4rc1b}GgC=W#In#0fM_KT}ghXuCCH`oIeY*(4>l5u?9-;$qUnL)2R zkn78@dms}}8r+v>o}^&2;qM<=`y&@|q+mZ}y(M7H2TZ__-QhZtAZkoV5UuJJ(wG2Z zx-BtN?1(0&ODP|z->4Mnq*s{X*wg?)m3$PFX$t?e8)jVP_2apRL(jehwY{U`I8DUW zi~G@90k(Hk?)Z^-XE~%uF8K3xl71$S#nM?x|JX-&mOXj-?%!ck+OJR<1ty1l{J3;I zNRe%C#c{PUAz18zPrQTO2<8ewxhL&>JwjVlaYIXFdAc+=zIx~(lUt(??96UdUBLG= zMUIwOhPik2f*HwXqhjkDWG>+{bqw;}&p^(Vn8gYcQ~bRvyAfd0UT=Co9DuzAq=@)& zlWN3DQ)amm|0tQ8)P zu8e1amhwutIwFi(g*TDtD7=qrz2d#wMN+30^L%Xror?FSX65GJ^@^eH;|%p^P1}^7 zKI6|ksyMrxR8{0O_XGfz{(B6ERwWmw!x5PZiAC>0n(l zYzgYUOD1Nnq)#V5fDV}JbjF=mUh)~lmaPYsKfW{)PPy~r_*9gCRNdl#D zFqoTTNQ%@Yoygx^npkd?d=JHRlHuv}{7(GUrZkkn&?e_l1Y)qN8uhV$DM^HH^Il#A zAmk#bJ2~iQk(C^dMuxnkp(}-_TV9X(JuO&5uUre}mB`;qFKGr2pUf%AWE7J$kDxoe zY0Y1CUWZ{*`3sgA6fIfyEhuxe>6f3lmgIq)U6SxKuB++OW!B58UP?J-pp5wwPkt(S zeZZV6i#68EBoiut3Sxa#PaxSs9>6E4zVgVwb=JY% zH0JBH?D?x}sy<)h{VM!=#^GjI>CEZ&`5W$cbzM$GUr8_UPfoMnK8s>Nr5Ui&SX2mf zuaaDebj+o@80TS5 zE$6(Os&gfMcs2iB%b6>OOf{jNSXmypCzvB65ilw}m`LrYwpk}}nqEvNb}tk4*~n+1<9P4(JRP`E&Snv7o&z&mu_55F zD%5;_H`2i8fS{T9=9%|Q5n!%a?+*S=PW|azqu%0GxDfvAEtC_v# z`^end3W&hLhIbo`jE?h>vQ2%q39i!oWsa04J5Ix z8CeZW7m$HC7X%Jy2G=~MBT0BR_76k$>VhzND`BZczDWnKKEfE1GCjmJK9zRU?DS zyx}bB{#EdSry+}G0iwPOyJ>eD+Ki)(p{VpRB$*e13TJ`f$~g zAe0oRyBTV44FRRfJp{m>o%n~R$44h;Z%<#fPJ@$ftMm4W-+j?(J4eFWs*rXy9c#Xo zK$&q^lm|S9=B~kx{gcy~12Mr=TCucuQ1$c-G1naLS@-np=;Y1JsWD@sZal6W$*`ze zGkeu~(LH@8Mq0U%O6X}`q$1r7%TYCdLy<1aaH|C+!(POmlPn!(;h3et(gK!cbt7vp zvN6qP1EKE=w92!8sr{+4@!)t-A1Y%Eu0^i*f>+}fR(U}oP2lO%rrP;?vr})GNLBe{ z03vJJi7a8ou|ez_l^z1mrsFVK15CkL($ub2XY8^jB#S$U1Q3`x*Lg|lm&dZy}>(S2g{{IsHapw7v91KD(iXMbXIHVb*dVzarwXr^CUoi8b ze9B-KQS@H5)k-7lZF#Qytz)jq^ZVhXfXMeDE!t7W!jZ!`5Go`EaI!U76W$sj>tzFX z*tk{y3e}C?Ut@RHy`jKpNg_+r(zC^TX7v|OuRZ3ubBcTHxuH*5WtueU&6lW)H$riJ zO0y`8qYuVvQDM2!-Y8hk}%iqi60 zNnG@Lie7m`23%`eCWB5nS#G$zOy;{=bp!@-K;;DRl}=Js2O-MQ2@Dh$z^H$p(kUh=gZLKKL^Tkd zwd9qoJu5KCZ1J)JA&fXf{lhV9;Y5DIT2O8dkIp$f$2~TyH!&qR_lXKGsLn7MpuKWV zJuJrnu{xqU7GR6BL&rH;xSh^W{5Ug2GbbkmV=8Uf4}Kiic(@z${1Mn!@pQ)tU$XWk z|7ZN)_0+zHOJJ`3XS-bgX=ADX`3myC{a80&8}&U)#0vQY_5Q?;BSOtcV9d@c$v{J~+5f!X6f#l~~ni zW7zPt!g+PmG!13!)h4I9&a71Fi>_@ zc@64xTIh+X!KZ3E9Fa*FmBRqQdrWqR(xa?=Allco8iM4&j; zwx(w&p@Tuxiy)Q>5JO3zu(FZI^s?kKsD51|tuWV{vr@d~Ru!f>ecfq1==SkGjhN%Y z^@Cj4QX-lyxJP7!#JmlnP6yuk`qAOop%jXrX|jleLrHg9lO$=X!GAL8@{^9n z&p&&dD%JZ<0Wx}4sI4k5jHOKRXT1M4&GCJz0O#EQ+gm05-}d(QQvdfA?*D(}py7l6 zbyyZYSGJebhf(20IpmKy~G0gs) z7FU!euAXp{phO~O6^lzIpY$9$L{?#Yyi`3t{h~xYo@Zai0*A7pQvBk&W>vU^!x&g0(?r>g@}H36;AD)dcMM+7}7}fQ`9WrmAch<~U^o=OiCtuV?~w%X(pN#KX@g zArvgDxalM<$QpT-M88j|oQd(|w>n7`0hPKV5IPH?GKhN7BuomsV_|Q%H6s3kyA>yl zH{%o(S26S4l-@_S-d~YRtHz~$9Wco9cV3BN&C-!I6%L+8fXB@;W0JAXkDJ7_fIt`|*g(u2$k0swRNr&u z?6OjJC0N{!ZHtOcC+%EoYb*5bh+gs!&}2*->}W>97QD`-eVp(e12WndH+Fo zDgurL9vZ#10h>Cz)f{?XMM?34pJm~-Ve>NVTFmaJ2*V4I&nFF6?XHnil-TY@7)>)= z7F309ZBk7m-dB)`;j{peG>e}i($5vllc&wl*k(d=;CEQRv6sf6 z_j5TqSgWhsBngBKmRNz2Oj+LF6Bjy)A4zfKy)ad zINRFeH2OgMVC62jMfD5=)L5G+Cl_?W@>StfK~hnILM}V7{^Et$6OU53*+a}fgSuEw z%`#2&h=ZTx1EI^?t3xCr{?aTOLLwuHs9Z$@QxI#^5|(2B2I3{dM|6nKykaMZ;VIBj zAtnn&H!1CF6`Hk#Y{|8BC}UlPyrZa+(R>;+5+h}Qp913nDX}CBbG&hf*Nn{|Ni}y4 zM#)kBu$LJ1J$&#EqJ;L#wZX~hbm2=cL8L6=u5jAdIikevt3n+`XVN1P^c&H`-dIZ+p z(`FJCMs~e(I!Mu<0`OpWgn}iA%?X^9%fL36tj<;*l9krk`ZLE#$zui?%}4qvGF3RB z@`!TK$_%E=b)yI`fZXX7WIY^36HKm~C=&IjXkja8nDx^uWl4FJ#L)$1n0QQ=(obog zJSfBpYxKdIvm=0m#Yl2;O^b?!1#ir%ovSaf!Kg*%C~*y6W#MGPl_Q!9N_frqm&sU4 z&J-d$SoQsE6->8NpkO{Q4o62*6#!I783oy8arzRHl@V12-z!xZe0B1I)UNU-)B(=O zRStjEOEjNm)>QQOFINu1ULMk7J)>-k;hE4XvZfa_Y3Q9Oyw+K>7+r&S-FjfS*c#1k z$Q6a#4f{;rPa>#yQpwoPpxI-4lQ8@ha}z=8iYAdE)sY(PjzdT;^)3hM6co$kl*uE5 z3m-Sdo=~G48c9spj^o)!Sjb;Q@ef5S4_S;z^;H^;XGJOHG+8{a$wIkJZk69;PL2~y ztMxPa!_IS}?ffW{4pPYF;T5q~q+IW;=L03DBiQv&y%Lh}L}REXFZ4f9TE2WZ3^y$O zJaDOKUHGbLI*tk6 z${i-U($8R$2B$H-6?l}>ERP8@W=l}H#5xu`pOr@{Q;*Z8{!(SXsRQPiM_^a)4 zH+$mwDBRk8_>8`j5<_SOIbl6H>SGnr)?rn(N=YE?XHMwM{Maw$V&uyzrVeKJC)!3s`B=|l z$cI^Dqngh$g9OKNKSDNh31fT+)7(y4j{d58=CX{{#2JOj5NN9v)iF3dtdvOYZ{;&}UI-mMGnTQQN9Qv5QcgtbfaIDj z5WB(Asg32{EGO1#mRH0<*Vs_aB4$zxD-|PoaE!5o1`&5^d zIKSX{WfW>Q0|-Oe&Rlww%6;DEo1-1C*?MeGOs`2wzqz%>_2xVkykjSGQ->C1W-gj= zjZofM5vxlRv0Q;f_%#>+q=!~Gmq|EuNss@5{ke9yV`+N^MW7_Whl z!0$o+a3HU8n(IYOE822-SjJN7O4J!Xx8+H=bo!b*?u@uf?Dq5gNEpMz)vLEE@5k=QNGe8$ahG_=gw<^cGGFX;f!uYdTw)9w$^|DEDUFZdM zw}x8Xa%?b6bu8pqR}XMGP&b+h+DPSY4)WV&)CXLId#uQk4hq@as${}$bE{^n_DmBn zWOtBeB#4?>FS1^-vI2!j)?% zmL2_c9H+e9RZtyK_b7NExCeI#P67dfy99!}yE_De`$2*PcXxLQ?(R--cXyY=>C5;1 z=T^8{Wv{i@Z~qB>8_YDW?Di~-Mnb-tK(1Sv%0BwFvqAgk7{_O^ z5c7X(A9F3`UJ-=MNn3$slE0`|2N&tJt`ldLSoT&WJbj=gNQy1Yt15PA0`s*hc*RD5 zhl{kv-Nv$lN;PSKE9{YE1nCEV?nD` zSpusn$3kTR&H1iRy>$R5vTgc>s~V%-hHjF0B(8XmQC8_1>3*a|th@PW`KZLiH&s`6z<_ z9lN^(#cbA0(Ubm@;_l~(2s4JMlI<%YkeedzaBlw!byPR{ihnBpgl>paxu!*b$I&+`? z*Id3-l<<2;zAdfCTybU(lvv|Dtyk8zn?9#*?!Ooi*5A>3xE1^;=6VCW6mi%=@y;@7 z!m1lArNZD{H%W{;Y^(_lk4e7)_!A~hMFnC@abk4kahvhzd$vwV(O&&uy#oe^+fBTo z#%1R=wgsH5e4<4AQ^iVa2=3yYu36$fm@*5rTBFL`d;EFOIvkpLAq_HVnZ-QyCIfAY z3vteJWwCIn1bgN}=bu{r+=R~mkc)&8%yN@*7GtwS?M4utMh@~0Ib`}6#G?^EhmM<( z!kZeoGxyQq{-!X~dNA z%TYz8zqMBV7U?bh@eBvzz*y}Z8k*A(qh4kG%r@J%*?iZ-^OYF_B5j? z^ML!gV>_q(t4gReNa8l2q7s^IWNW4kgM8tJzDVj**GWx!Gv7}kn3WC~7G%Qdyx-ZJ zPa2(MGy_E7hAcU38)dmp5qqKfqmmD8Fe3L4IhtG^C9gOn^;WatC9Y3p`B`bE_rTnG zh@4jHmJ0+}GH$C~UW#LiyW=l9-D2wD?g#8ReHEcpd)&ZwO|`2_v)665Qz9Sla!$I3 zuQ^ajN94 z7)D#A7cRpO4I5`S@@f@mDmP)GYk97z&5eSCql0?mrnbtS@VE^bJiyp{Z)cr))zLy`d1#AX3W$Mr4eGV=w(@cJ$32vUYV9d4|m< ztn{TcyhwJ>QUI zc}KGEl_;@R9(8EB;Fg#X_xs$m%l<>_)cJ)ZpN7RU0dF+|(VMD`Jd1|gGD)j_liEMS zoQ55Pw$9Cc>Q%1`gy@w3-5#NSgTO|&nFp(!MmlaKr5G8Yg}1B!zAFwy!&a^fd`VT> zORK03hZ&api&VU6c~RnNcem8Q$6apN2H&z=_p!;^gxFB8uvShi;$WY5nRB6FL817N zLQ`d&a>8$M-91s-I=y)rx8eMFg)Mc)vn`Cf@b4=8bXnQz4=RZnyVJ+fwACMNCw>g+ z!^V{3;w|eQJ6)c?9v&*Zrg=PFTvwd!)&BG!46a5-qG5C%uR3V>ay+KlUJ%_DIJw>w z9j_P?o~K73ws zeQUdfBr@vHk*pZM>LhSjoFC3}gs7i7CQnvaqQ@tHuz~iw^6@_sTCi0=%C=+`x`=M5 z*yy`@MGGF>a0jH%hz@twpv@$a-u^33tgZz{JKmtpq^-FR)<<+U>s3aLBbIoMlxhn+ zH!|Mi9f!dF{)V*VW>-j5$hmjDtF?+F(wEfvtnv3{bz(6FxLoDstcINaV1U?o*_aRl z_w)Ops@BeId_M@()mu~y1nncK_|XI&www~IDl@b#J?sO9C-MC^mX)&;ySeWo4MsCJ`e*=9`Gy0BG8jo{u#U)zYCwsIOH6xMVKB9rAT5bzr^`f@yxMItY zn028|*e0J0Q4)?WbAIiYe%^#wNPZUzY6YEtH?N&QEt#lB<&6v?f)}!(L%L*v&WiIM zv$gTd*<6O|cOE{UFl*zo+>AWIsnO+H4xX#5m!4eVgMGvMgVQV!7eEejfpoQ009R;N zz;ztJ{0zhaz`zsmgmMZrktN*&7|9R=gG?ZL(}(t=OBKRg03JHvzAuC}OoBEny*IFa ze>8x?1TmhlkR2*VKtv3!UAOu-K4?pU?nNEgj!L+Dq+8PVg@ZZ2@bBNVuHUe=Ag?C+TrB z18gTgI@Y{dXU~w5`CtyB#?OUL?O(JmXz0tb(g{<0zK}WLT)R)KwQ^W}BdyyISt&mI z0Uc{VEg-}8O^)UGgA8ksSAx`=80QKdhCZpF5WlC-Q#UqL);lEPh<@zfrtQZN^2v!| z7}0dYDVv|0?;1lPI$HkSjp?+`gr{3qKRn0odd>Ui8u?DPklf7P&DP?TGk#86?IZne zv#2-(89A;nf_K0#T~a1Hi4e(DQ}G(KXC1PagxO$`brH z*fL|rlQo<=a8O4=j$%tSYc}Fne+N|qr}eV^KQ$)Y8D#|F04yhzzuSA%OX@|JC=v4J zK-}t$Y|%ikzx&^9n^=OfEthIBhi`N-4^L6{xfPST4}U};3gcSRlk8W zSFkkwFICMGqq;ieIA2cEkP)IBLeW_5G7IJ1%!TKFVnsBy2g>rlY5V)Ud2>@kYpR@{ zu>%%~L$3sPDnWz8PD=uVVDc+k##9}u;sI8ig8&awH0$Y^ItI>@rsZ%U@4H zjmaj2=NEf{!6-4z|9y-Y6{%kZg^+| zZ?yDJLh#y8!OiD-Ci&rb?v$gGt4#LDF!Y=&Xzi?&{#Y3HJ8tb?z@ad)NB9U7&17PJ z%i;b__jAyEKRtMSCCK@%@7H9msa_buqPskW_?Z--*GNI`Fq=x$IM{qYO5G5Y* zafd*)Zw|-aJ>FfgyVusaYrD(%E$HRVL3tY0Eb70t#M#aeR(mMr09fATw2D9VC7({M z43#G%tBm#}Nv)k1s#s+(6@y zN#kYBG9vFD1=gCjfS>$4`w( zTPkK{Uf_B93V8C*(wf>`NZN$`0}0~rzepiED|4xr$xatP@JUu3J9jnq z4(>ywF~26&(d|V!kg8B+|Mup#dC}SYf2++yo z$9DOjW(~Yf@b|kU* zVg*IsYeGZ^S|6r5*5}#9GRx!2S*U%a-$*HT^_pQ+^MzkJrn=qgD|vqV*T7)zCMEHS zwfJodwJZ`T_~peQI#cI$kGq={@+td?JyukF6V= zbC}@O+j`R7zqX$|8L*41uJXhmrmv75_^#e0kf(z`C?1j)L$5Y51+&s4CftY&j|>$N z1z?bFspHz*U>wFleS$g_M6Y`s#Be7gjeRSZRpb_}<_Hl7hdUv0_;dNjII}BOWjZ1& zgmffNBluKp`t41c!*q4nuv*AnoV!QHR!3*mcaXyX+wq2%uWZ0KLmL+MS>jSvzTzF| z1o&^{QNl7sxnJy4(OZkjpCCn!hwaK2>B)92-#Q;zwlJuvK$-VTtR&nfnLNYneBDt` zvo%&!h+OMt##AD1u{g&qH=o2o3ymEqPq(-x0nq$}n-p$6o0y^Laz^MyTu+0_RxxXG z+isSvRTeHzT;dXYT*F^+I@aV^(f*Z^gO|Q3tg_h!s7rsJF^wnxDnJVr75keCe+TaM z@TqjOWwvp?a1s|cFwdd7v_Q7GVdV(E<6ki7%yCC%KMPP5R0Q=8*+wBWPPBIQ;5V*l z5Ggb|p7sd)k9^6ptbnPS&*J}msk5^D{(gVq>@`ELpHr25;W`_ca)z~^Bend{hLvvJ zHlT`CJRBSG2J|+(Wk%boW5x=gMlnqAN-?($a-mAUb^ScQ_z79^EfRo#1M?mmp>dp> zq|;;~<=A_6k0x$UJ)M<2RSeE<>OK8a(awnKjd%#O{I*kV58meK5JbAs`uLAs2|zA9^`IU^m`vWWJ8Nw%gMTx+x`wdP^5if#c<0D{uOI zH#|O6HSk=NB6vnI9l+}?(2K{sAWC2DFJwKJ%BE&l7U%*4S^X2At$!g{R(ALbryY1O za9l(W3-U^b_i2?Uuat~7ycU|gVm~VUa5KxoDv>?QzMBUVYQHfKvA*S6$g8z}&ktv# zL**43yEDGw@>#&L$G0ez8Zv+C!ZaV$3CAt9avA%w7_RVJ_V3X*#v>=s38dHmG(&+I zQL1fLb?!TEV2QNeYi+D#yMr-!bWc+TEGhBYN*gB!%hcF)J0*6 zI)UE|8WwV3r2aCq#{7GswmzXi4*wME(}JzUZ2hLYOOO^WdM+~_pxnCGAC|a)*+Byeid&J44-%ks2qzP|`&7d3^)ln%dgqu+fwU|Ey^@#HfLf;L^HF~~ zEu0kPGuDifv&Y)>ukFg8k{DrGW!H;~Qf?_Z9#9+`?WvsAJmpI`kG>sRt*NpbbnyFQ z{YPs5Tm>+|il*g;c|!4T*>=eK`xdzDB>y#{c;;~CxjAv;MC1ZbOY>=Blbo3@e~0 zF)!53ZJ0tqJoNgya4Fj9Ur%bQB=s6Qq9&J|U=~2%(Hw#1d20tB&vP#Hcm^%V3=EmK zK!zT^0aILtfY#av?~f=kmt8k#_ZC;-4%J6@azekp^&cRrA!!I=Tb+O!7j4G_~ip=De5u=M*^CeIXf(&Jk03R}i5>N^q#SbNDxmhO$w!CYh0r$>zWi``otA4XU4G^n^ z^o6o9(Nz%*!nBe_^bGZRu@iMZjFsH51X0i5`6DU(qbL(?zb76Q`D`B zE>LWOj+4hWLP{K#PXp3E-tkq7A0zb5D%C6M)a8RNUOwVTZH*@0Csld-px>L;NC&WZ zkR;4a3e}I&TqGL#e~)h^Q#=RfsYxroPyKlDKCg`}=ntzYFxz&#%IGNQUUZh)crcx_ zpA;AbELV6MudwX@k-Ph|J*6x&(qPWb#-k)`2}Qb^mNzjsuJf+NN@L!gYblC2i9<__ z5ces=!U>V^dZ~-&kCj_yoP__e5wfyk>zLm`vB|G}I$YT)@2Z$L`MjeOg_zdct$1eQ zd`xZW<*`-VbJkiWm^xj6u?!Yb%q1+h^UcDeiksn&WM+6ra!2S*2k74IxT(I4AjUfX zvckosTRLwBfYG|{c0*3N?f0czfKa{|GzO;r6@+wQu`0KDs_yMn?q`JOS;={f863fF zw=pV@+{-E9`OHyVoRlUGyG(uSnXdQvh-q51WwvglrD$Bj6&c|l#|!~Q=)JVfVrS{s z3fO)m`+V)t4VY;9L(*Y2oFV0v5aWwW6p~pj%mAwSUM<8GEh0MjV7Lw41;z-I(}aVwl-{qzKavwP=zb+#84hqR z)X!3ie;!1j@O(piSdKQi6;AV_DMGnOC|APyr^uj9lAzoS zer`E|h1^`-q;G)l?%0g`fc^J(dX)HShZ!nlYl?&;(FMN}vgPw^GKLqKXk|cj)dS6T zfX3vZI%?vj2h9Jp363}lCV7$&f`ph;W%@vd49>xqjGMsb^fNj06ku@m{uIYRsxtsL z3kxSF4@g(ytq?@_MX2c}ny>ULho21EmhuNnE2w1YuEJUS(mS2cZ%NW!`=Cc=fR7~p zvJc776?~X~9`LS7+D=rp8zL=nYV?n0gQrYaXGy(Je7`UR)W?kQ* zZU@R(Y@{aIv}h7@%Uj0?$fyB6;!lA}XAfu;P{;Q0gFZ zpLfJ6{ejwuU9fOk1#H)Nq{Naa2 zXxIlIs~-i|M?990fXYO~-+WER@M`>=@OPsu({a|Kln;EtV{gM+u!DqDJ8=!fJ--)V zzM$Kk-9M6gWviZGy3g|jcBB(4Fo^N4*!DvU1TSc~S8SLl9>S63aZjsnY~hn@ulOgo zz2R53Aa{H3B1BgRPl7DD>5WN@lA!QXG)Gh-4kTy?8UqKocZn-ZFEjkcpM`8*jbH17 z`u|S4&D{hV82MSuv#HB41$+f%k}tKMZUhl@zx_B!QEbXG{7t2wA@i}`88=m%k-ERF zF;aB+aK7;;24SjxeMZQ4a!EV+hUdz$ft6OQPgKp%Dk%rVsY@a!t63ukS=?5IL3(Vu z0oFT5{~GS}8Fke+b@t(-@hrD^O_=uy>$pNG2_%i7jNL@O7D5~?-*|&rOwBZ6dY1gL zX%f#xbMAHp@(Q9FUODThVSnhv#Th1oTnyF=19xGN<(dL{_!dsEUwTR^zwUM%){ssd z9Sdx3dM`Y1u0RIsQgi3psngWb#!;has!|pl5pCm&u%q|#9c&)A*%>FUn8N!LBmo$Gg$K}FkZ4r?@%#Co{0|N7T`_Trh4&aoH#a2+p1VsgJ#u- zrCpdaG5GXN^p!5J!lyL6E9e5Z!P`;F+D|*M;oa43012kp%8|LFMug;h6i9-~1FcVf z7<|PugbR0RcVVO(FlZ_u2!b1#WvC}Wxp!tAojrCCqs4q2_Oi4-lV2j1f>N4vIQp>s zJ+cukSBo~E<#;IP3v8zA|K+}YRDXBhc5J@orn3IG^`YHw%O+R$)){+B z?aultcU8x#r<{@&@pQ9Bqo8u4qd2h{;hhD_0^Q9#5-tpA|++U9E1T>#V zZjU`S#`2t2FkmA!Yt}`$pa1mj%y6khca?}$|-szTul&> z?K9>5h$(M%9WTJ*46DZ`MAmK+KW7b=`KQUc+$IlEY<&}erWp<7o|qA1LEOzaiM?ot zJuIb;Mi$vkANlxURMbK(lu#3Yj{*eVrf@aEIe; zylPw_82>!bb;>@_@ zyG$T|aJ{$`jIFIT`BIg7=(qOct zlfF05Y9shJ(v=LLe+;$4ZLgpdHJIkf#Oq_14)*RD_6-uI&hvUF?E_H0&a;k$#`Dw5 zc~M7qL)%ENHX)NGI(;F(j@?_UBW&VNc(>&eqSG~LxlYGc&C+zKkJWDKSkgHgPNhGN z?4g+&7#a>~E!s9JpIB~CeU@IxV9}Pcu}+h>4@TCtxR(AgF?b1az)vd{V6fKM;3aKfhl6@N)l zxJ^2vH0BUau1isai=#tZtW)_)_~w0pwC zZlum!q?-4`iX({h&aQJ`(*{%iOHrR@j3L`?KPiIkWXb2ADL#rsQhS^ZLIs_dl(h|D zmn@+B;NZQ!0=m55*F69rjbI@>44Ae`gwRp=s=24pxwqhFkI;@|T z+2EPFD3cDY?-P|-%d9O3%U?-k3#hYEp+x;NiyB8z4NniNEt(ClhVY znNm4-AFXY~T}AWD@q;&2JU$O6GmB5vGAstTrm`et5##Jp=f_7Ju=^V)S;07(5TdGd zihfnDL}o9k4QJ2y!3rqz7TZK|;SbG6Y)g)!`_C%;TBy9as!%5n`kX?^AB;9IY|ZX7 zUtsH}%zKUO7@)8b)MlL|jYscpfB(tk2^G%}iTfYYur)xP(2mUbXRRXl;l=MB8cbw? zlxX$ojkO@-+ndRXy1WuD1%6^H2Lcx{t}%#8z|Zr~ z%P8a28a!V5fh1Z-`C>d$v%Lbi0-JWRFoo82);Wvml5*ugFC@xU-BhC1Yr@@5S7pDP zR;`rZ>}Tbh*VzP>Ny3G*n~WYHcLZwH&i7y!8LBd2vRA@h=zm0KAC*eb4gNJpXZ2%H zDQY1n0wNf>HHSe-^yuVcLOm8^xSF}b$-~{)v1&2U`5%k}oJLca z=9pPW*0giC*X8A9Jc9T7FbHU}?DPE%-h77;8_3D7<~@b;emBj662^AHnbG6sFik1M z*R4xfsC=%c{LR=TE?y@&6}CIs$IIIHkhba-`|y6?Ea6nh?&sIjE+6d6u7N%Xo;P%B zo=<{Xx}#;V_q0k(TlP=4vxl|#VPy1B8GA{6L>l{T8Dx^wt>@T;&+(!7h_)j{O z7(Hh>IVbNEk+MUOjTUfD%meK1KLhUw(l!1Z5Ky%^3kYft0=~rW83&%bF7@$jUZ)f0 zDzHmga7*!x#w>=+{h@&xTLq$nOy-p`TD$58=J{?Jj{j!}zMt=2c*UTyxtM>p*m!+! zv)DC~dM_yjKK*6rg@J~LYFNq#AX#M_W%zyxjH{`rH%32bJu5G&s;c%syT#Uf z=3qro;Mo*>!uKA_e>XL0+mGVIl18DgsXfK8KL<~PJ`7yDL+OEzU8k<>_3@f;O1ytL zSP)$V&w`*-`HozpRZs2zRH=s)>L?xtyu-=q{O>6_4t3WYv(;8mvuqGlI#Tw z9Fk<*%`-@t`e$h*!ZGgTS(N>~xJSB*JBSeMFBI%qpCt*1*a^uBAXa^%4dWz-f7pil z93_>$Aoy^OgD-hBCq~)&K<2D736CfC5mWliv7%T+!J9%EFDCfwj+ud?V;+&0Mbmdh zXUY5B&Tq+<8Avw7U7`cDl<3gLs)X*dT{0{4%@@sKO3$ z_0#^v!n$ubIeT@Y|@e))4%`eyEf}!1-^k zzHx~FN9cUDe*xF$Z*199ckE{zsQE5%Q5>1Rh9E3Uv&i8GTUW#CX+9ikRR5b==weG$ zic5y2j^}QQ#LE;dL#~j3%C_m?7lOx+Ws)NkZ&p(@-vI+>?6u2MD*6L-0te(yq=%T0 zRo-976I*j$(?yG?P7h>S=A{9VCaH{leW6H zWk${^yKt88^m*0^VS&I6WVQ^+V}yWvP_4dgQ71?HA|t!E4ljDe2eZ=iZ#dYRA8>VV<)PU1$p&`^;xCV{t`-4 zxc**9MYd`_hY;O1En?E^2pUGqT7t;zU{U(w1K;RsS`W#u->~KG@Zynvk=fcKJ7h#s zfwxFcZm)I+YzC345I#fOZ6gG+SV3g48<9_vc8!TLbDTDo>v@kfJj@$^-3yW`TW$Go63(fIJuuA9+SOqG)e^D>|MwiQ$ z{JUYwtt6;;WP+Yj@>920yVg|plf1xIiMfFP^Fs-$m^?a_+#QSeKWf21gGx{e6+O*0A@RA&ht{IeVHxY3Gl40Zg@%yVvSh#Dc zT}Iu)F#0wUu5v=K$gi7%1$avgq3N03cv<%#k}EZX$w3SJWM$~fYZuh1Ep*%A+Iq`s z64@mAm3|v_6-Dg5KWKNqr4P4=lh;rk4JjM^E$1xDy=9ZXrqyXvcL1Tu%|oE{OG~)^ zl50Teg=6}G#^Mr*@H5Jkuo%}K>K9BC>_eDK@p~tYzp*ZAJ?3%|yHvnROM2gH3 ztyHsL$izK1JjAGcxu;_o3B1{}=^In5Ne;-&A@qTt_PwtdN8h(7+G9$AV42+^ZL$yq zk))0^;~qaqyG6QzV)B?caMFp@jCR}bD_oMP9vH;;x(0hndb5k~-q&`hbNw8ObHn+b z?o7WvHzh8ir2EEsYN=f6kc5s^Y1aLh4?%`S+ousHdsm^4*||mxFC%gb^YUbhPyO4I zSt2$KGads&y4wP>L|6vt4LzhCyFcX9;~4(RpuB3La?0M`eb3pb;cHd8_6cY*2sHM*wWlDjmurG^z=4>F2`R` zN}JeYW;bg<6cd^&4BzC(x7n=)T0*nYi=VZ8k3N|?uUdonA{Rwk+U^e>YhyJA-b}xs za~y?Iz0?sp{>W{xcaFW)wh)qJi8=WsDEXu0NwVjLkoArjPyf+{Wn}Kn=N>IiX{4Wi z)(+MDDRUCN;xs59VJ9O*Ru}Nah1huR=Vv@=7XU7&xJIKY=%+iiG(lw9nX9e6C6i56 zx%Mg3X~W*%dtrQbV26+NCoh#gU>ryuyU*$i^2N5t)So-P+fqJ#4FSH%U%2WiF0FInC@ z(S^vc}Z?6GtyYc%-Y!-u;D_Zt1Co^Ax_m}XK1FMc-9w#WH z9E<#Rg%)yGCXRD|_oX9bq7m!EUUp~grJ=s0Dj(*9<7!A?uRW6Rj_D&svZPd5x>OAd zbO)jj8d=8-b&R`tsVk|^&oKg6zi4uX$I&xWPOG3q{yX=hp+NC#jwxnMBSDQ1s^2lH zD-#DG>W$<$3H2Vm7796A2sLmSjV%u(fAV##rv*bH?aS9`sQdbWx>GjnmEmk;rY$`QzdZj*>2qLS`H<@sui^4Y1FU!EQ@mysrK(?gslLe{2zv5% z(216)&A75x#34(;Q0N~nv0uB{rgwEJ7G@mHA94U z?Hd31m&D#?Q{?eNTy19N_?K|Si`AlAHnsbDAn*;8Eta2r*};%~*K1-&C!xNj=Qd19 zf5%Q54b8LURkG?P$}67ET>>EWk6;R}@xpRqt3U2x{8kX8BL z7}mLJ8f9pK1W|f=zGQ7VyU{Q8f7YC;)O=M>OBsFdH(~o5gVxkz@Nw9_Q7O{OQfJxt z1cCJyRa2kKmXNw57^BnEoxk$1s^p`+dZu>h_Lj>Uvo|CcwAF4W&NRrCis{=6hNrFO z%11RgBTsgh(y!F>vF^?N)z>`^K2rhc-I@ySC`S4qJ(UPLbz|B3_{2 zZ`7z$W7AnRqsaO$Z!GiA_F`q$%}&rd!A;7h`NMmPfcPMbMGV#CkiruSC6!u@_D!gn zO)f2`7ei|c>9(stSYhP+XWmD>=>*1!PE@%J4Fjp!j>J|asgnR$+Sbjw1voM6X zQ+qJ67Dx@>e(LW=Mjz$tum?HQOcbEk+m)!xwdQSNow7CVfXZ4$IBew8FR9TGvE!7& z9PNHn^)Q=>++bfyCM{^o716-tbFl*dS)MR!6e7*bFw^%fQ#BDiv$&q_4g9_*9L>t@ zGi5fX*rNe2lE~X;HVu%CsrHlQxlb8Cw$IaY?v(oX0x{`lln!lozM)6ZUV_k3Ni^%oAU;G@HMmXI6a8qUiRg@OXU!FV!HygCIB0V z!7Tu=LWEw+p1O1%FSx;=*GcsWpo{1I%1>YePwcyF8G3&sH&D1POmzgmTyAI^8D&O~ zxWX3fDGWPZLX^XTt?J>Q8wzA{%l0-R^`~m2FS37s&4>1aM8%9+1vye2&}el7kroYm zL8L6(Z$z?BU1YMF8%rD!4ro-meTa)Jz5llm^od}bh^P4ndR1(o&ED33L(YX#cr*y= z^j6IvY87js&G;4(Tl4?#AtktP9f`=M8IkKHmKC6 z2I~c%Suf~KZNGmib1HDr=$pKSfMHnA{n{BrbbTtN&p*As;*IP5%i+3XDw&1Ptu=n$vMhtGIu-kTwZ- zC7bpAKA4Xovr-k*yV!u;JZ~4R;BB%O&D%`2tl5%0wmnKbeHEvUUWm3HzgRt2srleO z5RDuCz5|EZ%1<222g-m99nVg4sKv|q78rzDYmGmkH*6z$O2+&;R`nHF!Am9>8UBWN=HSh%x2ZT;Ub`dzJKD4uprT&*AAr zrgrh6{wDe*!RfNIBzJP@z;44JO?>FeT=~1&m~{j$wR1<2XuIm6Gv)eUaZp>;Kxmhw zDK^u(qw4U;k9Z0r+wno=qc@rek~-r_%K!13_$e33!9$fj7=SEeFA(odHrOx~|?9^FTl z8D+Ro2vCeB+o5zn$4iB`q*dmYcbXQ0m#~d9mu+9EUqt4lIcBu{lHD|8J2qQo?bFUTFfqgj_GHF#6{%TtmOx!+!l=XbI!9xKQ%giiSAj?qX{rsQj&5H;^8xe0C&50GT(d$-0VC^ZVbY7l(83)=&Wq&)8c~GbK}LLzQ3ULo zug&XNLUp$u33}{f%`3$8cf%WBysv)O!M%y=7qE%O497eMz}v=T&$8NpglQyu6n*xm z4 zmM?q0_z)mA$P+Rey{#OBo>WyMY@!@Q5ocX&(1GRz2$a_%v`*0QXTqr;Uku||S?q|k;ciq9yybv#fe@>SEb5ke(_>JF9dGI&# z+ixXynwelQ?;s=dM|1d4uh5~p-?Z>Gn19&ii$q&R*h#{q_C=I_L}X{7x#x`~$%BbaMV$6h`G9J*G zTCu_mfn}ePT$-~j@^_t-T@`zCz0=?N z<>6zc)rcajBtEK0&7aq|pO1nMS2+fqlyTl#%g85P-gi#1ON}p;mk8?@kBAnHJTCs%_mFVc4^xFQ?wO<3_Yu`bHv? z3~GV*pL$EQxL&Hg3mykgGg6s9 zSZ{SFzv+!?iQ(b!j7{hx9R~4y2oCUCks2GnUos`r8a;{cGy z02Uy4VAJc-`WYa8zitI+dH^clKsLbU|A+9A`2YwfZ0d9b0}l^)C1bX4oARUpaDE4} zCa)*}b_>hzKy7U~hZlKPCtp{dFYk3WAKTU~YgW%MKm^0GB?zLQd_#k038z3C5@-VE z)bKir!5(luH7psDneIRf*JjQ5Chw(|)|wPyYTJDe?!Wn-+?LNTw`w$Q$T5Qa8g z;Dt`y^}W<%Dys~Y$vL0VYRp3aS8Z6uiOoX*JxFh*QKY=e}Ke@0p<1j&!$(XNCCaX=qDIGnuKMSN2$j|*8fG^IX!s}cX_yb+O}=mwry+Lwrz9T zwr$(C-P5++znyn?|AM{Qn^Y>5N^->-?MpJ(BZ!ll&lH5tolbZomQ$;CN zsTy4grwQBMMPhPY<_!@$RO>7Vv;Osd0mh)yhawyX$YOnqb}w&HuPcnv&KqU@%;~Y5 zx_tnm>`?-jpW)nS6a4zhvOFc6tLBS-~Gshs|b)>Q#&9$ZKIkQ<^Ypu|8L( zGdBkCx4QV*n5R;eF_+isaQVPZ45?41uLY4G7+WsO+#>xpfxyWf5!$;CXy5au5)S;Q z6r#FY5*cr+JYcDrQ@5%0gcldjGCcHchSS67pL?}7|4kKt_y~k$6qmyJF|kPlon<9y z`orqF)_sAGVSy==(ccblSY)VQ)vt#r*k~_-<-qePLF?t;x@orrFDk_)Xu*eORSQ{vKr|n{}{TC&a9sSj;4EgyGNsvDdK<@q<6Ipj8G) zNB=Teg__mGwGXIGik&$jO+9yXnM77B5r32{{(CIPEu{|6ihEjdRx2k{N!mU1$OU1n zUIQH?7ZsFAH?OOKI6O^x3a1lAWBM86lue!kj-Ar;4z07jZ9`FOOPvO0i7zC$7zW(F zLC-jmoj$(@phsk4IEgU~kguQSUIVL5OUZnfsdx&Rv`dkX2!r#*bVE{Bsk>id%nLH@ z-G8Y;l{H`B!m%Hw<)SYgNY0sxhmmb|d28-!@V)JADke4{{8dmnYG}S`&1%{R_k|l$ zU*y7JdRgp@>ClwqWQ5qGG$s8<7g_sorlcBwV39Zh5p1_s1u(fFWS{2Rx!8ri|fWm#owr z2vecxw_aIK(nthc3*?{=2{L*J#-jR8&_GUO^>}EGx@!Pe%gX9FCZf1@O!l!k@!pe; zaks_G$m)TDO{#jPq%*b@Q;i==Q+5QpM{NRQl=GiMawchZ1d$*obG(B>638uOJpLp* zo6HC|=VHl3y{~aRq<*JIM6F}e23z7{&_;cx$v7eW*#JH@&K00eZg}T7X8N_&=XgO~ zXG)rK+$%7ht_uQz8!GE&U9jhW(j8ZMhfFNNE1*Pv9-{eUNN$4hW~ICsP^}ejxQ6y) z6T)~1DV$nlBclrtKjXWTyH?5g0RI77BEL}4jtSy88JxAx6=4tdChd1*hxaVU)}CKJ zWu%HQ^{2=?(ob+ylBycLFYFvb4w5L0GNFz@6ihjWlggtC@FuF$n(0lrv`+4ej}&2k7V40C>s?-{Xgn&_KYPCLUoeBu%0@avtm4%;T5VVQWcYlEwu zFOt_hUC@hRQ2G^|2yp^)wEN788@l2{G*QMC;o967J8I$4I^FD|MUVpZ51wH0fKfSM zlaKX>grJz+Kxg%@45J29GHt%Xlw?O8?vX!w2bM+H!*SspQsgFbKbqF8J)6@pHo33V z={N~q%PX(@fIhTaJ(-3<0Fje0Tm~^toDd~f+~^M9w`UgxpHB6C;YA&V)B; z?DT8f>2~y&N^lNFCCK*8*;3f_*rf&ZCk!)w@G|iaVCWP z3|HtJlO53f;heVus><>a5Fz2y*#eH{Glf(am2N?v#VJ=ymcsn4sPXcw(e%~(KYwZC zeVW=sy5!0*P&^kLD9)a8W7W4hm_T5F&es-Jxef!-OYcTuMRx2A?suh1eMO zzlJIxCBXjjG~)(Yk|ZY)ayeQxi%HRV>f|fz=3-Vd70P@d2>F(!Q-IB}ffq)3Dc@>} zbSz`ZN|j3@FRo9i0nF?BDg7vpxBw+(y!$ zdz%%2W`^EadfQ*Oht$Q48sT^eo~3)As&hT5ZN2opSa=Qz;TqL-Pl;tkDPCOc=rZ7w z_p~1j#3~<(r%ZCqP#V3E@TYWn5ALZ~%8{o8Iv1@^5Tj{)9Ys3F#Ar1thYkEil28vC zP(j~w3RM;(Xwb zcIj03vgjm0$8WCO)|~-fQ|<-Y+HUtA_x)Gj1pXDE_O4%Z$M^yKUj)~6g$)>}xpO~b z1$y{7*tYgextB+nty^NcPHz5$<=<(uRp7*1t?G)PVyss~|^_WI91V2O1 zecsUXkPH!db2LWZF|4pba|7~E)*kCz!_D&u%_vC@E}p5dj)G|WD5>#q1Gw{e7Yx3F z9{F$RCb^7<-4Z@FpccSyoK^trsUBhP8`u^#eTGex>v>DGTld?i##}xg;B}(YR|JOA zpnvWdMB3_eD@+oM<)NoQWExXWAvoJMG8jy73@O;wz3=1Q`sHJZ=2{;=T~PwYAA~DU z>hOHieI@?(T#B<_>d-`_l^F4m+-$qLYwN&Z ztH;j0iz3^@6*$($y$#}C?T1jv20Z!)?H3pxk%&Saudt$luezd)nr1#-v}Qm*jTo5M z2nye!fG_NRv@-TGQGhF?A>OZcQqXyn=Q0F;ymxn&M1Q{xgQX7m_bQQ}i-f+u-{X7r z!mHobq95;|YZrID+@*DK=T*iZ7wP}k-H*PiPe4U68V+~E7Mux4`4gb4t*Pl~AF$U2 z!5`QH*yDHr&}=}^c!GRf&`-b#0)_+w2?*briordQ5VXyM?cD#3*Ew-p=sk7q+?KFF ze;pybIVHd`I_=Yc8GG0po@&B})(zkIy;i^Gk2QtfMklYGCEovPqPy+AuzPOwqrrCR zGT)bD?#n+uUBrQ0gQLTpB@iCV~<4OLv2G{+j>uvi)OxUr`2I$@o3wghA9tA`2K0+^7On z_<-!^x$il}zZ&#w{imLs?_AQTXe=4a!@oD%o86D=uibBXhmmk=$-dny2q(!n4z=rD zB#83Pd9Kiru)KJW_|(CDRb`>B2CXvVV+eJ^U&tRqnnrI5yjCERdfO&6dT>sYzk?_S zOFE`^H^a;Pv!Wjy#-pOaF0hTAev{?@6cP+Fqap9bd+v?gT(maA6NA@o#%6%+`Uc0p zcj@`qn)Q=?Hc?*jvVN0&F90~pkhf3mN^{eh7d^Z&U)-}abONphqQ{=EkmOMCn*Fq@ zXlW`+gz>Rro5}JIow=3KzV9!z04jR|`3Mzl_aOMt#}Jk(|C}^8^N`+&`3wA+=GglI zC?(musj4SFgfGc4nfENQe<NIsIsM$uGg{AK<^%fxuzEP|-{Rwne}^aI}Z&&?lOhIe(ZVqf3d4`P@y% zxdn4D-F5~4eCrM09X9o8v8pFXg->NwWm_G_y@Y8mepNd}Q-gw9wmu%R<2b#^8A=*D z+tNuk+848vKiSPMrZbRLb=4Wl3GB5>bAtBNZW|^===xss0ciHB__L9D`06(&dfoSd{bMK1EV=9uriE9PP788;Sb) z8!fFo?1wsY-EP%-sec7xwTHR3;`UOxi-{JKryr|_zSgxb(wQqM{sdmSF@)RKV(w>*91h;7 zH+iX|SJ8X-e<=S#Y+>%xxX6#OpTRK?J3Gu@n_V~p#iKkvkPSKSqJcNDwDI(C(s^|Y z_Z=lwP1AkmYdHPk6bpa#s=C*?&iaJU(f&RT1?f4s(|yZei6qjW%uTVUd6+)F(vsTj zX9o*M?~}sEL+AyLSI>UepX)71bviMkT6lg@k2VR<6#V|}z|vh)iSmxCnaFWoE-E%; z37DJB+BE{u+yF#Soo&U@m9`j#l zE~w%ng`j-H6@C~+>86C*K9wFPx+OLrc-_JE`3v}VH8MMKmwg1e|D3*x#M^Mav;G)L z^c5wFh6!f8B6o2lc{t>59Z3WeVlI3*F5yH@6t;%M|h?re4o~H|8l`a%T`wY7mb~ zV8)e!>N|||MnU-{?rb+I^&tvv?L^ewWEh1Xx^h?Ho{5g&RoEhG#qwHLo(~Ey#+r|80njN|QG zjPFSi6WGJj(SsyC6>G{EvIAJG@QCy={LN8%s;iDYXyJDsg>+pqFq$4UzDl=G3B(u~ zi7@m2&ASw?JmRJ%7r1&}xulF)CU#yVgxPUM6n|@cR#H;@5LqzRlVmd}OrO?cjNWQ4 zs$p+g_>ap}!XU~u;UU>P=(n-CEqI8&7Bt%^UGC2>I%+}-aDHDKF(gE5g=qgFHAC8b z2lo8CjiN=3NYXCuE};+wVZlvY;Ob^7TQvopf4Hur*ac}&gc!CeiiE#9jbFNiv?hnZPN#O(eNGFB=p8#A%hk9f78sKOb=`yWgVYTg zdvZrZc*&0>RFVh3J4Jce_9*~TP9XjzX@@&b9pEI=7t3lg*_b?r9waO4) zayP*;KPPWLk;Qv#?_|8FkP0ha?&s@T2@Vo7opO?w1#r8;=$nljmQ=IH5+ zOaFisO+wRL{_gdWoHP#&fv2LD{LQxw3mI_kq!ehqrO4bC^P({EyY~^2Jy(J{NO60b z22FhbV$N|Xm412!RD2-ZcbqFpW@l-Ke+5PpEe=41*`&m6Rue#W2OxzO5C9KvlDh|x zCgnnm1oshxR~r_cf^q9#1Pl^-bZ|x$A&uJ26Ng6^`6P>J);|M~H})dP#L(r?$7~)g zkVcMX{u7~C`29QN?p3YAuq4Wj5TH=aDbw_af7C2#-50;w(q$w^zD`jH5yawMyi?_# zlTr^3M8k=J?sJJzN>ldeuNuo+ox%D7k(YHv9$O0^CFrWCD6L|&_(Q9xW^SzODinf)z*UE?+<%i?&LLLb^Ale?3!u)X?568g6{<;Pw9aJg`WuFa+FP-0W%Et?L ziCAR3HduX{&K!ZiG}8wUiq+TPmXv|X?GahR2#bqn%ecU{3JZUmvhJ`>Ztam-BFWKv z3}?F}hYzGn)D^&+e%8QhVC*zI4%=ecQmhINZ$&CSvLIseKhHfrr-p_Q%|lu?75)2PAD%~DILvywoP`!0sYYdVzk z8bsC;{|Q3siRMHXGkr@s(_^9OEqd)z$Mj47t=yifgw0$6Uc8c8w5)DnxaIPT-192&W=(@7X z6;Q3;bVSLpUv3SAgG5d{`im$tNjzw4p!!tH3p>^by3iw_Ou(1~J*QEZJ@qjwJe=)e zvr#Xojwnm9g{V=bWqzMlA=SzYA>U>Y)N!AvP$f0}jkl1XL;i+HT@wdpIrpwbt00T?G*_3pjc8QS$~e{BL3?D?npJE9Oa8Vp^TgQ$=jhxgcN_! z_?c2>tUszoa;H&)?w_!lsc-Tiy)+Y&9N|D;wz})rNNd z*{YSA_`ZM3-s8})&j`{i)D|t)F!0KekSViR#b5~NVyHZ6-cM9VTtTU0TJGyST1`pL zEt=aq9!J4ooo0QI6JGNjCNT?_qn24Ot3_oUMGO@QWTmF)F=J}q-mD{?ShE@&QPF98 z1ANI360WAA7lX#kqmWr;QRWh}pHIvZf3G7?og@d-`$TSIgNEB`4PaS4bU>3Cml_t`6c6`ZicZ2;d8qT^<8{zc|`tdGvp zjeL_a- zRDHBB`La76l^1%#epaXlBMekOy&+vEB zJPqGlRJukXeFJv5nv4m_*-8qe&( zjeLy&Trh+A6-c8dTDLFQi3P3ql+{P6*74%3`l;_zKEQKhZzV21PYar507;?4Y_#MDMpwQ{2h(8>%}o_ME#Fm7F3OZ@^V&uM5Ykp=^&h`@3k57CYsY{e4E zPAeLqoUP_L9s>HWcg~p@$D|YOz{juZ-vu3ch)_I_NQGkN?9vKa==dYmD5x941Yw?; z5{giwtPg?ZB1l5i{_y!i-nt0p4r09>-1szUW|3j~Y)U-X9%(R^LmdBf(8>A6y$yK} zIosnstg1tySQQ5ElFx~`3~}SpNdovKHD_;5odF-dN!Jl7JFLi@QCkhIppVw2bDK=0Z=#*CF;v9pg4Dlvw4`TM zja+&qzT4Vjhd#jxHH7nIksWW+ysaw|7Me~TOnU2B{^;ON;RQU1Mpt!9{HRy59B#EH zm3UxA<>w5^uc|>U_aZGUA7GrIjq-`^za09rQQjW&0AolR0nT;8WZh=NOFG&uTBUAm zW0!0*Ab`i5(R1BL+}REr*3uzM;s>*pWDZR;GF5YwkFI6pI8vB@+vO<36GWY~k7VC) z%V#g1Hu2{4{Yyx0AewJAw-lqW!H&5k8OTKaT1#1;oL$eT|-M`Fo~e$KWX*NC!z58x#}4_X12z*A^?k(R|89iY5$ywP&mW2?M5 zio3=RwYGS$vGl3h7k0s(Vuf$MN^ybDm2X}hKvXx85zGU8l9oDGEFL_ z)Mnz&PRo-!SYamNA1#-l_C+0-?V02*h0{=Xbi^VwP&rrgTM z17n>5#2Mk@s(vw;n~^q!%YO(hx-D$jf;r_B&>zW#O(_D;@d{YUd=pZbL?$^SmX5Mn z7>KULCpfudM`7A>Ct$ezx~VMg_o7%471TuzCl`aPZ`(cS4P0boAMoO{fb9c3;f#2*v?;UmGanyV;IQY#e4(^}PBLEGg-@IZ$8{f9nP{w{kKF&;&$Ug7#$43f~{ z*ktxb{Y8S(nU?iAvcn`I7pcIIAW)QVZ9GxUphw+GeECXgEj*iU!Vyb6Xq4kBep=I6 zEWcUFeETAOx0!b&krqFPMIi`(Jb8P~G_5S`{_29JEFx(a^RVRj7&nFTFrs zv({+mQg-H9I*rDMo(!2<|Mls+ai`Ov$QG;P3!RGPa z!Ho*2c}hPQDP0Y;o+?tp)Y12Px%5#J^7_?7Nye+Bp&Gr6|Gu#jq%G`RUl})~$HT=B z#fgv<@m3@B_#oO;Uay~7h^hR@`(3Nd&ob45FoL&-XRn52~;l$&L4GT?dRfHO%Tf zT?6v6_YCn6duIvgEhzG{E>u|nvZo(|8mF?P6g0{fSIME?TperEyz6K{a(695~a?i~QY{1<<`|6*`I z$jSkfqyhjYU;Zy>4}FFY7zBjzKQebZx!B?uD}ot7myU}V=f7QmL;08S4$+XFMUk=e zL`8mC&1vkVqY!{;*9hy@BEb#BTAYnfkE zv$(Eia9_&ixSse^UrtL>SqXp2nC2Jb10+;lge;-D8{;VFLgIFFANP|CVh}mk!&{KBNt{mjs` zEe5cq+qfam0cML^*G|gvKb#J=BloPBO-wP#vB930mOZ%ar(u$UG7L>F-H1Q7QOMD_ z!^nk^Yx10!yzJ7(#kc$+F2hVoX13SgpRW=|{CZuGO{9=xM;9r+fS>P8?aXtl37=78 z(L;~W%spR>?$C}QgFUbw#(43l$?$5;=>{X%m%%A}xiIeVThYt8=qe>++l%sK zh~q<#v*pBdp)i12sPZ=sa@@(buI?PZdx~)?{ux{HWyiOJpxbOeAUOsUXH7+0=MTt9 zLk+bF8hz=jFeRecE`*YDy$y1l<`y}7UCox*j{*tg+CQ2_S&7}nx1)?tB*h&|F4KF_ zZ{wVRuuZd2pECXsCqbb?n)~MlYjnoW!Oi4!>p>%k7eljHNyglNn$1-x_$Pswx~Gab zkiT=T{E)`h(G?U&G_In%Nu5RET^Gz`Te7zp!T54$^t66KlW4tNUDjzI<0-n_*4EUl zY)Traqf4e_!l)7Q+R^1USud-ZV4K42J9dH?D@>d;>t!}85a@R7d~FJ5eVW>D}c*6Vlzg4fTV7gAg<>m1(sCky8iV&%*<5uytZZ%B7Bm`$e3 zY&4=YLvi{d)4_|(h*q>>!o4DHB_Xd7KZT_I64E3v0x(F=iFg@(ZWUThszU;6?U-qh3q&xt$@BX2k4lhK2sE@oFZ+HClkG$(^ z9@n3-x`(}!>n$OUxZov5{d(Ldn*VO%2f&Yd^0fC*nXC`oIlf*$a`g3W2w?Vle4Z#W z;-Ph{LA4ql>dG!Ki%_(e`IUINt9?1GUF<*K&0M-yx*p1|K;7KCB~PKU-`Exc4$Jbw zzBxLv0-pkQT1}=7%W{$ud3OYS9uJ?p_6QL6p7MTvd;o?7*xl}@vp1Xnp2m9MdOyIF zuw$AFTMVdo8_TNoj)=qTj{_2hr#D^yBD1VP%4Py5IT(M4#X;6hGLt+m`-4`d4|TvOM>IskK~JAZorL zN9B>1wkudQLQVNgw3=3R@lwF@S%Nq&i-g3|W=kN@q4?k=b6`K8QU zpy%zlOsA`o&-S3`Kz42)yMJm?dbl`DEg0M z4sZ8sGf^;V5G8qw##;gc#D9*Vs}6z2`}jomI7jSil}2uxF?}R@^u@` zhD#AWI`ROf+ID4i09*m1iE#tS6(b}oqqo0t^(6Cw1JT|ru7}c>GqOH`;e_@NFx1Qr z+YpW6@S0_a?!a46@3{By7{O^vtxRP7Z8jRi!7+lf7t=rjC{AlVUmBR`YnrKhOYhoG z)$Ij?A3$OvJ!kLjx$o+`t;Kb$18{q_{0dm>0B{8RWi|vnHtRmLD&#j|#76`(Ld9nY zL-nleX5SkyQasfviB-P4YFP?5O{Q+Re|xDe?)rH565+*!a&OpesqG|wn0C}ieU}JU zv~7y2x|u)oetFz$8~$#lK9CFj?Uy{ep{c_!yS$|H>v>tkwn6aIg8&sHoc2H9B8zIZ zTE8cRRTx6=DmfRSHn(qGHOJ`8iXX8z0j@QJSb+WY_QtN;8(?-P6X2f$*av(4yrrD% zoi1FeH;gAik4M1r7|vfu`})4sb$P_F4sxm8^Ig=Ohi0^B9zlz)*(a3-qF0qpKMtTP;qvgAJLw-Iwl6$2ZuZwuKrXGh-+LIT(}7Ed^^{`Sl@ z8_-IJ_;rD_;sz;<^0%5&Q`{R!h$ROU+x##!`?+R6kr~!FnU>)(4*=?)1)8&O|2Fr+ zI^9B5V&d@4n1}*8m6rW=6=7r|m<~!6H8!xpI+NiM7()W90i1TjIjxC^=CqESKSwxQ zWVi@^CDf?eEG5s+BLFJ8y_{HbDK-zWr>*5w*nRXf4pv0G1=$|7jCXKLUSGCN`kt5hA| z6-yhxrAs5Tj82(ty_=C9j-vP~ZUqw#)Xj$dehUt(YY&4I3FfYhlv^KmD@`Iws3_j} zLNZ0AR{(c5GD6AYjGUD<@Z%T+Eud6k0f~lZEeIjn9 znxejym>kAa;u+cmQt$P^)aXEUn&h(`1LA< zYdz^c$+-Nqy-Jr-9Y0(czq+uy?)MP!+i9+}$HN=EDNX*qQr_xhhlm(w;txWPq<~CF zKsSTg^(NK?;lG+CJWowgmZiCgq~80O25xoEWO0FYPM^?BeIKGssR0k)qA zuP5t>>CGIb;FYa_TiyUo|D~TM=e|~ezsjdVO*3GM`uZK~_5ca+9S^1>s7-Qdo{-2G zBlWgkfURhZW<>Oq65L-PhqcWIp}9<}^m7Bj+=u}5W;g$hTU zx537VdCTrS0Rvhyo=sxHC)a(_J%YoPD3j$JOl>dlD!35cn~ymco)mC7(hTIKf(Evj zN?SiPU!pXVapxLLhW^W>5SfLCtXApCUZn8n*m{>h?7{(cr>M z*b%A1pae?|{pZQaqEPFROQ!c}5`#g=Sc3ovh?$5Q0tgbY8Cke$!7cGy(n45Q2$^Qt zI|2v>U&YB}F4RAgJpS9f(`9I=meKK(%14wR?D41>VDS0 zB|BkV6&FB&z>A$YtD!3e>rvm33CUxt@sGP7nGgyVRIMN&*bx~et+9#FGNXp#m3)w< zwOWN(h#DqH{}plVGy!ly_4P)5>r~M-as_>MK&`0Gz`tN&A+Q`o+CZrz?|PFMr7pQ5 zLkt{0Det~ay(O&c@8#JBAK%XqA*ggwsSs!b(Nql)jsvq(2<4S2EUs8VOur*yKezwwV?t zNhZtA8p}%-Z*eOCxSz%%aB6HcNPX0vr8ElSV~C6qoh>D#irV_Vr-*uM_bw@zAGah@ zAAH=lk8%JyXF5n)cE9A^{zbl7BO72g{K01hlWZ19Br2od>hc>&Wa);Z=)>jC>Hxom zkCY8c*=&PU@EoqHF@DhQUr?K^Znd3{Aju$TTCx}r7~SgRmK%;9nOoMiC3!$Mus@aA zYs0Qv2H%VIocdtX+}dAc|MrscYO-G8(d~w5FJ~-#vX6@r_pW8x(UW*Rmi;&`%N(0z zo}(|MoDOSHwX&1VD|ZVei!tJeO*ZD6;hpm=k6)`Jv2+sgPMc7Bo@c`W#)vM z^x-6kcSaN z>XJnJ1SQCh*bim8EpiE-*6byFsy;X>QMeS^eNg12^bz)WA>Qe*ikLu zn|bSATD27<5_d_DpiGTWI@X-Oyu5{CctYj($R9_f!32!NR8A-uI6~v{ksSD-m>HJT zreeWiXFWKCXwd#h^73o7?+8f!TNhcF--&~z%8rD|owJYlVj4Hv>q@>Yr>LA-Xey4_ zc-Z5GH0PXgsn^At4ZNnJheBP)&Mm2@BDv(hE%SvIrOz(}Hy&QTr-(9Rfo|_nXPF3{ zW(slDF0nq1k%V@epd8+|bOG~1^w<_&oSF*=mbPQs@F^xz$xph)kbhDlB zo_(1-=O&1k4eZs|-(_A1pZa_NNUxgu1x~kEiCAi=)v%tLvFT*BQ&np0-`d56qPi2r zt+>ikFc0AVfUZnh0Q8zuV}$XCsh*-U9-*`+(@_<1P9M;9PmS<~A?h}0o4$TJ=&z)Q z87F^jm8hRT0<5&}24I(dY+vy?U0e8H|K1kYpE7Jf(I}w&Uf`naZ{9l<)uH2cc+uB} z@M(eo%da>g|I=J+bsR*lV&x)UHE`;(;RK7~nOp9y$Q74lgk-H?Z=p+jWi)t;+!G8V ziAMirxeS9C(2UjoAr_0hcO_09t!7#ONM>>8Ud%ixMbcM%R)Og0;zDlEDv}Vo1iHL1 zistvKgZT8^6;SGVM`;&F3EA#$8m>Yi;(n2&p$b;#d>6(fXb?+eG?xxVE0AF)49FX$ zLcD@6=wF$u<-g9*!ZPYOWsmgAccKK{3+CXw3>`0GOK#DcVG|UBI|z*OF)TZ z5~0TcEeG?^%nZT$@O&vK=QvrBcw~wC)236@Np4jXch{vqtb36*?v9KA!%iD2IgARI`PrAjK4}j|A1D-Iet!zz1oz95Y6~;zucSXLtMuUAU{jz7~7E)?G1;?sl%QAPzhR zm}|_!Z9-OKL_o{2@{sEH^eRVrin*uOMRt{2=rx0BUW7~^TNnjSsU_7QDP1YES+QuZ z8pB)czFc}>5oILfeTi~^+X6WMo={;6GiAFchA6kNoh|bSU#5Pu35;jAOx-Z^HAQW_ zGj|x$du5~(absh{9x^cs48^cP-%~Bg;Onj$0-322W2{7Z+TAwA@pclo7ZMd476S=@ z?W?5^Zc2-Ne@bs|5D8$PAL^o?+>kXjq$XoGDB;rI-B3kH@u9|qX$2LJ&g`^E{k}pd zRD$z)3MdDw+W+DvMo}Ch@+4Ua=(9x;gzzf;_cn3ft_7V<06fl^Rz>_ycYlY&1E)&61~EQsDFHP+ho&W=n+9Q2Bl zaq7T15Sk*qP-*eVw-BQl*p!(P^064GNXnf|8%S!1M8W`7#6xmfF!Ph0@=o}5CHd4W z)(9;!@Hbj8CO5^*x#NKWgqp+MP97?M1{${j-472CX-5A+GJfgkU&it**GBG9a5IbC@5&y^|iRG z&sZ>ZgR>Su>ALRgQxCPJ(TlpZQF<-@_T$U3vm3sRy54WOYYHQ80wUTc{R$6QQJ48d z8E*FAsa-KF`USTspLbRJH53o0Y<3rob=yyBvJg?Y{EjD0s&^@Lb&vtI9HBq6*B#!y z7)Be;^HI~N>F(zy&#p?lwB$wi<2-|0!og-*Bz39+dd|GF11mMqa*+c0Ihn1mwZs(3 z|8y`zwHh5p77G&B%!c#`6n7%iU00Qz+4=6SM0-#=CE$DMk_HOn>IAaImC3Mu5dX*<=>mXhT%3WHfD>zCQ=|6^Hza`+U z3jx{Z0;kyEJIV?W-huFk(xXD?xdmXd9U4gz#Hd~{>A(^gPtVxl7=t}#)O?Fh7w^NP z>xH4{YL!BiG^EYlZaV!#IOjacW0{0j|k3mRQWLv zr>I^F3lH?))xQ>T<~%l}A2`)jUj?_@AthbN4AS9LiHNkS{*L)A)D6WQmlny@{fQIO z{${+iph^00TwN$$Gvve54Nc8~98g0-At&@#@&SUwx+6w^n2lu)ggzQm(+pT%)&G9# zt5sY8XuNf4kB7EX#35+>!%|?*z70)&} ztM|$-IzqF@*cu(vZf?WED-?F{`<;=-jsAh+D_6n~VFIQ5N5u+{$c8lRU6X!V%qxIT zXK@pDPo=*fFS2_Hf&R16i4mTAqTj*cGsNaRAgQXSQ1+tFyi0yrdEzz2toM<@`Biq& zJkm@1e&OVb@()3hGDVivXk*O#uNqjDUEb_v!L<)YIiW^T&R^C5 z8GA_?F@yBi2b69hIzIU;T0~4`or$Nh%uO<+sMz4h{KukJn%(&9taO?D-<9O2vm@KpuW zRp;^3)_Qwm<2~yi3jny@13DfRp~O*qp3Jih;Fh77ghhEtrS?(4&1vH}fPx}p@uppp z(n*brEb#9MN4Bi_87@JtdpxMQ9|s2z=1z5cYBE*1f59F18X{AL!=h(Bi?qNXqH+Vj z>n99&v*Sjseg_T8!Oo2kDs`@#8j?w?=x8faIe<{@m!KrAWS?MpV4tYmtqzHEPA-#j zn;-qFn#M*@1FZi_z>oR=@62Zlw{RkKoP8(NIw5*bnJjyNK`^3Q*v*iF^YxXuT9q>H z+PHi-E`#Xln@VTmtgEva_TOkOLha)L;)7J+Ct1-k3%d*vfdNLD!oQj&ii%B&sgxx0 zm|(6BuV8CLOR3fHV~9)=NJrL**`((g!*!8&9D&=UtFTgS0#Jp&_4;~EE7S=kP|$?Z z?L~1(ufaUv^TfEUr7)>z`+>O5p-w_vTSuxeW zOY8>qFm2Lb)c2LCu9dG*&lc{&BN`z;$Pg@cZ+{D6A5M55gY6hT~W;&Z)sj zgs4Ix`6+uR-kCyC*xR*E8t=G#`TwjRP-Wa;LLNxl@U6jHZO^bAL3DKrkqkOn4nNep zqAaQO&^g0$E~~Uql1=5t4-u|@je%W`t}PTT3IjZ_a14)2aaAMlQsa~ia; zeNButI>;99vqA^IS`2y4twl)y8)hmvEPNRQdqcjc)0ZG_ zp!L=1c+`T4oK%#}wr^EUPEl=*;YCIA+}pGrbB5|LtA_g~M%$=5X6Iu06@p_*CWFl< zhBenS$52rsoe|9x9z{$MKx=@O&!O<7>YDyay3e{l8YX7D13F9ecLB*1kbjavZ+3$|Q1DljpxpwiqE z7wMxWNCKPyZBeCJ5pk{-cTdctBo=(FB3!+!qFc%DocQ^((AmC9veA?e$;!ymw7pTy zK?Hl|?(t^es4gUm4ZV3L>}y%%O(ZE3%^OGtFKRq2_*apF^cye6a9bFW zB3Ay(@L1G@zn0gcdKK@;%B~E|e>hh0FAXu!7W8YH9>tf#G~=G4;@|B&jz-`-pQl-2 zWf>GTxPrkkTN(`-6-Vjp2zG#~X}0t|3~skt_hU98-sV1Ihthxl?-)A_{r?#|%G1#a zD>k&-%$Lspdw1u~j+_7Y!Ts9)SL6TI_`fy&Z;k)^r^WyMg&iH@b)is74IE(4=MhVa zESfA32-o)=Ro*4vcoHuW^HS2mahx87OUlsHbxX%-p$hxT@{nGc0L@=WMAg&8dj5h* zT11lz2{7y_V8aP}iU@zQr%?{CM`E@eGsta@sG;eY0{rI3AO~!U|EHGTzM_OGu8FgG zw20#ICgOJ-Eha9qugDwG$7Ll*zI>f+G+>-{A4jMr!&QvBrT|>^;s^^}T8kQjT0(hT zi6X3MNW7sz6_l|s-T=*x-ig2)ANcx~~*FzWh$)5Fu$c3?T+yWm>+X zZd252$bqkf&?4-;qHd`%SCS=-8Q)ip^`U{$jX!qQTY=Hi(satJLyH_%5>!euo_kQT z(ssgs1ry;X7v)mKTh9xyYg$>jqvW-`O9o($>|A}tqNMG3ZOh8E1i^Dow6 zc<$*?X(Br6W>PtC(sB8$Fi@(pCc_6z7oha_SN&j;g{=+n=aK71uV9Nx;O8)zJ=8FoTE(ic0N}%XmYu z%<9kHA|!_*@)@j!;PF%mY80TqNj1YR5VjQQicmq8{|$9 zcKKvF(p&42n=M6-W8oVxvadI?cjy=wiM&*CKUyom_l^b#pUQ`jM;a9BT9wpV6nP|< z&cc3^Io5qQow`={uCXcYXQB!sQe8fJ|6h}B>PYLA&fN>s7J#1w3=%Wh;d>1edL84kc@O(h9_TDwtE^GhsGl5V-qElxq+dtK;%X zlPK1do_c$qDT@o&v%D?ZwVxn`>c@1=Gl$U%-uIPuOV#22ivRb?EPFZ2-@Bl*T#x!x z-~GS-Rs|wxg5+i4h*=@l@?N(+J4^fkeOOn0f`%j=0i4V$YHd&ZTS~`jnSi1_0)?DM# z*cZKqY|VqkT&N(g4%}+IjDWFm@96dkyU1_iP9a96+#t*o%3f&3*N_wvrleD;Q#z)ePXNA>WvE{CXCMK+6tB_C%nt>8TceYS)jr}(#(e^!utEFj30M4y|I42-SVvIg@floPHq-Y<7~ zmt*4om)_rBg8`K#6qATyIgf#sx=n8G2PH13U!|^CX$1z{{Q|T(7XlcFje{xo#*h(Q z~|v&*KLqM>JtoE79$Lb)F+Ur?>>F2uqvKIvXtcbaB*}kmTFTP zavqS$dDMLvwW)i3MDHOr;H$ifS7{KU;hSC#`bESekH3#0B9Y-LdFhHbVtzvlmasG5 zg0&p-kJ3|?f!}3^!~C0G{b?RSb$G^_Un#G{c%%F|$B9IHm3<2;ATN6P$!kd+$m&#c zUvOPb*D5m~Qu$J4@yhw)=z8kTRYq^6W_3mORxn>2wIx=9XPg26VlTL} z>$9rniz8IV%@;?p$gH&9(n=6dU>H(n#Z<0eg#e&|@#O7XDH>Z438fF1b7>O3rb{yU z>MtSISM=LaAJlPsjQXio@SZXX;bE~{X=Tq=UrNp8^6fX_&J{{|N# zf4tjiv4_ARLx@|Z8VEE+ z#QBqoa#t4M7J7_0M4u2x%J{^%FKwOu=6h+kJl$_@<@jr$@i zbCT5v2X6=l6v*2w@f!0Gi`$c`mkTbR^zDL>NSDE$Z1l^hbpBRtUm`IGOYZWn{l0kK90hiS$=buWhX`X&=UVy%jGHu$QABXZYQ*Uj2ID#Y@^iAC}_NBY&C;8 zO-~l{CSpM(f$2V_-^kbW(X|gMhcz*eDgUiV10k!M=apYD(w_;-x(AjQ(%DriJYfCa zugdck+pk0$sE`vpKCTDmvS}|@R+%S=i0GS}@)B~F@TTt+@VY`0NFT7~BlXfTXxuc$ z_r%;ZH>{$s8USVls^l!5>@=kZcEz$L=~k;`&TO?hhGk*Jv3Y$XR;x=yxz#FErS@4a zJdnf_eI=?HrZMU!X=B7FPg<{)pM7N4{1Qe5-y@-^o<(RwuA${pq|{Yg z2}l-46715d&GGHhti87MNdVn5imH{byz4x(YZwn%1Hplg@y0D`v~eTXNQphoGZ!^Xjic9jts6_*dnHn!PDuu=n<`Sf&+Ue`8wd5vAAY^yw%-Lk^^;gf}D z0|Jw4Zg`2%f+-tS);SCA|kOS{ULO~OT7*jg1`Y6G)-L@J~euC5t0X+jqEwI1QzrE$j|8o!jzvlm6ga2a=64Wrjvrm}6r7XCSu^7?9 zg~c@Yo<)`{W*WXp!xpI|A!!yY5){xmZavpkNTZA_wHMra`_r?ZUi|XYt(Iy+%evIt zk-WcTD`kEw(CnMn-JcF$zIgrS_rtfn!~X02-thOw-Tmjofww120}42+>5RB|0%c}l z;db~Eiu*cy*?oOj*%24tl`)RI4a=uzJO<4Ep6(yMdGY#P<GY+9eqNDB1KyokgJ}m$- zc3z;n0RNZXpBmH$`-{eqsbX>=A=?X)B3il&ND2{wXUsZ!=kHeIb(w=Ia~6StY>*S_ zj7p3KN7pWOU3j{fg~~z>jI;)RRU7!S)zTs5sHgLmPKJ4eKb}pS3jB!;AQrSzZf~fP~IX>%pyzU*l5Tz z2+@Y35+T@QnPbGHhFDPrSn95o8Vk*z*m5^yUucqDGEmB?gthK6t%OEuPSmKJzeIB>1tb($99 zH^Hr1fnr+(KbRHaEX+?fXOnQ_ER17TY(z=HvPn2%8mCuu;yb@3hrRk-n-af!fkmK9 znz5wFt%?2U4ndlez7zP{yB^6HM${=As&q9#P9s}wG&|B$z}F|OBFNf^#K(@8PPrt0 zN3R3v!(A`Y2Mr6=#Q`w)VW~iGaD!T+{~wV5#anMf#4+8}HL#5T-+AEV|G0C1r{@1( zQUBLP4+E(@5J8xj`Hv;zO@UY$%)w7SOO%@bKzc-gpAY0w0GID1)magu9G%}laRH3_ zj|E#G8X5%N@FKc^tW1zHd3l~Ekoza7M1s&`h5AQ~tcMf%5py~DIXrqOq4BE|V6kQg zam0mVSa`xH!{iO^x&VHdoB@J}WOgeUEzS-d22~<+R-yRu0!?#IFKotC+VBtEIBtkI zJnsG^M_wmt1{BJ+j%@?^(yF$fsahE&+ z?{J~@9&6Q(@^Ish&QKpN7be&ozYgrwtx#$ML;YBW#^(z=IbMqS(#3t_D}gESpb`9k z!0rdf{&_gU+_7p@4c0zxw@9YErfHgngmoJ-lx^g==#?3#z3Qz7%u8z2TS1ZPzj3rm z*)IRsb#o5B+5&uno#4d!9whChHuovIG0LwUYHw6Bq*fx&?t zE(Vj-;bhQM9Da=Ai(t;O4GNUPCX;9sK}Z%13?+fWf-AN;S!vhdkzzG|@nF4!Prn2S9rL{_Sarx}$jUOeQUu?wE{I?XUY zdJqOvb}q9cz#W85Lkw3~L`P99+AuvE-F|=!$~zD556w8m<53Mj|E^u;2nk^j9(wSk ze%u3sN0H>9a)f6w%>IZKXN)DjnsAe#L?UMu%S$G&dKFh0kchU_+yEsCPQ%!@Nj= ze3g3KW(P@JLAA26^CY0?^AlJU;9G=eGN=YX7@?_nzzjdB4v8 z{8jJ&Py7-bCZp^E&GEvTUw!$rZRXzlw!QErzd4EFuqB_9kOKF*;zfyS!XCF*)HrzD zm7~F{dA5j)XoD*cM(J#}NJP|`e3Z(|S?Z=mwB3ceqD4}AeELO+emqxS#&Qg?ZsWOO0 z(L78FyJBH)w~avl1$Qe>7;nY}D6V4exv9L5Y`woG7fJm^ex2~9oqS;;E}T{}QXnFh z0UCCM^ODm50j(yjXk#(v6gmn=C*=;KzD?M&_g%}OSjRcCroy2`2=J)mGA0%9{iqX| z77z#x1sjM7#SF#Nr{=B?Ow7U(OE8lj-xejCPT9FOf;V9XDfWm0(&v_o3~vKdf5SGj zp_UvU-j^bn`skvtGY)|6R0d(oaS(cw4m*_H5{KTmQBwTS&9d;quz4AFEpGSIgc0e; zmy<@Qb~l1Ul-RCE7@`?=1y!kAo0ro_)Rko7V_Jbo#Nwxk^st0^3b+u$HWQWuzr(cH zqcjF3Uov(eta>TuX?SmpSq6j#mXU*coV2(<2*#{n*(^!`GZ;>!1pT@lup~SJPxv6p zQ|?x8@M(D;6;s+|*^(`e4ha|4EZNIJ>3ae04!8MD>fNHgHfT4d7mIoK?UtwP;PVlK zQ{aq?d0BRj>v+EyvhZK`Rbj~gTvYnX{@)Mo-`jEhzwd3;^8eS@|Gf^AaXRY(C%uN` zulTBQ-+Z>e;PriQOI>h@p)wlLB)oDET>Q19eIBVKWpxfqA%ra%`3ww+`^`ZSctw(a0O)6dO`cg)gNok&%<*a%sFBix_Jo@iZqd=jIShqaI31Zpu3R<}<#r+9Q zz$xT@vYiCP2v=E$CoCrq6mLr|zjGSHf@S&O+%`aKh#svS6^oU7FB{gCErcVPBNjhe$^JrCD?gXijRNY88!@)M(24z<$d`=h z(IY;K^q``RmTo#KH0(LN&D@QJ#H#!$Cce+j^HYa zXxX8Mtu_yzqDdW$P}C+ZTS7%tIpm}fr2^Gd^@t6vGic~19j}v&7o*mazFS}(Ghdi1jdob-6mV0cnZqjBTCcE*&v17i&u3K zkh;t*ZRQs4I7a#>cf41iRlLiqm-L2>1pSnfRGLP!6?~M_7-K@mY_XIR)p57|Ik=^o zXq+yjrAq(Cy)mc{Umy634&KPY^EM5>{(Fx<@EQ`fh?-GJ7wOMK4uTSJ%x43j6#hd2 zzyC1)bMiRj|9$k;-v9eA;6JIa_MxDS4gyzNc+@rfE6_*0OHxDvmBrSq*K>GHm}SDP z6#+Jlxv(2cenQD7jy{#2qHp1Ye^{0_e0H;;`lq4EKrD}x-_9@xMsyaN9jEkKhXa)Z zi2#gtZTDPhQ-S(;nLR7r3NQpBydk&h(%zWm+*fN8Y&hahzkK%H{P^T?PefRo#;2|g zO|43bv(kXSZvg-8Gw#L*?pEdT$>V3{W9dR}i7y5hPG@U~phRfH)m*%0)x zQ0VU{(SVAH=(yLxILJEWebtY8qtPf%j)fT=NWap}{k@E##u~8Xl!AE)F1`fXk zu9x5@LJlIQB&@|Sx(Gx{3`PaLU}xc}Phv#d8I}7=l0f#&oHCpFahc>|^wY{i2eDYfG}-g+=SJPiUuG4Eg=$csym%rR{i6W_%~p) z3nda65*{!9W+0U(XZ{GM7rHgJf#?iJs{{U_x5=kWcnat1=PgR1DY#D^*TALnGf<33 zrW{3rPUi^2DtwA`%v{z`X`fYDU4d*xZ6KxTVWzmGK`)UxtAM z3@FUm9bz}Q(82q#Hq*pfi*!aZa$PHE7JC*@*Z|Bhy26rqMK~edV_g950u`scxB$)i z!Is5agl*mmP;UrZL^T(H98?Hc^~ozBOQAh$u5XX-tiX z>?}+5cr4`#EST4z8VC%n^2*-{-*(?6PdC|~?UHgYr_;oDIGo#FznMW-#C8{)y{!2s ztg+9>KT!5xE>+fNkN-V+^mUT|;pF&uAOHIW#Q%ysU&AQ#s6Cg%wL!-=rd-eAkl^2g z-f$?+opG|d>cF-BJRD*vC5Sr0xlIR=!}J9mcOF!3lL_|6^z3Gw$DFNwj=?3YNZei2lrn}>Re9wB< zcP_uVn)_L6e)Bv-8;y?epF#qTMZfs2@XtK|HVb-lTA!kIaFtly;7Ib#MxN5F@o0Qc zwT^{diqL_ERdLxxtGoNz4;4Z2EO|%Tk?0_VeUl73E*%k-CnR0CO^3O#OB~J;A-x+h zbjM2(dpxe1oVsSiYZx1w29JoXI+m+%?Sw7Ki1TK|F5DWo&A6(icRAW|tb0gH>5(c1D~@-pRv3Ri!F&P)OENnr ztlHhlc2aZ)f6?uV;8#MTV{A+-s^HynxZ^qNgzgs~yc8oXG{ZkmvY#-ahWrwsSQF`-;eEtZa7ZF-%hhM_fi%qI2N%d}7@ z>enBhsrKvb58dotHQhdmzoB?!(+Q6By6`yU@%C(6?>5`q^y8_UEm)=E%)T86C*DJS zeTxW$6Ar)Ifv<;j&B!ypmkF^@lygEWsfnLZn>K>F2IHx(=M|WQ;;rWig!FaABm-1A zZ~Ewkg%eTHh8~fNI173#oPbJk!BuHky*9)P5_N-y7NT7guNIHbJ1mKlOphRVCSfFIj|zU7KSzdD~noFV8iI5l;4_QqOS zj9R>!T1dGQnzXFkofTIo;bTZjZs z23K8GtysE9g8n&yoEWzmLG2z>UaP~7kQ+k*#`&K&a7U1zEbjSUT-fj1G+Gfj35@&5 zao=o>dcm$+gi9ffJ-tb5U!j?JuIm*2&nc%7?KG~mR9ltK(+FdZq$Qr{ykhz?SCX1t zU@!|fb@zcR#sMt_aSJfomzNvG~J$~Z>q1P4u zCMf@jK)|!t8S67^`%jTD9K(FC&KIiIn^p2eWOx4As*&Z97d1K}+hSq1om~NT;2z2B z^uD{bHC+%8XwHb0dZ{sc5$86mTGZsVQ;JDfp4^649c28vv;T{l&PzD~@bR8aG1Bvp zddCU3g=!bulHH&;pdd^ZleJ$-h-k4FN1PS+hZdX>*Fr442XVA;CbRLKlzwFJa_cO{ z()Wh`8E!J3OpiA=BN+AXFDy7An3WZQKg(-oFCL=XQOF}tY=c+0&_C_-8A{AI)cN@* z;#Tu#wrgj=FkD-)e@=2*9v?|9ZrD#`()rhO*kQC~2YEQyi$n=$q>lB|S%Z$B7+w{~ zW@YMQLUWyCgN|jmoxt64>u=oN51E+{AMI4uHMg&ukUj*Jd?c_Uy-Ry0R#jxejRQ< zb!+|rvQM7SurHRTYjom|4*I9U`;|pdi(f@kQEDvHgY(S@Yp{BjOg zF)yLo8eGCD*gHPuyPgTT22^AcQT6YAo{%A+@+|?m(zQ}jKYF7 zQ{Sn3ddu?NP~@lHjTebaew_sz7P2ywJ7iKY0jYQ!a^){htwjyZUM#!Sc@gP1Bid^1 z(mL#7haZRdPlZnzf_nP%`A5w#&KWnY3u4-37d)tKIe)q`KRnthClQ(#69ZW7K^zeeWt1)WvX|Z|X^&dmm zB^63x7*)x#xV-+k6Qe1DQ5C;b>0?z|w*Fu=15&o5EOc1}oG^Kbz=_H!rPwq|o5Wq? zOb~=09%~TD{C9f`wlP6MuK3m*=sC*lS z-QcRwKj@V!Tg)SfNVqt~UQ@o$VeeH=a(|?Y17m>a(4~D>>KMhGXP=*|QcTGTwwTwZ zBig!exKLbVD3Rr0(jy1Z=jZP@H^$Lg=#JH8p`p6BgA~)@qcm?|h@#}_83$#S znaR4~$vvJ3k6@wQim4o}$%&H7qM`X2FMlB$z9`di5OSz1W08olR+`1(FE&OI|?rX!}(5MND}zqR!3q)UL+5^85^CYmiLxzCf4e&JN#1mr`QtN00ow2 zIIN(0pvK+e~31&Z}}-Hc`c#Z1*vvMpJ6`N zC`r?ABO~||sWTU!Ye0;yi=AU}{#c})W*lAq z2CfdW7Ya@sexLR%zw4*&%P(`IF<=Sm^k04!v~V2GT!N$i#pW049mSp?kuNrG`#KR1&&Us@6yAr9^!s&uSvG;6oxtmn9hUU4zhe-H4->AdWm4nVGmY~{!tN4ffwxXJ7!zQ#`&Y99qO{uy>#Q~;{vdS6QB|8SfH zEM8dH5&Ok1_pR22B$;2qwFP_#tX(quk}IcrxfUxEI2tV<&py`yzRm|`3IL^%;$wUo zWhUskW*a$5*VaI;YQqzH=xBR6U#yk6cIJw7%b8ZM3NOXc6A6T8TFSmX6z;NE72jB( zOq`^tiANdp<0r-`&2t<{yGqm~&P938>0?5irbLQG8hccPs=@)4~d6 zWR`z@=ua7w1}vFc0tv3FUlulUn9g;y-8m2U^s7uO!X-P(9liU>E5KR@3Yt^xamPNI z?rjJ8lDv$d8+#qTD>nh8bZxOPs`g{VXIxPltF*1=Zp3<`E4HC6QHfIhFGEHn+mV(5 z5I1WLkv$o8gW1VeKXv9Y)WWRqgy*K+Sqn9{v_c`V>(@;LJk`gAu(GBQix3rmv;*b~ zEJ0d?ZisyW5A4FfIHC|!c3{rr0?&N1`_8cK?BpGCs>Zv}8|BZf?H%U5tSmeYbFm-t zO=a~_ng?`8{Hm2KuUsNBlgv5R0%A<_8$jVG-rkF(4m!mq1cy_|zN5cOG-sRrToS|; z?{Co)_alEBvVIt)S$_+2t@ZWAd_HZv7SA-wWO>M`vILr6p4Uy9ywyvl$G1#`FiitU zwIY=gjQNfP#>1cwe*rtvY6npW<^5InvaQ4G=K%ibJ9WGJUqXdeWlSuMJT`tO`*Il7 zsmdSvgZfaC?dK>OQI+RgFqkKWE+NurgvInofkb!og;PBoa!xE7yhq&@jhrLnjmI{Q zWwC3s?7kzMrk0D#jGzwwk=3_~=zfLn#Z8LV2ch}zuRGFLsu!8t1hR4#<(dp`5x-_R zH;%Ra#n%$hga%q~p*i^R>=k?Cv_7A))a<>e?pHPTyC{uF zMO6B*ZLD48MtE4_erwUx106a70jG`W)Ol_(hW|NXyz)4IGf3Je@YDYqiS(CJ#A z#AI_ii@d_#qSoj$w&{dVoB73Iy~MxY$dTP&wmRA%OuAM4jjL8gFW37VH{KCtLXuJ_ zr>yy2v(ZQPhnq7{rYDc8Nk33mflE#lp%31|BMMnvI3kcGJzF?R%n`~cUkD|;DHP55 zQZApSTBTPre?ZkItXzW%vO9UTTOWzO#5O-Qi})zS7Mv|)RV-X+Tb?S$RK1ujw4=nl zID-20twTbiU{4L%{B1@i`nH_=TdQ#u@fpf;JyG}IC&G^W3L~%<--aMum)a74UUlEn zkR{JuAzUd!-fwqy`kl-^#feVE&Si|CFpmFcotod}il-|k5J_V^-HKmwRPll*xUY3g zV05XZg;76OASRV-NIGsl2R2F!Q+GZ_)Y)d5C>}T*+Z7>}^GFo)!@4=YB_cn?LKqUX zTVm!~KSwBI8$O-NRgHWi9mV&C^uo(SF7hfvE|M<}Nueh%D-XU76$`6Z^?Xv+ygGE_ ztCfwm?BdFm7u+cDR^4}>rH3~gMxAXO<_xR!_1vw0Gq>p$5j<5Y@>#))0Djp@X?d*S z$8Pqh4_q<*8Sqp=Xlhk0E^k0JUjQoYpqYnZ>`$bUSejiW3%)&74B{_Afn$PJNYjK) zGOc?h^Ofn|n+x`zrIx``h}B=4<~a`cE!voOsn;}vj(A<& zN2511ugl0PE(iu&LR=;fM>91F#{!;K`|jE(^QY?BT`k9gqLymQy^3E@$^{F0=S;L zax#4{bWQ5}+&BY(LGO+60YA5jGvLl`y8HC97W-Oy8T2_I22KA(vFDB%G~8Z2+~2*FG0bKtF*r%3l!HUl0P&9G>U?*(Pd`mitA>AO_KFIk8u3|Cl$p!qK6f1P4#JgN-gr(s8KAQA3LYMT*D5)6gWw$OLf*f7^np zJ2xyh9Sh`hX(;iBHf?cCT@~-vLxe2goVkx}2*Zgd?KOFnSj9RuU~IgZqx9-h}R8G}$^n`YWz6!Yy2Rm*Gee zs+{sjTy1Z#Gg~6VIc>F%^LWM=49xqjFbXxHBC!VQ*f0KjSJ_ukv z0J!_>#=w05&%g`tNfJ<32KEM|HG%v-08ylcgd)&M8(D+uAzC`X1rFE^14EI2(}0&r zy@Gj#X@K+sz}OYXJ8DRN@?@@9da-!lOArg3xqa}DI2TDUx#S=MpK|d_&i$Bsyimp4 zrd%R?W7bx;JakuwCm!Q}C0`J6&iHEOaVF)jiJ+?n3#d8j^tL?IP3mDr1lo>78qw}DK#9p406ryz>RFWV#~l0T?#XhJZX`I-07(dU z+G#kP$d?AIk;M)i#QdrHLB@P{#r23=?dDMi&aXcaUvnfY!3v(Ed^vr|vs5Z^7S<>? zjavMod#E?o#{6vqdhf}98R9-5ml~fd7O?+?FsNLMxX{xe*{DA7bbbWK$h(wd5L`)` zvS1qqa%I`MeCz)tfHa6aFLo&wS{@AbiQFJ+fRWxDy=pYa3`G5GvVh7?WH~V$78E@` zgnuiBoOXB(BEsy}2H36?sjP#~L-To5V@`U-2x1iNv(ClBKF2BKbnV%dF@E+foCW);E0B0s z)m~DwO8|v`7tbP?`D5wt?yTG0@ZkAut_`TjmNZ=8Q8qHZ7vZ%wq-UT+)CxgN z%m{XORa8JaS}Dku%a>$TpQ6l9&p=R@j%h_?MyLUr;m|WB)NAHk!Q|(uhJz&;Hf&yD zDS;IIOS$f7!u1lw^}8(e{OK@$cDMQd`bm3<6#lHBzNI{XJ;wGX6emQiSOzC#rz6** zO9@=8q|a?mCV>V(R=6FfE@SS@jrZ|WmEZHtgmb+B=t%7Y&QFYpbmtf7X!up6p;5|6 zXS57oqC=hndnOnTBK~d?y>+ybq1j>=tj(%qHeRB{upFr@G3x)OEVe)b*O<}F z<75t7wOMqf>SkVpd=C@BEGIJg%h%#S=^ey|b~NB}#b`gT;T41F@ov40_z9ICR}_Hh zR1i78g;yZ!+tmN@bE43tnJ|&CcMkmPG|hqJkd~Lyp91Sj*THCO`o)&nkJdl1WPEDu zYU9?0N+sd)J_emje7p$RR1XQxGQ9Bn^hnhK_5u)HTG(6yH7bu9CYF?YwGtmWdo@KR zk7t#2=zihYJMwa#TK`#+nO?r8oe+yWK-Sp=(g}fptec$iw3QXb$H}edc;+s^r02!A z2ge!yJW`YC1Ke;5Lz5Umw{am@L%{GQ&7fO(-V>DoJ?KwlXyBwX^DZ9c3Viik`eP+gU2Z zGU8)p-Xf8sv()N3DD4XsaXB50dGn(|%aYZ)mq0Qbm&nQ?mRxnBJziakz%-=-;~e3T z6Uyw2l%MC$n$t%7P$m{*b`qXA>Y+-Dq-ZH~^^ROG_EZMfRu}S7W7ca*1 z@$BJ8U1+rm{*H~esQ#M~)YOyx^1J|gN9e5dw{L9FvFvED|5N?Y(UIy_{$E~VtElhO z|0o^PDA;u=IiRjwMnEJn7F|?tKKJqe!MDBBf8g6m=|AvIqW3p|`_V~mC>sn$%cG3k4la_VVBS>#dn)40cz0Cv(8&!*Tv% zw-7r9#gc#DFem}|VgB6aSAf-@)Z>J7lLGx9f^UvSm9$_)6hDNGAGx)n&Af$14N0fZ zqhF^7hA!qVtsUDG?IunQ>9=3b z?1;^Ol{KXHOo4NXIA`}xoCt2W3yd!e>VZc9qcGssQ0zl_h?rk~%d#899No3Y}(NyjHsktF0xVNEZJGN%Nor@ebD zC3M0{@6Y^0@#FOEiu7XKNzgeovOfI{S0f6UI3*`SS)5$Iy91g@1;c2n_QmjQOXpSGE2abtJnkPW7og?y!_lzd}1VC_yV1nP^uq=c+A@z4#yn#kkWJ zCn)YBoN({=JWe`%bQ_o&-}1Vjy^2TCTcTKMDn@9fFu{Y+U*sQb4x5oPGQnIH7Tpn-9_C* z;1&cGt;tttQmD@AjcHtv_b%KjjB%x)wvQ=T|&r8g*_#|?w zk&6ZUralWu4~KJ9^2sp~OVtTsdVL``6@$>*9zHI?3-pUAqmr>Z4mprq)8p0wH~7lpHwkh2t6wnk55_NL%^2}SlUFOvfJw#kkTuWvOMxaF zSab!(`sKR#Y;{!LT12z%X66@jUsFlXeQ)TF9{)lT?e#k8wwvj>zLWO)QNQAOam?!J zubd>X9`w8je$TH7J^2h=Z_ITs0i3)4Kn%#DP7MJb;Rh;0lD$dFO8m|RecBfwc?KsZC6O#1@2On`m3qbgb!o8qh=dsla zAu+bK?qg2lEFfp+CZY(eggs9hifQIFdD$#%tkLZs>5Y1Nu(=nu%RjL1%0A#UQ{G$n z7cp8sDZQKTt1a!Ch`z||BT9?8QqJ_6s1g-6D8}-PPF+##%)i>U@R+Ga3i;K^d$~3( z=?7Y%4sr8HZ4VyB@Lx7K$!B7Y+CeI^bs_k)C>jkNjtMz!;hk7;TVvCcGYE zEBf)=KAN?5-0~ dv)=DI_k=~MpR}{^ariVLV zv0GEpSY{%T0!gPa0{`~|sTaSed)*|L0-7&W_{@-;q2?J)wHruIob8ekamycSgrI&6z?_TCz|#kFKZ$9NjFA+Grsj;e>IFnomZ0o}rk)`8*9o z(rV=`(c)Z7#^X;kZnZuH7Z_@i+kBKFL#gL&>~8CJZ?0%EL%(?@qFI8?MW?&ZxcT`r zszR+ad*UuSi%uIm-P8gEuQMUF8sVbT?i?M|@>e9gHUDR6L{i58CI@^a|A&JYoB2QJ z54<}6A3~l?W<1nP@HTdJ=cXxa1~x}!suxMsYHcBT*1g_HKHcv4&2r48a-PwxR^Aet zMz8b7_V0gmb;sW~@9Ux^o3Eyw2zPcb+Iq@V*AUxn>?TpqB>!GG&XU?auv(e_MN2M} z;CDU8?fL)Q8*J5oZ*VxM^Zy~_rUB68G{vS5%{M7(no@OXDuI)*Ksl_;tY+6}UZGSb zv3+Q|#3gRt8=(p%%XEQ!nrC=!JD@U$iIAYD2xlMPLBaw_B|}AW`;;ITaOsJ$Lq91rN2gk{zFuVa;g@R95Y%_Iz zoM6VI#BSuAN)BHzFj)q#9sj8LBc}9S>S9Sn13cyK1;$r7JXHTpJLTc>=Q9 z?@!qEDh7^YdJ2t+kSVpcnMAD_N^qPnl4)FylI!^46vN96P1^^=Y|5f|L9b3QLmnXy z8Ln*3n|%J>h4uZU8c(0XkERMR9yc1}@i?Gr+E^9`(^WIU zqrLxc9(Nun|6gq7fB*T5`u_hQlHaf422L&$<-Piq1>NHD43(lW{wT#IN?5=|iWHip z7g@BIu%LO=sLX$oLTPJlpY|C~3|QFK$@e((f4RYTL;|xex7(N}8(j&S6CI_#83v;a`mF zmv5z{^P>t3MVk7%7P3^y5;{kDYHFDee3k~=;5#wndK=VJiE1j6XdPN~U~~*0rAV2= zAC?Qf#QFJ@O5DBDY!+A#H?hTW-YORJd+|Rs&Y{ex)C^TVj5pjE=iAnPFX1(ks(ANo zsfth4&s0r)2!t>nRCevj((?GIzHxpt|5r8ouFim8;s58}V4MHVov`NrN6G&?4tQUu z|MGlz7wn$~{*Fc5PJR978-v^_%a`bX-U%Uh5ZnD!fjf!A{W!Ll&1=T~Ue5oiiXZ0u zZ-48n`TzeTy=TuF@XUVXQ`J|!U+5LhKlwTT#^9||m$VtBT6S)0ql%rjJ75`M;=w9 zMJ5vC)HGudlRQG>)HGwz0!-DI!&r-PIT5_E^$E1t=&(kceP-aT6}GiluCJRL`sJ>5 zb0fdpr;cCMi7UaP<1K%FLmJulmE*Op9NYB89%Kq{%=4B%rt=n$^3_WWkzHZiDDQV+ xWyysTkG-z#J^$Yw{eKJpdtUwhx90zv|7-rQ`M;K0YN_R3yupj_5004!dRXzX! literal 0 HcmV?d00001 From 72ed805978f1191bba16a33ffe1e401154b401bc Mon Sep 17 00:00:00 2001 From: Matthias Geihs Date: Tue, 24 Feb 2026 12:20:40 +0100 Subject: [PATCH 44/44] measure time and log to console --- packages/keyring-eth-mpc/src/mpc-keyring.ts | 67 +++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/packages/keyring-eth-mpc/src/mpc-keyring.ts b/packages/keyring-eth-mpc/src/mpc-keyring.ts index 37be1ecb2..c3038025a 100644 --- a/packages/keyring-eth-mpc/src/mpc-keyring.ts +++ b/packages/keyring-eth-mpc/src/mpc-keyring.ts @@ -235,6 +235,9 @@ export class MPCKeyring implements Keyring { this.#serializer.networkIdentity.fromJson(joinerIdentityJson); const ephemeralJoinerId = ephemeralJoinerIdentity.partyId; + const totalStartTime = performance.now(); + const session1StartTime = performance.now(); + // Session 1: establish with ephemeral joiner identity and nonce, // receive the actual static joiner identity const joinSession1Id = createScopedSessionId( @@ -253,6 +256,10 @@ export class MPCKeyring implements Keyring { const custodianId = new TextDecoder().decode(staticJoinerIdBytes); await joinSession1.disconnect(); + const session1Time = performance.now() - session1StartTime; + console.log('addCustodian session1 time', session1Time); + const session2StartTime = performance.now(); + // Session 2: establish with static joiner identity, // send partial key, key id, and fresh nonce const sessionNonce = bytesToHex(this.#rng.generateRandomBytes(32)); @@ -282,6 +289,10 @@ export class MPCKeyring implements Keyring { new TextEncoder().encode(joinPayload), ); + const session2Time = performance.now() - session2StartTime; + console.log('addCustodian session2 time', session2Time); + const initCloudStartTime = performance.now(); + // Notify the cloud custodian const onlineCustodians = [localId, cloudCustodian.partyId]; const newCustodians = [...onlineCustodians, custodianId]; @@ -298,6 +309,10 @@ export class MPCKeyring implements Keyring { token, }); + const initCloudTime = performance.now() - initCloudStartTime; + console.log('initCloudKeyUpdate time', initCloudTime); + const updateKeyStartTime = performance.now(); + // Run the key update protocol const sessionId = createScopedSessionId(newCustodians, sessionNonce); const networkSession = await this.#networkManager.createSession( @@ -313,6 +328,12 @@ export class MPCKeyring implements Keyring { networkSession, }); + const updateKeyTime = performance.now() - updateKeyStartTime; + console.log('dkm.updateKey time', updateKeyTime); + + const totalTime = performance.now() - totalStartTime; + console.log('addCustodian total time', totalTime); + await networkSession.disconnect(); // We disconnect session 2 after receiving message from custodian to avoid // a bug where messages are not sent when disconnecting immediately. @@ -417,6 +438,9 @@ export class MPCKeyring implements Keyring { const localId = await this.#setupIdentity(); const { networkIdentity } = this.#assertNetworkIdentity(); + const totalStartTime = performance.now(); + const initCloudStartTime = performance.now(); + const sessionNonce = bytesToHex(this.#rng.generateRandomBytes(32)); const { cloudId } = await initCloudKeyGen({ localId, @@ -424,6 +448,11 @@ export class MPCKeyring implements Keyring { baseURL: this.#cloudURL, verifierIds, }); + + const initCloudTime = performance.now() - initCloudStartTime; + console.log('initCloudKeyGen time', initCloudTime); + const createKeyStartTime = performance.now(); + const custodians = [localId, cloudId]; const threshold = 2; @@ -438,6 +467,13 @@ export class MPCKeyring implements Keyring { threshold, networkSession, }); + + const createKeyTime = performance.now() - createKeyStartTime; + console.log('dkm.createKey time', createKeyTime); + + const totalTime = performance.now() - totalStartTime; + console.log('setupCreate total time', totalTime); + this.#keyId = networkSession.sessionId; this.#custodians = [ { partyId: localId, type: 'user' }, @@ -468,6 +504,9 @@ export class MPCKeyring implements Keyring { const myId = await this.#setupIdentity(); const { networkIdentity } = this.#assertNetworkIdentity(); + const totalStartTime = performance.now(); + const session1StartTime = performance.now(); + // Session 1: establish with initiator using ephemeral joiner identity, // send own static identity (public id) const joinSession1Id = createScopedSessionId( @@ -485,6 +524,10 @@ export class MPCKeyring implements Keyring { new TextEncoder().encode(myId), ); + const session1Time = performance.now() - session1StartTime; + console.log('setupJoin session1 time', session1Time); + const session2StartTime = performance.now(); + // Session 2: establish with initiator using static identity, // receive partial key, key id, and nonce const joinSession2Id = createScopedSessionId([myId, initiator], nonce); @@ -502,6 +545,9 @@ export class MPCKeyring implements Keyring { // a bug where messages are not sent when disconnecting immediately. await joinSession1.disconnect(); + const session2Time = performance.now() - session2StartTime; + console.log('setupJoin session2 time', session2Time); + const joinPayload = JSON.parse(new TextDecoder().decode(joinPayloadBytes)); const { cloudCustodian, @@ -517,6 +563,8 @@ export class MPCKeyring implements Keyring { const onlineCustodians = [initiator, cloudCustodian]; const newCustodians = [...onlineCustodians, myId]; + const updateKeyStartTime = performance.now(); + const sessionId = createScopedSessionId(newCustodians, sessionNonce); const networkSession = await this.#networkManager.createSession( networkIdentity, @@ -531,6 +579,12 @@ export class MPCKeyring implements Keyring { networkSession, }); + const updateKeyTime = performance.now() - updateKeyStartTime; + console.log('dkm.updateKey time', updateKeyTime); + + const totalTime = performance.now() - totalStartTime; + console.log('setupJoin total time', totalTime); + await networkSession.disconnect(); this.#keyShare = key; @@ -693,6 +747,9 @@ export class MPCKeyring implements Keyring { const message = hash; const token = await this.#getVerifierToken(verifierId); + const totalStartTime = performance.now(); + const initCloudStartTime = performance.now(); + await initCloudSign({ keyId: this.#keyId, localId, @@ -702,6 +759,10 @@ export class MPCKeyring implements Keyring { token, }); + const initCloudTime = performance.now() - initCloudStartTime; + console.log('initCloudSign time', initCloudTime); + const dkls19StartTime = performance.now(); + const networkSession = await this.#networkManager.createSession( this.#networkIdentity, sessionId, @@ -715,6 +776,12 @@ export class MPCKeyring implements Keyring { networkSession, }); + const dkls19SignTime = performance.now() - dkls19StartTime; + console.log('dkls19.sign time', dkls19SignTime); + + const totalTime = performance.now() - totalStartTime; + console.log('total time', totalTime); + await networkSession.disconnect(); return toEthSig(signature, hash, publicKey);