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==}