Skip to content

Commit

Permalink
Merge pull request #126 from planetarium/feature/aws-kms
Browse files Browse the repository at this point in the history
  • Loading branch information
moreal authored Dec 6, 2024
2 parents 2ce740d + 5c7ee90 commit 476b90d
Show file tree
Hide file tree
Showing 12 changed files with 1,223 additions and 51 deletions.
2 changes: 2 additions & 0 deletions background/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"dependencies": {
"@noble/hashes": "^1.4.0",
"@planetarium/account": "~4.4.2",
"@planetarium/account-aws-kms": "~4.4.2",
"@planetarium/bencodex": "0.2.2",
"@planetarium/lib9c": "npm:@jsr/planetarium__lib9c@^0.3.0-dev.202407170506408289+2af04aea533872ccba4f01c583b2e53694d2e18e",
"@planetarium/tx": "^4.4.2",
Expand All @@ -28,6 +29,7 @@
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-typescript": "^11.1.6",
"@rollup/plugin-json": "^6.1.0",
"@types/chrome": "^0.0.266",
"@types/node": "20.12.7",
"@typescript-eslint/eslint-plugin": "^7.2.0",
Expand Down
2 changes: 2 additions & 0 deletions background/rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from "@rollup/plugin-commonjs";
import typescript from "@rollup/plugin-typescript";
import json from "@rollup/plugin-json"

export default {
input: "src/main.ts",
Expand All @@ -13,5 +14,6 @@ export default {
preferBuiltins: false,
}),
typescript(),
json(),
commonjs({ include: /node_modules/ })]
};
2 changes: 2 additions & 0 deletions background/src/constants/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export const NETWORKS = "n";
export const CURRENT_NETWORK = "cn";
export const PASSWORD_CHECKER = "passwordChecker";
export const PASSWORD_CHECKER_VALUE = "password";
export const ACCOUNT_TYPE_WEB3 = 'web3-secret-storage'
export const ACCOUNT_TYPE_KMS = 'kms'

