Skip to content

Commit

Permalink
Update grammar for chain ID (spruceid#21)
Browse files Browse the repository at this point in the history
* make chainId required

* update abnf, regex and tests to move `Chain ID` between `Version` and `Nonce`

* fix address comparison in sig verification
  • Loading branch information
chunningham authored Nov 26, 2021
1 parent 68f7bdf commit 482aa32
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 35 deletions.
28 changes: 14 additions & 14 deletions lib/abnf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ sign-in-with-ethereum =
LF
%s"URI: " URI LF
%s"Version: " version LF
%s"Chain ID: " chain-id LF
%s"Nonce: " nonce LF
%s"Issued At: " issued-at
[ LF %s"Expiration Time: " expiration-time ]
[ LF %s"Not Before: " not-before ]
[ LF %s"Request ID: " request-id ]
[ LF %s"Chain ID: " chain-id ]
[ LF %s"Resources:"
resources ]
Expand Down Expand Up @@ -155,12 +155,12 @@ export class ParsedMessage {
statement: string;
uri: string;
version: string;
chainId: string;
nonce: string;
issuedAt: string;
expirationTime: string | null;
notBefore: string | null;
requestId: string | null;
chainId: string | null;
resources: Array<string> | null;

constructor(msg: string) {
Expand All @@ -178,7 +178,7 @@ export class ParsedMessage {
parser.ast = new apgLib.ast();
const id = apgLib.ids;

const domain = function (
const domain = function(
state,
chars,
phraseIndex,
Expand All @@ -196,7 +196,7 @@ export class ParsedMessage {
return ret;
};
parser.ast.callbacks.domain = domain;
const address = function (
const address = function(
state,
chars,
phraseIndex,
Expand All @@ -214,7 +214,7 @@ export class ParsedMessage {
return ret;
};
parser.ast.callbacks.address = address;
const statement = function (
const statement = function(
state,
chars,
phraseIndex,
Expand All @@ -232,7 +232,7 @@ export class ParsedMessage {
return ret;
};
parser.ast.callbacks.statement = statement;
const uri = function (state, chars, phraseIndex, phraseLength, data) {
const uri = function(state, chars, phraseIndex, phraseLength, data) {
const ret = id.SEM_OK;
if (state === id.SEM_PRE) {
if (!data.uri) {
Expand All @@ -246,7 +246,7 @@ export class ParsedMessage {
return ret;
};
parser.ast.callbacks.uri = uri;
const version = function (
const version = function(
state,
chars,
phraseIndex,
Expand All @@ -264,7 +264,7 @@ export class ParsedMessage {
return ret;
};
parser.ast.callbacks.version = version;
const chainId = function (
const chainId = function(
state,
chars,
phraseIndex,
Expand All @@ -282,7 +282,7 @@ export class ParsedMessage {
return ret;
};
parser.ast.callbacks['chain-id'] = chainId;
const nonce = function (state, chars, phraseIndex, phraseLength, data) {
const nonce = function(state, chars, phraseIndex, phraseLength, data) {
const ret = id.SEM_OK;
if (state === id.SEM_PRE) {
data.nonce = apgLib.utils.charsToString(
Expand All @@ -294,7 +294,7 @@ export class ParsedMessage {
return ret;
};
parser.ast.callbacks.nonce = nonce;
const issuedAt = function (
const issuedAt = function(
state,
chars,
phraseIndex,
Expand All @@ -312,7 +312,7 @@ export class ParsedMessage {
return ret;
};
parser.ast.callbacks['issued-at'] = issuedAt;
const expirationTime = function (
const expirationTime = function(
state,
chars,
phraseIndex,
Expand All @@ -330,7 +330,7 @@ export class ParsedMessage {
return ret;
};
parser.ast.callbacks['expiration-time'] = expirationTime;
const notBefore = function (
const notBefore = function(
state,
chars,
phraseIndex,
Expand All @@ -348,7 +348,7 @@ export class ParsedMessage {
return ret;
};
parser.ast.callbacks['not-before'] = notBefore;
const requestId = function (
const requestId = function(
state,
chars,
phraseIndex,
Expand All @@ -366,7 +366,7 @@ export class ParsedMessage {
return ret;
};
parser.ast.callbacks['request-id'] = requestId;
const resources = function (
const resources = function(
state,
chars,
phraseIndex,
Expand Down
11 changes: 11 additions & 0 deletions lib/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ var parsingPositive: object = require('../test/parsing_positive.json');
var validationPositive: object = require('../test/validation_positive.json');
var validationNegative: object = require('../test/validation_negative.json');
import { SiweMessage } from "./client";
import { Wallet } from "ethers";

describe(`Message Generation`, () => {
test.concurrent.each(Object.entries(parsingPositive))('Generates message successfully: %s', (_, test) => {
Expand All @@ -20,3 +21,13 @@ describe(`Message Validation`, () => {
await expect(msg.validate()).rejects.toThrow();
});
});

describe(`Round Trip`, () => {
let wallet = Wallet.createRandom();
test.concurrent.each(Object.entries(parsingPositive))('Generates a Successfully Verifying message: %s', async (_, test) => {
const msg = new SiweMessage(test.fields);
msg.address = wallet.address.toLowerCase();
msg.signature = await wallet.signMessage(msg.toMessage());
await expect(msg.validate()).resolves.not.toThrow();
});
})
20 changes: 9 additions & 11 deletions lib/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,14 @@ export class SiweMessage {
uri: string;
/**Current version of the message. */
version: string;
/**EIP-155 Chain ID to which the session is bound, and the network where
* Contract Accounts must be resolved. */
chainId: string;
/**Randomized token used to prevent replay attacks, at least 8 alphanumeric
* characters. */
nonce?: string;
nonce: string;
/**ISO 8601 datetime string of the current time. */
issuedAt?: string;
issuedAt: string;
/**ISO 8601 datetime string that, if present, indicates when the signed
* authentication message is no longer valid. */
expirationTime?: string;
Expand All @@ -51,9 +54,6 @@ export class SiweMessage {
/**System-specific identifier that may be used to uniquely refer to the
* sign-in request. */
requestId?: string;
/**EIP-155 Chain ID to which the session is bound, and the network where
* Contract Accounts must be resolved. */
chainId?: string;
/**List of information or references to information the user wishes to have
* resolved as part of authentication by the relying party. They are
* expressed as RFC 3986 URIs separated by `\n- `. */
Expand Down Expand Up @@ -117,9 +117,11 @@ export class SiweMessage {
this.nonce = (Math.random() + 1).toString(36).substring(4);
}

const chainField = `Chain ID: ` + this.chainId || "1";

const nonceField = `Nonce: ${this.nonce}`;

const suffixArray = [uriField, versionField, nonceField];
const suffixArray = [uriField, versionField, chainField, nonceField];

if (this.issuedAt) {
Date.parse(this.issuedAt);
Expand All @@ -143,10 +145,6 @@ export class SiweMessage {
suffixArray.push(`Request ID: ${this.requestId}`);
}

if (this.chainId) {
suffixArray.push(`Chain ID: ${this.chainId}`);
}

if (this.resources) {
suffixArray.push(
[`Resources:`, ...this.resources.map((x) => `- ${x}`)].join(
Expand Down Expand Up @@ -223,7 +221,7 @@ export class SiweMessage {
this.signature
);

if (addr !== this.address) {
if (addr.toLowerCase() !== this.address.toLowerCase()) {
try {
//EIP-1271
const isValidSignature =
Expand Down
4 changes: 2 additions & 2 deletions lib/regex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ export class ParsedMessage {
statement: string;
uri: string;
version: string;
chainId: string;
nonce: string;
issuedAt: string;
expirationTime: string | null;
notBefore: string | null;
requestId: string | null;
chainId: string | null;
resources: string[] | null;
match?: RegExpExecArray;

constructor(msg: string) {
const REGEX = new RegExp(
`^(?<domain>${DOMAIN})\\ wants\\ you\\ to\\ sign\\ in\\ with\\ your\\ Ethereum\\ account\\:\\n(?<address>${ADDRESS})\\n\\n((?<statement>[^\\n]+)\\n)?\\nURI\\:\\ (?<uri>${URI})\\nVersion\\:\\ (?<version>1)\\nNonce\\:\\ (?<nonce>[a-zA-Z0-9]{8})\\nIssued\\ At\\:\\ (?<issuedAt>${DATETIME})(\\nExpiration\\ Time\\:\\ (?<expirationTime>${DATETIME}))?(\\nNot\\ Before\\:\\ (?<notBefore>${DATETIME}))?(\\nRequest\\ ID\\:\\ (?<requestId>${REQUESTID}))?(\\nChain\\ ID\\:\\ (?<chainId>[0-9]+))?(\\nResources\\:(?<resources>(\\n-\\ ${URI})+))?$`,
`^(?<domain>${DOMAIN})\\ wants\\ you\\ to\\ sign\\ in\\ with\\ your\\ Ethereum\\ account\\:\\n(?<address>${ADDRESS})\\n\\n((?<statement>[^\\n]+)\\n)?\\nURI\\:\\ (?<uri>${URI})\\nVersion\\:\\ (?<version>1)\\nChain\\ ID\\:\\ (?<chainId>[0-9]+)\\nNonce\\:\\ (?<nonce>[a-zA-Z0-9]{8})\\nIssued\\ At\\:\\ (?<issuedAt>${DATETIME})(\\nExpiration\\ Time\\:\\ (?<expirationTime>${DATETIME}))?(\\nNot\\ Before\\:\\ (?<notBefore>${DATETIME}))?(\\nRequest\\ ID\\:\\ (?<requestId>${REQUESTID}))?(\\nResources\\:(?<resources>(\\n-\\ ${URI})+))?$`,
'g'
);

Expand Down
10 changes: 6 additions & 4 deletions test/parsing_positive.json
Original file line number Diff line number Diff line change
@@ -1,38 +1,40 @@
{
"couple of optional fields": {
"message": "service.org wants you to sign in with your Ethereum account:\n0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nNonce: 32891757\nIssued At: 2021-09-30T16:25:24.000Z\nChain ID: 1\nResources:\n- ipfs://Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu\n- https://example.com/my-web2-claim.json",
"message": "service.org wants you to sign in with your Ethereum account:\n0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 32891757\nIssued At: 2021-09-30T16:25:24.000Z\nResources:\n- ipfs://Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu\n- https://example.com/my-web2-claim.json",
"fields": {
"domain": "service.org",
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"statement": "I accept the ServiceOrg Terms of Service: https://service.org/tos",
"uri": "https://service.org/login",
"version": "1",
"chainId": "1",
"nonce": "32891757",
"issuedAt": "2021-09-30T16:25:24.000Z",
"chainId": "1",
"resources": ["ipfs://Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu", "https://example.com/my-web2-claim.json"]
}
},
"no optional field": {
"message": "service.org wants you to sign in with your Ethereum account:\n0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nNonce: 32891757\nIssued At: 2021-09-30T16:25:24.000Z",
"message": "service.org wants you to sign in with your Ethereum account:\n0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 32891757\nIssued At: 2021-09-30T16:25:24.000Z",
"fields": {
"domain": "service.org",
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"statement": "I accept the ServiceOrg Terms of Service: https://service.org/tos",
"uri": "https://service.org/login",
"version": "1",
"chainId": "1",
"nonce": "32891757",
"issuedAt": "2021-09-30T16:25:24.000Z"
}
},
"timestamp without microseconds": {
"message": "service.org wants you to sign in with your Ethereum account:\n0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nNonce: 32891757\nIssued At: 2021-09-30T16:25:24Z",
"message": "service.org wants you to sign in with your Ethereum account:\n0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2\n\nI accept the ServiceOrg Terms of Service: https://service.org/tos\n\nURI: https://service.org/login\nVersion: 1\nChain ID: 1\nNonce: 32891757\nIssued At: 2021-09-30T16:25:24Z",
"fields": {
"domain": "service.org",
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"statement": "I accept the ServiceOrg Terms of Service: https://service.org/tos",
"uri": "https://service.org/login",
"version": "1",
"chainId": "1",
"nonce": "32891757",
"issuedAt": "2021-09-30T16:25:24Z"
}
Expand Down
8 changes: 4 additions & 4 deletions test/validation_positive.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"example message": {
"domain": "login.xyz",
"address": "0xe2f03cb7a54ddd886da9b0d227bfcb2d61429699",
"address": "0xb8a316ea8a9e48ebd25b73c71bc0f22f5c337d1f",
"statement": "Sign-In With Ethereum Example Statement",
"uri": "https://login.xyz",
"version": "1",
"nonce": "k13wuejc",
"issuedAt": "2021-11-12T17:37:48.462Z",
"chainId": "1",
"signature": "0x795110331a07a4d475419fbdb346feb4c0579dcc8228989964474e07d98dbf425f38776cd6ca037f58288acc7b15e720c9cecac988479177fb70592f2391aaff1b"
"nonce": "uolthxpe",
"issuedAt": "2021-11-25T02:36:37.013Z",
"signature": "0x6eabbdf0861ca83b6cf98381dcbc3db16dffce9a0449dc8b359718d13b0093c3285b6dea7e84ad1aa4871b63899319a988ddf39df3080bcdc60f68dd0942e8221c"
}
}

0 comments on commit 482aa32

Please sign in to comment.