@metaphi/airwallet-api
Version:
Metaphi Airwallet API to add whitelabel, non-custodial wallets to dApps
994 lines (852 loc) • 29.5 kB
JavaScript
/**
* Metaphi Airwallet API.
*
* Use this library to integrate the Metaphi Airwallet into your dApp.
* This library allows you to connect to Metaphi Wallet APIs, to recover or create new wallets.
* The KMS systems allows users to create wallets locally and backup encrypted shares
* to Metaphi and the dApp.
*
* To learn more about the KMS architecture, visit: https://docs.metaphi.xyz/kms-whitepaper.
*
*/
// Device
import axios from "axios";
import store from "store";
import expirePlugin from "store/plugins/expire";
import Cookies from "js-cookie";
// Cryptography
import sss from "shamirs-secret-sharing";
import crypto from "crypto";
// Wallets & Transactions
import Web3 from "web3";
import EthereumWallet from "ethereumjs-wallet";
import Common, { Chain } from "@ethereumjs/common";
import { Transaction } from "@ethereumjs/tx";
import { ecsign, hashPersonalMessage, toBuffer } from "ethereumjs-util";
import { concatSig } from "./utils";
// Providers & Polygon
import HDWalletProvider from "@truffle/hdwallet-provider";
import { PlasmaClient } from "@maticnetwork/maticjs-plasma";
import { use } from "@maticnetwork/maticjs";
import { Web3ClientPlugin } from "@maticnetwork/maticjs-web3";
import { ethers } from 'ethers'
// Initialization
const common = new Common({ chain: Chain.Mainnet });
store.addPlugin(expirePlugin);
console.log("Loaded Metaphi Wallet Api.");
use(Web3ClientPlugin);
const METAPHI_LOCAL_SHARE_PREFIX = "metaphi-key-share-";
class MetaphiWalletApi {
/* Static properties */
// Environment
_environment = "development";
// Endpoint for wallets.
_METAPHI_WALLET_API = "https://api-staging.metaphi.xyz/v1/wallets";
// Endpoint for wallet verification.
_METAPHI_WALLET_VERIFY_API =
"https://api-staging.metaphi.xyz/v1/wallets/verify";
// Endpoint that exposes Metaphi Secret API.
_METAPHI_WALLET_SECRET_API =
"https://api-staging.metaphi.xyz/v1/wallets/secret";
// Endpoint that exposes the dApp Secret API.
_DAPP_WALLET_SECRET_API = null;
// Local share prefix.
// ClientId of the dApp.
_clientId = null;
// API key of the dApp.
_clientApiKey = null;
/** Wallet Properties */
// Wallet Public Address
_publicAddress = null;
// Wallet Private Key
_privateKey = null;
/** Network Properties */
// Rpc
_rpc = null;
// Chain Id
_chainId = 0;
// Wallet Provider.
_provider = null;
// Web3 Provider
_web3Provider = null;
// Plasma Client, for Polygon
_plasmaClient = null;
// Additional features.
_logger = null;
constructor(options) {
const { accountConfig, networkConfig, custom } = options;
// Throw error, when accountConfig is missing.
if (!accountConfig || !accountConfig.clientId || !accountConfig.apiKey) {
throw new Error("Error initializing wallet: Missing clientId or apiKey");
}
if (!networkConfig || !networkConfig.rpcUrl) {
throw new Error("Error: Missing RPC");
}
// Account Config.
this._clientId = accountConfig.clientId;
this._clientApiKey = accountConfig.apiKey;
this._DAPP_WALLET_SECRET_API =
"https://api-staging.metaphi.xyz/v1/dapp/secret"; // TODO: Should be initialized by dApp.
this._environment = accountConfig.env || "development";
// RPC
this._rpc = networkConfig.rpcUrl;
// Chain Id
this._chainId = networkConfig.chainId;
// Custom functions.
if (custom?.logger) {
this._logger = custom.logger;
} else {
this._logger = function () {
console.log(...arguments);
};
}
// User Id
this._userId = null;
}
/* Public methods */
// Login Metaphi wallet
login = async (userId, userPinInput) => {
// Persist user id.
this._userId = userId;
// Fail-safe.
store.removeExpiredKeys()
let userPin = userPinInput || this._getCachedPin()
this._logger(`Logging in: ${userId} | ${userPin}`);
let response = { verified: false, autoconnect: false };
// Check if logged-in user. Skip verification, if true.
let address;
let jwt = this._getAuthenticatedJwt();
if (jwt) {
try {
// TODO: When wallet is overriden, this will lead to stale results.
const userDetails = await this._getUserDetails(jwt);
if (userDetails.username === this._userId) {
address = userDetails.address;
} else {
jwt = null;
this._resetAuthenticatedJwt();
}
} catch (ex) {
jwt = null;
// Reset expired jwt.
this._resetAuthenticatedJwt();
}
}
// Get wallet.
if (!jwt) {
const response = await this._retrieveWallet(userId);
jwt = response.jwt;
address = response.address;
}
// Check if logged-in user. Skip verification, if true.
response.verified = !!jwt;
// Set public address.
this._publicAddress = address;
// If the user is logged-in, and reconnects via pin
// cache the pin.
const shouldCachePin = !!jwt && !!userPin
console.log('Caching pin: ', shouldCachePin, jwt, userPin)
if (!!jwt && userPin !== undefined && userPin !== null && userPin !== 'null') {
this._setCachedPin(userPin)
response.autoconnect = true
}
// Extract public address.
this._logger(`Wallet Authenticated: ${this._publicAddress}`);
return response;
};
// Check if user is logged-in.
isUserLoggedIn = () => {
return !!this._userId;
};
/**
* Gets all logged-in users, with autoconnect status.
*
* @returns Array
*/
getLoggedInUsers = () => {
store.removeExpiredKeys()
const loggedInUsers = [];
store.each(function(value, key) {
// Get all valid jwts.
const groups = key.match(/(metaphi-jwt-)(\S*)/);
if (groups && groups[2]) {
const email = groups[2]
// Find corresponding cached pin, if exists.
const cachedPin = store.get(`metaphi-pin-${email}`)
// Add to array.
loggedInUsers.push({ email, autoconnect: !!cachedPin });
}
})
return loggedInUsers;
};
// Wallet Provider.
getProvider = () => {
return this._web3Provider;
};
// Get public address of wallet.
getAddress = () => {
this._logger(`Connected Wallet Address: ${this._publicAddress}`);
return this._publicAddress;
};
// Create a new wallet for the user. Use with caution.
createNewWallet = async (userPin) => {
if (userPin === undefined) {
throw new Error('Error creating wallet: User pin not found.')
}
// If the public address does not exist or minimum shares are not met
// a. Generate the wallet key and address locally.
// b. Break it up into three parts, encrypt using symmetric key.
// c. Store one piece locally, store one piece on the dApp and the final
// piece on Metaphi.
const userCreds = await this._getUserCreds(this._userId, userPin, true);
const wallet = await this._createNewWallet(userCreds);
this._logger(`Created New Wallet: ${wallet.address}`, "green");
// Setup wallet.
this._setupWallet(wallet);
};
// Transfer.
transfer = async (toAddress, valueInWei) => {
const plasmaClient = this._plasmaClient;
// Source: https://maticnetwork.github.io/matic.js/docs/plasma/erc20/transfer/
// initialize token with null means use MATIC tokens
const erc20Token = plasmaClient.erc20(null);
const result = await erc20Token.transfer(valueInWei, toAddress);
const txHash = await result.getTransactionHash();
const txReceipt = await result.getReceipt();
return txReceipt;
};
// Personal Sign.
// Inspired from: https://github.com/MetaMask/eth-sig-util/blob/8f5a90bed37e6891fe4e9ab98a8cd4f62188d5c4/src/personal-sign.ts
personalSign = async (messageString) => {
if (!messageString) {
throw new Error("Missing parameter: messageString");
}
if (!this._privateKey) {
throw new Error("Missing private key.");
}
// const message = Buffer.from(messageString);
// const msgHash = hashPersonalMessage(message);
// return this._personalSign(msgHash, this._privateKey);
try {
const wallet = new ethers.Wallet(this._privateKey)
const signedTx = await wallet.signMessage(messageString)
return signedTx;
} catch (ex) {
console.log(ex)
this._logger(`Error signing message: ${ex.toString()}`);
}
};
// Sign a transaction.
signTransaction = async (transaction) => {
if (!this._privateKey) {
this._logger("Error signing transaction: Private key missing", "red");
}
try {
const wallet = new ethers.Wallet(this._privateKey)
const signedTx = await wallet.signTransaction(transaction)
return signedTx;
} catch (ex) {
console.log(ex)
this._logger(`Error signing transaction: ${ex.toString()}`);
}
};
// Sign a message
signMessage = () => {
// TODO.
// await walletMnemonic.signMessage("Hello World")
};
// Connect wallet.
connect = async (userPin) => {
let pin = userPin || this._getCachedPin()
console.log('Connecting wallet with pin: ', pin)
// Connect wallet.
this._logger("Connecting wallet.");
// If user pin isn't provided, check cache.
if (pin === undefined) {
this._logger("Error connecting wallet: Pin not found.");
return
}
await this._connectWallet(pin);
if (this._publicAddress && this._privateKey) {
this._logger("Wallet reconstruction successful. Wallet connected.");
console.log('Caching pin.')
this._setCachedPin(pin)
} else {
this._logger(`Error connecting wallet.`);
throw new Error("Wallet Reconstruction Unsuccessful.");
}
};
// Disconnect
disconnect = () => {
this._reset();
};
/* Factory methods */
// Recovers a wallet using the secret shares stored in Metaphi and the dApp.
recoverOrCreateWallet = async (userCreds) => {
// Public Address & Email of the user
const { publicAddress, userEmail } = userCreds;
let wallet = { address: publicAddress, privateKey: null };
// If the wallet secret and public address exist:
// a. Retrieve 2/3 shares from local device, dApp and/or Metaphi
// b. Decrypt it using userPin
// c. Persist reconstructed privateKey in Wallet scope
if (publicAddress !== null && userEmail != null) {
this._logger(`Attempting to reconstruct key for ${userEmail}`);
/**
* List of shares, to reconstruct the secret key
* These shares will be retrieved in the following order
* 1. Device Share
* 2. dApp Share
* 3. Metaphi Share
*
* We need 2/3 shares to reconstruct the wallet. Incase, this criteria is not met,
* we will recreate the wallet and shared secrets
*/
let shares = [];
// Retrieve share from local device.
let localShare = this._getShareFromDevice(userEmail);
if (localShare) {
this._logger(`Retrieved share from device.`);
shares.push(localShare);
} else {
this._logger(`Share on device: Not found.`);
}
// Retrieve share from Metaphi.
let metaphiShare = await this._getShareFromMetaphi(userCreds);
if (metaphiShare) {
shares.push(metaphiShare);
}
// Retrieve backup dApp share.
// This happens when either the local share or dApp share is missing
if (shares.length < 2) {
let dAppShare = await this._getShareFromdApp(userCreds);
if (dAppShare) {
this._logger(`Retrieved share from dApp.`);
shares.push(dAppShare);
}
}
// Required number of shares exist.
// Reconstruct private key.
if (shares.length == 2) {
const symmetric_key = this._generateSymmetricKey(userCreds);
const privateKey = this._reconstructWalletFromSecret(
symmetric_key,
shares[0],
shares[1]
);
wallet.privateKey = privateKey;
} else {
this._logger(
"Error fetching minimum number of shares. Please contact Metaphi or create a new wallet.",
"red"
);
// TODO: Give the user an option to recreate wallet.
}
} else {
this._logger("Error recovering wallet. Doesnot exist");
}
// Return reconstructed wallet.
return wallet;
};
// Default function to retrieve user credential.
// Override, with dApp functionality.
defaultUserPinFunction = async () => {
const userPin = prompt("Please enter your secret pin", "1234");
return userPin;
};
/* Private methods */
_personalSign = (msgHash, privateKey) => {
const sig = ecsign(
msgHash,
new Buffer.from(this._privateKey.substr(2), "hex")
);
const serialized = concatSig(toBuffer(sig.v), sig.r, sig.s);
return serialized;
};
_reset = async () => {
/** Empty Caches. */
// Authentication
this._resetAuthenticatedJwt();
this._resetCachedPin();
/** Reset statics. */
// Wallet Public Address
this._publicAddress = null;
// Wallet Private Key
this._privateKey = null;
// User ID
this._userId = null;
this._logger("Wallet disconnected.");
};
_triggerManualAuthentication = async (userId) => {
this._logger("User is not logged in. Triggering authentication flow");
// Get wallet.
let jwt = await this._retrieveWallet(userId);
// Verification Flow.
// This is triggered when there is no oauth flow setup for this dApp.
if (!jwt) {
this._logger("Triggering verification flow.");
var verificationCode = prompt("Enter your verification code", "123456");
this._logger(`Entered Verification Code: ${verificationCode}.`);
let { address, verified, jwt, wallet_id } =
await this._verifyUserVerificationCode(userId, verificationCode);
// Set public address.
this._publicAddress = address;
// Set jwt authorization
this._setAuthenticatedJwt(jwt);
return verified;
}
// OAuth one-click setup.
// TODO: Get public address from jwt and set it.
// TODO: Cache jwt.
// Authenticated.
return 1;
};
// Get/Create new wallet.
_retrieveWallet = async (userId) => {
let myHeaders = new Headers();
myHeaders.append("X-Metaphi-Api-Key", this._clientApiKey);
myHeaders.append("x-metaphi-account-id", this._clientId);
myHeaders.append("Content-Type", "application/json");
// Wallet Authentication Flow.
// TODO: Switch to axios.
try {
let raw = JSON.stringify({
email: userId,
});
var requestOptions = {
method: "POST",
headers: myHeaders,
body: raw,
redirect: "follow",
};
const URL = this._METAPHI_WALLET_API;
const response = await fetch(URL, requestOptions);
const wallet = await response.json();
// Set userId
this._userId = userId;
this._logger("Retrieved wallet.");
return { jwt: wallet.jwt, address: wallet.address };
} catch (ex) {
this._logger("Error recovering wallet.", ex);
}
};
// Authenticate user via verification code.
_verifyUserVerificationCode = async (userId, verificationCode) => {
let myHeaders = new Headers();
myHeaders.append("X-Metaphi-Api-Key", this._clientApiKey);
myHeaders.append("x-metaphi-account-id", this._clientId);
myHeaders.append("Content-Type", "application/json");
try {
// Verify code.
// TODO: Switch to axios.
const URL = this._METAPHI_WALLET_VERIFY_API;
let raw = JSON.stringify({
email: userId,
verification_code: verificationCode,
});
var requestOptions = {
method: "POST",
headers: myHeaders,
body: raw,
redirect: "follow",
};
const response = await fetch(URL, requestOptions);
const wallet = await response.json();
const {
jwt,
wallet: { address, wallet_id },
} = wallet;
// Set jwt.
this._setAuthenticatedJwt(jwt);
// Set public address.
this._publicAddress = address;
return { verified: true, jwt, address, wallet_id };
} catch (ex) {
this._logger(`Error verifying wallet.`);
}
return { verified: false };
};
// Connect Metaphi Wallet
_connectWallet = async (userPin) => {
const userId = this._userId;
const userCreds = await this._getUserCreds(userId, userPin);
const wallet = await this.recoverOrCreateWallet(userCreds);
// Setup wallet.
this._setupWallet(wallet);
};
_getUserCreds = async (userId, userPin, isNewWallet) => {
if (!userPin) {
throw new Error("User pin unknown.");
}
// Get user pin.
// Prompt the user for a pin and generate a symmetric key.
// This should be protected using faceid, webauthn, etc.
// Key must be 32 bytes for aes256.
this._logger("Retrieving user credential.");
// Retrieve reconstructed wallet.
this._logger("Reconstructing wallet.");
const authorizedJwt = this._getAuthenticatedJwt();
const userCreds = {
userPin,
userEmail: userId,
authorizedJwt: authorizedJwt,
publicAddress: isNewWallet ? null : this._publicAddress,
};
return userCreds;
};
_setupWallet = async (wallet) => {
// If the wallet address has changed, update user
if (wallet.address !== this._publicAddress) {
// TODO: Handle this case, NS to comment
this._publicAddress = wallet.address;
this._logger(
`Public Address changed from ${this._publicAddress} to ${wallet.address}`,
"red"
);
console.warn(
`Public Address changed from ${this._publicAddress} to ${wallet.address}`
);
}
// Persist public key.
this._publicAddress = wallet.address;
// Persist private key.
this._privateKey = wallet.privateKey;
// Setup wallet provider.
this._setupWalletProvider();
// Setup plasmaClient, for Polygon
this._setupPlasmaClient();
};
_setupWalletProvider = () => {
const privateKey = this._privateKey;
const rpc = this._rpc;
if (!privateKey || !rpc) {
throw new Error("Invalid private key or rpc");
}
// Assign.
this._provider = new HDWalletProvider(privateKey, rpc);
// Assign.
// Source: www.reddit.com/r/ethdev/comments/8d70mz/using_infura_with_web3_html_providerengine/
this._web3Provider = new Web3(this._provider);
window.metaphi = this._web3Provider;
};
_setupPlasmaClient = async () => {
const network = "testnet"; // 'testnet' or 'mainnet'
const version = "mumbai"; // 'mumbai' or 'v1'
const provider = this._provider;
const publicAddress = this._publicAddress;
// Setup Plasma Client.
const plasmaClient = new PlasmaClient();
await plasmaClient.init({
network,
version,
parent: {
provider: this._provider,
defaultConfig: {
from: publicAddress,
},
},
child: {
provider: provider,
defaultConfig: {
from: publicAddress,
},
},
});
// Assign.
this._plasmaClient = plasmaClient;
};
_getCachedPinName = () => {
if (!this._userId) {
throw new Error("User not found.");
}
return `metaphi-pin-${this._userId}`;
}
_getCachedPin = () => {
try {
const keyName = this._getCachedPinName();
return store.get(keyName);
} catch (ex) {
return null;
}
};
_setCachedPin = (userPin) => {
if (userPin === null || userPin === 'null') return
const keyName = this._getCachedPinName();
const expires = new Date().getTime() + 30 * 60000; // Expires in half an hour.
store.set(keyName, userPin, expires);
};
_resetCachedPin = () => {
const keyName = this._getCachedPinName();
store.remove(keyName);
};
_getAuthenticatedJwtCookieName = () => {
if (!this._userId) {
throw new Error("User not found.");
}
return `metaphi-jwt-${this._userId}`;
};
_getAuthenticatedJwt = () => {
try {
const keyName = this._getAuthenticatedJwtCookieName();
return store.get(keyName);
} catch (ex) {
return null;
}
};
_setAuthenticatedJwt = (jwt) => {
const keyName = this._getAuthenticatedJwtCookieName();
const expires = new Date().getTime() + 60 * 60000; // Expires in 1 hour.
store.set(keyName, jwt, expires);
};
_resetAuthenticatedJwt = () => {
const keyName = this._getAuthenticatedJwtCookieName();
store.remove(keyName);
};
// Get user details from jwt.
_getUserDetails = async (jwt) => {
this._logger(`Fetch wallet details: ${this._METAPHI_WALLET_API}`);
const response = await axios.get(this._METAPHI_WALLET_API, {
api_key: { api_key: this._clientApiKey },
headers: {
Authorization: `Bearer ${jwt}`,
"Content-Type": "application/json",
"X-Metaphi-Api-Key": this._clientApiKey,
"x-metaphi-account-id": this._clientId,
},
});
return response.data.user;
};
// Get share from device.
_getShareFromDevice = (userEmail) => {
let share = store.get(`${METAPHI_LOCAL_SHARE_PREFIX}-${userEmail}`);
return share;
};
// Retrive share from dApp.
_getShareFromdApp = async (userCreds) => {
this._logger(`Fetch share from dApp: ${this._DAPP_WALLET_SECRET_API}`);
try {
const response = await axios.get(this._DAPP_WALLET_SECRET_API, {
api_key: { api_key: this._clientApiKey },
params: {
wallet_address: this._publicAddress,
},
headers: {
Authorization: `Bearer ${userCreds.authorizedJwt}`,
"Content-Type": "application/json",
"X-Metaphi-Api-Key": this._clientApiKey,
"x-metaphi-account-id": this._clientId,
},
});
if (response.data.key_share.length)
this._logger(`Fetched share from dApp.`);
else this._logger(`Fetched empty share from dApp.`, "red");
return response.data.key_share;
} catch (ex) {
this._logger(`Error fetching share from dApp ${ex.toString()}`);
}
};
// Retrieve share from Metaphi.
_getShareFromMetaphi = async (userCreds) => {
this._logger(
`Fetch share from Metaphi: ${this._METAPHI_WALLET_SECRET_API}`
);
try {
const response = await axios.get(this._METAPHI_WALLET_SECRET_API, {
headers: {
Authorization: `Bearer ${userCreds.authorizedJwt}`,
"Content-Type": "application/json",
"X-Metaphi-Api-Key": this._clientApiKey,
"x-metaphi-account-id": this._clientId,
},
});
if (response.data.key_share.length)
this._logger(`Fetched share from Metaphi.`);
else this._logger(`Fetched empty share from Metaphi.`, "red");
return response.data.key_share;
} catch (ex) {
this._logger(`Error fetching share from Metaphi: ${ex.toString()}`);
}
};
// Set share on device.
_uploadToDevice = (userCreds, share) => {
const key = `${METAPHI_LOCAL_SHARE_PREFIX}-${userCreds.userEmail}`;
store.set(key, share);
this._logger(`Saved local share on device.`);
};
// Uploads share to Metaphi.
_uploadToMetaphi = async (userCreds, address, share) => {
try {
this._logger(`Uploading share to Metaphi: ${this._METAPHI_WALLET_API}`);
var data = {
address: address,
key_share: share,
};
var config = {
method: "patch",
url: this._METAPHI_WALLET_API,
headers: {
Authorization: `Bearer ${userCreds.authorizedJwt}`,
"Content-Type": "application/json",
"X-Metaphi-Api-Key": this._clientApiKey,
"x-metaphi-account-id": this._clientId,
},
data: data,
};
const response = await axios(config);
console.log("Wallet Patched: ", response.data);
if (!response.data.jwt) {
throw new Error("Error uploading share to Metaphi");
}
this._logger(`Successfully uploaded share to Metaphi`);
this._setAuthenticatedJwt(response.data.jwt);
} catch (ex) {
this._logger(`Error uploading share to Metaphi: ${ex.toString()}`);
throw ex;
}
};
// Uploads share to dApp.
_uploadTodApp = async (userCreds, address, share) => {
// TODO: We assume for now that the dApp access to their
// secret share contract is via Metaphi. We will also support
// the dApps hosting their own contract gateway in the future.
// This is why for now, we pass in the newWalletJwt.
try {
this._logger(`Uploading share to dApp: ${this._DAPP_WALLET_SECRET_API}`);
const data = {
key_share: share,
wallet_address: address,
};
const config = {
url: this._DAPP_WALLET_SECRET_API,
method: "post",
headers: {
Authorization: `Bearer ${userCreds.authorizedJwt}`,
"Content-Type": "application/json",
"X-Metaphi-Api-Key": this._clientApiKey,
"x-metaphi-account-id": this._clientId,
},
data,
};
const response = await axios(config);
if (!response.data.success) {
throw new Error("Error uploading share to dApp");
}
} catch (ex) {
this._logger(`Error uploading share to dApp: ${ex.toString()}`);
throw ex;
}
};
// Generates the symmetric key from user credentials.
_generateSymmetricKey = (userCreds) => {
const seed = userCreds.userEmail + ":" + userCreds.userPin;
// Prompt the user for a pin and generate a symmetric key.
// This should be protected using faceid, webauthn, etc.
// Key must be 32 bytes for aes256.
return Buffer.from(
crypto.createHash("sha256").update(seed).digest("base64"),
"base64"
);
};
// Encrypt using an AES256 cipher.
_aes256_encrypt = (value, key) => {
var ivlength = 16; // AES blocksize
var iv = crypto.randomBytes(ivlength);
var cipher = crypto.createCipheriv("aes256", key, iv);
var encrypted = cipher.update(value, "base64", "base64");
encrypted += cipher.final("base64");
const final = iv.toString("base64") + "::" + encrypted;
return final;
};
// Decrypts using an AES256 cipher.
_aes256_decrypt = (ciphertext, key) => {
var components = ciphertext.split("::");
var iv_from_ciphertext = Buffer.from(components.shift(), "base64");
try {
var decipher = crypto.createDecipheriv("aes256", key, iv_from_ciphertext);
var deciphered = decipher.update(
components.join("::"),
"base64",
"base64"
);
deciphered += decipher.final("base64");
return deciphered;
} catch (err) {
this._logger("Error: ", err);
}
};
// Creates a new wallet.
_createNewWallet = async (userCreds) => {
// Generate a wallet
const EthWallet = EthereumWallet.generate();
const address = EthWallet.getAddressString();
const privateKey = EthWallet.getPrivateKeyString();
// Create secrets from it.
const shares = sss.split(privateKey, { shares: 3, threshold: 2 });
// Generate symmetric key
var symmetric_key = this._generateSymmetricKey(userCreds);
this._logger(`Generated Symmetric Key`);
// Encrypt the shares with the generated symmetric key.
const encrypted_shares = shares.map((share) =>
this._aes256_encrypt(share.toString("base64"), symmetric_key)
);
this._logger(
`Generated Encrypted shares: ${encrypted_shares.length} \n${encrypted_shares[0]}\n\n${encrypted_shares[1]}\n\n${encrypted_shares[2]}}`
);
// Upload shares.
let uploadedShareCount = 0;
// Store a share locally.
try {
this._uploadToDevice(userCreds, encrypted_shares[0]);
uploadedShareCount++
} catch (ex) {
console.log('Error uploading local share.', ex)
}
// Store share in dApp contact.
try {
// Pass on a share to the dApp.
await this._uploadTodApp(userCreds, address, encrypted_shares[1]);
uploadedShareCount++;
} catch (ex) {
console.log('Error uploading dApp share.', ex)
}
if (uploadedShareCount) {
// Store share in Metaphi.
try {
// Pass on a share to Metaphi.
await this._uploadToMetaphi(userCreds, address, encrypted_shares[2]);
uploadedShareCount++;
} catch (ex) {
console.log('Error uploading Metaphi share.', ex)
}
}
if (uploadedShareCount == 2) {
// If one of the share uploads failed, add flag to re-upload.
store.set(`${userCreds.userEmail}-regenerate`, 'true');
}
// Wallet object.
return {
address,
privateKey,
};
};
// Reconstructs the secret key from the two shares.
_reconstructWalletFromSecret = (symmetric_key, keyShare1, keyShare2) => {
this._logger(
`Reconstructing secret:\n\nSymmetric Key: ${symmetric_key}\n\nShare1: ${keyShare1} \n\nShare 2: ${keyShare2}`
);
// Decrypt shares.
const decrypted_shares = [keyShare1, keyShare2].map((share) => {
return this._aes256_decrypt(share.toString("base64"), symmetric_key);
});
// Reconstruct the private key and return the wallet.
const privateKey = sss
.combine([
Buffer.from(decrypted_shares[0], "base64"),
Buffer.from(decrypted_shares[1], "base64"),
])
.toString();
this._logger(`Succesfully reconstructed secret.`);
return privateKey;
};
}
export default MetaphiWalletApi;