export interface Account {
name: string;
Expand Down
167 changes: 120 additions & 47 deletions background/src/wallet/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import {
TXS,
ACCOUNTS,
Account,
ACCOUNT_TYPE_WEB3,
ACCOUNT_TYPE_KMS,
} from "../constants/constants";
import { RawPrivateKey } from "@planetarium/account";
import { Account as Signer, RawPrivateKey, PublicKey } from "@planetarium/account";
import {
BencodexDictionary,
Value,
Expand All @@ -16,6 +18,7 @@ import {
} from "@planetarium/bencodex";
import * as ethers from "ethers";
import { Address } from "@planetarium/account";
import { AwsKmsAccount, KMSClient } from "@planetarium/account-aws-kms";
import {
UnsignedTx,
encodeSignedTx,
Expand Down Expand Up @@ -95,6 +98,7 @@ export default class Wallet {
"getPublicKey",
"connect",
"isConnected",
"checkKMSAccount",
];
}

Expand Down Expand Up @@ -126,6 +130,7 @@ export default class Wallet {
}

canCallExternal(method: string): boolean {
console.log("@@@@:" + method);
return this.canCall.indexOf(method) >= 0;
}
hexToBuffer(hex: string): Buffer {
Expand All @@ -143,21 +148,22 @@ export default class Wallet {
);
}
async createSequentialWallet(primaryAddress: string, index: number) {
const wallet = await this.loadWallet(
primaryAddress,
resolve(this.passphrase),
);

const mnemonic = wallet._mnemonic().phrase;

const newWallet = ethers.Wallet.fromMnemonic(
mnemonic,
"m/44'/60'/0'/0/" + index,
);
const encryptedWallet = await newWallet.encrypt(resolve(this.passphrase));
const address = newWallet.address;

return { address, encryptedWallet };
const stored = await this.storage.secureGet(ENCRYPTED_WALLET + primaryAddress.toLowerCase());
const { accountType, accountData } = Array.isArray(stored)
? { accountType: stored[0], accountData: stored[1] }
: { accountType: ACCOUNT_TYPE_WEB3, accountData: stored };

if (accountType === ACCOUNT_TYPE_WEB3) {
const wallet = ethers.Wallet.fromEncryptedJsonSync(accountData, resolve(this.passphrase));
const mnemonic = wallet._mnemonic().phrase;
const newWallet = ethers.Wallet.fromMnemonic(mnemonic, "m/44'/60'/0'/0/" + index);
const encryptedWallet = await newWallet.encrypt(resolve(this.passphrase));
const address = newWallet.address;

return { address, encryptedWallet };
} else {
throw new Error("Can't derive new wallet from non Web3 account.");
}
}
async createPrivateKeyWallet(privateKey: string): Promise<{
address: string;
Expand All @@ -169,18 +175,8 @@ export default class Wallet {

return { address, encryptedWallet };
}
async loadWallet(
address: string,
passphrase: string,
): Promise<ethers.Wallet> {
const encryptedWallet = await this.storage.secureGet<string>(
ENCRYPTED_WALLET + address.toLowerCase(),
);
return this.decryptWallet(encryptedWallet, passphrase);
}
async _transferNCG(sender, receiver, amount, nonce, memo?) {
const wallet = await this.loadWallet(sender, resolve(this.passphrase));
const account = RawPrivateKey.fromHex(wallet.privateKey.slice(2));
const signer = await this.getSigner(sender, resolve(this.passphrase));
const currentNetwork = await this.networkController.getCurrentNetwork();
const genesisHash = Buffer.from(currentNetwork.genesisHash, "hex");
const action = new TransferAsset({
Expand All @@ -196,13 +192,13 @@ export default class Wallet {
updatedAddresses: new Set([]),
nonce: BigInt(nonce),
genesisHash,
publicKey: (await account.getPublicKey()).toBytes("uncompressed"),
publicKey: (await signer.getPublicKey()).toBytes("uncompressed"),
timestamp: new Date(),
maxGasPrice: fav(MEAD, 1),
gasLimit: 4n,
};

const signedTx = await signTx(unsignedTx, account);
const signedTx = await signTx(unsignedTx, signer);
const encodedHex = Buffer.from(encode(encodeSignedTx(signedTx))).toString(
"hex",
);
Expand Down Expand Up @@ -236,7 +232,7 @@ export default class Wallet {
return result;
}

async sign(signer: string, actionHex: string): Promise<string> {
async sign(signerAddress: string, actionHex: string): Promise<string> {
const action = decode(Buffer.from(actionHex, "hex"));
if (!isDictionary(action)) {
throw new Error("Invalid action. action must be BencodexDictionary.");
Expand All @@ -246,14 +242,13 @@ export default class Wallet {
.request({
category: "sign",
data: {
signer,
signerAddress,
content: convertBencodexToJSONableType(action),
},
})
.then(async () => {
const wallet = await this.loadWallet(signer, resolve(this.passphrase));
const account = RawPrivateKey.fromHex(wallet.privateKey.slice(2));
const sender = Address.fromHex(wallet.address);
const signer = await this.getSigner(signerAddress, resolve(this.passphrase));
const sender = Address.fromHex(signerAddress);
const currentNetwork = await this.networkController.getCurrentNetwork();
const genesisHash = Buffer.from(currentNetwork.genesisHash, "hex");

Expand All @@ -270,7 +265,7 @@ export default class Wallet {
updatedAddresses: new Set([]),
nonce: BigInt(await this.api.getNextTxNonce(sender.toString())),
genesisHash,
publicKey: (await account.getPublicKey()).toBytes("uncompressed"),
publicKey: (await signer.getPublicKey()).toBytes("uncompressed"),
timestamp: new Date(),
maxGasPrice: fav(MEAD, 1),
gasLimit,
Expand All @@ -281,17 +276,16 @@ export default class Wallet {
});
}

async signTx(signer: string, encodedUnsignedTxHex: string): Promise<string> {
async signTx(signerAddress: string, encodedUnsignedTxHex: string): Promise<string> {
const encodedUnsignedTxBytes = Buffer.from(encodedUnsignedTxHex, "hex");
const encodedUnsignedTx = decode(encodedUnsignedTxBytes);

if (!isDictionary(encodedUnsignedTx)) {
throw new Error("Invalid unsigned tx");
}

const wallet = await this.loadWallet(signer, resolve(this.passphrase));
const account = RawPrivateKey.fromHex(wallet.privateKey);
const signature = await account.sign(encodedUnsignedTxBytes);
const signer = await this.getSigner(signerAddress, resolve(this.passphrase));
const signature = await signer.sign(encodedUnsignedTxBytes);

const SIGNATURE_KEY = new Uint8Array([83]);
const encodedSignedTx = new BencodexDictionary([
Expand All @@ -302,11 +296,10 @@ export default class Wallet {
return Buffer.from(encode(encodedSignedTx)).toString("hex");
}

async _signTx(signer, unsignedTx) {
const wallet = await this.loadWallet(signer, resolve(this.passphrase));
const account = RawPrivateKey.fromHex(wallet.privateKey.slice(2));
async _signTx(signerAddress, unsignedTx) {
const signer = await this.getSigner(signerAddress, resolve(this.passphrase));

return await signTx(unsignedTx, account);
return await signTx(unsignedTx, signer);
}

async addPendingTxs(tx) {
Expand All @@ -321,8 +314,13 @@ export default class Wallet {
}

async getPrivateKey(address: string, passphrase): Promise<string> {
let wallet = await this.loadWallet(address, passphrase);
return wallet.privateKey;
const signer = await this.getSigner(address, passphrase);

if (signer instanceof RawPrivateKey) {
return Buffer.from((await signer.exportPrivateKey()).toBytes()).toString("hex");
}

throw new Error("Can't export private key from other than RawPrivateKey account type.");
}

async connect(): Promise<string[]> {
Expand Down Expand Up @@ -365,8 +363,60 @@ export default class Wallet {
}

async getPublicKey(address: string): Promise<string> {
const wallet = await this.loadWallet(address, resolve(this.passphrase));
return wallet.publicKey;
const signer = await this.getSigner(address, resolve(this.passphrase));
return (await signer.getPublicKey()).toHex("uncompressed");
}

async checkKMSAccount(
keyId,
publicKeyHex,
region,
accessKeyId,
secretAccessKey
): Promise<string> {
const account = createAwsKmsAccount(
keyId,
publicKeyHex,
region,
accessKeyId,
secretAccessKey
);

return (await account.getAddress()).toHex();
}

async getSigner(address, passphrase): Promise<Signer> {
const stored = await this.storage.secureGet(
ENCRYPTED_WALLET + address.toLowerCase()
);
const { accountType, accountData } = Array.isArray(stored)
? { accountType: stored[0], accountData: stored[1] }
: { accountType: ACCOUNT_TYPE_WEB3, accountData: stored };

switch (accountType) {
case ACCOUNT_TYPE_WEB3:
const wallet = ethers.Wallet.fromEncryptedJsonSync(
accountData,
passphrase
);

return RawPrivateKey.fromHex(wallet.privateKey.slice(2));

case ACCOUNT_TYPE_KMS:
const [keyId, publicKeyHex, region, accessKeyId, secretAccessKey] =
accountData;

return createAwsKmsAccount(
keyId,
publicKeyHex,
region,
accessKeyId,
secretAccessKey
);

default:
break;
}
}
}

Expand Down Expand Up @@ -403,3 +453,26 @@ function convertBencodexToJSONableType(v: Value) {

return v;
}

function createAwsKmsAccount(
keyId,
publicKeyHex,
region,
accessKeyId,
secretAccessKey
)
{
const kmsClient = new KMSClient({
region,
credentials: {
accessKeyId,
secretAccessKey
}
});
const publicKey = PublicKey.fromHex(
publicKeyHex,
publicKeyHex.startsWith("04") ? "uncompressed" : "compressed"
);

return new AwsKmsAccount(keyId, publicKey, kmsClient);
}
Loading

0 comments on commit 476b90d

Please sign in to comment.