From f4e8302c60d1d0baf58a5d42f74cadb3e97276be Mon Sep 17 00:00:00 2001 From: Guillaume Grosbois Date: Mon, 24 Mar 2025 10:21:10 -0700 Subject: [PATCH 1/3] ERC-7754: updating the acronym to avoid negative connotation Signed-off-by: Guillaume Grosbois --- ERCS/erc-7754.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/ERCS/erc-7754.md b/ERCS/erc-7754.md index 93743eafcf5..397e65dc08d 100644 --- a/ERCS/erc-7754.md +++ b/ERCS/erc-7754.md @@ -1,6 +1,6 @@ --- eip: 7754 -title: Tamperproof Web Immutable Transaction (TWIT) +title: Tamperproof Web Immutable Secure Transaction (TWIST) 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: https://ethereum-magicians.org/t/erc-7754-tamperproof-web-immutable-transaction-twit/20767 @@ -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://example.com/.well-known/twit.json` + - This file SHOULD be at a well known address such as `https://example.com/.well-known/twist.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 @@ -77,10 +77,10 @@ Example DNS record for `my-crypto-dapp.invalid`: ```txt ... -TXT: TWIT=/.well-known/twit.json +TXT: TWIST=/.well-known/twist.json ``` -Example TWIT manifest at `https://my-crypto-dapp.invalid.com/twit.json`: +Example TWIST manifest at `https://my-crypto-dapp.invalid.com/twist.json`: ```json { @@ -100,7 +100,7 @@ We propose a simple and extensible schema: ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "TWIT manifest", + "title": "TWIST manifest", "type": "object", "properties": { "publicKeys": { @@ -155,17 +155,17 @@ const result = await ethereum.request({ #### Signature verification -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 +1. Upon receiving an [EIP-1193](./eip-1193.md) call, the wallet MUST check of the existence of the TWIST 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 + b. The wallet MAY first try the `/.well-known/twist.json` location +2. If TWIST is NOT configured for the `sender.tab.url` domain, then proceed as usual +3. If TWIST 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: +4. If TWIST 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 + b. The wallet MUST verify that the keyId exists in the TWIST 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` @@ -186,16 +186,16 @@ async function signedRequest( // 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'` + // This step is optional, and you could go directly to the well-known address first at `domain + '/.well-known/twist.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; + if (!record.data.startsWith('TWIST=')) continue; - manifestPath = record.data.substring(5); // This should be domain + '/.well-known/twit.json' + manifestPath = record.data.substring(5); // This should be domain + '/.well-known/twist.json' break; } @@ -221,10 +221,10 @@ async function signedRequest( ### 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 developer to adopt TWIT, making the overall ecosystem more secure +Similarly to the padlock icon for HTTPS, wallets should display a visible indication when TWIST 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 developer to adopt TWIST, 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 TWIST 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 From fbe06ff254463997a470d2b65cd5606a6b9ed6dd Mon Sep 17 00:00:00 2001 From: Guillaume Grosbois Date: Wed, 23 Jul 2025 16:10:45 -0700 Subject: [PATCH 2/3] ERC-7754: adding key security detail to the TWIST verification spec Signed-off-by: Guillaume Grosbois --- ERCS/erc-7754.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/ERCS/erc-7754.md b/ERCS/erc-7754.md index 397e65dc08d..d4bfac03b09 100644 --- a/ERCS/erc-7754.md +++ b/ERCS/erc-7754.md @@ -156,9 +156,12 @@ const result = await ethereum.request({ #### Signature verification 1. Upon receiving an [EIP-1193](./eip-1193.md) call, the wallet MUST check of the existence of the TWIST 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/twist.json` location + a. The wallet MUST enforce HTTPS as HTTP call would be vulnerable to DNS spoofing + b. The wallet MUST verify that the manifest is hosted on the `sender.tab.url` domain + c. The wallet SHOULD find the DNS TXT record to find the manifest location + d. The wallet MAY first try the `/.well-known/twist.json` location + e. The wallet MUST NOT follow redirects when querying the manifest location as it could lead to open redirect attacks + f. The wallet SHOULD validate the `Content-Type` header of the response is specifically set to `application/json` 2. If TWIST is NOT configured for the `sender.tab.url` domain, then proceed as usual 3. If TWIST 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 @@ -201,6 +204,9 @@ async function signedRequest( // 3. Parse the manifest and get the key and algo based on `keyId` const manifestReq = await fetch(manifestPath); + if(manifestReq.headers['content-type']!=='application/json'){ + throw new Error('The manifest is not a proper json file') + } const manifest = await manifestReq.json(); const keyData = manifest.publicKeys.filter((x) => x.id == keyId); if (!keyData) { @@ -251,6 +257,15 @@ 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. +### Malicious manifests + +The manifest itself could be attacked, defeating the purpose of TWIST. We identified the following possible attacks, and their counter measure: + +1. An attacker can spoof DNS entries and use it to serve their own manifest: to avoid this, the wallet implementation MUST only query the manifest from `'https://${sender.tab.url}/${pathFromDNSRecord}` +2. An attacker can leverage other flaws in a dapp to host a malicious manifest on the dapp domain itself + a. by leveraging open redirect: consequently the wallet MUST NOT follow redirect when querying the manifest + b. by managing to host a file on the dapp domain: consequently the wallet SHOULD verify the `content-type` header is equal to `application/json` to mitigate this attack vector + ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md). From 3bb198d7711c6d45824934ddf7821e5ff325bee7 Mon Sep 17 00:00:00 2001 From: Guillaume Grosbois Date: Wed, 23 Jul 2025 16:16:31 -0700 Subject: [PATCH 3/3] ERC-7754: shortening the title Signed-off-by: Guillaume Grosbois --- ERCS/erc-7754.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ERCS/erc-7754.md b/ERCS/erc-7754.md index d4bfac03b09..1dc7dc57807 100644 --- a/ERCS/erc-7754.md +++ b/ERCS/erc-7754.md @@ -1,6 +1,6 @@ --- eip: 7754 -title: Tamperproof Web Immutable Secure Transaction (TWIST) +title: Tamperproof Extension Wallets API (TWIST) 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: https://ethereum-magicians.org/t/erc-7754-tamperproof-web-immutable-transaction-twit/20767 @@ -13,7 +13,7 @@ requires: 1193 ## Abstract -Introduces a new RPC method to be implemented by wallets, `wallet_signedRequest`, that +Tamperproof Web Immutable Secure Transaction (TWIST) 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 the