Skip to content

Commit

Permalink
feat(ton): Add TONWallet support in JavaScript
Browse files Browse the repository at this point in the history
* Add `KeyStore` TypeScript tests
  • Loading branch information
satoshiotomakan committed Sep 9, 2024
1 parent f6494d3 commit e22de4c
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 4 deletions.
46 changes: 43 additions & 3 deletions wasm/src/keystore/default-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,31 @@ export class Default implements Types.IKeyStore {
});
}

importTON(
tonMnemonic: string,
name: string,
password: string,
coin: CoinType,
encryption: StoredKeyEncryption
): Promise<Types.Wallet> {
return new Promise((resolve, reject) => {
const { StoredKey, TONWallet } = this.core;

const passphrase = "";
if (!TONWallet.isValidMnemonic(tonMnemonic, passphrase)) {
throw Types.Error.InvalidMnemonic;
}

let pass = Buffer.from(password);
let storedKey = StoredKey.importTONWalletWithEncryption(tonMnemonic, name, pass, coin, encryption);
let wallet = this.mapWallet(storedKey);
storedKey.delete();
this.importWallet(wallet)
.then(() => resolve(wallet))
.catch((error) => reject(error));
});
}

addAccounts(
id: string,
password: string,
Expand All @@ -129,11 +154,23 @@ export class Default implements Types.IKeyStore {
): Promise<PrivateKey> {
return this.load(id).then((wallet) => {
let storedKey = this.mapStoredKey(wallet);
let hdWallet = storedKey.wallet(Buffer.from(password));
let coin = (this.core.CoinType as any).values["" + account.coin];
let privateKey = hdWallet.getKey(coin, account.derivationPath);

let privateKey: PrivateKey;
switch (wallet.type) {
// In case of BIP39 mnemonic, we should use the custom derivation path.
case Types.WalletType.Mnemonic:
let hdWallet = storedKey.wallet(Buffer.from(password));
privateKey = hdWallet.getKey(coin, account.derivationPath);
hdWallet.delete();
break;
// Otherwise, use the default implementation.
default:
privateKey = storedKey.privateKey(coin, Buffer.from(password));
break;
}

storedKey.delete();
hdWallet.delete();
return privateKey;
});
}
Expand All @@ -149,6 +186,9 @@ export class Default implements Types.IKeyStore {
case Types.WalletType.PrivateKey:
value = storedKey.decryptPrivateKey(Buffer.from(password));
break;
case Types.WalletType.TonMnemonic:
value = storedKey.decryptTONMnemonic(Buffer.from(password));
break;
default:
throw Types.Error.InvalidJSON;
}
Expand Down
12 changes: 11 additions & 1 deletion wasm/src/keystore/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export enum WalletType {
PrivateKey = "privateKey",
WatchOnly = "watchOnly",
Hardware = "hardware",
TonMnemonic = "ton-mnemonic"
}

export enum Error {
Expand Down Expand Up @@ -68,6 +69,15 @@ export interface IKeyStore {
// Import a Wallet object directly
importWallet(wallet: Wallet): Promise<void>;

// Import a TON wallet by 24-words mnemonic, name, password and initial active account (from coinType)
importTON(
tonMnemonic: string,
name: string,
password: string,
coin: CoinType,
encryption: StoredKeyEncryption
): Promise<Wallet>;

// Add active accounts to a wallet by wallet id, password, coin
addAccounts(id: string, password: string, coins: CoinType[]): Promise<Wallet>;

Expand All @@ -78,7 +88,7 @@ export interface IKeyStore {
account: ActiveAccount
): Promise<PrivateKey>;

// Delete a wallet by wallet id and password.aq1aq
// Delete a wallet by wallet id and password
delete(id: string, password: string): Promise<void>;

// Export a wallet by wallet id and password, returns mnemonic or private key
Expand Down
45 changes: 45 additions & 0 deletions wasm/tests/KeyStore+extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,51 @@ describe("KeyStore", async () => {
});
}).timeout(10000);

it("test ExtensionStorage TONWallet", async () => {
const { CoinType, HexCoding, StoredKeyEncryption } = globalThis.core;
const tonMnemonic = globalThis.tonMnemonic as string;
const password = globalThis.password as string;

const walletIdsKey = "all-wallet-ids";
const storage = new KeyStore.ExtensionStorage(
walletIdsKey,
new ChromeStorageMock()
);
const keystore = new KeyStore.Default(globalThis.core, storage);

const wallet = await keystore.importTON(tonMnemonic, "Coolton", password, CoinType.ton, StoredKeyEncryption.aes128Ctr);

assert.equal(wallet.name, "Coolton");
assert.equal(wallet.type, "ton-mnemonic");
assert.equal(wallet.version, 3);

const account = wallet.activeAccounts[0];
const key = await keystore.getKey(wallet.id, password, account);

assert.equal(
HexCoding.encode(key.data()),
"0x859cd74ab605afb7ce9f5316a1f6d59217a130b75b494efd249913be874c9d46"
);
assert.equal(account.address, "UQDdB2lMwYM9Gxc-ln--Tu8cz-TYksQxYuUsMs2Pd4cHerYz");
assert.isUndefined(account.extendedPublicKey);
assert.equal(
account.publicKey,
"c9af50596bd5c1c5a15fb32bef8d4f1ee5244b287aea1f49f6023a79f9b2f055"
);

assert.isTrue(await keystore.hasWallet(wallet.id));
assert.isFalse(await keystore.hasWallet("invalid-id"));

const exported = await keystore.export(wallet.id, password);
assert.equal(exported, tonMnemonic);

const wallets = await keystore.loadAll();

await wallets.forEach((w) => {
keystore.delete(w.id, password);
});
}).timeout(10000);

it("test ExtensionStorage AES256", async () => {
const { CoinType, HexCoding, StoredKeyEncryption } = globalThis.core;
const mnemonic = globalThis.mnemonic as string;
Expand Down
2 changes: 2 additions & 0 deletions wasm/tests/setup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { initWasm } from "../dist";
before(async () => {
globalThis.mnemonic =
"team engine square letter hero song dizzy scrub tornado fabric divert saddle";
globalThis.tonMnemonic =
"laundry myself fitness beyond prize piano match acid vacuum already abandon dance occur pause grocery company inject excuse weasel carpet fog grunt trick spike";
globalThis.password = "password";
globalThis.core = await initWasm();
});

0 comments on commit e22de4c

Please sign in to comment.