-
Notifications
You must be signed in to change notification settings - Fork 23
/
cryptography.ts
129 lines (114 loc) · 3.96 KB
/
cryptography.ts
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
import { ethers } from 'ethers'
import type { AnyObject } from './types'
const VALIDATOR_1271_ABI = [
'function isValidSignature(bytes32 hash, bytes signature) view returns (bytes4)'
]
const ON_CHAIN_ALLOWED_SIGNER_CACHE: AnyObject = {}
export function getEvmEIP712Types(chainId: number) {
if ([42161, 421613].includes(chainId)) {
return {
Order: [
{ name: 'user', type: 'address' },
{ name: 'sellToken', type: 'address' },
{ name: 'buyToken', type: 'address' },
{ name: 'sellAmount', type: 'uint256' },
{ name: 'buyAmount', type: 'uint256' },
{ name: 'expirationTimeSeconds', type: 'uint256' }
]
}
}
return null
}
export function modifyOldSignature(signature: string): string {
if (signature.slice(-2) === '00') return signature.slice(0, -2).concat('1B')
if (signature.slice(-2) === '01') return signature.slice(0, -2).concat('1C')
return signature
}
// Address recovery wrapper
function recoverAddress(hash: string, signature: string): string {
try {
return ethers.utils.recoverAddress(hash, signature)
} catch {
return ''
}
}
// Comparing addresses. targetAddr is already checked upstream
function addrMatching(recoveredAddr: string, targetAddr: string) {
if (recoveredAddr === '') return false
if (!ethers.utils.isAddress(recoveredAddr))
throw new Error(`Invalid recovered address: ${recoveredAddr}`)
return recoveredAddr.toLowerCase() === targetAddr.toLowerCase()
}
// EIP 1271 check
async function eip1271Check(
provider: ethers.providers.Provider,
signer: string,
hash: string,
signature: string
) {
let ethersProvider
if (ethers.providers.Provider.isProvider(provider)) {
ethersProvider = provider
} else {
ethersProvider = new ethers.providers.Web3Provider(provider)
}
const code = await ethersProvider.getCode(signer)
if (code && code !== '0x') {
const contract = new ethers.Contract(
signer,
VALIDATOR_1271_ABI,
ethersProvider
)
return (await contract.isValidSignature(hash, signature)) === '0x1626ba7e'
}
return false
}
// you only need to pass one of: typedData, finalDigest, message
export async function verifyMessage(param: {
provider: ethers.providers.Provider
signer: string
message?: string
typedData?: AnyObject
signature: string
}): Promise<boolean> {
const { message, typedData, provider, signer } = param
const signature = modifyOldSignature(param.signature)
let finalDigest: string
if (message) {
finalDigest = ethers.utils.hashMessage(message)
} else if (typedData) {
if (!typedData.domain || !typedData.types || !typedData.message) {
throw Error(
'Missing one or more properties for typedData (domain, types, message)'
)
}
// eslint-disable-next-line no-underscore-dangle
finalDigest = ethers.utils._TypedDataEncoder.hash(
typedData.domain,
typedData.types,
typedData.message
)
} else {
throw Error('Missing one of the properties: message or typedData')
}
// 1nd try: elliptic curve signature (EOA)
const recoveredAddress = recoverAddress(finalDigest, signature)
if (addrMatching(recoveredAddress, signer)) return true
// 2nd try: ON_CHAIN_ALLOWED_SIGNER_CACHE saves previus allowed signer
// optimistic assumtion: they are allowed to sign this time again.
// The contract does a real signature check anyway
const allowedAddress = ON_CHAIN_ALLOWED_SIGNER_CACHE[signer]
console.log(ON_CHAIN_ALLOWED_SIGNER_CACHE)
if (allowedAddress && addrMatching(recoveredAddress, allowedAddress)) return true
// 3st try: Getting code from deployed smart contract to call 1271 isValidSignature.
try {
if (await eip1271Check(provider, signer, finalDigest, signature)) {
ON_CHAIN_ALLOWED_SIGNER_CACHE[signer] = recoveredAddress
return true
}
} catch (err: any) {
console.error(`Failed to check signature on chain: ${err.message}`)
return true // better accept orders, as this check is optinal anyway
}
return false
}