Skip to content

Commit

Permalink
Support ethers v6 (#147)
Browse files Browse the repository at this point in the history
* upgrade jest, ts-jest to latest

* support ethers@6

* loosen peerDependencies on siwe

* set up ethers5 and ethers6 devDependencies to enable testing with both versions

* allow test runner to select ethers 5 or 6

* create a compatibility layer that exports the right functions based on whether we are using ethers v5 or v6
  • Loading branch information
freeatnet authored Apr 6, 2023
1 parent 359968e commit e12cd8d
Show file tree
Hide file tree
Showing 13 changed files with 2,166 additions and 1,947 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ jobs:
fail-fast: false
matrix:
node: [ 16, 18 ]
ethers: [ 5, 6 ]
steps:
- name: Checkout repository
uses: actions/checkout@v3
Expand All @@ -25,4 +26,4 @@ jobs:
npm install
npm run build
- name: Run tests
run: npm run test
run: ETHERSJS_VERSION=${{ matrix.ethers }} npm run test
10 changes: 0 additions & 10 deletions jest.config.js

This file was deleted.

3,963 changes: 2,057 additions & 1,906 deletions package-lock.json

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@
],
"scripts": {
"build": "lerna run build",
"test": "lerna run test",
"test": "lerna run --stream test",
"docs": "typedoc --plugin nlfurniss-typedoc-plugin-sourcefile-url --plugin typedoc-plugin-extras --sourcefile-url-prefix 'https://github.com/spruceid/siwe' --favicon ./favicon.svg --out docs packages/siwe/lib/siwe.ts"
},
"author": "Spruce Systems Inc.",
"license": "Apache-2.0",
"devDependencies": {
"@types/jest": "^27.0.2",
"ethers": "^5.5.1",
"jest": "^27.3.1",
"@types/jest": "^29.4.0",
"ethers5": "npm:ethers@^5.6.8",
"ethers6": "npm:ethers@^6.0.8",
"jest": "^29.4.3",
"lerna": "^6.5.1",
"nlfurniss-typedoc-plugin-sourcefile-url": "^2.0.0",
"ts-jest": "^27.0.7",
"ts-jest": "^29.0.5",
"typedoc": "^0.23.12",
"typedoc-plugin-extras": "^2.2.1",
"typescript": "^4.4.4"
Expand Down
2 changes: 1 addition & 1 deletion packages/siwe-parser/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
modulePathIgnorePatterns: ["<rootDir>/dist/"]
modulePathIgnorePatterns: ['<rootDir>/dist/'],
};
12 changes: 2 additions & 10 deletions packages/siwe-parser/lib/parsers.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { ParsedMessage } from "./abnf";

const parsingPositive: Object = require("../../../test/parsing_positive.json");
const parsingNegative: Object = require("../../../test/parsing_negative.json");

