-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
170 lines (160 loc) · 4.75 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import ethers from "ethers";
import crypto from "crypto";
/**
* @typedef Nonce
* @property {string} value
* @property {Date} issuedAt
* @property {Date|null} expirationTime
* @property {data|null} notBefore
*/
/**
* An error indicating the supplied nonce was invalid.
*/
export class InvalidNonceError extends Error {}
/**
* An error indicating the signature was invalid.
*/
export class InvalidSignatureError extends Error {}
/**
* Produce the EIP4361 Message for Wallet to Sign
*
* @param {string} domain - The domain making the request
* @param {string} address - The address that should sign the request.
* @param {string} statement - A statement for the user to agree to.
* @param {string} uri - The URI making the request.
* @param {number} version - The version making the request.
* @param {Nonce} nonce - The nonce for this request.
* @param {Number|null} [chainId=null] - The chain ID for this request.
* @param {string|null} [requestId=null] - The request ID associated with this request.
* @param {string[]} [resources=[]] - An array of URIs for resources associated with the request.
* @returns {string} - The message that the client should ask the wallet to sign with `personal_sign`.
*/
export function produceMessage(
domain,
address,
statement,
uri,
version,
nonce,
chainId,
requestId,
resources = []
) {
let message = `${domain} wants you to sign in with your Ethereum account:
${address}
${statement}
URI: ${uri}
Version: ${version}
Nonce: ${nonce.value}
Issued At: ${nonce.issuedAt.toISOString()}`;
if (nonce.expirationTime) {
message += `\nExpiration Time: ${nonce.expirationTime.toISOString()}`;
}
if (nonce.notBefore) {
message += `\nNot Before: ${nonce.notBefore.toISOString()}`;
}
if (chainId) {
message += `\nChain ID: ${chainId}`;
}
if (requestId) {
message += `\nRequest ID: ${requestId}`;
}
if (resources && resources.length) {
message += "\nResources:";
resources.forEach((resource) => {
message += `\n- ${resource}`;
});
}
return message;
}
/**
* Create a new nonce.
*
* @param {number|null} [expirationTTLSeconds] - Number of seconds the nonce should be valid for.
* @param {Data|null} [notBefore] - The date, before which, the request should not be valid.
* @returns {Nonce} - The requested nonce.
*/
export function makeNonce(expirationTTLSeconds = null, notBefore = null) {
const issuedAt = new Date();
let expirationTime = null;
if (expirationTTLSeconds) {
expirationTime = new Date(issuedAt.getTime());
expirationTime.setUTCSeconds(
expirationTime.getUTCSeconds() + expirationTTLSeconds
);
}
const value = crypto.randomBytes(16).toString("hex");
return {
value,
issuedAt,
expirationTime,
notBefore,
};
}
/**
* Verify a nonce.
*
* @param {Nonce} nonce - The nonce to verify
* @throws {InvalidNonceError} - If the nonce is invalid for any reason.
*/
export function verifyNonce(nonce) {
const currentTime = new Date();
if (nonce.expirationTime && nonce.expirationTime < currentTime) {
throw new InvalidNonceError(
`Nonce is expired: ${nonce.expirationTime.toISOString()} < ${currentTime.toISOString()}`
);
}
if (nonce.notBefore && nonce.notBefore > currentTime) {
throw new InvalidNonceError(
`Nonce is not valid yet: ${nonce.notBefore.toISOString()} > ${currentTime.toISOString()}`
);
}
}
/**
* Verifies that a signature was signed by an account.
*
* @param {string} domain - The domain that made the request.
* @param {string} address - The address that should have signed the message.
* @param {string} statement - The statement used in the request.
* @param {string} uri - The URI that made the request.
* @param {Number} version - The version of the request.
* @param {Nonce} nonce - The nonce that was included in the request.
* @param {Number} chainId - The chain ID that was used in the request.
* @param {string[]} resources - The same array of resource URIs included in the request.
* @throws {InvalidNonceError} - If the supplied nonce was invalid.
* @throws {InvalidSignatureError} - If the supplied signature was invalid.
*/
export async function verifySignature(
signature,
domain,
address,
statement,
uri,
version,
nonce,
chainId,
requestId,
resources = []
) {
verifyNonce(nonce);
const eip4361Message = produceMessage(
domain,
address,
statement,
uri,
version,
nonce,
chainId,
requestId,
resources
);
let whoSigned;
try {
whoSigned = await ethers.utils.verifyMessage(eip4361Message, signature);
} catch (error) {
throw new InvalidSignatureError("Signature is bad.");
}
if (address.toLowerCase() !== whoSigned.toLowerCase()) {
throw new InvalidSignatureError("Signature is signed by other account.");
}
}