From 551017e079d1463d29da39272cfecab96a3ab379 Mon Sep 17 00:00:00 2001 From: Ry Racherbaumer Date: Tue, 6 Feb 2024 11:55:56 -0600 Subject: [PATCH] feat: replace `ethers` with `viem` --- package-lock.json | 160 ++++++++++++-------- package.json | 8 +- rollup.config.bench.js | 1 + rollup.config.js | 1 + src/Client.ts | 13 +- src/conversations/Conversation.ts | 4 +- src/crypto/PublicKey.ts | 19 +-- src/crypto/Signature.ts | 17 +-- src/crypto/utils.ts | 36 +++++ src/keystore/providers/NetworkKeyManager.ts | 29 ++-- src/utils/topic.ts | 10 +- test/conversations/Conversation.test.ts | 4 +- test/keystore/InMemoryKeystore.test.ts | 6 +- 13 files changed, 187 insertions(+), 121 deletions(-) diff --git a/package-lock.json b/package-lock.json index 171adcc30..39ce00361 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,9 +14,9 @@ "@xmtp/user-preferences-bindings-wasm": "^0.3.4", "async-mutex": "^0.4.0", "elliptic": "^6.5.4", - "ethers": "^5.5.3", "js-sha3": "^0.9.3", - "long": "^5.2.0" + "long": "^5.2.0", + "viem": "^2.7.6" }, "devDependencies": { "@commitlint/cli": "^17.7.1", @@ -44,6 +44,7 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-promise": "^6.1.1", + "ethers": "^5.5.3", "find-up": "^7.0.0", "husky": "^7.0.4", "jest": "^29.6.0", @@ -57,8 +58,7 @@ "ts-jest": "^29.1.1", "tslib": "^2.6.2", "typedoc": "^0.25.1", - "typescript": "^5.2.2", - "viem": "^1.12.1" + "typescript": "^5.2.2" }, "engines": { "node": ">=18" @@ -74,10 +74,9 @@ } }, "node_modules/@adraffy/ens-normalize": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.9.4.tgz", - "integrity": "sha512-UK0bHA7hh9cR39V+4gl2/NnBBjoXIxkuWAPCaY4X7fbH4L/azIi7ilWOCjMUYfpJgraLUAqkRi2BqrjME8Rynw==", - "dev": true + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz", + "integrity": "sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==" }, "node_modules/@ampproject/remapping": { "version": "2.2.1", @@ -1437,6 +1436,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.5.0.tgz", "integrity": "sha512-loW7I4AohP5KycATvc0MgujU6JyCHPqHdeoo9z3Nr9xEiNioxa65ccdm1+fsoJhkuhdRtfcL8cfyGamz2AxZ5w==", + "dev": true, "funding": [ { "type": "individual", @@ -1463,6 +1463,7 @@ "version": "5.5.1", "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.5.1.tgz", "integrity": "sha512-m+MA/ful6eKbxpr99xUYeRvLkfnlqzrF8SZ46d/xFB1A7ZVknYc/sXJG0RcufF52Qn2jeFj1hhcoQ7IXjNKUqg==", + "dev": true, "funding": [ { "type": "individual", @@ -1487,6 +1488,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.5.0.tgz", "integrity": "sha512-lj//7r250MXVLKI7sVarXAbZXbv9P50lgmJQGr2/is82EwEb8r7HrxsmMqAjTsztMYy7ohrIhGMIml+Gx4D3mA==", + "dev": true, "funding": [ { "type": "individual", @@ -1509,6 +1511,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.5.0.tgz", "integrity": "sha512-l4Nj0eWlTUh6ro5IbPTgbpT4wRbdH5l8CQf7icF7sb/SI3Nhd9Y9HzhonTSTi6CefI0necIw7LJqQPopPLZyWw==", + "dev": true, "funding": [ { "type": "individual", @@ -1531,6 +1534,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.5.0.tgz", "integrity": "sha512-tdayUKhU1ljrlHzEWbStXazDpsx4eg1dBXUSI6+mHlYklOXoXF6lZvw8tnD6oVaWfnMxAgRSKROg3cVKtCcppA==", + "dev": true, "funding": [ { "type": "individual", @@ -1549,6 +1553,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.5.0.tgz", "integrity": "sha512-ZIodwhHpVJ0Y3hUCfUucmxKsWQA5TMnavp5j/UOuDdzZWzJlRmuOjcTMIGgHCYuZmHt36BfiSyQPSRskPxbfaQ==", + "dev": true, "funding": [ { "type": "individual", @@ -1568,6 +1573,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.5.0.tgz", "integrity": "sha512-6Xytlwvy6Rn3U3gKEc1vP7nR92frHkv6wtVr95LFR3jREXiCPzdWxKQ1cx4JGQBXxcguAwjA8murlYN2TSiEbg==", + "dev": true, "funding": [ { "type": "individual", @@ -1588,6 +1594,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.5.0.tgz", "integrity": "sha512-ABvc7BHWhZU9PNM/tANm/Qx4ostPGadAuQzWTr3doklZOhDlmcBqclrQe/ZXUIj3K8wC28oYeuRa+A37tX9kog==", + "dev": true, "funding": [ { "type": "individual", @@ -1606,6 +1613,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.5.0.tgz", "integrity": "sha512-2MsRRVChkvMWR+GyMGY4N1sAX9Mt3J9KykCsgUFd/1mwS0UH1qw+Bv9k1UJb3X3YJYFco9H20pjSlOIfCG5HYQ==", + "dev": true, "funding": [ { "type": "individual", @@ -1624,6 +1632,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.5.0.tgz", "integrity": "sha512-2viY7NzyvJkh+Ug17v7g3/IJC8HqZBDcOjYARZLdzRxrfGlRgmYgl6xPRKVbEzy1dWKw/iv7chDcS83pg6cLxg==", + "dev": true, "funding": [ { "type": "individual", @@ -1651,6 +1660,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.5.0.tgz", "integrity": "sha512-dnGVpK1WtBjmnp3mUT0PlU2MpapnwWI0PibldQEq1408tQBAbZpPidkWoVVuNMOl/lISO3+4hXZWCL3YV7qzfg==", + "dev": true, "funding": [ { "type": "individual", @@ -1676,6 +1686,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.5.0.tgz", "integrity": "sha512-mcSOo9zeUg1L0CoJH7zmxwUG5ggQHU1UrRf8jyTYy6HxdZV+r0PBoL1bxr+JHIPXRzS6u/UW4mEn43y0tmyF8Q==", + "dev": true, "funding": [ { "type": "individual", @@ -1705,6 +1716,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.5.0.tgz", "integrity": "sha512-9lA21XQnCdcS72xlBn1jfQdj2A1VUxZzOzi9UkNdnokNKke/9Ya2xA9aIK1SC3PQyBDLt4C+dfps7ULpkvKikQ==", + "dev": true, "funding": [ { "type": "individual", @@ -1735,6 +1747,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.5.0.tgz", "integrity": "sha512-5VoFCTjo2rYbBe1l2f4mccaRFN/4VQEYFwwn04aJV2h7qf4ZvI2wFxUE1XOX+snbwCLRzIeikOqtAoPwMza9kg==", + "dev": true, "funding": [ { "type": "individual", @@ -1753,12 +1766,14 @@ "node_modules/@ethersproject/keccak256/node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", - "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "dev": true }, "node_modules/@ethersproject/logger": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.5.0.tgz", "integrity": "sha512-rIY/6WPm7T8n3qS2vuHTUBPdXHl+rGxWxW5okDfo9J4Z0+gRRZT0msvUdIJkE4/HS29GUMziwGaaKO2bWONBrg==", + "dev": true, "funding": [ { "type": "individual", @@ -1774,6 +1789,7 @@ "version": "5.5.2", "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.5.2.tgz", "integrity": "sha512-NEqPxbGBfy6O3x4ZTISb90SjEDkWYDUbEeIFhJly0F7sZjoQMnj5KYzMSkMkLKZ+1fGpx00EDpHQCy6PrDupkQ==", + "dev": true, "funding": [ { "type": "individual", @@ -1792,6 +1808,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.5.0.tgz", "integrity": "sha512-SaDvQFvXPnz1QGpzr6/HToLifftSXGoXrbpZ6BvoZhmx4bNLHrxDe8MZisuecyOziP1aVEwzC2Hasj+86TgWVg==", + "dev": true, "funding": [ { "type": "individual", @@ -1811,6 +1828,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.5.0.tgz", "integrity": "sha512-l3zRQg3JkD8EL3CPjNK5g7kMx4qSwiR60/uk5IVjd3oq1MZR5qUg40CNOoEJoX5wc3DyY5bt9EbMk86C7x0DNA==", + "dev": true, "funding": [ { "type": "individual", @@ -1829,6 +1847,7 @@ "version": "5.5.2", "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.5.2.tgz", "integrity": "sha512-hkbx7x/MKcRjyrO4StKXCzCpWer6s97xnm34xkfPiarhtEUVAN4TBBpamM+z66WcTt7H5B53YwbRj1n7i8pZoQ==", + "dev": true, "funding": [ { "type": "individual", @@ -1861,10 +1880,32 @@ "ws": "7.4.6" } }, + "node_modules/@ethersproject/providers/node_modules/ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/@ethersproject/random": { "version": "5.5.1", "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.5.1.tgz", "integrity": "sha512-YaU2dQ7DuhL5Au7KbcQLHxcRHfgyNgvFV4sQOo0HrtW3Zkrc9ctWNz8wXQ4uCSfSDsqX2vcjhroxU5RQRV0nqA==", + "dev": true, "funding": [ { "type": "individual", @@ -1884,6 +1925,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.5.0.tgz", "integrity": "sha512-hLv8XaQ8PTI9g2RHoQGf/WSxBfTB/NudRacbzdxmst5VHAqd1sMibWG7SENzT5Dj3yZ3kJYx+WiRYEcQTAkcYA==", + "dev": true, "funding": [ { "type": "individual", @@ -1903,6 +1945,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.5.0.tgz", "integrity": "sha512-B5UBoglbCiHamRVPLA110J+2uqsifpZaTmid2/7W5rbtYVz6gus6/hSDieIU/6gaKIDcOj12WnOdiymEUHIAOA==", + "dev": true, "funding": [ { "type": "individual", @@ -1923,6 +1966,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.5.0.tgz", "integrity": "sha512-5VmseH7qjtNmDdZBswavhotYbWB0bOwKIlOTSlX14rKn5c11QmJwGt4GHeo7NrL/Ycl7uo9AHvEqs5xZgFBTng==", + "dev": true, "funding": [ { "type": "individual", @@ -1946,6 +1990,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.5.0.tgz", "integrity": "sha512-9NgZs9LhGMj6aCtHXhtmFQ4AN4sth5HuFXVvAQtzmm0jpSCNOTGtrHZJAeYTh7MBjRR8brylWZxBZR9zDStXbw==", + "dev": true, "funding": [ { "type": "individual", @@ -1969,6 +2014,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.5.0.tgz", "integrity": "sha512-9fy3TtF5LrX/wTrBaT8FGE6TDJyVjOvXynXJz5MT5azq+E6D92zuKNx7i29sWW2FjVOaWjAsiZ1ZWznuduTIIQ==", + "dev": true, "funding": [ { "type": "individual", @@ -1989,6 +2035,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.5.0.tgz", "integrity": "sha512-9RZYSKX26KfzEd/1eqvv8pLauCKzDTub0Ko4LfIgaERvRuwyaNV78mJs7cpIgZaDl6RJui4o49lHwwCM0526zA==", + "dev": true, "funding": [ { "type": "individual", @@ -2015,6 +2062,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.5.0.tgz", "integrity": "sha512-7+DpjiZk4v6wrikj+TCyWWa9dXLNU73tSTa7n0TSJDxkYbV3Yf1eRh9ToMLlZtuctNYu9RDNNy2USq3AdqSbag==", + "dev": true, "funding": [ { "type": "individual", @@ -2035,6 +2083,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.5.0.tgz", "integrity": "sha512-Mlu13hIctSYaZmUOo7r2PhNSd8eaMPVXe1wxrz4w4FCE4tDYBywDH+bAR1Xz2ADyXGwqYMwstzTrtUVIsKDO0Q==", + "dev": true, "funding": [ { "type": "individual", @@ -2067,6 +2116,7 @@ "version": "5.5.1", "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.5.1.tgz", "integrity": "sha512-olvLvc1CB12sREc1ROPSHTdFCdvMh0J5GSJYiQg2D0hdD4QmJDy8QYDb1CvoqD/bF1c++aeKv2sR5uduuG9dQg==", + "dev": true, "funding": [ { "type": "individual", @@ -2089,6 +2139,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.5.0.tgz", "integrity": "sha512-bL0UTReWDiaQJJYOC9sh/XcRu/9i2jMrzf8VLRmPKx58ckSlOJiohODkECCO50dtLZHcGU6MLXQ4OOrgBwP77Q==", + "dev": true, "funding": [ { "type": "individual", @@ -3512,7 +3563,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", - "dev": true, "dependencies": { "@noble/hashes": "1.3.2" }, @@ -3524,7 +3574,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", - "dev": true, "engines": { "node": ">= 16" }, @@ -4321,7 +4370,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.3.tgz", "integrity": "sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q==", - "dev": true, "funding": { "url": "https://paulmillr.com/funding/" } @@ -4330,7 +4378,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.2.tgz", "integrity": "sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==", - "dev": true, "dependencies": { "@noble/curves": "~1.2.0", "@noble/hashes": "~1.3.2", @@ -4344,7 +4391,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz", "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", - "dev": true, "dependencies": { "@noble/hashes": "~1.3.0", "@scure/base": "~1.1.0" @@ -5234,9 +5280,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.15.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz", - "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==" + "version": "18.15.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", + "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==" }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", @@ -5285,15 +5331,6 @@ "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", "dev": true }, - "node_modules/@types/ws": { - "version": "8.5.5", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", - "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/yargs": { "version": "17.0.24", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", @@ -5569,19 +5606,15 @@ "dev": true }, "node_modules/abitype": { - "version": "0.9.8", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-0.9.8.tgz", - "integrity": "sha512-puLifILdm+8sjyss4S+fsUN09obiT1g2YW6CtcQF+QDzxR0euzgEB29MZujC6zMk2a6SVmtttq1fc6+YFA7WYQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wagmi-dev" - } - ], + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.0.tgz", + "integrity": "sha512-NMeMah//6bJ56H5XRj8QCV4AwuW6hB6zqz2LnhhLdcWVQOsXki6/Pn3APeqxCma62nXIcmZWdu1DlHWS74umVQ==", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, "peerDependencies": { "typescript": ">=5.0.4", - "zod": "^3 >=3.19.1" + "zod": "^3 >=3.22.0" }, "peerDependenciesMeta": { "typescript": { @@ -5656,7 +5689,8 @@ "node_modules/aes-js": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", - "integrity": "sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0=" + "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", + "dev": true }, "node_modules/agent-base": { "version": "6.0.2", @@ -6092,7 +6126,8 @@ "node_modules/bech32": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "dev": true }, "node_modules/before-after-hook": { "version": "2.2.3", @@ -8588,6 +8623,7 @@ "version": "5.5.3", "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.5.3.tgz", "integrity": "sha512-fTT4WT8/hTe/BLwRUtl7I5zlpF3XC3P/Xwqxc5AIP2HGlH15qpmjs0Ou78az93b1rLITzXLFxoNX63B8ZbUd7g==", + "dev": true, "funding": [ { "type": "individual", @@ -10265,11 +10301,16 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, - "node_modules/isomorphic-ws": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", - "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", - "dev": true, + "node_modules/isows": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.3.tgz", + "integrity": "sha512-2cKei4vlmg2cxEjm3wVSqn8pcoRF/LX/wpifuuNquFO4SQmPwarClT+SUCA2lt+l581tTeZIPIZuIDo2jWN1fg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wagmi-dev" + } + ], "peerDependencies": { "ws": "*" } @@ -19019,7 +19060,8 @@ "node_modules/scrypt-js": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", - "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", + "dev": true }, "node_modules/semantic-release": { "version": "21.0.3", @@ -20746,7 +20788,7 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -20972,25 +21014,23 @@ } }, "node_modules/viem": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/viem/-/viem-1.12.1.tgz", - "integrity": "sha512-DCTLrqrsZIgfk86qU7FJGeunhaDDL/9xR4FfJZDVN/rDZCo6foXXDiofkyaYNyvFigps3hobj591Ah8uwaszEA==", - "dev": true, + "version": "2.7.6", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.7.6.tgz", + "integrity": "sha512-43TF0VYcTeNef9dax1/BhqlRLXpAo6HAiQ68hrJ8XRhDOou73nHZEjeFl8Eai4UFFodKhu+PbRUFzuuoixOUfg==", "funding": [ { "type": "github", - "url": "https://github.com/sponsors/wagmi-dev" + "url": "https://github.com/sponsors/wevm" } ], "dependencies": { - "@adraffy/ens-normalize": "1.9.4", + "@adraffy/ens-normalize": "1.10.0", "@noble/curves": "1.2.0", "@noble/hashes": "1.3.2", "@scure/bip32": "1.3.2", "@scure/bip39": "1.2.1", - "@types/ws": "^8.5.5", - "abitype": "0.9.8", - "isomorphic-ws": "5.0.0", + "abitype": "1.0.0", + "isows": "1.0.3", "ws": "8.13.0" }, "peerDependencies": { @@ -21006,7 +21046,6 @@ "version": "8.13.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "dev": true, "engines": { "node": ">=10.0.0" }, @@ -21247,11 +21286,12 @@ } }, "node_modules/ws": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", + "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "peer": true, "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", diff --git a/package.json b/package.json index f9037b94f..101c5c822 100644 --- a/package.json +++ b/package.json @@ -108,9 +108,9 @@ "@xmtp/user-preferences-bindings-wasm": "^0.3.4", "async-mutex": "^0.4.0", "elliptic": "^6.5.4", - "ethers": "^5.5.3", "js-sha3": "^0.9.3", - "long": "^5.2.0" + "long": "^5.2.0", + "viem": "^2.7.6" }, "devDependencies": { "@commitlint/cli": "^17.7.1", @@ -138,6 +138,7 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-promise": "^6.1.1", + "ethers": "^5.5.3", "find-up": "^7.0.0", "husky": "^7.0.4", "jest": "^29.6.0", @@ -151,8 +152,7 @@ "ts-jest": "^29.1.1", "tslib": "^2.6.2", "typedoc": "^0.25.1", - "typescript": "^5.2.2", - "viem": "^1.12.1" + "typescript": "^5.2.2" }, "engines": { "node": ">=18" diff --git a/rollup.config.bench.js b/rollup.config.bench.js index b6642db1d..e3c8e9c97 100644 --- a/rollup.config.bench.js +++ b/rollup.config.bench.js @@ -14,6 +14,7 @@ const external = [ 'ethers', 'js-sha3', 'long', + 'viem', ] const plugins = [ diff --git a/rollup.config.js b/rollup.config.js index ba4454f31..9879812a4 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -18,6 +18,7 @@ const external = [ 'ethers', 'js-sha3', 'long', + 'viem', ] const plugins = [ diff --git a/src/Client.ts b/src/Client.ts index 98f3e54fe..04b75ad13 100644 --- a/src/Client.ts +++ b/src/Client.ts @@ -10,7 +10,6 @@ import { EnvelopeMapperWithMessage, EnvelopeWithMessage, } from './utils' -import { utils } from 'ethers' import { Signer } from './types/Signer' import { Conversations } from './conversations' import { ContentTypeText, TextCodec } from './codecs/Text' @@ -44,7 +43,7 @@ import { import { hasMetamaskWithSnaps } from './keystore/snapHelpers' import { packageName, version } from './snapInfo.json' import { ExtractDecodedType } from './types/client' -import type { WalletClient } from 'viem' +import { getAddress, type WalletClient } from 'viem' import { Contacts } from './Contacts' import { KeystoreInterfaces } from './keystore/rpcDefinitions' const { Compression } = proto @@ -430,7 +429,7 @@ export default class Client { async getUserContact( peerAddress: string ): Promise { - peerAddress = utils.getAddress(peerAddress) // EIP55 normalize the address case. + peerAddress = getAddress(peerAddress) // EIP55 normalize the address case. const existingBundle = this.knownPublicKeyBundles.get(peerAddress) if (existingBundle) { return existingBundle @@ -456,7 +455,7 @@ export default class Client { ): Promise<(PublicKeyBundle | SignedPublicKeyBundle | undefined)[]> { // EIP55 normalize all peer addresses const normalizedAddresses = peerAddresses.map((address) => - utils.getAddress(address) + getAddress(address) ) // The logic here is tricky because we need to do a batch query for any uncached bundles, // then interleave back into an ordered array. So we create a map @@ -501,7 +500,7 @@ export default class Client { * Used to force getUserContact fetch contact from the network. */ forgetContact(peerAddress: string) { - peerAddress = utils.getAddress(peerAddress) // EIP55 normalize the address case. + peerAddress = getAddress(peerAddress) // EIP55 normalize the address case. this.knownPublicKeyBundles.delete(peerAddress) } @@ -552,7 +551,7 @@ export default class Client { const rawPeerAddresses: string[] = peerAddress // Try to normalize each of the peer addresses const normalizedPeerAddresses = rawPeerAddresses.map((address) => - utils.getAddress(address) + getAddress(address) ) // The getUserContactsFromNetwork will return false instead of throwing // on invalid envelopes @@ -563,7 +562,7 @@ export default class Client { return contacts.map((contact) => !!contact) } try { - peerAddress = utils.getAddress(peerAddress) // EIP55 normalize the address case. + peerAddress = getAddress(peerAddress) // EIP55 normalize the address case. } catch (e) { return false } diff --git a/src/conversations/Conversation.ts b/src/conversations/Conversation.ts index a12b4a47c..05b7dc107 100644 --- a/src/conversations/Conversation.ts +++ b/src/conversations/Conversation.ts @@ -6,7 +6,6 @@ import { concat, toNanoString, } from '../utils' -import { utils } from 'ethers' import Stream from '../Stream' import Client, { ListMessagesOptions, @@ -33,6 +32,7 @@ import { sha256 } from '../crypto/encryption' import { buildDecryptV1Request, getResultOrThrow } from '../utils/keystore' import { ContentTypeText } from '../codecs/Text' import { ConsentState } from '../Contacts' +import { getAddress } from 'viem' /** * Conversation represents either a V1 or V2 conversation with a common set of methods. @@ -178,7 +178,7 @@ export class ConversationV1 private client: Client constructor(client: Client, address: string, createdAt: Date) { - this.peerAddress = utils.getAddress(address) + this.peerAddress = getAddress(address) this.client = client this.createdAt = createdAt } diff --git a/src/crypto/PublicKey.ts b/src/crypto/PublicKey.ts index eeb8da39e..2d284e993 100644 --- a/src/crypto/PublicKey.ts +++ b/src/crypto/PublicKey.ts @@ -2,10 +2,10 @@ import { publicKey } from '@xmtp/proto' import * as secp from '@noble/secp256k1' import Long from 'long' import Signature, { WalletSigner } from './Signature' -import { equalBytes, hexToBytes } from './utils' -import { utils } from 'ethers' +import { computeAddress, equalBytes, splitSignature } from './utils' import { Signer } from '../types/Signer' import { sha256 } from './encryption' +import { hashMessage, Hex, hexToBytes } from 'viem' // SECP256k1 public key in uncompressed format with prefix type secp256k1Uncompressed = { @@ -90,7 +90,7 @@ export class UnsignedPublicKey implements publicKey.UnsignedPublicKey { // Derive Ethereum address from this public key. getEthereumAddress(): string { - return utils.computeAddress(this.secp256k1Uncompressed.bytes) + return computeAddress(this.secp256k1Uncompressed.bytes) } // Encode public key into bytes. @@ -256,16 +256,11 @@ export class PublicKey const sigString = await wallet.signMessage( WalletSigner.identitySigRequestText(this.bytesToSign()) ) - const eSig = utils.splitSignature(sigString) - const r = hexToBytes(eSig.r) - const s = hexToBytes(eSig.s) - const sigBytes = new Uint8Array(64) - sigBytes.set(r) - sigBytes.set(s, r.length) + const { bytes, recovery } = splitSignature(sigString as Hex) this.signature = new Signature({ ecdsaCompact: { - bytes: sigBytes, - recovery: eSig.recoveryParam, + bytes, + recovery, }, }) } @@ -278,7 +273,7 @@ export class PublicKey throw new Error('key is not signed') } const digest = hexToBytes( - utils.hashMessage(WalletSigner.identitySigRequestText(this.bytesToSign())) + hashMessage(WalletSigner.identitySigRequestText(this.bytesToSign())) ) const pk = this.signature.getPublicKey(digest) if (!pk) { diff --git a/src/crypto/Signature.ts b/src/crypto/Signature.ts index 906be632e..90c66c249 100644 --- a/src/crypto/Signature.ts +++ b/src/crypto/Signature.ts @@ -3,9 +3,9 @@ import Long from 'long' import * as secp from '@noble/secp256k1' import { PublicKey, UnsignedPublicKey, SignedPublicKey } from './PublicKey' import { SignedPrivateKey } from './PrivateKey' -import { utils } from 'ethers' import { Signer } from '../types/Signer' -import { bytesToHex, equalBytes, hexToBytes } from './utils' +import { bytesToHex, equalBytes, hexToBytes, splitSignature } from './utils' +import { Hex, hashMessage } from 'viem' // ECDSA signature with recovery bit. export type ECDSACompactWithRecovery = { @@ -164,7 +164,7 @@ export class WalletSigner implements KeySigner { signature: ECDSACompactWithRecovery ): UnsignedPublicKey | undefined { const digest = hexToBytes( - utils.hashMessage(this.identitySigRequestText(key.bytesToSign())) + hashMessage(this.identitySigRequestText(key.bytesToSign())) ) return ecdsaSignerKey(digest, signature) } @@ -174,16 +174,11 @@ export class WalletSigner implements KeySigner { const sigString = await this.wallet.signMessage( WalletSigner.identitySigRequestText(keyBytes) ) - const eSig = utils.splitSignature(sigString) - const r = hexToBytes(eSig.r) - const s = hexToBytes(eSig.s) - const sigBytes = new Uint8Array(64) - sigBytes.set(r) - sigBytes.set(s, r.length) + const { bytes, recovery } = splitSignature(sigString as Hex) const signature = new Signature({ walletEcdsaCompact: { - bytes: sigBytes, - recovery: eSig.recoveryParam, + bytes, + recovery, }, }) return new SignedPublicKey({ keyBytes, signature }) diff --git a/src/crypto/utils.ts b/src/crypto/utils.ts index 3f2a269c7..48ee70470 100644 --- a/src/crypto/utils.ts +++ b/src/crypto/utils.ts @@ -1,4 +1,12 @@ import * as secp from '@noble/secp256k1' +import { + Hex, + getAddress, + hexToSignature, + keccak256, + hexToBytes as viemHexToBytes, + bytesToHex as viemBytesToHex, +} from 'viem' export const bytesToHex = secp.utils.bytesToHex @@ -29,3 +37,31 @@ export function equalBytes(b1: Uint8Array, b2: Uint8Array): boolean { } return true } + +/** + * Compute the Ethereum address from uncompressed PublicKey bytes + */ +export function computeAddress(bytes: Uint8Array) { + const publicKey = viemBytesToHex(bytes.slice(1)) as `0x${string}` + const hash = keccak256(publicKey) + const address = hash.substring(hash.length - 40) + return getAddress(`0x${address}`) +} + +/** + * Split an Ethereum signature hex string into bytes and a recovery bit + */ +export function splitSignature(signature: Hex) { + const eSig = hexToSignature(signature) + const r = viemHexToBytes(eSig.r) + const s = viemHexToBytes(eSig.s) + let v = Number(eSig.v) + if (v === 0 || v === 1) { + v += 27 + } + const recovery = 1 - (v % 2) + const bytes = new Uint8Array(64) + bytes.set(r) + bytes.set(s, r.length) + return { bytes, recovery } +} diff --git a/src/keystore/providers/NetworkKeyManager.ts b/src/keystore/providers/NetworkKeyManager.ts index 351d74ca8..4ba8ee392 100644 --- a/src/keystore/providers/NetworkKeyManager.ts +++ b/src/keystore/providers/NetworkKeyManager.ts @@ -1,4 +1,3 @@ -import { utils } from 'ethers' import { Signer } from '../../types/Signer' import crypto from '../../crypto/crypto' import { @@ -14,6 +13,7 @@ import { bytesToHex, hexToBytes } from '../../crypto/utils' import Ciphertext from '../../crypto/Ciphertext' import { privateKey as proto } from '@xmtp/proto' import TopicPersistence from '../persistence/TopicPersistence' +import { getAddress, verifyMessage } from 'viem' const KEY_BUNDLE_NAME = 'key_bundle' /** @@ -39,7 +39,7 @@ export default class NetworkKeyManager { // I think we want to namespace the storage address by wallet // This will allow us to support switching between multiple wallets in the same browser let walletAddress = await this.signer.getAddress() - walletAddress = utils.getAddress(walletAddress) + walletAddress = getAddress(walletAddress) return `${walletAddress}/${name}` } @@ -91,24 +91,23 @@ export default class NetworkKeyManager { if (this.preEnableIdentityCallback) { await this.preEnableIdentityCallback() } - let sig = await wallet.signMessage(input) + const sig = await wallet.signMessage(input) // Check that the signature is correct, was created using the expected // input, and retry if not. This mitigates a bug in interacting with // LedgerLive for iOS, where the previous signature response is // returned in some cases. - let address = utils.verifyMessage(input, sig) - if (address !== walletAddr) { - sig = await wallet.signMessage(input) - console.log('invalid signature, retrying') - - address = utils.verifyMessage(input, sig) - if (address !== walletAddr) { - throw new Error('invalid signature') - } + const valid = verifyMessage({ + address: walletAddr as `0x${string}`, + message: input, + signature: sig as `0x${string}`, + }) + + if (!valid) { + throw new Error('invalid signature') } - const secret = hexToBytes(sig) + const secret = hexToBytes(sig as `0x${string}`) const ciphertext = await encrypt(bytes, secret) return proto.EncryptedPrivateKeyBundle.encode({ v1: { @@ -136,7 +135,9 @@ export default class NetworkKeyManager { await this.preEnableIdentityCallback() } const secret = hexToBytes( - await wallet.signMessage(storageSigRequestText(eBundle.walletPreKey)) + (await wallet.signMessage( + storageSigRequestText(eBundle.walletPreKey) + )) as `0x${string}` ) // Ledger uses the last byte = v=[0,1,...] but Metamask and other wallets generate with diff --git a/src/utils/topic.ts b/src/utils/topic.ts index 98d28006f..f706d321f 100644 --- a/src/utils/topic.ts +++ b/src/utils/topic.ts @@ -1,4 +1,4 @@ -import { utils } from 'ethers' +import { getAddress } from 'viem' export const buildContentTopic = (name: string): string => `/xmtp/0/${name}/proto` @@ -8,7 +8,7 @@ export const buildDirectMessageTopic = ( recipient: string ): string => { // EIP55 normalize the address case. - const members = [utils.getAddress(sender), utils.getAddress(recipient)] + const members = [getAddress(sender), getAddress(recipient)] members.sort() return buildContentTopic(`dm-${members.join('-')}`) } @@ -19,17 +19,17 @@ export const buildDirectMessageTopicV2 = (randomString: string): string => { export const buildUserContactTopic = (walletAddr: string): string => { // EIP55 normalize the address case. - return buildContentTopic(`contact-${utils.getAddress(walletAddr)}`) + return buildContentTopic(`contact-${getAddress(walletAddr)}`) } export const buildUserIntroTopic = (walletAddr: string): string => { // EIP55 normalize the address case. - return buildContentTopic(`intro-${utils.getAddress(walletAddr)}`) + return buildContentTopic(`intro-${getAddress(walletAddr)}`) } export const buildUserInviteTopic = (walletAddr: string): string => { // EIP55 normalize the address case. - return buildContentTopic(`invite-${utils.getAddress(walletAddr)}`) + return buildContentTopic(`invite-${getAddress(walletAddr)}`) } export const buildUserPrivateStoreTopic = (addrPrefixedKey: string): string => { diff --git a/test/conversations/Conversation.test.ts b/test/conversations/Conversation.test.ts index e7e354768..00869cdc4 100644 --- a/test/conversations/Conversation.test.ts +++ b/test/conversations/Conversation.test.ts @@ -372,9 +372,7 @@ describe('conversation', () => { }) it('throws when opening a conversation with an unknown address', () => { - expect(alice.conversations.newConversation('0xfoo')).rejects.toThrow( - 'invalid address' - ) + expect(alice.conversations.newConversation('0xfoo')).rejects.toThrow() const validButUnknown = '0x1111111111222222222233333333334444444444' expect( alice.conversations.newConversation(validButUnknown) diff --git a/test/keystore/InMemoryKeystore.test.ts b/test/keystore/InMemoryKeystore.test.ts index fdbe92d67..a0d7f57be 100644 --- a/test/keystore/InMemoryKeystore.test.ts +++ b/test/keystore/InMemoryKeystore.test.ts @@ -18,7 +18,7 @@ import { InMemoryPersistence } from '../../src/keystore/persistence' import Token from '../../src/authn/Token' import Long from 'long' import { CreateInviteResponse } from '@xmtp/proto/ts/dist/types/keystore_api/v1/keystore.pb' -import { ethers } from 'ethers' +import { toBytes } from 'viem' describe('InMemoryKeystore', () => { let aliceKeys: PrivateKeyBundleV1 @@ -525,7 +525,7 @@ describe('InMemoryKeystore', () => { it('generates known deterministic topic', async () => { aliceKeys = new PrivateKeyBundleV1( privateKey.PrivateKeyBundle.decode( - ethers.utils.arrayify( + toBytes( '0x0a8a030ac20108c192a3f7923112220a2068d2eb2ef8c50c4916b42ce638c5610e44ff4eb3ecb098' + 'c9dacf032625c72f101a940108c192a3f7923112460a440a40fc9822283078c323c9319c45e60ab4' + '2c65f6e1744ed8c23c52728d456d33422824c98d307e8b1c86a26826578523ba15fe6f04a17fca17' + @@ -545,7 +545,7 @@ describe('InMemoryKeystore', () => { ) bobKeys = new PrivateKeyBundleV1( privateKey.PrivateKeyBundle.decode( - ethers.utils.arrayify( + toBytes( '0x0a88030ac001088cd68df7923112220a209057f8d813314a2aae74e6c4c30f909c1c496b6037ce32' + 'a12c613558a8e961681a9201088cd68df7923112440a420a40501ae9b4f75d5bb5bae3ca4ecfda4e' + 'de9edc5a9b7fc2d56dc7325b837957c23235cc3005b46bb9ef485f106404dcf71247097ed5096355' +