From 95ee834aaadaec79eb1698d8e898d6dd21027298 Mon Sep 17 00:00:00 2001 From: Xiaoming Wang Date: Wed, 2 Apr 2025 12:14:03 +0100 Subject: [PATCH 1/4] feat: add sign typed data v4 with salt tests --- src/components/signatures/index.js | 1 + .../signTypedDataV4-sign-with-salt.js | 210 ++++++++++++++++++ src/index.js | 2 + 3 files changed, 213 insertions(+) create mode 100644 src/components/signatures/signTypedDataV4-sign-with-salt.js diff --git a/src/components/signatures/index.js b/src/components/signatures/index.js index f04254ce..5456bf2b 100644 --- a/src/components/signatures/index.js +++ b/src/components/signatures/index.js @@ -5,6 +5,7 @@ export * from './signTypedData'; export * from './signTypedData-variants'; export * from './signTypedDataV3-sign'; export * from './signTypedDataV4-sign'; +export * from './signTypedDataV4-sign-with-salt'; export * from './siwe'; export * from './malformed-signatures'; export * from './malformed-transactions'; diff --git a/src/components/signatures/signTypedDataV4-sign-with-salt.js b/src/components/signatures/signTypedDataV4-sign-with-salt.js new file mode 100644 index 00000000..7c3f89a9 --- /dev/null +++ b/src/components/signatures/signTypedDataV4-sign-with-salt.js @@ -0,0 +1,210 @@ +import { recoverTypedSignature } from '@metamask/eth-sig-util'; +import { toChecksumAddress } from 'ethereumjs-util'; +import globalContext from '../..'; +import { EIP712Domain } from '../../signatures/utils'; + +export function signTypedDataV4WithSaltComponent(parentContainer) { + parentContainer.insertAdjacentHTML( + 'beforeend', + `
+
+
+

+ Sign Typed Data V4 with salt +

+ + + +

+ Result: + +

+ + + +

+ Recovery result: + +

+
+
+
`, + ); + + const signTypedDataV4WithSalt = document.getElementById( + 'signTypedDataV4WithSalt', + ); + const signTypedDataV4WithSaltResult = document.getElementById( + 'signTypedDataV4WithSaltResult', + ); + const signTypedDataV4WithSaltVerify = document.getElementById( + 'signTypedDataV4WithSaltVerify', + ); + const signTypedDataV4WithSaltVerifyResult = document.getElementById( + 'signTypedDataV4WithSaltVerifyResult', + ); + + document.addEventListener('globalConnectionChange', function (e) { + if (e.detail.connected) { + // MetaMask is connected, enable the button + signTypedDataV4WithSalt.disabled = false; + } + }); + + document.addEventListener('disableAndClear', function () { + signTypedDataV4WithSalt.disabled = true; + signTypedDataV4WithSaltVerify.disabled = true; + }); + + /** + * Sign Typed Data V4 + */ + signTypedDataV4WithSalt.onclick = async () => { + const msgParams = { + domain: { + chainId: globalContext.chainIdInt.toString(), + name: 'Ether Mail', + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + version: '1', + salt: 'test', + }, + message: { + contents: 'Hello, Bob!', + from: { + name: 'Cow', + wallets: [ + '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + '0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF', + ], + }, + to: [ + { + name: 'Bob', + wallets: [ + '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + '0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57', + '0xB0B0b0b0b0b0B000000000000000000000000000', + ], + }, + ], + attachment: '0x', + }, + primaryType: 'Mail', + types: { + EIP712Domain, + Group: [ + { name: 'name', type: 'string' }, + { name: 'members', type: 'Person[]' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person[]' }, + { name: 'contents', type: 'string' }, + { name: 'attachment', type: 'bytes' }, + ], + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallets', type: 'address[]' }, + ], + }, + }; + try { + const from = globalContext.accounts[0]; + const sign = await globalContext.provider.request({ + method: 'eth_signTypedData_v4', + params: [from, JSON.stringify(msgParams)], + }); + signTypedDataV4WithSaltResult.innerHTML = sign; + signTypedDataV4WithSaltVerify.disabled = false; + } catch (err) { + console.error(err); + signTypedDataV4WithSaltResult.innerHTML = `Error: ${err.message}`; + } + }; + + /** + * Sign Typed Data V4 Verification + */ + signTypedDataV4WithSaltVerify.onclick = async () => { + const msgParams = { + domain: { + chainId: globalContext.chainIdInt, + name: 'Ether Mail', + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + version: '1', + salt: 'test', + }, + message: { + contents: 'Hello, Bob!', + from: { + name: 'Cow', + wallets: [ + '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + '0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF', + ], + }, + to: [ + { + name: 'Bob', + wallets: [ + '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + '0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57', + '0xB0B0b0b0b0b0B000000000000000000000000000', + ], + }, + ], + attachment: '0x', + }, + primaryType: 'Mail', + types: { + EIP712Domain, + Group: [ + { name: 'name', type: 'string' }, + { name: 'members', type: 'Person[]' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person[]' }, + { name: 'contents', type: 'string' }, + { name: 'attachment', type: 'bytes' }, + ], + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallets', type: 'address[]' }, + ], + }, + }; + try { + const from = globalContext.accounts[0]; + const sign = signTypedDataV4WithSaltResult.innerHTML; + const recoveredAddr = recoverTypedSignature({ + data: msgParams, + signature: sign, + version: 'V4', + }); + if (toChecksumAddress(recoveredAddr) === toChecksumAddress(from)) { + console.log(`Successfully verified signer as ${recoveredAddr}`); + signTypedDataV4WithSaltVerifyResult.innerHTML = recoveredAddr; + } else { + console.log( + `Failed to verify signer when comparing ${recoveredAddr} to ${from}`, + ); + } + } catch (err) { + console.error(err); + signTypedDataV4WithSaltVerifyResult.innerHTML = `Error: ${err.message}`; + } + }; +} diff --git a/src/index.js b/src/index.js index f2aa6651..ecfa78b8 100644 --- a/src/index.js +++ b/src/index.js @@ -33,6 +33,7 @@ import { signTypedDataVariantsComponent, signTypedDataV3Component, signTypedDataV4Component, + signTypedDataV4WithSaltComponent, siweComponent, malformedSignaturesComponent, malformedTransactionsComponent, @@ -201,6 +202,7 @@ personalSignComponent(signaturesRow); signTypedDataComponent(signaturesRow); signTypedDataV3Component(signaturesRow); signTypedDataV4Component(signaturesRow); +signTypedDataV4WithSaltComponent(signaturesRow); permitSignComponent(signaturesRow); signTypedDataVariantsComponent(signaturesRow); siweComponent(signaturesRow); From 462dc6bff741fbc83752ff7c2de5b5e44522cd40 Mon Sep 17 00:00:00 2001 From: Xiaoming Wang Date: Wed, 2 Apr 2025 12:34:55 +0100 Subject: [PATCH 2/4] feat: modify the tests to make error occur for old version. --- .../signTypedDataV4-sign-with-salt.js | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/components/signatures/signTypedDataV4-sign-with-salt.js b/src/components/signatures/signTypedDataV4-sign-with-salt.js index 7c3f89a9..27662251 100644 --- a/src/components/signatures/signTypedDataV4-sign-with-salt.js +++ b/src/components/signatures/signTypedDataV4-sign-with-salt.js @@ -103,7 +103,28 @@ export function signTypedDataV4WithSaltComponent(parentContainer) { }, primaryType: 'Mail', types: { - EIP712Domain, + EIP712Domain: [ + { + name: 'name', + type: 'string', + }, + { + name: 'version', + type: 'string', + }, + { + name: 'chainId', + type: 'uint256', + }, + { + name: 'verifyingContract', + type: 'string', + }, + { + name: 'salt', + type: 'string', + }, + ], Group: [ { name: 'name', type: 'string' }, { name: 'members', type: 'Person[]' }, From 70bf261bb0d017177652dd08f6e1b73621a1048e Mon Sep 17 00:00:00 2001 From: Xiaoming Wang Date: Wed, 2 Apr 2025 13:27:44 +0100 Subject: [PATCH 3/4] feat: fix the verification of sign typed data v4 with salt. --- .../signTypedDataV4-sign-with-salt.js | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/components/signatures/signTypedDataV4-sign-with-salt.js b/src/components/signatures/signTypedDataV4-sign-with-salt.js index 27662251..cee22ea9 100644 --- a/src/components/signatures/signTypedDataV4-sign-with-salt.js +++ b/src/components/signatures/signTypedDataV4-sign-with-salt.js @@ -190,7 +190,28 @@ export function signTypedDataV4WithSaltComponent(parentContainer) { }, primaryType: 'Mail', types: { - EIP712Domain, + EIP712Domain: [ + { + name: 'name', + type: 'string', + }, + { + name: 'version', + type: 'string', + }, + { + name: 'chainId', + type: 'uint256', + }, + { + name: 'verifyingContract', + type: 'string', + }, + { + name: 'salt', + type: 'string', + }, + ], Group: [ { name: 'name', type: 'string' }, { name: 'members', type: 'Person[]' }, From f841a9c548cacf26c5f577bf69d0a9157a6a6e44 Mon Sep 17 00:00:00 2001 From: Xiaoming Wang Date: Wed, 2 Apr 2025 15:57:02 +0100 Subject: [PATCH 4/4] fix: fix lint error. --- src/components/signatures/signTypedDataV4-sign-with-salt.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/signatures/signTypedDataV4-sign-with-salt.js b/src/components/signatures/signTypedDataV4-sign-with-salt.js index cee22ea9..88101d5c 100644 --- a/src/components/signatures/signTypedDataV4-sign-with-salt.js +++ b/src/components/signatures/signTypedDataV4-sign-with-salt.js @@ -1,7 +1,6 @@ import { recoverTypedSignature } from '@metamask/eth-sig-util'; import { toChecksumAddress } from 'ethereumjs-util'; import globalContext from '../..'; -import { EIP712Domain } from '../../signatures/utils'; export function signTypedDataV4WithSaltComponent(parentContainer) { parentContainer.insertAdjacentHTML(