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..88101d5c --- /dev/null +++ b/src/components/signatures/signTypedDataV4-sign-with-salt.js @@ -0,0 +1,251 @@ +import { recoverTypedSignature } from '@metamask/eth-sig-util'; +import { toChecksumAddress } from 'ethereumjs-util'; +import globalContext from '../..'; + +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: [ + { + 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[]' }, + ], + 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: [ + { + 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[]' }, + ], + 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);