From c826fdbce928a03a6eed1ba10b5fea4518739ec9 Mon Sep 17 00:00:00 2001 From: Guillaume Grosbois Date: Tue, 30 Jul 2024 14:15:18 -0700 Subject: [PATCH 1/7] TWIT erc draft Signed-off-by: Guillaume Grosbois --- ERCS/erc-XXXX.md | 211 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 ERCS/erc-XXXX.md diff --git a/ERCS/erc-XXXX.md b/ERCS/erc-XXXX.md new file mode 100644 index 0000000000..f47d94c59e --- /dev/null +++ b/ERCS/erc-XXXX.md @@ -0,0 +1,211 @@ +--- +eip: XXXX +title: Tamperproof Web Immutable Transaction (TWIT) +description: Provides a mechanism for DAP to use the API defined in EIP-1193 in a tamperproof way +author: Erik Marks, Guillaume Grosbois +discussions-to: TODO +status: Draft +type: Standards Track +category: ERC +created: 2024-07-29 +requires: 712, 1193 +--- + +## Abstract + +A new `secureRequest` method in addition to EIP-1193 `request` for dApp to interact with extension wallets in a tamperproof way, preventing any MITM from modifying the payload sent to the wallet. +This will allow for dApps to sign their interaction with EIP-1193, and for wallet to visibly display secure interaction, therefor improving the end user experience by explicitly displaying safety +visual cues such as a padlock. + +## Motivation + +The primary motivation for this standard is to enhance the end user experience by ensuring that they can feel safe when interacting with a trusted dApp (this is in essence very similar to HTTPS vs HTTP). + +Currently, the communication channel between a dApp and a wallet is vulnerable to man in the middle attacks: an attacker can intercept the communication by injecting javascript code in the page, and subsequently +modify the call data sent by the dApp to the wallet. XSS vulnerability or malicious extension both provide ways for an attacker to inject javascript. Despite EIP-720 increasing the transparency of the data being +signed by the wallet, the average user will in effect rarely confirm the actual content of a transaction because: + +* they have trust in specific well known dApp and will not read the detail thoroughly +* the content of the transaction is compressed to save gas and therefore unreadable +* the user is victim of social engineering + +The effect can be pernicious as end users will not necessarily realise there is no chain of trust between the dApp backend and the wallet that will send a transaction on the user's behalf: a MITM attack can +capitalize on trusted dApp to syphon funds. + +Such an attack can be used in a variety of ways: + +* Modify the call data on the fly, as a user is transacting +* Obtain a replayable signature from the user's wallet +* TODO + +Overall, the lack of a chain of trust between the dApp and the wallet hurts the ecosystem as a whole: + +* users cannot rely on trusting well known dApp, and are at risk of losing funds if they do not manually verify transactions before signing +* dApp maintainer are at risk of losing their trusted reputation if an attacker finds a viable MITM attack + +## Specification + +### Overview + +We are proposing to use the domain certificate of a dApp as a root of trust to establish a trust chain as follow: + +1. end user's browser verifies the domain certificate and display appropriate warning if overtaken +2. the DNS record of the dApp hosts a TXT field pointing to a URL where a JSON manifest is hosted (this file SHOULD be at a well known address such as ) +3. the config file contains an array of tuple `[alg, public key]` +4. for secure interaction, a dApp would first call their backend to securely sign the payload with a private key +5. the encrypted payload would be sent to the wallet via the `secureRequest` JSON api call +6. the wallet would verify the signature before sending the payload to the `request` JSON api call + +### Wallet integration + +#### Key discovery + +Attested public keys are necessary for the chain of trust to be established. Since this is traditionally done via DNS certificate, we are proposing to anchor directly on the DNS record to point to a +manifest file containing the keys. This is similar to DKIM, but the use of the configuration file provides more flexibility for future improvements, and support for multiple algo/key pairs. + +Similarly to standard JWT practices, wallet could heavily cache dApp keys. The correlary is that in the absence of a revocation mechanism, a compromised/leaked key could still be used until caches have expired. +Wallets SHOULD NOT cache dApp public keys for more than 2h in order to reduce balance a relatively short vulnerability window, and manageable overhead for both wallet and dApp maintainers. + +Example DNS record for my-crypto-dapp.org: + +```txt +... +TXT: TWIT=/.well-known/twit.json +``` + +Example TWIT manifest at : + +```json +{ + "publicKeys": [ + {"id": "1", "alg":"ECDSA", "publicKey": "0xaf34..."}, + {"id": "2", "alg":"RSA-PSS", "publicKey": "0x98ab..."} + ] +} +``` + +#### Manifest schema + +We are proposing a very simple schema that will allow for future security improvements if needed. + +```json +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "TWIT manifest", + "type": "object", + "properties":{ + "publicKeys": { + "type": "array", + "items":{ + "type": "object", + "properties": { + "id": { "type": "string"}, + "alg": { "type": "string"}, + "publicKey": { "type": "string"} + } + } + } + } +} +``` + +#### Example implementation + +We propose to rely on algorithms supported by [SubtleCrypto](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) as they are present in every browsers. + +We recommend adding new method `secureRequest` to EIP-1193 as it would provide a simple, yet extensive, implementation for secure calls. In addition, it provides +dApp maintainers/devloppers with an explicit way to ask wallets to verify the integrity of the payload, as opposed to an implicit implementations relying on bit +packing of the arguments. + +Example: + +```typescript +interface RequestArguments { // this interface comes from EIP-1193 + readonly method: string; + readonly params?: readonly unknown[] | object; +} +async function secureRequest(requestArg: RequestArguments, signature: byte[], keyId:string): Promise;{ + // 0. get the domain of the sender.tab.url + const domain = getActiveTabDomain() + + // 1. get the manifest for the current domain + // One can use RFC 8484 for the actual DNS-over-HTTPS specification (see https://datatracker.ietf.org/doc/html/rfc8484). + // here we are doing it with DoHjs + // + // Note: this step is optional, and one could opt to go directly to the well-known address first at `domain + '/.well-known/twit.json'` + const doh = require('dohjs'); + const resolver = new doh.DohResolver('https://1.1.1.1/dns-query'); + let manifestPath = "" + const dnsResp = await resolver.query(domain, 'TXT') + for(record of dnsResp.answers){ + if(!record.data.startsWith("TWIT=")) continue; + + manifestPath = record.data.substring(5) // this should be domain + '/.well-known/twit.json' + break + } + + // 2. parse the manifest and get they key and algo based on `keyId` + const manifestReq = await fetch(manifestPath) + const manifest = await manifestReq.json() + const keyData = manifest.publicKeys.filter(x => x.id == keyId) + if(!keyData){ throw new Error("Could not find the signing key")} + const key = keyData.publidKey + const alg = keyData.alg + + // 3. verify the signature + valid = await crypto.verify(alg, key, signature, requestArg) + if(!valid){ + throw new Error("The data was tampered with") + } + return await request(requestArg) +} +``` + +### Wallet UX suggestion + +Similarly to the padlock icon for HTTPS, wallets should display a visible indication when TWIT is configured on a domain. This will improve the UX of the end user who will immediately be able to tell +that interactions between the dApp they are using and the wallet are secure, and this will encourage dApp developper to adopt TWIT, making the overall ecosystem more secure + +When dealing with insecure request, either because the dApp (or an attacker) uses `request` on a domain where TWIT is configured, or because the signature does not match, wallets should warn the user but +not block: an eloquently worded warning will increase the transparency enough that end user may opt to cancel the interaction or proceed with the unsafe call. + +### Wallet verification spec + +1. Upon receiving an EIP-1193 call, a wallet MUST check of the existence of the TWIT manifest for the `sender.tab.url` domain + a. a wallet MUST verify that the manifest is hosted on the `sender.tab.url` domain + b. a wallet SHOULD find the DNS TXT record to find the manifest location + b. a wallet COULD first try the `/.well-known/twit.json` location +2. if TWIT is NOT configured for the `sender.tab.url` domain, then proceed as usual +3. if TWIT is configured AND the `request` method is used, then the wallet MUST display a visible and actionable warning to the user + a. if the user opts to ignore the warning, then proceed as usual + b. if the user opts to cancel, then the wallet MUST cancel the call +4. if TWIT is configured AND the `secureRequest` method is used with the parameters `requestArgs`, `signature` and `keyId` then: + a. the wallet SHOULD display a visible cue indicating that this interaction is signed + b. the wallet MUST verify that the keyId exists in the TWIT manifest and find the assiciated key record + c. from the key record, the wallet MUST use the `alg` field and the `publicKey` field to verify `requestArgs` integrity by calling `crypto.verify(alg, key, signature, requestArgs)` + d. if the signature is invalid, the wallet MUST display a visible and actionable warning to the user + i. if the user opts to ignore the warning, then proceed to call `request` with the argument `requestArgs` + ii. if the user opts to cancel, then the wallet MUST cancel the call + e. if the signature is valid, the wallet MUST call `request` with the argment `requestArgs` + +## Security Considerations + +### Replay prevention + +While signing the `requestArg` payload guarantees data integrity, it does not prevent replay attacks in itself: + +1. a signed payload can be replayed multiple times +2. a signed payload can be replayed accross multiple chains + +*Effective* time replay attacks as described in `1.` are generally prevented by the transaction nonce. +Cross chain replay can be prevented by leveraging the EIP-712 `signTypedData` method. + +Replay attack would still be possible on any method that is not protected by either: this affects effectively all the "readonly" methods +which are of very limited value for an attacker. + +For these reason, we do not recommend a specific replay protection mechanism at this time. If/when the need arise, the extensibility of +the manifest will provide the necessary room to enforce a replay protection envelope (eg:JWT) for affected dApp. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). From 3ca21f6935a974941d024675a93dec064145ed0a Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Thu, 1 Aug 2024 16:35:08 +0100 Subject: [PATCH 2/7] Change to RPC method, various edits --- ERCS/erc-XXXX.md | 264 +++++++++++++++++++++++++++-------------------- 1 file changed, 150 insertions(+), 114 deletions(-) diff --git a/ERCS/erc-XXXX.md b/ERCS/erc-XXXX.md index f47d94c59e..e542e9f8fb 100644 --- a/ERCS/erc-XXXX.md +++ b/ERCS/erc-XXXX.md @@ -13,60 +13,64 @@ requires: 712, 1193 ## Abstract -A new `secureRequest` method in addition to EIP-1193 `request` for dApp to interact with extension wallets in a tamperproof way, preventing any MITM from modifying the payload sent to the wallet. -This will allow for dApps to sign their interaction with EIP-1193, and for wallet to visibly display secure interaction, therefor improving the end user experience by explicitly displaying safety -visual cues such as a padlock. +Introduces a new RPC method `wallet_signedRequest` that enables dapps to interact with wallets in a tamperproof manner via "signed requests". +To send a signed request, the dapp first publishes a set of public keys via its DNS records. +Then, the dapp signs its plaintext RPC request with a corresponding private key, +and submits the original request payload, signature, and public key id as the parameters of `wallet_signedRequest`. +Upon receiving a signed request, the wallet retrieves the public key from the dapp's DNS records, verifies the signature, and finally processes the plaintext request as normal. +The wallet can subsequently indicate to the user that the request was not tampered with. ## Motivation -The primary motivation for this standard is to enhance the end user experience by ensuring that they can feel safe when interacting with a trusted dApp (this is in essence very similar to HTTPS vs HTTP). +This standard aims to enhance the end user's experience by granting them confidence that requests from their dapps have not been tampered with. +In essence, this is similar to how HTTPS is used in the web. -Currently, the communication channel between a dApp and a wallet is vulnerable to man in the middle attacks: an attacker can intercept the communication by injecting javascript code in the page, and subsequently -modify the call data sent by the dApp to the wallet. XSS vulnerability or malicious extension both provide ways for an attacker to inject javascript. Despite EIP-720 increasing the transparency of the data being -signed by the wallet, the average user will in effect rarely confirm the actual content of a transaction because: +Currently, the communication channel between dapps and wallets is vulnerable to man in the middle attacks. +Specifically, attackers can intercept RPC requests by injecting JavaScript code in the page, +via e.g. an XSS vulnerability or due to a malicious extension. +Once an RPC request is intercepted, it can be modified in a number of pernicious ways: -* they have trust in specific well known dApp and will not read the detail thoroughly -* the content of the transaction is compressed to save gas and therefore unreadable -* the user is victim of social engineering +- Editing the calldata in order to siphon funds or otherwise change the transaction outcome +- Modify the parameters of an EIP-712 request +- Obtain a replayable signature from the wallet +- TODO -The effect can be pernicious as end users will not necessarily realise there is no chain of trust between the dApp backend and the wallet that will send a transaction on the user's behalf: a MITM attack can -capitalize on trusted dApp to syphon funds. +Even if the user realizes that requests from the dapp may be tampered with, they have little to no recourse to mitigate the problem. +Overall, the lack of a chain of trust between the dapp and the wallet hurts the ecosystem as a whole: -Such an attack can be used in a variety of ways: - -* Modify the call data on the fly, as a user is transacting -* Obtain a replayable signature from the user's wallet -* TODO - -Overall, the lack of a chain of trust between the dApp and the wallet hurts the ecosystem as a whole: - -* users cannot rely on trusting well known dApp, and are at risk of losing funds if they do not manually verify transactions before signing -* dApp maintainer are at risk of losing their trusted reputation if an attacker finds a viable MITM attack +- Users cannot simply trust otherwise honest dapps, and are at risk of losing funds +- Dapp maintainers are at risk of hurting their reputations if an attacker finds a viable MITM attack ## Specification +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. + ### Overview -We are proposing to use the domain certificate of a dApp as a root of trust to establish a trust chain as follow: +We propose to use the dapp's domain certificate of a root of trust to establish a trust chain as follow: -1. end user's browser verifies the domain certificate and display appropriate warning if overtaken -2. the DNS record of the dApp hosts a TXT field pointing to a URL where a JSON manifest is hosted (this file SHOULD be at a well known address such as ) -3. the config file contains an array of tuple `[alg, public key]` -4. for secure interaction, a dApp would first call their backend to securely sign the payload with a private key -5. the encrypted payload would be sent to the wallet via the `secureRequest` JSON api call -6. the wallet would verify the signature before sending the payload to the `request` JSON api call +1. The user's browser verifies the domain certificate and displays appropriate warnings if overtaken +2. The DNS record of the dapp hosts a TXT field pointing to a URL where a JSON manifest is hosted + - This file SHOULD be at a well known address such as +3. The config file contains an array of objects of the form `{ id, alg, publicKey }` +4. For signed requests, the dapp first securely signs the payload with a private key, for example by submitting a request to its backend +5. The original payload, signature, and public key id are sent to the wallet via the `wallet_signedRequest` RPC method +6. The wallet verifies the signature before processing the request normally ### Wallet integration #### Key discovery -Attested public keys are necessary for the chain of trust to be established. Since this is traditionally done via DNS certificate, we are proposing to anchor directly on the DNS record to point to a -manifest file containing the keys. This is similar to DKIM, but the use of the configuration file provides more flexibility for future improvements, and support for multiple algo/key pairs. +Attested public keys are necessary for the chain of trust to be established. +Since this is traditionally done via DNS certificates, we propose the addition of a DNS record containing the public keys. +This is similar to DKIM, but the use of the configuration file provides more flexibility for future improvements, and support for multiple algorithm and key pairs. -Similarly to standard JWT practices, wallet could heavily cache dApp keys. The correlary is that in the absence of a revocation mechanism, a compromised/leaked key could still be used until caches have expired. -Wallets SHOULD NOT cache dApp public keys for more than 2h in order to reduce balance a relatively short vulnerability window, and manageable overhead for both wallet and dApp maintainers. +Similarly to standard JWT practices, the wallet could eagerly cache dapp keys. +However, in the absence of a revocation mechanism, a compromised key could still be used until caches have expired. +To mitigate this, wallets SHOULD NOT cache dapp public keys for more than 2 hours. +This practice establishes a relatively short vulnerability window, and manageable overhead for both wallet and dapp maintainers. -Example DNS record for my-crypto-dapp.org: +Example DNS record for `my-crypto-dapp.org`: ```txt ... @@ -77,117 +81,149 @@ Example TWIT manifest at : ```json { - "publicKeys": [ - {"id": "1", "alg":"ECDSA", "publicKey": "0xaf34..."}, - {"id": "2", "alg":"RSA-PSS", "publicKey": "0x98ab..."} + "publicKeys": [ + { "id": "1", "alg": "ECDSA", "publicKey": "0xaf34..." }, + { "id": "2", "alg": "RSA-PSS", "publicKey": "0x98ab..." } ] } ``` +Dapps SHOULD only rely on algorithms available via [SubtleCrypto](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto), since they are present in every browser. + #### Manifest schema -We are proposing a very simple schema that will allow for future security improvements if needed. +We propose a simple and extensible schema: ```json { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "TWIT manifest", - "type": "object", - "properties":{ - "publicKeys": { - "type": "array", - "items":{ - "type": "object", - "properties": { - "id": { "type": "string"}, - "alg": { "type": "string"}, - "publicKey": { "type": "string"} - } - } + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "TWIT manifest", + "type": "object", + "properties": { + "publicKeys": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "alg": { "type": "string" }, + "publicKey": { "type": "string" } } + } } + } } ``` -#### Example implementation +#### RPC method -We propose to rely on algorithms supported by [SubtleCrypto](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) as they are present in every browsers. +The parameters of `wallet_signedRequest` are specified by this TypeScript interface: -We recommend adding new method `secureRequest` to EIP-1193 as it would provide a simple, yet extensive, implementation for secure calls. In addition, it provides -dApp maintainers/devloppers with an explicit way to ask wallets to verify the integrity of the payload, as opposed to an implicit implementations relying on bit -packing of the arguments. +```typescript +type RequestPayload = { method: string; params: Params }; -Example: +type SignedRequestParameters = [ + requestPayload: RequestPayload, + signature: `0x${string}`, // TODO: Is this the format we want? + keyId: string, +]; +``` + +Here's a non-normative example of calling `wallet_signedRequest` using the EIP-1193 provider interface: ```typescript -interface RequestArguments { // this interface comes from EIP-1193 - readonly method: string; - readonly params?: readonly unknown[] | object; -} -async function secureRequest(requestArg: RequestArguments, signature: byte[], keyId:string): Promise;{ - // 0. get the domain of the sender.tab.url - const domain = getActiveTabDomain() - - // 1. get the manifest for the current domain - // One can use RFC 8484 for the actual DNS-over-HTTPS specification (see https://datatracker.ietf.org/doc/html/rfc8484). - // here we are doing it with DoHjs - // - // Note: this step is optional, and one could opt to go directly to the well-known address first at `domain + '/.well-known/twit.json'` +const keyId = '1'; +const requestPayload: RequestPayload = { + method: 'eth_sendTransaction', + params: [ + { + /* ... */ + }, + ], +}; +const signature: `0x${string}` = await getSignature(requestPayload, keyId); + +// Using the EIP-1193 provider interface +const result = await ethereum.request({ + method: 'wallet_signedRequest', + params: [requestPayload, signature, keyId], +}); +``` + +#### Signature verification + +1. Upon receiving an EIP-1193 call, the wallet MUST check of the existence of the TWIT manifest for the `sender.tab.url` domain + a. The wallet MUST verify that the manifest is hosted on the `sender.tab.url` domain + b. The wallet SHOULD find the DNS TXT record to find the manifest location + b. The wallet MAY first try the `/.well-known/twit.json` location +2. If TWIT is NOT configured for the `sender.tab.url` domain, then proceed as usual +3. If TWIT is configured and the `request` method is used, then the wallet SHOULD display a visible and actionable warning to the user + a. If the user opts to ignore the warning, then proceed as usual + b. If the user opts to cancel, then the wallet MUST cancel the call +4. If TWIT is configured and the `wallet_signedRequest` method is used with the parameters `requestPayload`, `signature` and `keyId` then: + a. The wallet MAY display a visible cue indicating that this interaction is signed + b. The wallet MUST verify that the keyId exists in the TWIT manifest and find the associated key record + c. From the key record, the wallet MUST use the `alg` field and the `publicKey` field to verify `requestPayload` integrity by calling `crypto.verify(alg, key, signature, requestPayload)` + d. If the signature is invalid, the wallet MUST display a visible and actionable warning to the user + i. If the user opts to ignore the warning, then proceed to call `request` with the argument `requestPayload` + ii. If the user opts to cancel, then the wallet MUST cancel the call + e. If the signature is valid, the wallet MUST call `request` with the argument `requestPayload` + +#### Example method implementation (wallet) + +```typescript +async function signedRequest( + requestPayload: RequestPayload, + signature: `0x${string}`, + keyId: string, +): Promise { + // 1. Get the domain of the sender.tab.url + const domain = getDappDomain(); + + // 2. Get the manifest for the current domain + // It's possible to use RFC 8484 for the actual DNS-over-HTTPS specification, see https://datatracker.ietf.org/doc/html/rfc8484. + // However, here we are doing it with DoHjs. + // This step is optional, and you could go directly to the well-known address first at `domain + '/.well-known/twit.json'` const doh = require('dohjs'); const resolver = new doh.DohResolver('https://1.1.1.1/dns-query'); - let manifestPath = "" - const dnsResp = await resolver.query(domain, 'TXT') - for(record of dnsResp.answers){ - if(!record.data.startsWith("TWIT=")) continue; - manifestPath = record.data.substring(5) // this should be domain + '/.well-known/twit.json' - break + let manifestPath = ''; + const dnsResp = await resolver.query(domain, 'TXT'); + for (record of dnsResp.answers) { + if (!record.data.startsWith('TWIT=')) continue; + + manifestPath = record.data.substring(5); // This should be domain + '/.well-known/twit.json' + break; } - // 2. parse the manifest and get they key and algo based on `keyId` - const manifestReq = await fetch(manifestPath) - const manifest = await manifestReq.json() - const keyData = manifest.publicKeys.filter(x => x.id == keyId) - if(!keyData){ throw new Error("Could not find the signing key")} - const key = keyData.publidKey - const alg = keyData.alg - - // 3. verify the signature - valid = await crypto.verify(alg, key, signature, requestArg) - if(!valid){ - throw new Error("The data was tampered with") + // 3. Parse the manifest and get they key and algo based on `keyId` + const manifestReq = await fetch(manifestPath); + const manifest = await manifestReq.json(); + const keyData = manifest.publicKeys.filter((x) => x.id == keyId); + if (!keyData) { + throw new Error('Could not find the signing key'); } - return await request(requestArg) + + const key = keyData.publicKey; + const alg = keyData.alg; + + // 4. Verify the signature + const valid = await crypto.verify(alg, key, signature, requestPayload); + if (!valid) { + throw new Error('The data was tampered with'); + } + return await processRequest(requestPayload); } ``` ### Wallet UX suggestion Similarly to the padlock icon for HTTPS, wallets should display a visible indication when TWIT is configured on a domain. This will improve the UX of the end user who will immediately be able to tell -that interactions between the dApp they are using and the wallet are secure, and this will encourage dApp developper to adopt TWIT, making the overall ecosystem more secure +that interactions between the dapp they are using and the wallet are secure, and this will encourage dapp developer to adopt TWIT, making the overall ecosystem more secure -When dealing with insecure request, either because the dApp (or an attacker) uses `request` on a domain where TWIT is configured, or because the signature does not match, wallets should warn the user but +When dealing with insecure request, either because the dapp (or an attacker) uses `request` on a domain where TWIT is configured, or because the signature does not match, wallets should warn the user but not block: an eloquently worded warning will increase the transparency enough that end user may opt to cancel the interaction or proceed with the unsafe call. -### Wallet verification spec - -1. Upon receiving an EIP-1193 call, a wallet MUST check of the existence of the TWIT manifest for the `sender.tab.url` domain - a. a wallet MUST verify that the manifest is hosted on the `sender.tab.url` domain - b. a wallet SHOULD find the DNS TXT record to find the manifest location - b. a wallet COULD first try the `/.well-known/twit.json` location -2. if TWIT is NOT configured for the `sender.tab.url` domain, then proceed as usual -3. if TWIT is configured AND the `request` method is used, then the wallet MUST display a visible and actionable warning to the user - a. if the user opts to ignore the warning, then proceed as usual - b. if the user opts to cancel, then the wallet MUST cancel the call -4. if TWIT is configured AND the `secureRequest` method is used with the parameters `requestArgs`, `signature` and `keyId` then: - a. the wallet SHOULD display a visible cue indicating that this interaction is signed - b. the wallet MUST verify that the keyId exists in the TWIT manifest and find the assiciated key record - c. from the key record, the wallet MUST use the `alg` field and the `publicKey` field to verify `requestArgs` integrity by calling `crypto.verify(alg, key, signature, requestArgs)` - d. if the signature is invalid, the wallet MUST display a visible and actionable warning to the user - i. if the user opts to ignore the warning, then proceed to call `request` with the argument `requestArgs` - ii. if the user opts to cancel, then the wallet MUST cancel the call - e. if the signature is valid, the wallet MUST call `request` with the argment `requestArgs` - ## Security Considerations ### Replay prevention @@ -195,16 +231,16 @@ not block: an eloquently worded warning will increase the transparency enough th While signing the `requestArg` payload guarantees data integrity, it does not prevent replay attacks in itself: 1. a signed payload can be replayed multiple times -2. a signed payload can be replayed accross multiple chains +2. a signed payload can be replayed across multiple chains -*Effective* time replay attacks as described in `1.` are generally prevented by the transaction nonce. +_Effective_ time replay attacks as described in `1.` are generally prevented by the transaction nonce. Cross chain replay can be prevented by leveraging the EIP-712 `signTypedData` method. Replay attack would still be possible on any method that is not protected by either: this affects effectively all the "readonly" methods which are of very limited value for an attacker. For these reason, we do not recommend a specific replay protection mechanism at this time. If/when the need arise, the extensibility of -the manifest will provide the necessary room to enforce a replay protection envelope (eg:JWT) for affected dApp. +the manifest will provide the necessary room to enforce a replay protection envelope (eg:JWT) for affected dapp. ## Copyright From 7e72a2eb1cac851a12f74e4d77f9eb90a598a2c3 Mon Sep 17 00:00:00 2001 From: Erik Marks Date: Wed, 7 Aug 2024 10:30:28 +0100 Subject: [PATCH 3/7] Respond to review --- ERCS/erc-XXXX.md | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/ERCS/erc-XXXX.md b/ERCS/erc-XXXX.md index e542e9f8fb..7bd73cf228 100644 --- a/ERCS/erc-XXXX.md +++ b/ERCS/erc-XXXX.md @@ -8,17 +8,15 @@ status: Draft type: Standards Track category: ERC created: 2024-07-29 -requires: 712, 1193 --- ## Abstract -Introduces a new RPC method `wallet_signedRequest` that enables dapps to interact with wallets in a tamperproof manner via "signed requests". -To send a signed request, the dapp first publishes a set of public keys via its DNS records. -Then, the dapp signs its plaintext RPC request with a corresponding private key, -and submits the original request payload, signature, and public key id as the parameters of `wallet_signedRequest`. -Upon receiving a signed request, the wallet retrieves the public key from the dapp's DNS records, verifies the signature, and finally processes the plaintext request as normal. -The wallet can subsequently indicate to the user that the request was not tampered with. +Introduces a new RPC method to be implemented by wallets, `wallet_signedRequest`, that +enables dapps to interact with wallets in a tamperproof manner via "signed requests". The +dapp associates a public key with its DNS record and uses the corresponding private key to +sign payloads sent to the wallet via `wallet_signedRequest`. Wallets can then use use the +public key in the DNS record to validate the integrity of the payload. ## Motivation @@ -28,12 +26,11 @@ In essence, this is similar to how HTTPS is used in the web. Currently, the communication channel between dapps and wallets is vulnerable to man in the middle attacks. Specifically, attackers can intercept RPC requests by injecting JavaScript code in the page, via e.g. an XSS vulnerability or due to a malicious extension. -Once an RPC request is intercepted, it can be modified in a number of pernicious ways: +Once an RPC request is intercepted, it can be modified in a number of pernicious ways, including: - Editing the calldata in order to siphon funds or otherwise change the transaction outcome -- Modify the parameters of an EIP-712 request -- Obtain a replayable signature from the wallet -- TODO +- Modifying the parameters of an EIP-712 request +- Obtaining a replayable signature from the wallet Even if the user realizes that requests from the dapp may be tampered with, they have little to no recourse to mitigate the problem. Overall, the lack of a chain of trust between the dapp and the wallet hurts the ecosystem as a whole: @@ -41,6 +38,11 @@ Overall, the lack of a chain of trust between the dapp and the wallet hurts the - Users cannot simply trust otherwise honest dapps, and are at risk of losing funds - Dapp maintainers are at risk of hurting their reputations if an attacker finds a viable MITM attack +For these reasons, we recommend that wallets implement the `wallet_signedRequest` RPC method. +This method provides dapp developers with a way to explicitly ask the wallet to verify the +integrity of a payload. This is a significant improvement over the status quo, which forces +dapps to rely on implicit approaches such as argument bit packing. + ## Specification The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. @@ -63,7 +65,7 @@ We propose to use the dapp's domain certificate of a root of trust to establish Attested public keys are necessary for the chain of trust to be established. Since this is traditionally done via DNS certificates, we propose the addition of a DNS record containing the public keys. -This is similar to DKIM, but the use of the configuration file provides more flexibility for future improvements, and support for multiple algorithm and key pairs. +This is similar to DKIM, but the use of a manifest file provides more flexibility for future improvements, as well as support for multiple algorithm and key pairs. Similarly to standard JWT practices, the wallet could eagerly cache dapp keys. However, in the absence of a revocation mechanism, a compromised key could still be used until caches have expired. @@ -242,6 +244,11 @@ which are of very limited value for an attacker. For these reason, we do not recommend a specific replay protection mechanism at this time. If/when the need arise, the extensibility of the manifest will provide the necessary room to enforce a replay protection envelope (eg:JWT) for affected dapp. +## References + +- Related EIPs / ERCs: + - [EIP-1193: Ethereum Provider JavaScript API](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md) + ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md). From 1e926b366804379d6d912261ddf7b84ac37214c0 Mon Sep 17 00:00:00 2001 From: Guillaume G <162377478+uni-guillaume@users.noreply.github.com> Date: Fri, 9 Aug 2024 14:54:10 -0400 Subject: [PATCH 4/7] Update ERCS/erc-XXXX.md Co-authored-by: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com> --- ERCS/erc-XXXX.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ERCS/erc-XXXX.md b/ERCS/erc-XXXX.md index 7bd73cf228..dd57b28b56 100644 --- a/ERCS/erc-XXXX.md +++ b/ERCS/erc-XXXX.md @@ -2,7 +2,7 @@ eip: XXXX title: Tamperproof Web Immutable Transaction (TWIT) description: Provides a mechanism for DAP to use the API defined in EIP-1193 in a tamperproof way -author: Erik Marks, Guillaume Grosbois +author: Erik Marks (@remarks), Guillaume Grosbois (@uni-guillaume) discussions-to: TODO status: Draft type: Standards Track From 712c1efca8dffc6b38f7ada359d5a3a9afee56c0 Mon Sep 17 00:00:00 2001 From: Guillaume Grosbois Date: Fri, 9 Aug 2024 11:55:06 -0700 Subject: [PATCH 5/7] Updating with ERC number Signed-off-by: Guillaume Grosbois --- ERCS/{erc-XXXX.md => erc-7754.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename ERCS/{erc-XXXX.md => erc-7754.md} (99%) diff --git a/ERCS/erc-XXXX.md b/ERCS/erc-7754.md similarity index 99% rename from ERCS/erc-XXXX.md rename to ERCS/erc-7754.md index dd57b28b56..5e6c2644cd 100644 --- a/ERCS/erc-XXXX.md +++ b/ERCS/erc-7754.md @@ -1,5 +1,5 @@ --- -eip: XXXX +eip: 7754 title: Tamperproof Web Immutable Transaction (TWIT) description: Provides a mechanism for DAP to use the API defined in EIP-1193 in a tamperproof way author: Erik Marks (@remarks), Guillaume Grosbois (@uni-guillaume) From df128d44e6fba54ef6276799fb1e382adcb5b34f Mon Sep 17 00:00:00 2001 From: Guillaume Grosbois Date: Fri, 9 Aug 2024 12:04:29 -0700 Subject: [PATCH 6/7] fix lints Signed-off-by: Guillaume Grosbois --- ERCS/erc-7754.md | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/ERCS/erc-7754.md b/ERCS/erc-7754.md index 5e6c2644cd..ffb626cf24 100644 --- a/ERCS/erc-7754.md +++ b/ERCS/erc-7754.md @@ -1,13 +1,14 @@ --- eip: 7754 title: Tamperproof Web Immutable Transaction (TWIT) -description: Provides a mechanism for DAP to use the API defined in EIP-1193 in a tamperproof way +description: Provides a mechanism for dapps to use the extension wallets API in a tamperproof way author: Erik Marks (@remarks), Guillaume Grosbois (@uni-guillaume) -discussions-to: TODO +discussions-to: https://ethereum-magicians.org/t/erc-7754-tamperproof-web-immutable-transaction-twit/20767 status: Draft type: Standards Track category: ERC created: 2024-07-29 +requires: 1193 --- ## Abstract @@ -29,7 +30,7 @@ via e.g. an XSS vulnerability or due to a malicious extension. Once an RPC request is intercepted, it can be modified in a number of pernicious ways, including: - Editing the calldata in order to siphon funds or otherwise change the transaction outcome -- Modifying the parameters of an EIP-712 request +- Modifying the parameters of an [EIP-712](./eip-712.md) request - Obtaining a replayable signature from the wallet Even if the user realizes that requests from the dapp may be tampered with, they have little to no recourse to mitigate the problem. @@ -53,7 +54,7 @@ We propose to use the dapp's domain certificate of a root of trust to establish 1. The user's browser verifies the domain certificate and displays appropriate warnings if overtaken 2. The DNS record of the dapp hosts a TXT field pointing to a URL where a JSON manifest is hosted - - This file SHOULD be at a well known address such as + - This file SHOULD be at a well known address such as `https://domain.com/.well-known/twit.json` 3. The config file contains an array of objects of the form `{ id, alg, publicKey }` 4. For signed requests, the dapp first securely signs the payload with a private key, for example by submitting a request to its backend 5. The original payload, signature, and public key id are sent to the wallet via the `wallet_signedRequest` RPC method @@ -79,7 +80,7 @@ Example DNS record for `my-crypto-dapp.org`: TXT: TWIT=/.well-known/twit.json ``` -Example TWIT manifest at : +Example TWIT manifest at `https://my-crypto-dapp.org/twit.json`: ```json { @@ -90,7 +91,7 @@ Example TWIT manifest at : } ``` -Dapps SHOULD only rely on algorithms available via [SubtleCrypto](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto), since they are present in every browser. +Dapps SHOULD only rely on algorithms available via [SubtleCrypto](https://www.w3.org/TR/2017/REC-WebCryptoAPI-20170126/), since they are present in every browser. #### Manifest schema @@ -131,7 +132,7 @@ type SignedRequestParameters = [ ]; ``` -Here's a non-normative example of calling `wallet_signedRequest` using the EIP-1193 provider interface: +Here's a non-normative example of calling `wallet_signedRequest` using the [EIP-1193](./eip-1193.md) provider interface: ```typescript const keyId = '1'; @@ -154,7 +155,7 @@ const result = await ethereum.request({ #### Signature verification -1. Upon receiving an EIP-1193 call, the wallet MUST check of the existence of the TWIT manifest for the `sender.tab.url` domain +1. Upon receiving an [EIP-1193](./eip-1193.md) call, the wallet MUST check of the existence of the TWIT manifest for the `sender.tab.url` domain a. The wallet MUST verify that the manifest is hosted on the `sender.tab.url` domain b. The wallet SHOULD find the DNS TXT record to find the manifest location b. The wallet MAY first try the `/.well-known/twit.json` location @@ -226,6 +227,12 @@ that interactions between the dapp they are using and the wallet are secure, and When dealing with insecure request, either because the dapp (or an attacker) uses `request` on a domain where TWIT is configured, or because the signature does not match, wallets should warn the user but not block: an eloquently worded warning will increase the transparency enough that end user may opt to cancel the interaction or proceed with the unsafe call. +## Rationale + +The proposed implementation does not modify any of the existing functionalities offered by [EIP-712](./eip-712.md) and [EIP-1193](./eip-1193.md). Its additive +nature makes it inherently backward compatible. Its core design is modeled after existing solutions to existing problems (such as DKIM). As a result the proposed specification will be non disruptive, easy to +implements for both wallets and dapps, with a predictable threat model. + ## Security Considerations ### Replay prevention @@ -236,7 +243,7 @@ While signing the `requestArg` payload guarantees data integrity, it does not pr 2. a signed payload can be replayed across multiple chains _Effective_ time replay attacks as described in `1.` are generally prevented by the transaction nonce. -Cross chain replay can be prevented by leveraging the EIP-712 `signTypedData` method. +Cross chain replay can be prevented by leveraging the [EIP-712](./eip-712.md) `signTypedData` method. Replay attack would still be possible on any method that is not protected by either: this affects effectively all the "readonly" methods which are of very limited value for an attacker. @@ -244,11 +251,6 @@ which are of very limited value for an attacker. For these reason, we do not recommend a specific replay protection mechanism at this time. If/when the need arise, the extensibility of the manifest will provide the necessary room to enforce a replay protection envelope (eg:JWT) for affected dapp. -## References - -- Related EIPs / ERCs: - - [EIP-1193: Ethereum Provider JavaScript API](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md) - ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md). From b82a1a2fe9697c644e40f202eee43809db6407ea Mon Sep 17 00:00:00 2001 From: Guillaume Grosbois Date: Tue, 17 Sep 2024 07:31:59 -0700 Subject: [PATCH 7/7] Adressing comments Signed-off-by: Guillaume Grosbois --- ERCS/erc-7754.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ERCS/erc-7754.md b/ERCS/erc-7754.md index ffb626cf24..2c8bb2dd8c 100644 --- a/ERCS/erc-7754.md +++ b/ERCS/erc-7754.md @@ -54,7 +54,7 @@ We propose to use the dapp's domain certificate of a root of trust to establish 1. The user's browser verifies the domain certificate and displays appropriate warnings if overtaken 2. The DNS record of the dapp hosts a TXT field pointing to a URL where a JSON manifest is hosted - - This file SHOULD be at a well known address such as `https://domain.com/.well-known/twit.json` + - This file SHOULD be at a well known address such as `https://example.com/.well-known/twit.json` 3. The config file contains an array of objects of the form `{ id, alg, publicKey }` 4. For signed requests, the dapp first securely signs the payload with a private key, for example by submitting a request to its backend 5. The original payload, signature, and public key id are sent to the wallet via the `wallet_signedRequest` RPC method @@ -66,21 +66,21 @@ We propose to use the dapp's domain certificate of a root of trust to establish Attested public keys are necessary for the chain of trust to be established. Since this is traditionally done via DNS certificates, we propose the addition of a DNS record containing the public keys. -This is similar to DKIM, but the use of a manifest file provides more flexibility for future improvements, as well as support for multiple algorithm and key pairs. +This is similar to [RFC-6636](https://www.rfc-editor.org/rfc/rfc6376.html)'s DKIM, but the use of a manifest file provides more flexibility for future improvements, as well as support for multiple algorithm and key pairs. -Similarly to standard JWT practices, the wallet could eagerly cache dapp keys. +Similarly to standard [RFC-7519](https://www.rfc-editor.org/rfc/rfc7519.html)'s JWT practices, the wallet could eagerly cache dapp keys. However, in the absence of a revocation mechanism, a compromised key could still be used until caches have expired. To mitigate this, wallets SHOULD NOT cache dapp public keys for more than 2 hours. This practice establishes a relatively short vulnerability window, and manageable overhead for both wallet and dapp maintainers. -Example DNS record for `my-crypto-dapp.org`: +Example DNS record for `my-crypto-dapp.invalid`: ```txt ... TXT: TWIT=/.well-known/twit.json ``` -Example TWIT manifest at `https://my-crypto-dapp.org/twit.json`: +Example TWIT manifest at `https://my-crypto-dapp.invalid.com/twit.json`: ```json { @@ -127,7 +127,7 @@ type RequestPayload = { method: string; params: Params }; type SignedRequestParameters = [ requestPayload: RequestPayload, - signature: `0x${string}`, // TODO: Is this the format we want? + signature: `0x${string}`, keyId: string, ]; ```