Skip to content

Commit

Permalink
clients/js: sapphire snap
Browse files Browse the repository at this point in the history
  • Loading branch information
CedarMist committed Oct 9, 2024
1 parent c0b462d commit 47c82fc
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 14 deletions.
6 changes: 3 additions & 3 deletions clients/js/src/cipher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ export class X25519DeoxysII extends Cipher {
public override readonly epoch: number | undefined;

private cipher: deoxysii.AEAD;
private key: Uint8Array; // Stored for curious users.
public secretKey: Uint8Array; // Stored for curious users.

/** Creates a new cipher using an ephemeral keypair stored in memory. */
static ephemeral(peerPublicKey: BytesLike, epoch?: number): X25519DeoxysII {
Expand Down Expand Up @@ -247,8 +247,8 @@ export class X25519DeoxysII extends Cipher {
.update(naclScalarMult(keypair.secretKey, peerPublicKey))
.digest().buffer;

this.key = new Uint8Array(keyBytes);
this.cipher = new deoxysii.AEAD(new Uint8Array(this.key)); // deoxysii owns the input
this.secretKey = new Uint8Array(keyBytes);
this.cipher = new deoxysii.AEAD(new Uint8Array(this.secretKey)); // deoxysii owns the input
}

public encrypt(plaintext: Uint8Array): {
Expand Down
49 changes: 42 additions & 7 deletions clients/js/src/provider.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: Apache-2.0

import { BytesLike } from './ethersutils.js';
import { BytesLike, getBytes, hexlify } from './ethersutils.js';
import { KeyFetcher } from './calldatapublickey.js';
import { keccak256 } from 'ethers';

// -----------------------------------------------------------------------------
// https://eips.ethereum.org/EIPS/eip-2696#interface
Expand Down Expand Up @@ -79,10 +80,10 @@ export function isWrappedEthereumProvider<P extends EIP2696_EthereumProvider>(
* @param options (optional) Re-use parameters from other providers
* @returns Sapphire wrapped provider
*/
export function wrapEthereumProvider<P extends EIP2696_EthereumProvider>(
export async function wrapEthereumProvider<P extends EIP2696_EthereumProvider>(
upstream: P,
options?: SapphireWrapOptions,
): P {
): Promise<P> {
if (isWrappedEthereumProvider(upstream)) {
return upstream;
}
Expand All @@ -98,7 +99,7 @@ export function wrapEthereumProvider<P extends EIP2696_EthereumProvider>(
// if we do this, don't then re-wrap the send() function
// only wrap the send() function if there was a request() function

const request = makeSapphireRequestFn(upstream, filled_options);
const request = await makeSapphireRequestFn(upstream, filled_options);
const hooks: Record<string, unknown> = { request };

// We prefer a request() method, but a provider may expose a send() method
Expand Down Expand Up @@ -137,28 +138,45 @@ export function isWrappedRequestFn<
* @param options
* @returns
*/
export function makeSapphireRequestFn(
export async function makeSapphireRequestFn(
provider: EIP2696_EthereumProvider,
options?: SapphireWrapOptions,
): EIP2696_EthereumProvider['request'] {
): Promise<EIP2696_EthereumProvider['request']> {
if (isWrappedRequestFn(provider.request)) {
return provider.request;
}

const filled_options = fillOptions(options);

/*
// Example return type of wallet_getSnaps
{
"npm:@metamask/example-snap": {
"version": "1.0.0",
"id": "npm:@metamask/example-snap",
"enabled": true,
"blocked": false
}
}
*/
const snaps = await provider.request({method: 'wallet_getSnaps'});
// TODO: detect sapphire snap exists? unusure of return type of `wallet_getSnaps`
const isSappireSnapEnabled = true;
// TODO: function in snap which confirms it's the sapphire snap?

const f = async (args: EIP1193_RequestArguments) => {
const cipher = await filled_options.fetcher.cipher(provider);
const { method, params } = args;

let transactionData : BytesLike | undefined = undefined;
// Encrypt requests which can be encrypted
if (
params &&
Array.isArray(params) &&
/^eth_((send|sign)Transaction|call|estimateGas)$/.test(method) &&
params[0].data // Ignore balance transfers without calldata
) {
params[0].data = cipher.encryptCall(params[0].data);
transactionData = params[0].data = cipher.encryptCall(params[0].data);
}

const res = await provider.request({
Expand All @@ -171,6 +189,23 @@ export function makeSapphireRequestFn(
return cipher.decryptResult(res as BytesLike);
}

if( isSappireSnapEnabled && transactionData !== undefined ) {
const secretKey = (cipher as any).secretKey as Uint8Array | undefined;
const peerPublicKey = await filled_options.fetcher.fetch(provider);
if( secretKey ) {
await provider.request({
method: 'wallet_invokeSnap',
params:[
'setTransactionDecryptKeys',
{
id: keccak256(getBytes(transactionData)),
ephemeralSecretKey: hexlify(secretKey),
peerPublicKey: peerPublicKey.key
}
]});
}
}

return res;
};

Expand Down
2 changes: 1 addition & 1 deletion clients/js/test/cipher.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('X25519DeoxysII', () => {
expect(hexlify(cipher.publicKey)).toEqual(
'0x3046db3fa70ce605457dc47c48837ebd8bd0a26abfde5994d033e1ced68e2576',
);
expect(hexlify(cipher['key'])).toEqual(
expect(hexlify(cipher['secretKey'])).toEqual(
'0xe69ac21066a8c2284e8fdc690e579af4513547b9b31dd144792c1904b45cf586',
);

Expand Down
73 changes: 70 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 47c82fc

Please sign in to comment.