From 47c82fc663fd80a1d03b0b1cf813210394d10a0b Mon Sep 17 00:00:00 2001
From: CedarMist <134699267+CedarMist@users.noreply.github.com>
Date: Wed, 9 Oct 2024 10:35:58 +0100
Subject: [PATCH] clients/js: sapphire snap
---
clients/js/src/cipher.ts | 6 +--
clients/js/src/provider.ts | 49 +++++++++++++++++++----
clients/js/test/cipher.spec.ts | 2 +-
pnpm-lock.yaml | 73 ++++++++++++++++++++++++++++++++--
4 files changed, 116 insertions(+), 14 deletions(-)
diff --git a/clients/js/src/cipher.ts b/clients/js/src/cipher.ts
index bffd072d..f05d3b19 100644
--- a/clients/js/src/cipher.ts
+++ b/clients/js/src/cipher.ts
@@ -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 {
@@ -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): {
diff --git a/clients/js/src/provider.ts b/clients/js/src/provider.ts
index 5f5a6ef6..62221f90 100644
--- a/clients/js/src/provider.ts
+++ b/clients/js/src/provider.ts
@@ -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
@@ -79,10 +80,10 @@ export function isWrappedEthereumProvider
(
* @param options (optional) Re-use parameters from other providers
* @returns Sapphire wrapped provider
*/
-export function wrapEthereumProvider
(
+export async function wrapEthereumProvider
(
upstream: P,
options?: SapphireWrapOptions,
-): P {
+): Promise
{
if (isWrappedEthereumProvider(upstream)) {
return upstream;
}
@@ -98,7 +99,7 @@ export function wrapEthereumProvider
(
// 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 = { request };
// We prefer a request() method, but a provider may expose a send() method
@@ -137,20 +138,37 @@ export function isWrappedRequestFn<
* @param options
* @returns
*/
-export function makeSapphireRequestFn(
+export async function makeSapphireRequestFn(
provider: EIP2696_EthereumProvider,
options?: SapphireWrapOptions,
-): EIP2696_EthereumProvider['request'] {
+): Promise {
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 &&
@@ -158,7 +176,7 @@ export function makeSapphireRequestFn(
/^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({
@@ -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;
};
diff --git a/clients/js/test/cipher.spec.ts b/clients/js/test/cipher.spec.ts
index 0e00287b..3b560046 100644
--- a/clients/js/test/cipher.spec.ts
+++ b/clients/js/test/cipher.spec.ts
@@ -15,7 +15,7 @@ describe('X25519DeoxysII', () => {
expect(hexlify(cipher.publicKey)).toEqual(
'0x3046db3fa70ce605457dc47c48837ebd8bd0a26abfde5994d033e1ced68e2576',
);
- expect(hexlify(cipher['key'])).toEqual(
+ expect(hexlify(cipher['secretKey'])).toEqual(
'0xe69ac21066a8c2284e8fdc690e579af4513547b9b31dd144792c1904b45cf586',
);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 574ff69f..a70df868 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,5 +1,9 @@
lockfileVersion: '6.0'
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
importers:
clients/js:
@@ -579,7 +583,7 @@ importers:
version: link:../viem-v2
'@wagmi/core':
specifier: 2.x
- version: 2.6.17(@types/react@18.2.79)(react@18.2.0)(typescript@5.4.5)(viem@2.9.19)
+ version: 2.6.17(react@18.2.0)(typescript@5.4.5)(viem@2.13.8)
viem:
specifier: 2.x
version: 2.13.8(typescript@5.4.5)
@@ -1305,6 +1309,16 @@ packages:
'@babel/helper-plugin-utils': 7.24.0
dev: true
+ /@babel/plugin-syntax-flow@7.24.1(@babel/core@7.23.5):
+ resolution: {integrity: sha512-sxi2kLTI5DeW5vDtMUsk4mTPwvlUDbjOnoWayhynCwrw4QXRld4QEYwqzY8JmQXaJUtgUuCIurtSRH5sn4c7mA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ dependencies:
+ '@babel/core': 7.23.5
+ '@babel/helper-plugin-utils': 7.24.0
+ dev: true
+
/@babel/plugin-syntax-flow@7.24.1(@babel/core@7.24.4):
resolution: {integrity: sha512-sxi2kLTI5DeW5vDtMUsk4mTPwvlUDbjOnoWayhynCwrw4QXRld4QEYwqzY8JmQXaJUtgUuCIurtSRH5sn4c7mA==}
engines: {node: '>=6.9.0'}
@@ -1313,6 +1327,7 @@ packages:
dependencies:
'@babel/core': 7.24.4
'@babel/helper-plugin-utils': 7.24.0
+ dev: false
/@babel/plugin-syntax-import-assertions@7.23.3(@babel/core@7.24.4):
resolution: {integrity: sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==}
@@ -1388,6 +1403,16 @@ packages:
'@babel/core': 7.24.4
'@babel/helper-plugin-utils': 7.24.0
+ /@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.23.5):
+ resolution: {integrity: sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ dependencies:
+ '@babel/core': 7.23.5
+ '@babel/helper-plugin-utils': 7.24.0
+ dev: true
+
/@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.24.4):
resolution: {integrity: sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==}
engines: {node: '>=6.9.0'}
@@ -2467,6 +2492,20 @@ packages:
'@babel/core': 7.24.4
'@babel/helper-plugin-utils': 7.24.0
+ /@babel/plugin-transform-react-jsx@7.23.4(@babel/core@7.23.5):
+ resolution: {integrity: sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ dependencies:
+ '@babel/core': 7.23.5
+ '@babel/helper-annotate-as-pure': 7.22.5
+ '@babel/helper-module-imports': 7.24.3
+ '@babel/helper-plugin-utils': 7.24.0
+ '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.23.5)
+ '@babel/types': 7.24.0
+ dev: true
+
/@babel/plugin-transform-react-jsx@7.23.4(@babel/core@7.24.4):
resolution: {integrity: sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==}
engines: {node: '>=6.9.0'}
@@ -8724,6 +8763,32 @@ packages:
- zod
dev: false
+ /@wagmi/core@2.6.17(react@18.2.0)(typescript@5.4.5)(viem@2.13.8):
+ resolution: {integrity: sha512-Ghr7PlD5HO1YJrsaC52j/csgaigBAiTR7cFiwrY7WdwvWLsR5na4Dv6KfHTU3d3al0CKDLanQdRS5nB4mX1M+g==}
+ peerDependencies:
+ '@tanstack/query-core': '>=5.0.0'
+ typescript: '>=5.0.4'
+ viem: 2.x
+ peerDependenciesMeta:
+ '@tanstack/query-core':
+ optional: true
+ typescript:
+ optional: true
+ dependencies:
+ eventemitter3: 5.0.1
+ mipd: 0.0.5(typescript@5.4.5)
+ typescript: 5.4.5
+ viem: 2.13.8(typescript@5.4.5)
+ zustand: 4.4.1(@types/react@18.2.79)(react@18.2.0)
+ transitivePeerDependencies:
+ - '@types/react'
+ - bufferutil
+ - immer
+ - react
+ - utf-8-validate
+ - zod
+ dev: false
+
/@walletconnect/core@2.11.0:
resolution: {integrity: sha512-2Tjp5BCevI7dbmqo/OrCjX4tqgMqwJNQLlQAlphqPfvwlF9+tIu6pGcVbSN3U9zyXzWIZCeleqEaWUeSeET4Ew==}
dependencies:
@@ -13279,8 +13344,8 @@ packages:
'@babel/plugin-transform-react-jsx': ^7.14.9
eslint: ^8.1.0
dependencies:
- '@babel/plugin-syntax-flow': 7.24.1(@babel/core@7.24.4)
- '@babel/plugin-transform-react-jsx': 7.23.4(@babel/core@7.24.4)
+ '@babel/plugin-syntax-flow': 7.24.1(@babel/core@7.23.5)
+ '@babel/plugin-transform-react-jsx': 7.23.4(@babel/core@7.23.5)
eslint: 8.57.0
lodash: 4.17.21
string-natural-compare: 3.0.1
@@ -13521,6 +13586,7 @@ packages:
/eslint@8.57.0:
resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
hasBin: true
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
@@ -13897,6 +13963,7 @@ packages:
dependencies:
is-hex-prefixed: 1.0.0
strip-hex-prefix: 1.0.0
+ bundledDependencies: false
/event-target-shim@5.0.1:
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}