Skip to content

Commit

Permalink
wallet-ext: use bg service for signing (MystenLabs#6941)
Browse files Browse the repository at this point in the history
* ts-sdk: deserialize a public key from base64 data and schema
* wallet-ext: move signing to background service 
* creates a new SignerWithProvider that communicates with the bg service
to sign the data
  * this will allow to remove key pairs from the UI.

closes: APPS-279
  • Loading branch information
pchrysochoidis authored Jan 13, 2023
1 parent f61ae12 commit f741812
Show file tree
Hide file tree
Showing 14 changed files with 247 additions and 52 deletions.
5 changes: 5 additions & 0 deletions .changeset/three-coats-rescue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@mysten/sui.js": minor
---

Add method to deserialize a public key, using it's schema and base64 data
15 changes: 14 additions & 1 deletion apps/wallet/src/background/keyring/Account.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import type { Keypair, SuiAddress } from '@mysten/sui.js';
import type {
SignaturePubkeyPair,
Keypair,
SuiAddress,
Base64DataBuffer,
} from '@mysten/sui.js';

export type AccountType = 'derived' | 'imported';

Expand All @@ -26,4 +31,12 @@ export class Account {
exportKeypair() {
return this.#keypair.export();
}

async sign(data: Base64DataBuffer): Promise<SignaturePubkeyPair> {
return {
signatureScheme: this.#keypair.getKeyScheme(),
signature: this.#keypair.signData(data),
pubKey: this.#keypair.getPublicKey(),
};
}
}
32 changes: 31 additions & 1 deletion apps/wallet/src/background/keyring/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { Ed25519Keypair } from '@mysten/sui.js';
import { Base64DataBuffer, Ed25519Keypair } from '@mysten/sui.js';
import mitt from 'mitt';
import { throttle } from 'throttle-debounce';

Expand Down Expand Up @@ -262,6 +262,36 @@ export class Keyring {
await this.setLockTimeout(payload.args.timeout);
}
uiConnection.send(createMessage({ type: 'done' }, id));
} else if (isKeyringPayload(payload, 'signData')) {
if (this.#locked) {
throw new Error('Keyring is locked. Unlock it first.');
}
if (!payload.args) {
throw new Error('Missing parameters.');
}
const { data, address } = payload.args;
const account = this.#accountsMap.get(address);
if (!account) {
throw new Error(
`Account for address ${address} not found in keyring`
);
}
const { signature, signatureScheme, pubKey } =
await account.sign(new Base64DataBuffer(data));
uiConnection.send(
createMessage<KeyringPayload<'signData'>>(
{
type: 'keyring',
method: 'signData',
return: {
signatureScheme,
signature: signature.toString(),
pubKey: pubKey.toBase64(),
},
},
id
)
);
}
} catch (e) {
uiConnection.send(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@

import { isBasePayload } from '_payloads';

import type { ExportedKeypair } from '@mysten/sui.js';
import type {
ExportedKeypair,
SignatureScheme,
SuiAddress,
} from '@mysten/sui.js';
import type { BasePayload, Payload } from '_payloads';

type MethodToPayloads = {
Expand Down Expand Up @@ -44,6 +48,14 @@ type MethodToPayloads = {
args: { timeout: number };
return: never;
};
signData: {
args: { data: string; address: SuiAddress };
return: {
signatureScheme: SignatureScheme;
signature: string;
pubKey: string;
};
};
};

export interface KeyringPayload<Method extends keyof MethodToPayloads>
Expand Down
43 changes: 24 additions & 19 deletions apps/wallet/src/ui/app/ApiProvider.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import {
RawSigner,
JsonRpcProvider,
LocalTxnDataSerializer,
} from '@mysten/sui.js';
import { JsonRpcProvider, LocalTxnDataSerializer } from '@mysten/sui.js';

import { BackgroundServiceSigner } from './background-client/BackgroundServiceSigner';
import { queryClient } from './helpers/queryClient';
import { growthbook } from '_app/experimentation/feature-gating';
import { FEATURES } from '_src/shared/experimentation/features';

import type { Keypair } from '@mysten/sui.js';
import type { BackgroundClient } from './background-client';
import type { SuiAddress, SignerWithProvider } from '@mysten/sui.js';

export enum API_ENV {
local = 'local',
Expand Down Expand Up @@ -93,7 +91,7 @@ export const generateActiveNetworkList = (): NetworkTypes[] => {

export default class ApiProvider {
private _apiFullNodeProvider?: JsonRpcProvider;
private _signer: RawSigner | null = null;
private _signerByAddress: Map<SuiAddress, SignerWithProvider> = new Map();

public setNewJsonRpcProvider(
apiEnv: API_ENV = DEFAULT_API_ENV,
Expand All @@ -105,7 +103,7 @@ export default class ApiProvider {
customRPC ?? getDefaultAPI(apiEnv).fullNode,
{ faucetURL: customRPC ? '' : getDefaultAPI(apiEnv).faucet }
);
this._signer = null;
this._signerByAddress.clear();
}

public get instance() {
Expand All @@ -118,21 +116,28 @@ export default class ApiProvider {
};
}

public getSignerInstance(keypair: Keypair): RawSigner {
public getSignerInstance(
address: SuiAddress,
backgroundClient: BackgroundClient
): SignerWithProvider {
if (!this._apiFullNodeProvider) {
this.setNewJsonRpcProvider();
}
if (!this._signer) {
this._signer = new RawSigner(
keypair,
this._apiFullNodeProvider,

growthbook.isOn(FEATURES.USE_LOCAL_TXN_SERIALIZER)
? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
new LocalTxnDataSerializer(this._apiFullNodeProvider!)
: undefined
if (!this._signerByAddress.has(address)) {
this._signerByAddress.set(
address,
new BackgroundServiceSigner(
address,
backgroundClient,
this._apiFullNodeProvider,
growthbook.isOn(FEATURES.USE_LOCAL_TXN_SERIALIZER)
? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
new LocalTxnDataSerializer(this._apiFullNodeProvider!)
: undefined
)
);
}
return this._signer;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return this._signerByAddress.get(address)!;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { SignerWithProvider } from '@mysten/sui.js';

import type { BackgroundClient } from '.';
import type {
Base64DataBuffer,
Provider,
SignaturePubkeyPair,
SuiAddress,
TxnDataSerializer,
} from '@mysten/sui.js';

export class BackgroundServiceSigner extends SignerWithProvider {
readonly #address: SuiAddress;
readonly #backgroundClient: BackgroundClient;

constructor(
address: SuiAddress,
backgroundClient: BackgroundClient,
provider?: Provider,
serializer?: TxnDataSerializer
) {
super(provider, serializer);
this.#address = address;
this.#backgroundClient = backgroundClient;
}

async getAddress(): Promise<string> {
return this.#address;
}

signData(data: Base64DataBuffer): Promise<SignaturePubkeyPair> {
return this.#backgroundClient.signData(this.#address, data);
}

connect(provider: Provider): SignerWithProvider {
return new BackgroundServiceSigner(
this.#address,
this.#backgroundClient,
provider
);
}
}
44 changes: 43 additions & 1 deletion apps/wallet/src/ui/app/background-client/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { Base64DataBuffer, publicKeyFromSerialized } from '@mysten/sui.js';
import { lastValueFrom, map, take } from 'rxjs';

import { growthbook } from '../experimentation/feature-gating';
Expand All @@ -17,7 +18,11 @@ import { setActiveOrigin } from '_redux/slices/app';
import { setPermissions } from '_redux/slices/permissions';
import { setTransactionRequests } from '_redux/slices/transaction-requests';

import type { SuiAddress, SuiTransactionResponse } from '@mysten/sui.js';
import type {
SignaturePubkeyPair,
SuiAddress,
SuiTransactionResponse,
} from '@mysten/sui.js';
import type { Message } from '_messages';
import type { KeyringPayload } from '_payloads/keyring';
import type {
Expand Down Expand Up @@ -203,6 +208,43 @@ export class BackgroundClient {
);
}

public async signData(
address: SuiAddress,
data: Base64DataBuffer
): Promise<SignaturePubkeyPair> {
return await lastValueFrom(
this.sendMessage(
createMessage<KeyringPayload<'signData'>>({
type: 'keyring',
method: 'signData',
args: { data: data.toString(), address },
})
).pipe(
take(1),
map(({ payload }) => {
if (
isKeyringPayload(payload, 'signData') &&
payload.return
) {
const { signatureScheme, signature, pubKey } =
payload.return;
return {
signatureScheme,
signature: new Base64DataBuffer(signature),
pubKey: publicKeyFromSerialized(
signatureScheme,
pubKey
),
};
}
throw new Error(
'Error unknown response for signData message'
);
})
)
);
}

private setupAppStatusUpdateInterval() {
setInterval(() => {
this.sendAppStatus();
Expand Down
7 changes: 5 additions & 2 deletions apps/wallet/src/ui/app/hooks/useSigner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import { thunkExtras } from '_redux/store/thunk-extras';

export function useSigner() {
const { api, keypairVault } = thunkExtras;
return api.getSignerInstance(keypairVault.getKeypair());
const { api, keypairVault, background } = thunkExtras;
return api.getSignerInstance(
keypairVault.getKeypair().getPublicKey().toSuiAddress(),
background
);
}
8 changes: 4 additions & 4 deletions apps/wallet/src/ui/app/redux/slices/sui-objects/Coin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import { Coin as CoinAPI, SUI_TYPE_ARG } from '@mysten/sui.js';
import type {
ObjectId,
SuiObject,
RawSigner,
SuiAddress,
SuiMoveObject,
JsonRpcProvider,
SuiExecuteTransactionResponse,
SignerWithProvider,
} from '@mysten/sui.js';

const COIN_TYPE = '0x2::coin::Coin';
Expand Down Expand Up @@ -85,7 +85,7 @@ export class Coin {
* @param validator The sui address of the chosen validator
*/
public static async stakeCoin(
signer: RawSigner,
signer: SignerWithProvider,
coins: SuiMoveObject[],
amount: bigint,
validator: SuiAddress
Expand All @@ -107,7 +107,7 @@ export class Coin {
}

private static async requestSuiCoinWithExactAmount(
signer: RawSigner,
signer: SignerWithProvider,
coins: SuiMoveObject[],
amount: bigint
): Promise<ObjectId> {
Expand Down Expand Up @@ -143,7 +143,7 @@ export class Coin {
}

private static async selectSuiCoinWithExactAmount(
signer: RawSigner,
signer: SignerWithProvider,
coins: SuiMoveObject[],
amount: bigint,
refreshData = false
Expand Down
7 changes: 5 additions & 2 deletions apps/wallet/src/ui/app/redux/slices/sui-objects/NFT.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import type { RawSigner, SuiExecuteTransactionResponse } from '@mysten/sui.js';
import type {
SignerWithProvider,
SuiExecuteTransactionResponse,
} from '@mysten/sui.js';

const DEFAULT_NFT_IMAGE =
'ipfs://QmZPWWy5Si54R3d26toaqRiqvCH7HkGdXkxwUgCm2oKKM2?filename=img-sq-01.png';
Expand All @@ -14,7 +17,7 @@ export class ExampleNFT {
* @param signer A signer with connection to the fullnode
*/
public static async mintExampleNFT(
signer: RawSigner,
signer: SignerWithProvider,
name?: string,
description?: string,
imageUrl?: string
Expand Down
Loading

0 comments on commit f741812

Please sign in to comment.