//
describe("Successfully parses with ABNF Client", () => {
let ParsedMessage;
beforeEach(
async () => (ParsedMessage = (await import("./abnf")).ParsedMessage)
);

test.concurrent.each(Object.entries(parsingPositive))(
"Parses message successfully: %s",
(test_name, test) => {
Expand All @@ -24,11 +21,6 @@ describe("Successfully parses with ABNF Client", () => {
});

describe("Successfully fails with ABNF Client", () => {
let ParsedMessage;
beforeEach(
async () => (ParsedMessage = (await import("./abnf")).ParsedMessage)
);

test.concurrent.each(Object.entries(parsingNegative))(
"Fails to parse message: %s",
(test_name, test) => {
Expand Down
1 change: 1 addition & 0 deletions packages/siwe/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
modulePathIgnorePatterns: ['<rootDir>/dist/'],
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
};
16 changes: 16 additions & 0 deletions packages/siwe/jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
jest.mock('ethers', () => {
const packages = {
5: 'ethers5',
6: 'ethers6',
};
const version = process.env.ETHERSJS_VERSION || '6';
console.log('Testing with ethers version', version);

// Require the original module.
const originalModule = jest.requireActual(packages[version]);

return {
__esModule: true,
...originalModule,
};
});
23 changes: 19 additions & 4 deletions packages/siwe/lib/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ const verificationPositive = require('../../../test/verification_positive.json')
const verificationNegative = require('../../../test/verification_negative.json');
const EIP1271 = require('../../../test/eip1271.json');

import { providers, Wallet } from 'ethers';
import {
providers,
// @ts-expect-error -- ethers v6 compatibility hack
InfuraProvider,
Wallet,
} from 'ethers';
import { SiweMessage } from './client';
import { SiweErrorType } from './types';

Expand Down Expand Up @@ -44,7 +49,7 @@ describe(`Message Generation`, () => {

describe(`Message verification without suppressExceptions`, () => {
test.concurrent.each(Object.entries(verificationPositive))(
'Verificates message successfully: %s',
'Verifies message successfully: %s',
async (_, test_fields: any) => {
const msg = new SiweMessage(test_fields);
await expect(
Expand All @@ -65,8 +70,11 @@ describe(`Message verification without suppressExceptions`, () => {
return res === data;
})
).resolves.toBeTruthy();

jest.useRealTimers();
}
);

test.concurrent.each(Object.entries(verificationNegative))(
'Fails to verify message: %s and rejects the promise',
async (n, test_fields: any) => {
Expand Down Expand Up @@ -146,9 +154,16 @@ describe(`Round Trip`, () => {
});

describe(`EIP1271`, () => {
function getProviderCompat(networkId: number | string) {
return typeof providers?.InfuraProvider !== 'undefined'
? new providers.InfuraProvider(networkId)
: new InfuraProvider(networkId);
}

test.concurrent.each(Object.entries(EIP1271))(
'Verificates message successfully: %s',
'Verifies message successfully: %s',
async (_, test_fields: any) => {
const provider = getProviderCompat(1);
const msg = new SiweMessage(test_fields.message);
await expect(
msg
Expand All @@ -157,7 +172,7 @@ describe(`EIP1271`, () => {
signature: test_fields.signature,
},
{
provider: new providers.CloudflareProvider(1),
provider,
}
)
.then(({ success }) => success)
Expand Down
8 changes: 5 additions & 3 deletions packages/siwe/lib/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import {
ParsedMessage,
parseIntegerNumber,
} from '@spruceid/siwe-parser';
import { providers, utils } from 'ethers';
import { providers } from 'ethers';
import * as uri from 'valid-url';

import { getAddress, verifyMessage } from './ethersCompat';
import {
SiweError,
SiweErrorType,
Expand Down Expand Up @@ -290,7 +292,7 @@ export class SiweMessage {
/** Recover address from signature */
let addr;
try {
addr = utils.verifyMessage(EIP4361Message, signature);
addr = verifyMessage(EIP4361Message, signature);
} catch (e) {
console.error(e);
}
Expand Down Expand Up @@ -379,7 +381,7 @@ export class SiweMessage {
if (!isEIP55Address(this.address)) {
throw new SiweError(
SiweErrorType.INVALID_ADDRESS,
utils.getAddress(this.address),
getAddress(this.address),
this.address
);
}
Expand Down
51 changes: 51 additions & 0 deletions packages/siwe/lib/ethersCompat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {
utils,
// @ts-expect-error -- ethers v6 compatibility hack
verifyMessage as ethersVerifyMessage,
// @ts-expect-error -- ethers v6 compatibility hack
hashMessage as ethersHashMessage,
// @ts-expect-error -- ethers v6 compatibility hack
getAddress as ethersGetAddress,
} from 'ethers';

type Ethers6BigNumberish = string | number | bigint;

// NB: This compatibility type omits the `Signature` class defined in ethers v6;
// however, a `Signature` instance is compatible with the object type defined.
type Ethers6SignatureLike =
| string
| {
r: string;
s: string;
v: Ethers6BigNumberish;
yParity?: 0 | 1;
yParityAndS?: string;
}
| {
r: string;
yParityAndS: string;
yParity?: 0 | 1;
s?: string;
v?: number;
}
| {
r: string;
s: string;
yParity: 0 | 1;
v?: Ethers6BigNumberish;
yParityAndS?: string;
};

export const verifyMessage =
utils?.verifyMessage ??
(ethersVerifyMessage as (
message: Uint8Array | string,
sig: Ethers6SignatureLike
) => string);

export const hashMessage =
utils?.hashMessage ??
(ethersHashMessage as (message: Uint8Array | string) => string);

export const getAddress =
utils?.getAddress ?? (ethersGetAddress as (address: string) => string);
11 changes: 5 additions & 6 deletions packages/siwe/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { randomStringForEntropy } from '@stablelib/random';
import { Contract, providers, Signer, utils } from 'ethers';
import { Contract, providers, Signer } from 'ethers';

import type { SiweMessage } from './client';
import { hashMessage } from './ethersCompat';

const EIP1271_ABI = ["function isValidSignature(bytes32 _message, bytes _signature) public view returns (bytes4)"];
const EIP1271_MAGICVALUE = "0x1626ba7e";
Expand All @@ -23,11 +25,8 @@ export const checkContractWalletSignature = async (
}

const walletContract = new Contract(message.address, EIP1271_ABI, provider);
const hashMessage = utils.hashMessage(message.prepareMessage());
const res = await walletContract.isValidSignature(
hashMessage,
signature
);
const hashedMessage = hashMessage(message.prepareMessage());
const res = await walletContract.isValidSignature(hashedMessage, signature);
return res == EIP1271_MAGICVALUE;
};

Expand Down
2 changes: 1 addition & 1 deletion packages/siwe/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"prettier": "^2.6.2"
},
"peerDependencies": {
"ethers": "^5.5.1"
"ethers": "^5.6.8 || ^6.0.8"
},
"repository": {
"type": "git",
Expand Down

0 comments on commit e12cd8d

Please sign in to comment.